├── README.md ├── composer.json ├── laraview ├── compatibility.js ├── example.local.css ├── images │ ├── kogmbh.png │ ├── nlnet.png │ ├── texture.png │ ├── toolbarButton-download.png │ ├── toolbarButton-fullscreen.png │ ├── toolbarButton-menuArrows.png │ ├── toolbarButton-pageDown.png │ ├── toolbarButton-pageUp.png │ ├── toolbarButton-presentation.png │ ├── toolbarButton-zoomIn.png │ └── toolbarButton-zoomOut.png ├── index.html ├── pdf.js ├── pdf.worker.js ├── pdfjsversion.js ├── text_layer_builder.js ├── ui_utils.js └── webodf.js └── src ├── Facade └── LaravelPdfViewer.php └── LaravelPdfViewerServiceProvider.php /README.md: -------------------------------------------------------------------------------- 1 | ## Laravel PDF VIEWER 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/davcpas1234/laravel-pdf-viewer/v/stable)](https://packagist.org/packages/davcpas1234/laravelpdfviewer) 4 | [![License](https://poser.pugx.org/davcpas1234/laravel-pdf-viewer/license)](https://packagist.org/packages/davcpas1234/laravelpdfviewer) 5 | [![Build Status](https://scrutinizer-ci.com/g/davcpas1234/laravel-pdf-viewer/badges/build.png?b=master)](https://scrutinizer-ci.com/g/goodnesskay/LARAVEL-PDF-VIEWER/build-status/master) 6 | 7 | > This package is meant to help with viewing portable document file(PDF) on the web seamlessly when developing with Laravel. The package makes use of [ViewerJS](http://viewerjs.org) 8 | 9 | ## Requirement 10 | 11 | - [PHP](https://php.net) 5.6+ 12 | - [Composer](https://getcomposer.org) 13 | - Pdf files 14 | 15 | ## Installation 16 | To install into your project, run the command below in your terminal. 17 | 18 | ``` 19 | composer require davcpas1234/laravelpdfviewer 20 | ``` 21 | 22 | Once the package is done being installed, register the service provider. Open `config/app.php` and add the following to the `providers` key. 23 | 24 | ``` 25 | Davcpas1234\LaravelPdfViewer\LaravelPdfViewerServiceProvider::class, 26 | ``` 27 | 28 | ## Configure 29 | Run this in your terminal: 30 | ``` 31 | php artisan vendor:publish --provider="Davcpas1234\LaravelPdfViewer\LaravelPdfViewerServiceProvider" 32 | ``` 33 | It will publish a folder named `laraview` to the root folder of your project. 34 | 35 | ## How it Works 36 | Simple!!! After installations and configurations have been carried out successfully, add the code below to your html file 37 | ``` 38 | {{ asset('/laraview/#../folder-name/the-pdf-file.pdf') }} 39 | 40 | ``` 41 | It should look like this: 42 | ``` 43 | 44 | ``` 45 | Then, you should have something like this: 46 | 47 | ![Goodness Kayode Laravel-pdfviewer](https://cloud.githubusercontent.com/assets/16525886/26499445/9483e444-422a-11e7-81cf-9569b8f33669.png) 48 | 49 | 50 | **Note:** 51 | > After `#../` in `{{ asset('/laraview/#../folder-name/the-pdf-file.pdf') }}`, what should follow is the folder name of the pdf files in the public 52 | folder then, the pdf file name can follow. 53 | 54 | 55 | 56 | ## Contribute 57 | 58 | You can `fork` this package, `contribute` and `submit a pull request`. I will really love it. 59 | 60 | 61 | ## License 62 | 63 | MIT License (MIT). 64 | 65 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "davcpas1234/laravelpdfviewer", 3 | "description": "A Laravel Package for viewing PDF files or documents on Web App", 4 | "version": "1.0.1", 5 | "license": "MIT", 6 | "autoload": { 7 | "psr-4": { 8 | "Davcpas1234\\LaravelPdfViewer\\": "src/" 9 | } 10 | }, 11 | "keywords": ["laravel","pdf-viewer","laravel-viewerJS","ViewerJS"], 12 | "authors": [ 13 | { 14 | "name": "Goodness Toluwanimi Kayode", 15 | "email": "gtkbrain@gmail.com" 16 | }, 17 | { 18 | "name": "David Passmore", 19 | "email": "david.passmore@verscreative.co.uk" 20 | } 21 | ], 22 | "minimum-stability": "stable", 23 | "require": {}, 24 | "extra": { 25 | "laravel": { 26 | "providers": [ 27 | "Davcpas1234\\LaravelPdfViewer\\LaravelPdfViewerServiceProvider" 28 | ] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /laraview/compatibility.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 3 | /* Copyright 2012 Mozilla Foundation 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* globals VBArray, PDFJS */ 18 | 19 | 'use strict'; 20 | 21 | // Initializing PDFJS global object here, it case if we need to change/disable 22 | // some PDF.js features, e.g. range requests 23 | if (typeof PDFJS === 'undefined') { 24 | (typeof window !== 'undefined' ? window : this).PDFJS = {}; 25 | } 26 | 27 | // Checking if the typed arrays are supported 28 | // Support: iOS<6.0 (subarray), IE<10, Android<4.0 29 | (function checkTypedArrayCompatibility() { 30 | if (typeof Uint8Array !== 'undefined') { 31 | // Support: iOS<6.0 32 | if (typeof Uint8Array.prototype.subarray === 'undefined') { 33 | Uint8Array.prototype.subarray = function subarray(start, end) { 34 | return new Uint8Array(this.slice(start, end)); 35 | }; 36 | Float32Array.prototype.subarray = function subarray(start, end) { 37 | return new Float32Array(this.slice(start, end)); 38 | }; 39 | } 40 | 41 | // Support: Android<4.1 42 | if (typeof Float64Array === 'undefined') { 43 | window.Float64Array = Float32Array; 44 | } 45 | return; 46 | } 47 | 48 | function subarray(start, end) { 49 | return new TypedArray(this.slice(start, end)); 50 | } 51 | 52 | function setArrayOffset(array, offset) { 53 | if (arguments.length < 2) { 54 | offset = 0; 55 | } 56 | for (var i = 0, n = array.length; i < n; ++i, ++offset) { 57 | this[offset] = array[i] & 0xFF; 58 | } 59 | } 60 | 61 | function TypedArray(arg1) { 62 | var result, i, n; 63 | if (typeof arg1 === 'number') { 64 | result = []; 65 | for (i = 0; i < arg1; ++i) { 66 | result[i] = 0; 67 | } 68 | } else if ('slice' in arg1) { 69 | result = arg1.slice(0); 70 | } else { 71 | result = []; 72 | for (i = 0, n = arg1.length; i < n; ++i) { 73 | result[i] = arg1[i]; 74 | } 75 | } 76 | 77 | result.subarray = subarray; 78 | result.buffer = result; 79 | result.byteLength = result.length; 80 | result.set = setArrayOffset; 81 | 82 | if (typeof arg1 === 'object' && arg1.buffer) { 83 | result.buffer = arg1.buffer; 84 | } 85 | return result; 86 | } 87 | 88 | window.Uint8Array = TypedArray; 89 | window.Int8Array = TypedArray; 90 | 91 | // we don't need support for set, byteLength for 32-bit array 92 | // so we can use the TypedArray as well 93 | window.Uint32Array = TypedArray; 94 | window.Int32Array = TypedArray; 95 | window.Uint16Array = TypedArray; 96 | window.Float32Array = TypedArray; 97 | window.Float64Array = TypedArray; 98 | })(); 99 | 100 | // URL = URL || webkitURL 101 | // Support: Safari<7, Android 4.2+ 102 | (function normalizeURLObject() { 103 | if (!window.URL) { 104 | window.URL = window.webkitURL; 105 | } 106 | })(); 107 | 108 | // Object.defineProperty()? 109 | // Support: Android<4.0, Safari<5.1 110 | (function checkObjectDefinePropertyCompatibility() { 111 | if (typeof Object.defineProperty !== 'undefined') { 112 | var definePropertyPossible = true; 113 | try { 114 | // some browsers (e.g. safari) cannot use defineProperty() on DOM objects 115 | // and thus the native version is not sufficient 116 | Object.defineProperty(new Image(), 'id', { value: 'test' }); 117 | // ... another test for android gb browser for non-DOM objects 118 | var Test = function Test() {}; 119 | Test.prototype = { get id() { } }; 120 | Object.defineProperty(new Test(), 'id', 121 | { value: '', configurable: true, enumerable: true, writable: false }); 122 | } catch (e) { 123 | definePropertyPossible = false; 124 | } 125 | if (definePropertyPossible) { 126 | return; 127 | } 128 | } 129 | 130 | Object.defineProperty = function objectDefineProperty(obj, name, def) { 131 | delete obj[name]; 132 | if ('get' in def) { 133 | obj.__defineGetter__(name, def['get']); 134 | } 135 | if ('set' in def) { 136 | obj.__defineSetter__(name, def['set']); 137 | } 138 | if ('value' in def) { 139 | obj.__defineSetter__(name, function objectDefinePropertySetter(value) { 140 | this.__defineGetter__(name, function objectDefinePropertyGetter() { 141 | return value; 142 | }); 143 | return value; 144 | }); 145 | obj[name] = def.value; 146 | } 147 | }; 148 | })(); 149 | 150 | 151 | // No XMLHttpRequest#response? 152 | // Support: IE<11, Android <4.0 153 | (function checkXMLHttpRequestResponseCompatibility() { 154 | var xhrPrototype = XMLHttpRequest.prototype; 155 | var xhr = new XMLHttpRequest(); 156 | if (!('overrideMimeType' in xhr)) { 157 | // IE10 might have response, but not overrideMimeType 158 | // Support: IE10 159 | Object.defineProperty(xhrPrototype, 'overrideMimeType', { 160 | value: function xmlHttpRequestOverrideMimeType(mimeType) {} 161 | }); 162 | } 163 | if ('responseType' in xhr) { 164 | return; 165 | } 166 | 167 | // The worker will be using XHR, so we can save time and disable worker. 168 | PDFJS.disableWorker = true; 169 | 170 | Object.defineProperty(xhrPrototype, 'responseType', { 171 | get: function xmlHttpRequestGetResponseType() { 172 | return this._responseType || 'text'; 173 | }, 174 | set: function xmlHttpRequestSetResponseType(value) { 175 | if (value === 'text' || value === 'arraybuffer') { 176 | this._responseType = value; 177 | if (value === 'arraybuffer' && 178 | typeof this.overrideMimeType === 'function') { 179 | this.overrideMimeType('text/plain; charset=x-user-defined'); 180 | } 181 | } 182 | } 183 | }); 184 | 185 | // Support: IE9 186 | if (typeof VBArray !== 'undefined') { 187 | Object.defineProperty(xhrPrototype, 'response', { 188 | get: function xmlHttpRequestResponseGet() { 189 | if (this.responseType === 'arraybuffer') { 190 | return new Uint8Array(new VBArray(this.responseBody).toArray()); 191 | } else { 192 | return this.responseText; 193 | } 194 | } 195 | }); 196 | return; 197 | } 198 | 199 | Object.defineProperty(xhrPrototype, 'response', { 200 | get: function xmlHttpRequestResponseGet() { 201 | if (this.responseType !== 'arraybuffer') { 202 | return this.responseText; 203 | } 204 | var text = this.responseText; 205 | var i, n = text.length; 206 | var result = new Uint8Array(n); 207 | for (i = 0; i < n; ++i) { 208 | result[i] = text.charCodeAt(i) & 0xFF; 209 | } 210 | return result.buffer; 211 | } 212 | }); 213 | })(); 214 | 215 | // window.btoa (base64 encode function) ? 216 | // Support: IE<10 217 | (function checkWindowBtoaCompatibility() { 218 | if ('btoa' in window) { 219 | return; 220 | } 221 | 222 | var digits = 223 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 224 | 225 | window.btoa = function windowBtoa(chars) { 226 | var buffer = ''; 227 | var i, n; 228 | for (i = 0, n = chars.length; i < n; i += 3) { 229 | var b1 = chars.charCodeAt(i) & 0xFF; 230 | var b2 = chars.charCodeAt(i + 1) & 0xFF; 231 | var b3 = chars.charCodeAt(i + 2) & 0xFF; 232 | var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); 233 | var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; 234 | var d4 = i + 2 < n ? (b3 & 0x3F) : 64; 235 | buffer += (digits.charAt(d1) + digits.charAt(d2) + 236 | digits.charAt(d3) + digits.charAt(d4)); 237 | } 238 | return buffer; 239 | }; 240 | })(); 241 | 242 | // window.atob (base64 encode function)? 243 | // Support: IE<10 244 | (function checkWindowAtobCompatibility() { 245 | if ('atob' in window) { 246 | return; 247 | } 248 | 249 | // https://github.com/davidchambers/Base64.js 250 | var digits = 251 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 252 | window.atob = function (input) { 253 | input = input.replace(/=+$/, ''); 254 | if (input.length % 4 === 1) { 255 | throw new Error('bad atob input'); 256 | } 257 | for ( 258 | // initialize result and counters 259 | var bc = 0, bs, buffer, idx = 0, output = ''; 260 | // get next character 261 | buffer = input.charAt(idx++); 262 | // character found in table? 263 | // initialize bit storage and add its ascii value 264 | ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, 265 | // and if not first of each 4 characters, 266 | // convert the first 8 bits to one ascii character 267 | bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 268 | ) { 269 | // try to find character in table (0-63, not found => -1) 270 | buffer = digits.indexOf(buffer); 271 | } 272 | return output; 273 | }; 274 | })(); 275 | 276 | // Function.prototype.bind? 277 | // Support: Android<4.0, iOS<6.0 278 | (function checkFunctionPrototypeBindCompatibility() { 279 | if (typeof Function.prototype.bind !== 'undefined') { 280 | return; 281 | } 282 | 283 | Function.prototype.bind = function functionPrototypeBind(obj) { 284 | var fn = this, headArgs = Array.prototype.slice.call(arguments, 1); 285 | var bound = function functionPrototypeBindBound() { 286 | var args = headArgs.concat(Array.prototype.slice.call(arguments)); 287 | return fn.apply(obj, args); 288 | }; 289 | return bound; 290 | }; 291 | })(); 292 | 293 | // HTMLElement dataset property 294 | // Support: IE<11, Safari<5.1, Android<4.0 295 | (function checkDatasetProperty() { 296 | var div = document.createElement('div'); 297 | if ('dataset' in div) { 298 | return; // dataset property exists 299 | } 300 | 301 | Object.defineProperty(HTMLElement.prototype, 'dataset', { 302 | get: function() { 303 | if (this._dataset) { 304 | return this._dataset; 305 | } 306 | 307 | var dataset = {}; 308 | for (var j = 0, jj = this.attributes.length; j < jj; j++) { 309 | var attribute = this.attributes[j]; 310 | if (attribute.name.substring(0, 5) !== 'data-') { 311 | continue; 312 | } 313 | var key = attribute.name.substring(5).replace(/\-([a-z])/g, 314 | function(all, ch) { 315 | return ch.toUpperCase(); 316 | }); 317 | dataset[key] = attribute.value; 318 | } 319 | 320 | Object.defineProperty(this, '_dataset', { 321 | value: dataset, 322 | writable: false, 323 | enumerable: false 324 | }); 325 | return dataset; 326 | }, 327 | enumerable: true 328 | }); 329 | })(); 330 | 331 | // HTMLElement classList property 332 | // Support: IE<10, Android<4.0, iOS<5.0 333 | (function checkClassListProperty() { 334 | var div = document.createElement('div'); 335 | if ('classList' in div) { 336 | return; // classList property exists 337 | } 338 | 339 | function changeList(element, itemName, add, remove) { 340 | var s = element.className || ''; 341 | var list = s.split(/\s+/g); 342 | if (list[0] === '') { 343 | list.shift(); 344 | } 345 | var index = list.indexOf(itemName); 346 | if (index < 0 && add) { 347 | list.push(itemName); 348 | } 349 | if (index >= 0 && remove) { 350 | list.splice(index, 1); 351 | } 352 | element.className = list.join(' '); 353 | return (index >= 0); 354 | } 355 | 356 | var classListPrototype = { 357 | add: function(name) { 358 | changeList(this.element, name, true, false); 359 | }, 360 | contains: function(name) { 361 | return changeList(this.element, name, false, false); 362 | }, 363 | remove: function(name) { 364 | changeList(this.element, name, false, true); 365 | }, 366 | toggle: function(name) { 367 | changeList(this.element, name, true, true); 368 | } 369 | }; 370 | 371 | Object.defineProperty(HTMLElement.prototype, 'classList', { 372 | get: function() { 373 | if (this._classList) { 374 | return this._classList; 375 | } 376 | 377 | var classList = Object.create(classListPrototype, { 378 | element: { 379 | value: this, 380 | writable: false, 381 | enumerable: true 382 | } 383 | }); 384 | Object.defineProperty(this, '_classList', { 385 | value: classList, 386 | writable: false, 387 | enumerable: false 388 | }); 389 | return classList; 390 | }, 391 | enumerable: true 392 | }); 393 | })(); 394 | 395 | // Check console compatibility 396 | // In older IE versions the console object is not available 397 | // unless console is open. 398 | // Support: IE<10 399 | (function checkConsoleCompatibility() { 400 | if (!('console' in window)) { 401 | window.console = { 402 | log: function() {}, 403 | error: function() {}, 404 | warn: function() {} 405 | }; 406 | } else if (!('bind' in console.log)) { 407 | // native functions in IE9 might not have bind 408 | console.log = (function(fn) { 409 | return function(msg) { return fn(msg); }; 410 | })(console.log); 411 | console.error = (function(fn) { 412 | return function(msg) { return fn(msg); }; 413 | })(console.error); 414 | console.warn = (function(fn) { 415 | return function(msg) { return fn(msg); }; 416 | })(console.warn); 417 | } 418 | })(); 419 | 420 | // Check onclick compatibility in Opera 421 | // Support: Opera<15 422 | (function checkOnClickCompatibility() { 423 | // workaround for reported Opera bug DSK-354448: 424 | // onclick fires on disabled buttons with opaque content 425 | function ignoreIfTargetDisabled(event) { 426 | if (isDisabled(event.target)) { 427 | event.stopPropagation(); 428 | } 429 | } 430 | function isDisabled(node) { 431 | return node.disabled || (node.parentNode && isDisabled(node.parentNode)); 432 | } 433 | if (navigator.userAgent.indexOf('Opera') !== -1) { 434 | // use browser detection since we cannot feature-check this bug 435 | document.addEventListener('click', ignoreIfTargetDisabled, true); 436 | } 437 | })(); 438 | 439 | // Checks if possible to use URL.createObjectURL() 440 | // Support: IE 441 | (function checkOnBlobSupport() { 442 | // sometimes IE loosing the data created with createObjectURL(), see #3977 443 | if (navigator.userAgent.indexOf('Trident') >= 0) { 444 | PDFJS.disableCreateObjectURL = true; 445 | } 446 | })(); 447 | 448 | // Checks if navigator.language is supported 449 | (function checkNavigatorLanguage() { 450 | if ('language' in navigator) { 451 | return; 452 | } 453 | PDFJS.locale = navigator.userLanguage || 'en-US'; 454 | })(); 455 | 456 | (function checkRangeRequests() { 457 | // Safari has issues with cached range requests see: 458 | // https://github.com/mozilla/pdf.js/issues/3260 459 | // Last tested with version 6.0.4. 460 | // Support: Safari 6.0+ 461 | var isSafari = Object.prototype.toString.call( 462 | window.HTMLElement).indexOf('Constructor') > 0; 463 | 464 | // Older versions of Android (pre 3.0) has issues with range requests, see: 465 | // https://github.com/mozilla/pdf.js/issues/3381. 466 | // Make sure that we only match webkit-based Android browsers, 467 | // since Firefox/Fennec works as expected. 468 | // Support: Android<3.0 469 | var regex = /Android\s[0-2][^\d]/; 470 | var isOldAndroid = regex.test(navigator.userAgent); 471 | 472 | // Range requests are broken in Chrome 39 and 40, https://crbug.com/442318 473 | var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(navigator.userAgent); 474 | 475 | if (isSafari || isOldAndroid || isChromeWithRangeBug) { 476 | PDFJS.disableRange = true; 477 | PDFJS.disableStream = true; 478 | } 479 | })(); 480 | 481 | // Check if the browser supports manipulation of the history. 482 | // Support: IE<10, Android<4.2 483 | (function checkHistoryManipulation() { 484 | // Android 2.x has so buggy pushState support that it was removed in 485 | // Android 3.0 and restored as late as in Android 4.2. 486 | // Support: Android 2.x 487 | if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) { 488 | PDFJS.disableHistory = true; 489 | } 490 | })(); 491 | 492 | // Support: IE<11, Chrome<21, Android<4.4, Safari<6 493 | (function checkSetPresenceInImageData() { 494 | // IE < 11 will use window.CanvasPixelArray which lacks set function. 495 | if (window.CanvasPixelArray) { 496 | if (typeof window.CanvasPixelArray.prototype.set !== 'function') { 497 | window.CanvasPixelArray.prototype.set = function(arr) { 498 | for (var i = 0, ii = this.length; i < ii; i++) { 499 | this[i] = arr[i]; 500 | } 501 | }; 502 | } 503 | } else { 504 | // Old Chrome and Android use an inaccessible CanvasPixelArray prototype. 505 | // Because we cannot feature detect it, we rely on user agent parsing. 506 | var polyfill = false, versionMatch; 507 | if (navigator.userAgent.indexOf('Chrom') >= 0) { 508 | versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); 509 | // Chrome < 21 lacks the set function. 510 | polyfill = versionMatch && parseInt(versionMatch[2]) < 21; 511 | } else if (navigator.userAgent.indexOf('Android') >= 0) { 512 | // Android < 4.4 lacks the set function. 513 | // Android >= 4.4 will contain Chrome in the user agent, 514 | // thus pass the Chrome check above and not reach this block. 515 | polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent); 516 | } else if (navigator.userAgent.indexOf('Safari') >= 0) { 517 | versionMatch = navigator.userAgent. 518 | match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//); 519 | // Safari < 6 lacks the set function. 520 | polyfill = versionMatch && parseInt(versionMatch[1]) < 6; 521 | } 522 | 523 | if (polyfill) { 524 | var contextPrototype = window.CanvasRenderingContext2D.prototype; 525 | contextPrototype._createImageData = contextPrototype.createImageData; 526 | contextPrototype.createImageData = function(w, h) { 527 | var imageData = this._createImageData(w, h); 528 | imageData.data.set = function(arr) { 529 | for (var i = 0, ii = this.length; i < ii; i++) { 530 | this[i] = arr[i]; 531 | } 532 | }; 533 | return imageData; 534 | }; 535 | } 536 | } 537 | })(); 538 | 539 | // Support: IE<10, Android<4.0, iOS 540 | (function checkRequestAnimationFrame() { 541 | function fakeRequestAnimationFrame(callback) { 542 | window.setTimeout(callback, 20); 543 | } 544 | 545 | var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent); 546 | if (isIOS) { 547 | // requestAnimationFrame on iOS is broken, replacing with fake one. 548 | window.requestAnimationFrame = fakeRequestAnimationFrame; 549 | return; 550 | } 551 | if ('requestAnimationFrame' in window) { 552 | return; 553 | } 554 | window.requestAnimationFrame = 555 | window.mozRequestAnimationFrame || 556 | window.webkitRequestAnimationFrame || 557 | fakeRequestAnimationFrame; 558 | })(); 559 | 560 | (function checkCanvasSizeLimitation() { 561 | var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent); 562 | var isAndroid = /Android/g.test(navigator.userAgent); 563 | if (isIOS || isAndroid) { 564 | // 5MP 565 | PDFJS.maxCanvasPixels = 5242880; 566 | } 567 | })(); 568 | 569 | // Disable fullscreen support for certain problematic configurations. 570 | // Support: IE11+ (when embedded). 571 | (function checkFullscreenSupport() { 572 | var isEmbeddedIE = (navigator.userAgent.indexOf('Trident') >= 0 && 573 | window.parent !== window); 574 | if (isEmbeddedIE) { 575 | PDFJS.disableFullscreen = true; 576 | } 577 | })(); 578 | -------------------------------------------------------------------------------- /laraview/example.local.css: -------------------------------------------------------------------------------- 1 | /* This is just a sample file with CSS rules. You should write your own @font-face declarations 2 | * to add support for your desired fonts. 3 | */ 4 | 5 | @font-face { 6 | font-family: 'Novecentowide Book'; 7 | src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot"); 8 | src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"), 9 | url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"), 10 | url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"), 11 | url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg"); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | @font-face { 17 | font-family: 'exotica'; 18 | src: url('/ViewerJS/fonts/Exotica-webfont.eot'); 19 | src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'), 20 | url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'), 21 | url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'), 22 | url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg'); 23 | font-weight: normal; 24 | font-style: normal; 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /laraview/images/kogmbh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/kogmbh.png -------------------------------------------------------------------------------- /laraview/images/nlnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/nlnet.png -------------------------------------------------------------------------------- /laraview/images/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/texture.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-download.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-fullscreen.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-menuArrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-menuArrows.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-pageDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-pageDown.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-pageUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-pageUp.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-presentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-presentation.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-zoomIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-zoomIn.png -------------------------------------------------------------------------------- /laraview/images/toolbarButton-zoomOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davcpas1234/laravel-pdf-viewer/be0a3a8ff9719708cc62a12db091551ba91c2f5f/laraview/images/toolbarButton-zoomOut.png -------------------------------------------------------------------------------- /laraview/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 31 | 32 | ViewerJS 33 | 38 | 39 | 77 | 78 | 79 | 80 |
81 |
82 |
83 |
84 | 85 | 86 | 87 |
88 |
89 |
90 |
91 |
92 | 97 | 98 | 99 | 100 |
101 |
102 |
103 |
104 | 105 |
106 | 107 |
108 | 109 | 121 | 122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | ✖ 140 |
141 |
142 |
143 |
144 | 145 | 146 | -------------------------------------------------------------------------------- /laraview/pdfjsversion.js: -------------------------------------------------------------------------------- 1 | var /**@const{!string}*/pdfjs_version = "v1.1.114"; 2 | -------------------------------------------------------------------------------- /laraview/text_layer_builder.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* Copyright 2012 Mozilla Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* globals CustomStyle, PDFJS */ 17 | 18 | 'use strict'; 19 | 20 | var MAX_TEXT_DIVS_TO_RENDER = 100000; 21 | 22 | var NonWhitespaceRegexp = /\S/; 23 | 24 | function isAllWhitespace(str) { 25 | return !NonWhitespaceRegexp.test(str); 26 | } 27 | 28 | /** 29 | * @typedef {Object} TextLayerBuilderOptions 30 | * @property {HTMLDivElement} textLayerDiv - The text layer container. 31 | * @property {number} pageIndex - The page index. 32 | * @property {PageViewport} viewport - The viewport of the text layer. 33 | * @property {PDFFindController} findController 34 | */ 35 | 36 | /** 37 | * TextLayerBuilder provides text-selection functionality for the PDF. 38 | * It does this by creating overlay divs over the PDF text. These divs 39 | * contain text that matches the PDF text they are overlaying. This object 40 | * also provides a way to highlight text that is being searched for. 41 | * @class 42 | */ 43 | var TextLayerBuilder = (function TextLayerBuilderClosure() { 44 | function TextLayerBuilder(options) { 45 | this.textLayerDiv = options.textLayerDiv; 46 | this.renderingDone = false; 47 | this.divContentDone = false; 48 | this.pageIdx = options.pageIndex; 49 | this.pageNumber = this.pageIdx + 1; 50 | this.matches = []; 51 | this.viewport = options.viewport; 52 | this.textDivs = []; 53 | this.findController = options.findController || null; 54 | } 55 | 56 | TextLayerBuilder.prototype = { 57 | _finishRendering: function TextLayerBuilder_finishRendering() { 58 | this.renderingDone = true; 59 | 60 | var event = document.createEvent('CustomEvent'); 61 | event.initCustomEvent('textlayerrendered', true, true, { 62 | pageNumber: this.pageNumber 63 | }); 64 | this.textLayerDiv.dispatchEvent(event); 65 | }, 66 | 67 | renderLayer: function TextLayerBuilder_renderLayer() { 68 | var textLayerFrag = document.createDocumentFragment(); 69 | var textDivs = this.textDivs; 70 | var textDivsLength = textDivs.length; 71 | var canvas = document.createElement('canvas'); 72 | var ctx = canvas.getContext('2d'); 73 | 74 | // No point in rendering many divs as it would make the browser 75 | // unusable even after the divs are rendered. 76 | if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) { 77 | this._finishRendering(); 78 | return; 79 | } 80 | 81 | var lastFontSize; 82 | var lastFontFamily; 83 | for (var i = 0; i < textDivsLength; i++) { 84 | var textDiv = textDivs[i]; 85 | if (textDiv.dataset.isWhitespace !== undefined) { 86 | continue; 87 | } 88 | 89 | var fontSize = textDiv.style.fontSize; 90 | var fontFamily = textDiv.style.fontFamily; 91 | 92 | // Only build font string and set to context if different from last. 93 | if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) { 94 | ctx.font = fontSize + ' ' + fontFamily; 95 | lastFontSize = fontSize; 96 | lastFontFamily = fontFamily; 97 | } 98 | 99 | var width = ctx.measureText(textDiv.textContent).width; 100 | if (width > 0) { 101 | textLayerFrag.appendChild(textDiv); 102 | var transform; 103 | if (textDiv.dataset.canvasWidth !== undefined) { 104 | // Dataset values come of type string. 105 | var textScale = textDiv.dataset.canvasWidth / width; 106 | transform = 'scaleX(' + textScale + ')'; 107 | } else { 108 | transform = ''; 109 | } 110 | var rotation = textDiv.dataset.angle; 111 | if (rotation) { 112 | transform = 'rotate(' + rotation + 'deg) ' + transform; 113 | } 114 | if (transform) { 115 | CustomStyle.setProp('transform' , textDiv, transform); 116 | } 117 | } 118 | } 119 | 120 | this.textLayerDiv.appendChild(textLayerFrag); 121 | this._finishRendering(); 122 | this.updateMatches(); 123 | }, 124 | 125 | /** 126 | * Renders the text layer. 127 | * @param {number} timeout (optional) if specified, the rendering waits 128 | * for specified amount of ms. 129 | */ 130 | render: function TextLayerBuilder_render(timeout) { 131 | if (!this.divContentDone || this.renderingDone) { 132 | return; 133 | } 134 | 135 | if (this.renderTimer) { 136 | clearTimeout(this.renderTimer); 137 | this.renderTimer = null; 138 | } 139 | 140 | if (!timeout) { // Render right away 141 | this.renderLayer(); 142 | } else { // Schedule 143 | var self = this; 144 | this.renderTimer = setTimeout(function() { 145 | self.renderLayer(); 146 | self.renderTimer = null; 147 | }, timeout); 148 | } 149 | }, 150 | 151 | appendText: function TextLayerBuilder_appendText(geom, styles) { 152 | var style = styles[geom.fontName]; 153 | var textDiv = document.createElement('div'); 154 | this.textDivs.push(textDiv); 155 | if (isAllWhitespace(geom.str)) { 156 | textDiv.dataset.isWhitespace = true; 157 | return; 158 | } 159 | var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform); 160 | var angle = Math.atan2(tx[1], tx[0]); 161 | if (style.vertical) { 162 | angle += Math.PI / 2; 163 | } 164 | var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3])); 165 | var fontAscent = fontHeight; 166 | if (style.ascent) { 167 | fontAscent = style.ascent * fontAscent; 168 | } else if (style.descent) { 169 | fontAscent = (1 + style.descent) * fontAscent; 170 | } 171 | 172 | var left; 173 | var top; 174 | if (angle === 0) { 175 | left = tx[4]; 176 | top = tx[5] - fontAscent; 177 | } else { 178 | left = tx[4] + (fontAscent * Math.sin(angle)); 179 | top = tx[5] - (fontAscent * Math.cos(angle)); 180 | } 181 | textDiv.style.left = left + 'px'; 182 | textDiv.style.top = top + 'px'; 183 | textDiv.style.fontSize = fontHeight + 'px'; 184 | textDiv.style.fontFamily = style.fontFamily; 185 | 186 | textDiv.textContent = geom.str; 187 | // |fontName| is only used by the Font Inspector. This test will succeed 188 | // when e.g. the Font Inspector is off but the Stepper is on, but it's 189 | // not worth the effort to do a more accurate test. 190 | if (PDFJS.pdfBug) { 191 | textDiv.dataset.fontName = geom.fontName; 192 | } 193 | // Storing into dataset will convert number into string. 194 | if (angle !== 0) { 195 | textDiv.dataset.angle = angle * (180 / Math.PI); 196 | } 197 | // We don't bother scaling single-char text divs, because it has very 198 | // little effect on text highlighting. This makes scrolling on docs with 199 | // lots of such divs a lot faster. 200 | if (textDiv.textContent.length > 1) { 201 | if (style.vertical) { 202 | textDiv.dataset.canvasWidth = geom.height * this.viewport.scale; 203 | } else { 204 | textDiv.dataset.canvasWidth = geom.width * this.viewport.scale; 205 | } 206 | } 207 | }, 208 | 209 | setTextContent: function TextLayerBuilder_setTextContent(textContent) { 210 | this.textContent = textContent; 211 | 212 | var textItems = textContent.items; 213 | for (var i = 0, len = textItems.length; i < len; i++) { 214 | this.appendText(textItems[i], textContent.styles); 215 | } 216 | this.divContentDone = true; 217 | }, 218 | 219 | convertMatches: function TextLayerBuilder_convertMatches(matches) { 220 | var i = 0; 221 | var iIndex = 0; 222 | var bidiTexts = this.textContent.items; 223 | var end = bidiTexts.length - 1; 224 | var queryLen = (this.findController === null ? 225 | 0 : this.findController.state.query.length); 226 | var ret = []; 227 | 228 | for (var m = 0, len = matches.length; m < len; m++) { 229 | // Calculate the start position. 230 | var matchIdx = matches[m]; 231 | 232 | // Loop over the divIdxs. 233 | while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { 234 | iIndex += bidiTexts[i].str.length; 235 | i++; 236 | } 237 | 238 | if (i === bidiTexts.length) { 239 | console.error('Could not find a matching mapping'); 240 | } 241 | 242 | var match = { 243 | begin: { 244 | divIdx: i, 245 | offset: matchIdx - iIndex 246 | } 247 | }; 248 | 249 | // Calculate the end position. 250 | matchIdx += queryLen; 251 | 252 | // Somewhat the same array as above, but use > instead of >= to get 253 | // the end position right. 254 | while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { 255 | iIndex += bidiTexts[i].str.length; 256 | i++; 257 | } 258 | 259 | match.end = { 260 | divIdx: i, 261 | offset: matchIdx - iIndex 262 | }; 263 | ret.push(match); 264 | } 265 | 266 | return ret; 267 | }, 268 | 269 | renderMatches: function TextLayerBuilder_renderMatches(matches) { 270 | // Early exit if there is nothing to render. 271 | if (matches.length === 0) { 272 | return; 273 | } 274 | 275 | var bidiTexts = this.textContent.items; 276 | var textDivs = this.textDivs; 277 | var prevEnd = null; 278 | var pageIdx = this.pageIdx; 279 | var isSelectedPage = (this.findController === null ? 280 | false : (pageIdx === this.findController.selected.pageIdx)); 281 | var selectedMatchIdx = (this.findController === null ? 282 | -1 : this.findController.selected.matchIdx); 283 | var highlightAll = (this.findController === null ? 284 | false : this.findController.state.highlightAll); 285 | var infinity = { 286 | divIdx: -1, 287 | offset: undefined 288 | }; 289 | 290 | function beginText(begin, className) { 291 | var divIdx = begin.divIdx; 292 | textDivs[divIdx].textContent = ''; 293 | appendTextToDiv(divIdx, 0, begin.offset, className); 294 | } 295 | 296 | function appendTextToDiv(divIdx, fromOffset, toOffset, className) { 297 | var div = textDivs[divIdx]; 298 | var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset); 299 | var node = document.createTextNode(content); 300 | if (className) { 301 | var span = document.createElement('span'); 302 | span.className = className; 303 | span.appendChild(node); 304 | div.appendChild(span); 305 | return; 306 | } 307 | div.appendChild(node); 308 | } 309 | 310 | var i0 = selectedMatchIdx, i1 = i0 + 1; 311 | if (highlightAll) { 312 | i0 = 0; 313 | i1 = matches.length; 314 | } else if (!isSelectedPage) { 315 | // Not highlighting all and this isn't the selected page, so do nothing. 316 | return; 317 | } 318 | 319 | for (var i = i0; i < i1; i++) { 320 | var match = matches[i]; 321 | var begin = match.begin; 322 | var end = match.end; 323 | var isSelected = (isSelectedPage && i === selectedMatchIdx); 324 | var highlightSuffix = (isSelected ? ' selected' : ''); 325 | 326 | if (this.findController) { 327 | this.findController.updateMatchPosition(pageIdx, i, textDivs, 328 | begin.divIdx, end.divIdx); 329 | } 330 | 331 | // Match inside new div. 332 | if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { 333 | // If there was a previous div, then add the text at the end. 334 | if (prevEnd !== null) { 335 | appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset); 336 | } 337 | // Clear the divs and set the content until the starting point. 338 | beginText(begin); 339 | } else { 340 | appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset); 341 | } 342 | 343 | if (begin.divIdx === end.divIdx) { 344 | appendTextToDiv(begin.divIdx, begin.offset, end.offset, 345 | 'highlight' + highlightSuffix); 346 | } else { 347 | appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, 348 | 'highlight begin' + highlightSuffix); 349 | for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) { 350 | textDivs[n0].className = 'highlight middle' + highlightSuffix; 351 | } 352 | beginText(end, 'highlight end' + highlightSuffix); 353 | } 354 | prevEnd = end; 355 | } 356 | 357 | if (prevEnd) { 358 | appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset); 359 | } 360 | }, 361 | 362 | updateMatches: function TextLayerBuilder_updateMatches() { 363 | // Only show matches when all rendering is done. 364 | if (!this.renderingDone) { 365 | return; 366 | } 367 | 368 | // Clear all matches. 369 | var matches = this.matches; 370 | var textDivs = this.textDivs; 371 | var bidiTexts = this.textContent.items; 372 | var clearedUntilDivIdx = -1; 373 | 374 | // Clear all current matches. 375 | for (var i = 0, len = matches.length; i < len; i++) { 376 | var match = matches[i]; 377 | var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); 378 | for (var n = begin, end = match.end.divIdx; n <= end; n++) { 379 | var div = textDivs[n]; 380 | div.textContent = bidiTexts[n].str; 381 | div.className = ''; 382 | } 383 | clearedUntilDivIdx = match.end.divIdx + 1; 384 | } 385 | 386 | if (this.findController === null || !this.findController.active) { 387 | return; 388 | } 389 | 390 | // Convert the matches on the page controller into the match format 391 | // used for the textLayer. 392 | this.matches = this.convertMatches(this.findController === null ? 393 | [] : (this.findController.pageMatches[this.pageIdx] || [])); 394 | this.renderMatches(this.matches); 395 | } 396 | }; 397 | return TextLayerBuilder; 398 | })(); 399 | 400 | /** 401 | * @constructor 402 | * @implements IPDFTextLayerFactory 403 | */ 404 | function DefaultTextLayerFactory() {} 405 | DefaultTextLayerFactory.prototype = { 406 | /** 407 | * @param {HTMLDivElement} textLayerDiv 408 | * @param {number} pageIndex 409 | * @param {PageViewport} viewport 410 | * @returns {TextLayerBuilder} 411 | */ 412 | createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { 413 | return new TextLayerBuilder({ 414 | textLayerDiv: textLayerDiv, 415 | pageIndex: pageIndex, 416 | viewport: viewport 417 | }); 418 | } 419 | }; 420 | -------------------------------------------------------------------------------- /laraview/ui_utils.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* Copyright 2012 Mozilla Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | var CSS_UNITS = 96.0 / 72.0; 20 | var DEFAULT_SCALE = 'auto'; 21 | var UNKNOWN_SCALE = 0; 22 | var MAX_AUTO_SCALE = 1.25; 23 | var SCROLLBAR_PADDING = 40; 24 | var VERTICAL_PADDING = 5; 25 | 26 | // optimised CSS custom property getter/setter 27 | var CustomStyle = (function CustomStyleClosure() { 28 | 29 | // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ 30 | // animate-css-transforms-firefox-webkit.html 31 | // in some versions of IE9 it is critical that ms appear in this list 32 | // before Moz 33 | var prefixes = ['ms', 'Moz', 'Webkit', 'O']; 34 | var _cache = {}; 35 | 36 | function CustomStyle() {} 37 | 38 | CustomStyle.getProp = function get(propName, element) { 39 | // check cache only when no element is given 40 | if (arguments.length === 1 && typeof _cache[propName] === 'string') { 41 | return _cache[propName]; 42 | } 43 | 44 | element = element || document.documentElement; 45 | var style = element.style, prefixed, uPropName; 46 | 47 | // test standard property first 48 | if (typeof style[propName] === 'string') { 49 | return (_cache[propName] = propName); 50 | } 51 | 52 | // capitalize 53 | uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); 54 | 55 | // test vendor specific properties 56 | for (var i = 0, l = prefixes.length; i < l; i++) { 57 | prefixed = prefixes[i] + uPropName; 58 | if (typeof style[prefixed] === 'string') { 59 | return (_cache[propName] = prefixed); 60 | } 61 | } 62 | 63 | //if all fails then set to undefined 64 | return (_cache[propName] = 'undefined'); 65 | }; 66 | 67 | CustomStyle.setProp = function set(propName, element, str) { 68 | var prop = this.getProp(propName); 69 | if (prop !== 'undefined') { 70 | element.style[prop] = str; 71 | } 72 | }; 73 | 74 | return CustomStyle; 75 | })(); 76 | 77 | function getFileName(url) { 78 | var anchor = url.indexOf('#'); 79 | var query = url.indexOf('?'); 80 | var end = Math.min( 81 | anchor > 0 ? anchor : url.length, 82 | query > 0 ? query : url.length); 83 | return url.substring(url.lastIndexOf('/', end) + 1, end); 84 | } 85 | 86 | /** 87 | * Returns scale factor for the canvas. It makes sense for the HiDPI displays. 88 | * @return {Object} The object with horizontal (sx) and vertical (sy) 89 | scales. The scaled property is set to false if scaling is 90 | not required, true otherwise. 91 | */ 92 | function getOutputScale(ctx) { 93 | var devicePixelRatio = window.devicePixelRatio || 1; 94 | var backingStoreRatio = ctx.webkitBackingStorePixelRatio || 95 | ctx.mozBackingStorePixelRatio || 96 | ctx.msBackingStorePixelRatio || 97 | ctx.oBackingStorePixelRatio || 98 | ctx.backingStorePixelRatio || 1; 99 | var pixelRatio = devicePixelRatio / backingStoreRatio; 100 | return { 101 | sx: pixelRatio, 102 | sy: pixelRatio, 103 | scaled: pixelRatio !== 1 104 | }; 105 | } 106 | 107 | /** 108 | * Scrolls specified element into view of its parent. 109 | * element {Object} The element to be visible. 110 | * spot {Object} An object with optional top and left properties, 111 | * specifying the offset from the top left edge. 112 | */ 113 | function scrollIntoView(element, spot) { 114 | // Assuming offsetParent is available (it's not available when viewer is in 115 | // hidden iframe or object). We have to scroll: if the offsetParent is not set 116 | // producing the error. See also animationStartedClosure. 117 | var parent = element.offsetParent; 118 | var offsetY = element.offsetTop + element.clientTop; 119 | var offsetX = element.offsetLeft + element.clientLeft; 120 | if (!parent) { 121 | console.error('offsetParent is not set -- cannot scroll'); 122 | return; 123 | } 124 | while (parent.clientHeight === parent.scrollHeight) { 125 | if (parent.dataset._scaleY) { 126 | offsetY /= parent.dataset._scaleY; 127 | offsetX /= parent.dataset._scaleX; 128 | } 129 | offsetY += parent.offsetTop; 130 | offsetX += parent.offsetLeft; 131 | parent = parent.offsetParent; 132 | if (!parent) { 133 | return; // no need to scroll 134 | } 135 | } 136 | if (spot) { 137 | if (spot.top !== undefined) { 138 | offsetY += spot.top; 139 | } 140 | if (spot.left !== undefined) { 141 | offsetX += spot.left; 142 | parent.scrollLeft = offsetX; 143 | } 144 | } 145 | parent.scrollTop = offsetY; 146 | } 147 | 148 | /** 149 | * Helper function to start monitoring the scroll event and converting them into 150 | * PDF.js friendly one: with scroll debounce and scroll direction. 151 | */ 152 | function watchScroll(viewAreaElement, callback) { 153 | var debounceScroll = function debounceScroll(evt) { 154 | if (rAF) { 155 | return; 156 | } 157 | // schedule an invocation of scroll for next animation frame. 158 | rAF = window.requestAnimationFrame(function viewAreaElementScrolled() { 159 | rAF = null; 160 | 161 | var currentY = viewAreaElement.scrollTop; 162 | var lastY = state.lastY; 163 | if (currentY !== lastY) { 164 | state.down = currentY > lastY; 165 | } 166 | state.lastY = currentY; 167 | callback(state); 168 | }); 169 | }; 170 | 171 | var state = { 172 | down: true, 173 | lastY: viewAreaElement.scrollTop, 174 | _eventHandler: debounceScroll 175 | }; 176 | 177 | var rAF = null; 178 | viewAreaElement.addEventListener('scroll', debounceScroll, true); 179 | return state; 180 | } 181 | 182 | /** 183 | * Use binary search to find the index of the first item in a given array which 184 | * passes a given condition. The items are expected to be sorted in the sense 185 | * that if the condition is true for one item in the array, then it is also true 186 | * for all following items. 187 | * 188 | * @returns {Number} Index of the first array element to pass the test, 189 | * or |items.length| if no such element exists. 190 | */ 191 | function binarySearchFirstItem(items, condition) { 192 | var minIndex = 0; 193 | var maxIndex = items.length - 1; 194 | 195 | if (items.length === 0 || !condition(items[maxIndex])) { 196 | return items.length; 197 | } 198 | if (condition(items[minIndex])) { 199 | return minIndex; 200 | } 201 | 202 | while (minIndex < maxIndex) { 203 | var currentIndex = (minIndex + maxIndex) >> 1; 204 | var currentItem = items[currentIndex]; 205 | if (condition(currentItem)) { 206 | maxIndex = currentIndex; 207 | } else { 208 | minIndex = currentIndex + 1; 209 | } 210 | } 211 | return minIndex; /* === maxIndex */ 212 | } 213 | 214 | /** 215 | * Generic helper to find out what elements are visible within a scroll pane. 216 | */ 217 | function getVisibleElements(scrollEl, views, sortByVisibility) { 218 | var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight; 219 | var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth; 220 | 221 | function isElementBottomBelowViewTop(view) { 222 | var element = view.div; 223 | var elementBottom = 224 | element.offsetTop + element.clientTop + element.clientHeight; 225 | return elementBottom > top; 226 | } 227 | 228 | var visible = [], view, element; 229 | var currentHeight, viewHeight, hiddenHeight, percentHeight; 230 | var currentWidth, viewWidth; 231 | var firstVisibleElementInd = (views.length === 0) ? 0 : 232 | binarySearchFirstItem(views, isElementBottomBelowViewTop); 233 | 234 | for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) { 235 | view = views[i]; 236 | element = view.div; 237 | currentHeight = element.offsetTop + element.clientTop; 238 | viewHeight = element.clientHeight; 239 | 240 | if (currentHeight > bottom) { 241 | break; 242 | } 243 | 244 | currentWidth = element.offsetLeft + element.clientLeft; 245 | viewWidth = element.clientWidth; 246 | if (currentWidth + viewWidth < left || currentWidth > right) { 247 | continue; 248 | } 249 | hiddenHeight = Math.max(0, top - currentHeight) + 250 | Math.max(0, currentHeight + viewHeight - bottom); 251 | percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0; 252 | 253 | visible.push({ 254 | id: view.id, 255 | x: currentWidth, 256 | y: currentHeight, 257 | view: view, 258 | percent: percentHeight 259 | }); 260 | } 261 | 262 | var first = visible[0]; 263 | var last = visible[visible.length - 1]; 264 | 265 | if (sortByVisibility) { 266 | visible.sort(function(a, b) { 267 | var pc = a.percent - b.percent; 268 | if (Math.abs(pc) > 0.001) { 269 | return -pc; 270 | } 271 | return a.id - b.id; // ensure stability 272 | }); 273 | } 274 | return {first: first, last: last, views: visible}; 275 | } 276 | 277 | /** 278 | * Event handler to suppress context menu. 279 | */ 280 | function noContextMenuHandler(e) { 281 | e.preventDefault(); 282 | } 283 | 284 | /** 285 | * Returns the filename or guessed filename from the url (see issue 3455). 286 | * url {String} The original PDF location. 287 | * @return {String} Guessed PDF file name. 288 | */ 289 | function getPDFFileNameFromURL(url) { 290 | var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; 291 | // SCHEME HOST 1.PATH 2.QUERY 3.REF 292 | // Pattern to get last matching NAME.pdf 293 | var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i; 294 | var splitURI = reURI.exec(url); 295 | var suggestedFilename = reFilename.exec(splitURI[1]) || 296 | reFilename.exec(splitURI[2]) || 297 | reFilename.exec(splitURI[3]); 298 | if (suggestedFilename) { 299 | suggestedFilename = suggestedFilename[0]; 300 | if (suggestedFilename.indexOf('%') !== -1) { 301 | // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf 302 | try { 303 | suggestedFilename = 304 | reFilename.exec(decodeURIComponent(suggestedFilename))[0]; 305 | } catch(e) { // Possible (extremely rare) errors: 306 | // URIError "Malformed URI", e.g. for "%AA.pdf" 307 | // TypeError "null has no properties", e.g. for "%2F.pdf" 308 | } 309 | } 310 | } 311 | return suggestedFilename || 'document.pdf'; 312 | } 313 | 314 | var ProgressBar = (function ProgressBarClosure() { 315 | 316 | function clamp(v, min, max) { 317 | return Math.min(Math.max(v, min), max); 318 | } 319 | 320 | function ProgressBar(id, opts) { 321 | this.visible = true; 322 | 323 | // Fetch the sub-elements for later. 324 | this.div = document.querySelector(id + ' .progress'); 325 | 326 | // Get the loading bar element, so it can be resized to fit the viewer. 327 | this.bar = this.div.parentNode; 328 | 329 | // Get options, with sensible defaults. 330 | this.height = opts.height || 100; 331 | this.width = opts.width || 100; 332 | this.units = opts.units || '%'; 333 | 334 | // Initialize heights. 335 | this.div.style.height = this.height + this.units; 336 | this.percent = 0; 337 | } 338 | 339 | ProgressBar.prototype = { 340 | 341 | updateBar: function ProgressBar_updateBar() { 342 | if (this._indeterminate) { 343 | this.div.classList.add('indeterminate'); 344 | this.div.style.width = this.width + this.units; 345 | return; 346 | } 347 | 348 | this.div.classList.remove('indeterminate'); 349 | var progressSize = this.width * this._percent / 100; 350 | this.div.style.width = progressSize + this.units; 351 | }, 352 | 353 | get percent() { 354 | return this._percent; 355 | }, 356 | 357 | set percent(val) { 358 | this._indeterminate = isNaN(val); 359 | this._percent = clamp(val, 0, 100); 360 | this.updateBar(); 361 | }, 362 | 363 | setWidth: function ProgressBar_setWidth(viewer) { 364 | if (viewer) { 365 | var container = viewer.parentNode; 366 | var scrollbarWidth = container.offsetWidth - viewer.offsetWidth; 367 | if (scrollbarWidth > 0) { 368 | this.bar.setAttribute('style', 'width: calc(100% - ' + 369 | scrollbarWidth + 'px);'); 370 | } 371 | } 372 | }, 373 | 374 | hide: function ProgressBar_hide() { 375 | if (!this.visible) { 376 | return; 377 | } 378 | this.visible = false; 379 | this.bar.classList.add('hidden'); 380 | document.body.classList.remove('loadingInProgress'); 381 | }, 382 | 383 | show: function ProgressBar_show() { 384 | if (this.visible) { 385 | return; 386 | } 387 | this.visible = true; 388 | document.body.classList.add('loadingInProgress'); 389 | this.bar.classList.remove('hidden'); 390 | } 391 | }; 392 | 393 | return ProgressBar; 394 | })(); 395 | -------------------------------------------------------------------------------- /src/Facade/LaravelPdfViewer.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) David Passmore 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Davcpas1234\LaravelPdfViewer\Facade; 14 | use Illuminate\Support\Facades\Facade; 15 | 16 | class LaravelPdfViewer extends Facade 17 | { 18 | protected static function getFacadeAccessor() 19 | { 20 | return 'laravelpdfviewer'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaravelPdfViewerServiceProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) David Passmore 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Davcpas1234\LaravelPdfViewer; 14 | 15 | use Illuminate\Support\ServiceProvider; 16 | 17 | class LaravelPdfViewerServiceProvider extends ServiceProvider 18 | { 19 | /** 20 | * Bootstrap the application services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->publishes([ 27 | __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'laraview' => public_path('laraview'), 28 | ], 'public'); 29 | } 30 | 31 | /** 32 | * Register the application services. 33 | * 34 | * @return void 35 | */ 36 | public function register() 37 | { 38 | // 39 | } 40 | } 41 | --------------------------------------------------------------------------------