PDFJS.reader = {}; PDFJS.reader.plugins = {}; PDFJS.TextLayerBuilder = {}; (function(root, $) { var previousReader = root.pdfReader || {}; var pdfReader = root.pdfReader = function(path, options) { return new PDFJS.Reader(path, options); }; })(window, jQuery); PDFJS.Reader = function(bookPath, _options) { var reader = this, book, loader, $viewer = $("#viewer"), search = window.location.search; var TEXT_RENDER_DELAY = 200, // ms PAGE_RENDER_DELAY = 200, // ms LAZY_DELAY = 200, // ms INITIAL_LAZY_DELAY = 2000, // ms PRELOAD_OFFSET = 500, // px THUMBNAIL_WIDTH = 200, // px MAX_CANVAS_PIXELS = 5242880, CSS_UNITS = 96.0 / 72.0, MIN_SCALE = 0.25, MAX_SCALE = 10.0, DEFAULT_SCALE = 1; this.settings = this.defaults(_options || {}, { bookPath: bookPath, textRenderDelay: TEXT_RENDER_DELAY, pageRenderDelay: PAGE_RENDER_DELAY, canvasLimit: 0, cssZoomOnly: false, // true || false, only zoom using CSS, render document at 100% size textSelect: false, // true || false, add selectable text layer doubleBuffer: true, // true || false, draw to off-screen canvas cacheNext: true, // true || false, pre-render next page (by creathing thumbnail)) numPages: 0, currentPage: 1, scale: DEFAULT_SCALE, oddPageRight: true, // when true, odd pages render on the right side zoomLevel: window.outerWidth > window.outerHeight ? "spread" : "fit_page", // spread, fit_page, fit_width, percentage rotation: 0, // 0 || 90 || 180 || 270 thumbnails: false, // true || false, show thumbnails (visual index) thumbnailWidth: THUMBNAIL_WIDTH, lazyDelay: LAZY_DELAY, // ms, delay before lazyloader loads image initialLazyDelay: INITIAL_LAZY_DELAY, // ms, delay before preloading images for lazyloader preloadOffset: PRELOAD_OFFSET, // px, preload thumbs when they are within this distance from viewport history: true, keyboard: { 32: 'next', // space 34: 'next', // page-down 39: 'next', // cursor-right 33: 'previous', // page-up 37: 'previous', // cursor-left 36: 'first', // home 35: 'last', // end 65: 'annotate', // a 66: 'bookmark', // b 76: 'rotateLeft', // l 82: 'rotateRight', // r 90: 'cycleZoom', // z 83: 'toggleSidebar',// s 84: 'toggleTitlebar', // t 68: 'toggleDay', // d 78: 'toggleNight', // n 70: 'toggleFullscreen', // f 27: 'closeSidebar' // esc }, nightMode: false, dayMode: false, maxWidth: 72, pageArrows: false, annotations: {}, customStyles: {}, activeStyles: {}, session: {} }); // used for annotations and bookmarks this.Annotation = function (type, anchor, body, id) { this.id = id || PDFJS.core.uuid(); this.type = type; this.date = Date.now(); this.edited = this.date; this.anchor = anchor; this.body = body; }; // used for UI and book styles this.Style = function (name, selector, rules, extra) { this.name = name; this.selector = selector; this.rules = rules; this.extra = extra || null; }; this.resourcelst = [ { canvas: document.getElementById("left"), ctx: document.getElementById("left").getContext('2d'), textdiv: document.getElementById("text_left"), textLayer: null, renderTask: null, oscanvas: null, osctx: null, pageNum: null }, { canvas: document.getElementById("right"), ctx: document.getElementById("right").getContext('2d'), textdiv: document.getElementById("text_right"), textLayer: null, renderTask: null, oscanvas: null, osctx: null, pageNum: null } ]; this.cancelPage = {}; this.renderQueue = false; // define which zoom states to cycle through in cycleZoom this.zoomCycle = { 'spread': 'fit_page', 'fit_page': 'fit_width', 'fit_width':'spread' }; this.thumbs = []; this.pageLabels = []; this.pageRefs = {}; // Overide options with search parameters if(search) { parameters = search.slice(1).split("&"); parameters.forEach(function(p){ var split = p.split("="); var name = split[0]; var value = split[1] || ''; reader.settings[name] = decodeURIComponent(value); }); } //this.restoreDefaults(this.settings.session.defaults); //this.restorePreferences(this.settings.session.preferences); //this.restoreAnnotations(this.settings.session.annotations); this.sideBarOpen = false; this.viewerResized = false; this.pageNumPending = null; this.output_scaled = false; this.restricted_scaling = false; this.CSS_UNITS = CSS_UNITS; this.MIN_SCALE = MIN_SCALE; this.MAX_SCALE = MAX_SCALE; reader.ProgressController = PDFJS.reader.ProgressController.call(reader); loadingTask = PDFJS.getDocument(reader.settings.bookPath); reader.ProgressController.show(); reader.ProgressController.setMessage("Loading " + reader.settings.session.title, "download", "active"); loadingTask.onProgress = function getDocumentProgress(progress) { reader.ProgressController.setProgress(progress); }; loadingTask.then( function(_book) { reader.book = book = _book; console.log(book); reader.settings.numPages = reader.book.numPages; document.getElementById('total_pages').textContent = reader.settings.numPages; if(!$.isEmptyObject(reader.settings.session.cursor)) { console.log("setting cursor:", reader.settings.session.cursor); reader.settings.currentPage = parseInt(reader.settings.session.cursor.anchor); } var firstPagePromise = book.getPage(1); reader.firstPagePromise = firstPagePromise; // set labels reader.book.getPageLabels().then(function (labels) { if (labels) { for (var i = 0; i < labels.length; i++) { if (labels[i] !== (i + 1).toString()) { reader.pageLabels[i + 1] = labels[i]; } } } }); reader.ReaderController = PDFJS.reader.ReaderController.call(reader, book); reader.SettingsController = PDFJS.reader.SettingsController.call(reader, book); reader.ControlsController = PDFJS.reader.ControlsController.call(reader, book); reader.SidebarController = PDFJS.reader.SidebarController.call(reader, book); reader.queuePage(reader.settings.currentPage); reader.ReaderController.hideLoader(); reader.ProgressController.hide(); reader.book.getOutline().then(function (outline) { reader.OutlineController = PDFJS.reader.OutlineController.call(reader, outline); }); reader.book.getMetadata().then(function (metadata) { console.log("metadata", metadata); reader.settings.pdfMetadata = metadata; }); reader.book.getAttachments().then(function (attachments) { console.log("attachments", attachments); }); // BookmarksController depends on NotesController so load NotesController first //reader.NotesController = PDFJS.reader.NotesController.call(reader, book); //reader.BookmarksController = PDFJS.reader.BookmarksController.call(reader, book); reader.SearchController = PDFJS.reader.SearchController.call(reader, book); //reader.MetaController = PDFJS.reader.MetaController.call(reader, meta); reader.TocController = PDFJS.reader.TocController.call(reader, book); }, function getDocumentError(exception) { var message = exception && exception.message; var errormsg = "An error occurred while loading the PDF"; if (exception instanceof PDFJS.InvalidPDFException) { errormsg = "Invalid or corrupted PDF file"; } else if (exception instanceof PDFJS.MissingPDFException) { errormsg = "Missing PDF file"; } else if (exception instanceof PDFJS.UnexpectedResponseException) { errormsg = "Unexpected server response"; } console.log("Reader: ", errormsg); reader.ProgressController.setMessage(errormsg, "download", "error"); } ); return this; }; PDFJS.Reader.prototype.getThumb = function (pageNum, insert) { var reader = this, thumb, scale, initial_viewport, viewport, canvas, ctx, page_aspect, page_width, page_height, page_rotation, rotation, outputscale, transform, renderContext, renderTask; if (reader.thumbs[pageNum] === undefined) { reader.thumbs[pageNum] = true; reader.book.getPage(parseInt(pageNum)).then(function(page) { page_rotation = page.rotate; rotation = (page_rotation + reader.settings.rotation) % 360; initial_viewport = page.getViewport(1, rotation); canvas = document.createElement("canvas"); ctx = canvas.getContext("2d"); outputscale = reader.getOutputScale(ctx); if (outputscale < 1) outputscale = 1; // ignore browser zoom page_width = initial_viewport.width; page_height = initial_viewport.height; page_aspect = parseFloat(page_width / page_height); scale = parseFloat(reader.settings.thumbnailWidth / page_width); canvas.width = parseInt(reader.settings.thumbnailWidth * outputscale); canvas.height = parseInt(canvas.width / page_aspect); viewport = initial_viewport.clone({scale: scale, rotation: rotation}); //ctx.scale(outputscale, outputscale); transform = (outputscale === 1) ? null : [outputscale, 0, 0, outputscale, 0, 0]; renderContext = { canvasContext: ctx, viewport: viewport, transform: transform }; renderTask = page.render(renderContext); renderTask.promise.then( function pdfPageRenderCallback () { thumb = new Image(); thumb.id = "thumb_" + pageNum; thumb.className = "thumbnail"; thumb.src = canvas.toDataURL(); canvas.width = canvas.height = 0; delete canvas; if (insert) { reader.TocController.tocInsert(thumb, pageNum, true); } else { reader.thumbs[pageNum] = thumb; } }, function pdfPageRenderError (error) { console.log("pdfPageRenderError in getThumb: " + error); } ); }); } }; PDFJS.Reader.prototype.setZoom = function(zoom) { var reader = this, page = reader.settings.currentPage; reader.settings.zoomLevel = zoom; reader.ControlsController.setZoomIcon(zoom); reader.queuePage(page); }; PDFJS.Reader.prototype.cycleZoom = function() { var reader = this, zoom = reader.settings.zoomLevel, nextzoom, page = reader.settings.currentPage; nextzoom = reader.zoomCycle[zoom]; if (nextzoom !== undefined) { reader.setZoom(nextzoom); } }; PDFJS.Reader.prototype.setRotation = function (rotation) { var reader = this, page = reader.settings.currentPage; if (rotation % 90 === 0) { reader.settings.rotation = rotation; reader.ControlsController.setRotateIcon(rotation); reader.queuePage(page); } }; PDFJS.Reader.prototype.cancelRender = function (index) { var reader = this, resourcelst = reader.resourcelst[index]; if (resourcelst.renderTask) { resourcelst.renderTask.cancel(); resourcelst.renderTask = resourcelst.pageNum = null; resourcelst.oscanvas = resourcelst.osctx = null; } if (resourcelst.textLayer) { resourcelst.textLayer.cancel(); resourcelst.textLayer = null; } }; PDFJS.Reader.prototype.renderPage = function(pageNum) { var reader = this, $viewer = $("#viewer"); var index, canvas, // actual canvas ctx, // actual context oscanvas, // off-screen canvas osctx, // off-screen context textdiv, textLayer, outputscale, max_view_width, max_view_height, page_width, page_height, scale_width, scale_height, view_aspect, document_aspect, scale, page_rotation, rotation, initial_viewport, viewport, zoom, fraction, offset, renderContext, renderTask, resourcelst, swap_orientation, double_buffer, cache_next; max_view_width = window.innerWidth; max_view_height = window.innerHeight; if (this.settings.zoomLevel === "spread") { // show second canvas reader.resourcelst[1].canvas.style.display = "block"; max_view_width /= 2; // select canvas and ctx based on pageNum, pageShift and oddPageRight pageShift = 2; oddPageShift = reader.settings.oddPageRight ? 0 : 1; index = (pageNum - oddPageShift) % pageShift; } else { index = 0; // hide second canvas reader.resourcelst[1].canvas.style.display = "none"; // clear text layer reader.resourcelst[1].textdiv.innerHTML = ""; // don't try to render non-existing page 0 (which is used // to indicate the empty left page when oddPageRight === true) if (pageNum === 0) pageNum++; } resourcelst = reader.resourcelst[index]; canvas = resourcelst.canvas; ctx = resourcelst.ctx; textdiv = resourcelst.textdiv; outputscale = reader.getOutputScale(resourcelst.ctx); fraction = reader.approximateFraction(outputscale); double_buffer = reader.settings.doubleBuffer; cache_next = reader.settings.cacheNext; if (pageNum <= this.settings.numPages && pageNum >= 1) { if (resourcelst.renderTask) { resourcelst.renderTask.cancel(); resourcelst.renderTask = null; } if (resourcelst.textLayer) { resourcelst.textLayer.cancel(); resourcelst.textLayer = null; } resourcelst.pageNum = pageNum; if (reader.cancelPage[pageNum]) delete reader.cancelPage[pageNum]; this.book.getPage(pageNum).then(function(page) { //console.log(page); page_rotation = page.rotate; rotation = (page_rotation + reader.settings.rotation) % 360; //initial_viewport = page.getViewport({scale: 1, rotation: rotation}); initial_viewport = page.getViewport(1, rotation); page_width = initial_viewport.width; page_height = initial_viewport.height; document_aspect = parseFloat(page_width / page_height); view_aspect = parseFloat(max_view_width / max_view_height); scale_height = parseFloat(max_view_height / page_height); scale_width = parseFloat(max_view_width / page_width); /* console.log("m_v_w: " + max_view_width + " m_v_h: " + max_view_height + " p_w: " + page_width + " p_h: " + page_height + " d_a: " + document_aspect + " v_a: " + view_aspect + " s_w: " + scale_width + " s_h: " + scale_height + " p_r: " + page_rotation + " r: " + rotation + " o: " + outputscale); console.log("fraction: ", fraction); */ switch (reader.settings.zoomLevel) { case "spread": // INTENTIONAL FALL-THROUGH case "fit_page": $viewer.addClass("flex"); if (scale_width > scale_height) { scale = scale_height; canvas.height = reader.roundToDivide(max_view_height * outputscale, fraction[0]); canvas.width = reader.roundToDivide(parseInt(canvas.height * document_aspect), fraction[0]); } else { scale = scale_width; canvas.width = reader.roundToDivide(max_view_width * outputscale, fraction[0]); canvas.height = reader.roundToDivide(parseInt(canvas.width / document_aspect), fraction[0]); } break; case "fit_width": $viewer.removeClass("flex"); if (scale_width < scale_height) { scale = scale_height; canvas.height = reader.roundToDivide(max_view_height * outputscale, fraction[0]); canvas.width = reader.roundToDivide(parseInt(canvas.height * document_aspect), fraction[0]); } else { scale = scale_width; canvas.width = reader.roundToDivide(max_view_width * outputscale, fraction[0]); canvas.height = reader.roundToDivide(parseInt(canvas.width / document_aspect), fraction[0]); } break; default: $viewer.removeClass("flex"); scale = parseFloat(reader.settings.zoomLevel * reader.CSS_UNITS); canvas.width = reader.roundToDivide(parseInt(page_width * scale * outputscale), fraction[0]); canvas.height = reader.roundToDivide(parseInt(page_height * scale * outputscale), fraction[0]); break; } viewport = initial_viewport.clone({scale: scale, rotation: rotation}); if (reader.settings.cssZoomOnly) { var actualSizeViewport = viewport.clone({scale: 1}); canvas.width = actualSizeViewport.width; canvas.height = actualSizeViewport.height; outputscale = actualSizeViewport.width / viewport.width; reader.output_scaled = true; } if (reader.settings.canvasLimit > 0) { var pixelsInViewport = viewport.width * viewport.height; var maxscale = Math.sqrt(reader.settings.canvasLimit / pixelsInViewport); if (outputscale > maxscale) { outputscale = maxscale; reader.output_scaled = true; reader.restricted_scaling = true; } else { reader.restricted_scaling = false; } } //console.log("outputscale: " + outputscale); //console.log("canvas w x h: " + canvas.width + " x " + canvas.height); transform = (outputscale === 1) ? null : [outputscale, 0, 0, outputscale, 0, 0]; if (outputscale !== 1) { canvas.style.width = reader.roundToDivide(viewport.width, fraction[1]) + 'px'; canvas.style.height = reader.roundToDivide(viewport.height, fraction[1]) + 'px'; } else { canvas.style.width = ""; canvas.style.height = ""; } /* textlayer */ if (reader.settings.textSelect) { textdiv.style.width = reader.roundToDivide(viewport.width, fraction[1]) + 'px'; textdiv.style.height = reader.roundToDivide(viewport.height, fraction[1]) + 'px'; offset = $(canvas).offset(); $(textdiv).offset({ top: offset.top, left: offset.left }); page.getTextContent({ normalizeWhitespace: true }).then(function (textContent) { resourcelst.textLayer = textLayer = new PDFJS.Reader.TextLayerController({ textLayerDiv: textdiv, pageIdx: pageNum - 1, viewport: viewport, enhanceTextSelection: true }); textLayer.setTextContent(textContent); }); } else { resourcelst.textLayer = textLayer = null; } /* /textLayer */ if (double_buffer) { resourcelst.oscanvas = oscanvas = document.createElement("canvas"); resourcelst.osctx = context = osctx = oscanvas.getContext('2d'); oscanvas.width = canvas.width; oscanvas.height = canvas.height; } else { context = ctx; } renderContext = { canvasContext: context, viewport: viewport, transform: transform, textLayer: textLayer }; resourcelst.renderTask = renderTask = page.render(renderContext); renderTask.promise.then( function pdfPageRenderCallback (something) { if (reader.cancelPage[pageNum] === undefined) { if (double_buffer) ctx.drawImage(oscanvas, 0, 0); if (textLayer) textLayer.render(reader.settings.textRenderDelay); if (cache_next) reader.getThumb(parseInt(pageNum + pageShift), true); } }, function pdfPageRenderError(error) { console.log("pdfPageRenderError: " + error); } ); }); } else { // clear canvas (by resizing it), use maximum size canvas.width = 0; canvas.height = 0; canvas.width = reader.roundToDivide(max_view_width * outputscale, fraction[0]); canvas.height = reader.roundToDivide(max_view_height * outputscale, fraction[0]); if (outputscale !== 1) { canvas.style.width = reader.roundToDivide(max_view_width, fraction[1]) + 'px'; canvas.style.height = reader.roundToDivide(max_view_height, fraction[1]) + 'px'; } } }; PDFJS.Reader.prototype.queuePage = function(page) { var reader = this, zoom = reader.settings.zoomLevel, oddPageRight = reader.settings.oddPageRight, pageShift; if (zoom === "spread") { pageShift = 2; if (oddPageRight === true) { page -= page % 2; } else { page -= (page + 1) % 2; } } else { pageShift = 1; } reader.settings.currentPage = page; reader.ControlsController.setCurrentPage(page); if (typeof reader.renderQueue === 'number') { window.clearTimeout(reader.renderQueue); reader.renderQueue = false; } reader.renderQueue = window.setTimeout(function queuePages() { for (var i = 0; i < pageShift; i++) { reader.renderPage(page + i); } }, reader.settings.pageRenderDelay); }; PDFJS.Reader.prototype.prevPage = function() { var reader = this; var pageShift = (this.settings.zoomLevel === "spread") ? 2 : 1; var oddPageShift = this.settings.oddPageRight ? 0 : 1; if (this.settings.currentPage - pageShift < oddPageShift) { return; } else { for (var i = 0; i < pageShift; i++) { reader.cancelPage[this.settings.currentPage - i] = true; } this.queuePage(this.settings.currentPage - pageShift); } }; PDFJS.Reader.prototype.nextPage = function() { var reader = this; var pageShift = (this.settings.zoomLevel === "spread") ? 2 : 1; if (this.settings.currentPage + pageShift > this.settings.numPages) { return; } else { for (var i = 0; i < pageShift; i++) { reader.cancelPage[this.settings.currentPage + i] = true; } this.queuePage(this.settings.currentPage + pageShift); } }; PDFJS.Reader.prototype.defaults = function (obj) { for (var i = 1, length = arguments.length; i < length; i++) { var source = arguments[i]; for (var prop in source) { if (obj[prop] === void 0) obj[prop] = source[prop]; } } return obj; }; PDFJS.Reader.prototype.setScale = function (scale) { }; PDFJS.Reader.prototype.getOutputScale = function (ctx) { var devicePixelRatio = window.devicePixelRatio || 1, backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1, pixelRatio = devicePixelRatio / backingStoreRatio; return pixelRatio; }; PDFJS.Reader.prototype.roundToDivide = function (x, div) { var r = x % div; return r === 0 ? x : Math.round(x - r + div); }; /** * Approximates float number as a fraction using Farey sequence (max order * of 8). * @param {number} x - Positive float number. * @returns {Array} Estimated fraction: the first array item is a numerator, * the second one is a denominator. */ PDFJS.Reader.prototype.approximateFraction = function (x) { // Fast paths for int numbers or their inversions. if (Math.floor(x) === x) { return [x, 1]; } var xinv = 1 / x; var limit = 8; if (xinv > limit) { return [1, limit]; } else if (Math.floor(xinv) === xinv) { return [1, xinv]; } var x_ = x > 1 ? xinv : x; // a/b and c/d are neighbours in Farey sequence. var a = 0, b = 1, c = 1, d = 1; // Limiting search to order 8. while (true) { // Generating next term in sequence (order of q). var p = a + c, q = b + d; if (q > limit) { break; } if (x_ <= p / q) { c = p; d = q; } else { a = p; b = q; } } var result; // Select closest of the neighbours to x. if (x_ - a / b < c / d - x_) { result = x_ === x ? [a, b] : [b, a]; } else { result = x_ === x ? [c, d] : [d, c]; } return result; }; PDFJS.Reader.prototype.isMobile = function () { var reader = this; var isMobile = (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1); if (isMobile) { reader.isMobile = true; reader.canvasLimit = reader.settings.canvasLimit; } return isMobile; }; PDFJS.Reader.prototype.getPageLabel = function (page) { var reader = this; if (reader.pageLabels[parseInt(page)] !== undefined) { return reader.pageLabels[parseInt(page)].toString(); } else { return page.toString(); } }; PDFJS.Reader.prototype.navigateTo = function (destination) { var reader = this, destString = "", destinationPromise, goToDestination; goToDestination = function (destRef) { var pageNumber; if (destRef instanceof Object) { pageNumber = reader.cachedPageNum(destRef); } else if ((destRef | 0) === destRef) { // Integer pageNumber = destRef + 1; } else { console.error('PDFJS.Reader.navigateTo: "' + destRef + '" is not a valid destination reference.'); return; } if (pageNumber) { if (pageNumber < 1 || pageNumber > reader.settings.numPages) { console.error('PDFJS.Reader.navigateTo: "' + pageNumber + '" is a non-existent page number.'); return; } reader.queuePage(pageNumber); } else { reader.book.getPageIndex(destRef).then(function (pageIndex) { reader.cachePageRef(pageIndex + 1, destRef); goToDestination(destRef); }).catch(function () { console.error('PDFJS.Reader.navigateTo: "' + destRef + '" is not a valid page reference.'); return; }); } }; if (typeof destination === 'string') { destString = destination; destinationPromise = reader.book.getDestination(destination); } else { destinationPromise = Promise.resolve(destination); } destinationPromise.then(function (_destination) { destination = _destination; if (!(destination instanceof Array)) { console.error('PDFJS.Reader.navigateTo: "' + destination + '" is not a valid destination array.'); return; } goToDestination(destination[0]); }); }; PDFJS.Reader.prototype.pageRefStr = function (pageRef) { return pageRef.num + ' ' + pageRef.gen + ' R'; }; PDFJS.Reader.prototype.cachePageRef = function (pageNum, pageRef) { var reader = this, refStr; reader.pageRefs[reader.pageRefStr(pageRef)] = pageNum; }; PDFJS.Reader.prototype.cachedPageNum = function (pageRef) { var reader = this; return (reader.pageRefs[reader.pageRefStr(pageRef)]) || null; }; PDFJS.Reader.prototype.getPageTextContent = function (pageIndex) { var reader = this, book = reader.book; return book.getPage(pageIndex + 1).then(function (page) { return page.getTextContent({ normalizeWhitespace: true, }); }); };