| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819 |
- (function (name, context, definition) {
- if (typeof module != 'undefined' && module.exports) module.exports = definition()
- else if (typeof define == 'function' && define.amd) define(definition)
- else context[name] = definition()
- })('PDFListView', this, function (name, context) {
- //#expand __BUNDLE__
- function Logger() {
- this.logLevel = Logger.INFO;
- var self = this;
- if (typeof(console) == "object" && typeof(console.log) == "function") {
- this.debug = function() {
- if (self.logLevel <= Logger.DEBUG) {
- console.log.apply(console, arguments);
- }
- };
- this.info = function() {
- if (self.logLevel <= Logger.INFO) {
- console.log.apply(console, arguments);
- }
- };
- this.error = function() {
- if (self.logLevel <= Logger.ERROR) {
- console.log.apply(console, arguments);
- }
- };
- } else {
- this.debug = this.info = this.error = function nop() {};
- }
- }
- Logger.DEBUG = 0;
- Logger.INFO = 1;
- Logger.ERROR = 2;
- var logger = new Logger();
- function _flat(arr) {
- var res = arr.reduce(function(a, b) {
- return a.concat(b);
- });
- return res;
- }
- function failDumper(err) {
- logger.error(err);
- }
- if (typeof(PDFJS) === "undefined") {
- logger.error("PDF.js is not yet loaded.");
- }
- // -----------------------------------------------------------------------------
- /**
- * Wrapper around the raw PDF.JS document.
- */
- function Document(url, password, onProgress) {
- this.pdfDocument = null;
- this.pages = null;
- var parameters = {password: password};
- if (typeof url === 'string') { // URL
- parameters.url = url;
- } else if (url && 'byteLength' in url) { // ArrayBuffer
- parameters.data = url;
- }
- this.initialized = new PDFJS.LegacyPromise();
- PDFJS.getDocument(parameters, null, null, onProgress).then(this.loadPages.bind(this), failDumper);
- }
- Document.prototype.loadPages = function(pdfDocument) {
- this.pdfDocument = pdfDocument;
- var pagesCount = this.pagesCount = pdfDocument.numPages;
- var pagePromises = [];
- for (var i = 1; i <= pagesCount; i++) {
- pagePromises.push(pdfDocument.getPage(i));
- }
- this.pageRefMap = pageRefMap = {};
- var pagesPromise = Promise.all(pagePromises);
- var doc = this
- pagesPromise.then(function(promisedPages) {
- doc.pages = promisedPages.map(function(pdfPage, i) {
- var pageRef = pdfPage.ref
- pageRefMap[pageRef.num + ' ' + pageRef.gen] = i;
- return new Page(pdfPage);
- });
- });
- var destinationsPromise = pdfDocument.getDestinations();
- destinationsPromise.then(function(destinations) {
- doc.destinations = destinations;
- });
- Promise.all([pagesPromise, destinationsPromise]).then(function() {
- doc.initialized.resolve();
- }, failDumper);
- };
- /**
- * Handles the rendering. Multiple ListViews can be bound to a RenderController.
- * In that case, the RenderController figures out what's page has the highest
- * priority to render
- */
- function RenderController() {
- this.listViews = [];
- this.renderList = [];
- }
- RenderController.prototype = {
- addListView: function(listView) {
- // TODO: assert listView not already in list of this.listView
- this.listViews.push(listView);
- },
- updateRenderList: function() {
- this.renderList = _flat(this.listViews.map(function(listView) {
- return listView.getPagesToRender();
- }));
- // TODO: Some "highest-priority" sorting algorithm on the renderList.
- this.doRender();
- },
- pageToRender: function() {
- if (this.renderList.length === 0) return null;
- return this.renderList[0];
- },
- doRender: function() {
- var pageToRender = this.pageToRender();
- if (!pageToRender) return;
- pageToRender.render(this);
- },
- finishedRendering: function(pageView) {
- var idx = this.renderList.indexOf(pageView);
- // If the finished pageView is in the list of pages to render,
- // then remove it from the list and render start rendering the
- // next page.
- if (idx !== -1) {
- this.renderList.splice(idx, 1);
- this.doRender();
- }
- },
- onResize: function() {
- var renderAgain = false
- this.listViews.map(function(listView) {
- if (listView.calculateScale()) {
- listView.layout();
- renderAgain = true;
- }
- });
- if (renderAgain) {
- this.updateRenderList();
- }
- }
- };
- var LAYOUT_SINGLE = 'layout_single';
- var SCALE_MODE_AUTO = 'scale_mode_auto';
- var SCALE_MODE_VALUE = 'scale_mode_value';
- var SCALE_MODE_FIT_WIDTH = 'scale_mode_fit_width';
- var SCALE_MODE_FIT_HEIGHT = 'scale_mode_fit_height';
- /**
- * Main view that holds the single pageContainer/pageViews of the pdfDoc.
- */
- function ListView(dom, options) {
- this.dom = dom;
- this.options = options;
- this.pageLayout = LAYOUT_SINGLE;
- this.scaleMode = SCALE_MODE_VALUE;
- this.scale = 1.0;
- this.pageWidthOffset = 0;
- this.pageHeightOffset = 0;
- this.pageViews = [];
- this.containerViews = [];
- }
- ListView.prototype = {
- setDocument: function(pdfDoc) {
- this.clearPages();
- this.pdfDoc = pdfDoc;
- this.assignPagesToContainer();
- this.layout();
- },
- clearPages: function() {
- var self = this;
- this.containerViews.map(function(container) {
- self.dom.removeChild(container.dom);
- });
- this.pageViews = [];
- this.containerViews = [];
- },
- assignPagesToContainer: function() {
- // TODO: Handle multiple layout types here. For now, assume to have one page
- // per pageContainer.
- this.pdfDoc.pages.map(function(page) {
- var pageView = new PageView(page, this);
- this.pageViews.push(pageView);
- var container = new PageContainerView(this);
- container.setPageView(pageView, 0);
- this.containerViews.push(container);
- this.dom.appendChild(container.dom);
- }, this);
- },
- layout: function() {
- this.savePdfPosition();
- this.containerViews.forEach(function(containerView) {
- containerView.layout();
- });
- this.restorePdfPosition();
- },
- getScale: function() {
- return this.scale;
- },
- getScaleMode: function() {
- return this.scaleMode;
- },
- setScaleMode: function(scaleMode, scale) {
- this.scaleMode = scaleMode;
- if (scaleMode == SCALE_MODE_VALUE) {
- this.scale = scale;
- }
- this.calculateScale();
- this.layout();
- },
- setScale: function(scale) {
- this.setScaleMode(SCALE_MODE_VALUE, scale);
- },
- setToAutoScale: function() {
- this.setScaleMode(SCALE_MODE_AUTO);
- },
- setToFitWidth: function() {
- this.setScaleMode(SCALE_MODE_FIT_WIDTH);
- },
- setToFitHeight: function() {
- this.setScaleMode(SCALE_MODE_FIT_HEIGHT);
- },
- // Calculates the new scale. Returns `true` if the scale changed.
- calculateScale: function() {
- var newScale = this.scale;
- var oldScale = newScale;
- var scaleMode = this.scaleMode;
- if (scaleMode === SCALE_MODE_FIT_WIDTH || scaleMode === SCALE_MODE_AUTO) {
- var clientWidth = this.dom.clientWidth;
- if (clientWidth == 0) {
- logger.debug("LIST VIEW NOT VISIBLE")
- return false;
- }
- var maxNormalWidth = 0;
- this.containerViews.forEach(function(containerView) {
- maxNormalWidth = Math.max(maxNormalWidth, containerView.normalWidth);
- });
- var scale = (clientWidth - this.pageWidthOffset)/maxNormalWidth;
- if (scaleMode === SCALE_MODE_AUTO) {
- scale = Math.min(1.0, scale);
- }
- newScale = scale;
- } else if (scaleMode === SCALE_MODE_FIT_HEIGHT) {
- var clientHeight = this.dom.clientHeight;
- if (clientHeight == 0) {
- logger.debug("LIST VIEW NOT VISIBLE")
- return false;
- }
- var maxNormalHeight = 0;
- this.containerViews.forEach(function(containerView) {
- maxNormalHeight = Math.max(maxNormalHeight, containerView.normalHeight);
- });
- newScale = (clientHeight - this.pageHeightOffset)/maxNormalHeight;
- }
- this.scale = newScale;
- return newScale !== oldScale;
- },
- getPagesToRender: function() {
- // Cache these results to avoid dom access.
- this.scrollTop = this.dom.scrollTop;
- this.scrollBottom = this.scrollTop + this.dom.clientHeight;
- // TODO: For now, this only returns the visible pages and not
- // +1/-1 one to render in advance.
- return this.pageViews.filter(function(pageView) {
- var isVisible = pageView.isVisible();
- if (isVisible && !pageView.isRendered) {
- return true;
- }
- });
- },
- navigateTo: function(destRef) {
- var destination = this.pdfDoc.destinations[destRef]
- if (typeof destination !== "object") {
- return;
- }
- logger.debug("NAVIGATING TO", destination);
- var pageRef = destination[0];
- var pageNumber = this.pdfDoc.pageRefMap[pageRef.num + ' ' + pageRef.gen];
- if (typeof pageNumber !== "number") {
- return;
- }
- logger.debug("PAGE NUMBER", pageNumber);
- var pageView = this.pageViews[pageNumber];
- var destinationType = destination[1].name;
- switch(destinationType) {
- case "XYZ":
- var x = destination[2];
- var y = destination[3];
- break;
- default:
- // TODO
- return;
- }
- var position = pageView.getPdfPositionInViewer(x,y);
- this.dom.scrollTop = position.top;
- this.dom.scrollLeft = position.left;
- },
- savePdfPosition: function() {
- this.pdfPosition = this.getPdfPosition();
- logger.debug("SAVED PDF POSITION", this.pdfPosition);
- },
- restorePdfPosition: function() {
- logger.debug("RESTORING PDF POSITION", this.pdfPosition);
- this.setPdfPosition(this.pdfPosition);
- },
- getPdfPosition: function() {
- var pdfPosition = null;
- for (var i = 0; i < this.pageViews.length; i++) {
- var pageView = this.pageViews[i];
- var pdfOffset = pageView.getUppermostVisiblePdfOffset();
- if (pdfOffset !== null) {
- pdfPosition = {
- page: i,
- offset: {
- top: pdfOffset,
- left: 0 // TODO
- }
- }
- break;
- }
- }
- return pdfPosition;
- },
- setPdfPosition: function(pdfPosition) {
- if (typeof pdfPosition !== "undefined" && pdfPosition != null) {
- var offset = pdfPosition.offset;
- var page_index = pdfPosition.page;
- var pageView = this.pageViews[page_index];
- var position = pageView.getPdfPositionInViewer(offset.left, offset.top);
- this.dom.scrollTop = position.top;
- }
- }
- };
- /*
- * A PageContainerView holds multiple PageViews. E.g. in a two-page layout,
- * every pageContainerView holds two PageViews and is responsible to layout
- * them.
- */
- function PageContainerView(listView) {
- this.listView = listView;
- var dom = this.dom = document.createElement('div');
- dom.className = 'plv-page-container page-container';
- this.pages = [];
- }
- PageContainerView.prototype = {
- setPageView: function(pageView, idx) {
- // TODO: handle case if there is already a page here
- this.pages[idx] = pageView;
- // TODO: handle page idx properly
- this.dom.appendChild(pageView.dom);
- },
- removePageView: function(idx) {
- // TODO: check if idx is set on page[]
- this.dom.removeChild(this.pages[idx].dom);
- },
- layout: function() {
- var scale = this.listView.scale;
- var normalWidth = 0;
- var normalHeight = 0;
- this.pages.forEach(function(pageView) {
- pageView.layout();
- normalWidth += pageView.normalWidth;
- normalHeight = Math.max(pageView.normalHeight, normalHeight);
- });
- this.normalWidth = normalWidth;
- this.normalHeight = normalHeight;
- this.dom.style.width = (normalWidth * scale) + 'px';
- this.dom.style.height = (normalHeight * scale) + 'px';
- }
- };
- var RenderingStates = {
- INITIAL: 0,
- RUNNING: 1,
- PAUSED: 2,
- FINISHED: 3
- };
- var idCounter = 0;
- /**
- * The view for a single page.
- */
- function PageView(page, listView) {
- this.page = page;
- this.listView = listView;
- this.id = idCounter++;
- this.number = this.page.number;
- this.rotation = 0;
- this.isRendered = false;
- this.renderState = RenderingStates.INITIAL;
- var dom = this.dom = document.createElement('div');
- dom.className = "plv-page-view page-view";
- this.createNewCanvas();
- }
- PageView.prototype = {
- layout: function() {
- var scale = this.listView.scale;
- var viewport = this.viewport =
- this.page.pdfPage.getViewport(scale, this.rotation);
- this.normalWidth = viewport.width / scale;
- this.normalHeight = viewport.height / scale;
- // Only change the width/height property of the canvas if it really
- // changed. Every assignment to the width/height property clears the
- // content of the canvas.
- var newWidth = Math.floor(viewport.width);
- var newHeight = Math.floor(viewport.height);
- if (this.canvas.width !== newWidth) {
- this.canvas.width = newWidth;
- this.resetRenderState();
- }
- if (this.canvas.height !== newHeight) {
- this.canvas.height = newHeight;
- this.resetRenderState();
- }
- this.width = viewport.width;
- this.height = viewport.height;
- },
- isVisible: function() {
- var listView = this.listView;
- var dom = this.dom;
- var offsetTop = dom.offsetTop;
- var offsetBottom = offsetTop + this.height;
- return offsetBottom >= listView.scrollTop &&
- offsetTop <= listView.scrollBottom;
- },
- resetRenderState: function() {
- this.renderState = RenderingStates.INITIAL;
- this.isRendered = false;
- if (this.textLayerDiv) {
- this.dom.removeChild(this.textLayerDiv);
- delete this.textLayerDiv;
- }
- if (this.annotationsLayerDiv) {
- this.dom.removeChild(this.annotationsLayerDiv);
- delete this.annotationsLayerDiv;
- }
- },
- render: function(renderController) {
- return this.page.render(this, renderController);
- },
- getCanvasContext: function() {
- return this.canvas.getContext('2d');
- },
- createNewCanvas: function() {
- if (this.canvas) {
- this.dom.removeChild(this.canvas);
- }
- var canvas = this.canvas = document.createElement('canvas');
- this.dom.appendChild(canvas);
- this.layout();
- },
- pdfPositionToPixels: function(x, y) {
- return this.viewport.convertToViewportPoint(x, y);
- },
- getCanvasPositionInViewer: function() {
- return {
- left: this.canvas.offsetLeft + this.dom.offsetLeft,
- top: this.canvas.offsetTop + this.dom.offsetTop
- }
- },
- getPdfPositionInViewer: function(x, y) {
- pageOffset = this.pdfPositionToPixels(x, y);
- canvasOffset = this.getCanvasPositionInViewer();
- return {
- left: canvasOffset.left + pageOffset[0],
- top: canvasOffset.top + pageOffset[1]
- }
- },
- getUppermostVisibleCanvasOffset: function() {
- var pagePosition = this.getCanvasPositionInViewer();
- var pageHeight = this.canvas.height;
- var viewportTop = this.listView.dom.scrollTop;
- var viewportHeight = this.listView.dom.clientHeight;
- // Check if the top of the page is showing, i.e:
- // _______________
- // | |
- // | ........ |
- // | . . |
- // ----.------.---
- // . .
- // ........
- var topVisible = (pagePosition.top > viewportTop && pagePosition.top < viewportTop + viewportHeight);
- // Check if at least some of the page is showing, i.e:
- // ........ ........
- // ____.______.___ ---.------.---
- // | . . | or | . . |
- // | . . | | . . |
- // | ........ | | . . |
- // --------------- ---.------.---
- // ........
- var someContentVisible = (pagePosition.top < viewportTop && pagePosition.top + pageHeight > viewportTop);
- if (topVisible) {
- return 0;
- } else if (someContentVisible) {
- return viewportTop - pagePosition.top;
- } else {
- return null;
- }
- },
- getUppermostVisiblePdfOffset: function() {
- var canvasOffset = this.getUppermostVisibleCanvasOffset();
- if (canvasOffset === null) {
- return null;
- }
- var pdfOffset = this.viewport.convertToPdfPoint(0, canvasOffset);
- return pdfOffset[1];
- }
- };
- /**
- * An abstraction around the raw page object of PDF.JS, that also handles the
- * rendering logic of (maybe multiple) pageView(s) that are based on this page.
- */
- function Page(pdfPage, number) {
- this.number = number;
- this.pdfPage = pdfPage;
- this.renderContextList = {};
- }
- Page.prototype = {
- render: function(pageView, renderController) {
- var renderContext;
- // FEATURE: If the page was rendered already once, then use the old
- // version as a placeholder until the new version is rendered at the
- // expected quality.
- // FEATURE: If the page can be rendered at low quality (thumbnail) and
- // there is already a higher resolution rendering, then use this one
- // instead of rerendering from scratch again.
- // PageView is not layouted.
- if (!pageView.viewport) return;
- // Nothing todo.
- if (pageView.isRendered) return;
- // Not most important page to render ATM.
- if (renderController.pageToRender() !== pageView) return;
- var self = this;
- var viewport;
- if (renderContext = this.renderContextList[pageView.id]) {
- viewport = renderContext.viewport;
- // TODO: handle rotation
- if (viewport.height !== pageView.viewport.height ||
- viewport.height !== pageView.viewport.height)
- {
- // The viewport changed -> need to rerender.
- renderContext.abandon = true;
- delete self.renderContextList[pageView.id];
- pageView.createNewCanvas();
- self.render(pageView, renderController);
- } else if (renderContext.state === RenderingStates.PAUSED) {
- // There is already a not finished renderState ->
- logger.debug('RESUME', pageView.id);
- renderContext.resume();
- }
- }
- if (!renderContext) {
- viewport = pageView.viewport;
- // No rendering data yet -> create a new renderContext and start
- // the rendering process.
- var textLayer;
- var textLayerBuilder = pageView.listView.options.textLayerBuilder;
- if (textLayerBuilder) {
- var textLayerDiv = pageView.textLayerDiv = document.createElement("div");
- textLayerDiv.className = 'plv-text-layer text-layer';
- pageView.dom.appendChild(textLayerDiv);
- textLayer = new textLayerBuilder(textLayerDiv);
- this.pdfPage.getTextContent().then(
- function(textContent) {
- textLayer.setTextContent(textContent);
- }
- );
- }
- var annotationsLayerBuilder = pageView.listView.options.annotationsLayerBuilder;
- if (annotationsLayerBuilder) {
- var annotationsLayerDiv = pageView.annotationsLayerDiv = document.createElement("div");
- annotationsLayerDiv.className = 'plv-annotations-layer annotations-layer';
- pageView.dom.appendChild(annotationsLayerDiv);
- var annotationsLayer = new annotationsLayerBuilder(pageView, annotationsLayerDiv);
- this.pdfPage.getAnnotations().then(
- function(annotations) {
- annotationsLayer.setAnnotations(annotations)
- }
- );
- }
- renderContext = {
- canvasContext: pageView.getCanvasContext(),
- viewport: viewport,
- textLayer: textLayer,
- continueCallback: function pdfViewContinueCallback(cont) {
- if (renderContext.abandon) {
- logger.debug("ABANDON", pageView.id);
- return;
- }
- if (renderController.pageToRender() !== pageView) {
- logger.debug('PAUSE', pageView.id);
- renderContext.state = RenderingStates.PAUSED;
- renderContext.resume = function resumeCallback() {
- renderContext.state = RenderingStates.RUNNING;
- cont();
- };
- return;
- }
- logger.debug('CONT', pageView.id);
- cont();
- }
- };
- this.renderContextList[pageView.id] = renderContext;
- logger.debug("BEGIN", pageView.id);
- renderContext.renderPromise = this.pdfPage.render(renderContext).promise;
- renderContext.renderPromise.then(
- function pdfPageRenderCallback() {
- logger.debug('DONE', pageView.id);
- pageView.isRendered = true;
- renderController.finishedRendering(pageView);
- },
- failDumper
- );
- }
- return renderContext.renderPromise;
- }
- };
- function PDFListView(mainDiv, options) {
- if (typeof(options) != "object") {
- options = {};
- }
- if (typeof(options.logLevel) != "number") {
- options.logLevel = Logger.INFO;
- }
- logger.logLevel = options.logLevel;
- this.listView = new ListView(mainDiv, options);
- this.renderController = new RenderController();
- this.renderController.addListView(this.listView);
- this.renderController.updateRenderList();
- var self = this;
- mainDiv.addEventListener('scroll', function() {
- // This will update the list AND start rendering if needed.
- self.renderController.updateRenderList();
- });
- window.addEventListener('resize', function() {
- // Check if the scale changed due to the resizing.
- if (self.listView.calculateScale()) {
- // Update the layout and start rendering. Changing the layout
- // of the PageView makes it rendering stop.
- self.listView.layout();
- self.renderController.updateRenderList();
- }
- });
- }
- PDFListView.prototype = {
- loadPdf: function(url, onProgress) {
- this.doc = new Document(url, null, onProgress);
- var self = this;
- var promise = this.doc.initialized;
- promise.then(function() {
- logger.debug('LOADED');
- self.listView.setDocument(self.doc);
- self.renderController.updateRenderList();
- }, failDumper);
- return promise;
- },
- getScale: function() {
- return this.listView.getScale();
- },
- getScaleMode: function() {
- return this.listView.getScaleMode()
- },
- setScaleMode: function(scaleMode, scale) {
- this.listView.setScaleMode(scaleMode, scale);
- this.renderController.updateRenderList();
- },
- setScale: function(scale) {
- this.listView.setScale(scale);
- this.renderController.updateRenderList();
- },
- setToAutoScale: function() {
- this.listView.setToAutoScale();
- this.renderController.updateRenderList();
- },
- setToFitWidth: function() {
- this.listView.setToFitWidth();
- this.renderController.updateRenderList();
- },
- setToFitHeight: function() {
- this.listView.setToFitHeight();
- this.renderController.updateRenderList();
- },
- onResize: function() {
- this.renderController.onResize();
- },
- getPdfPosition: function() {
- return this.listView.getPdfPosition();
- },
- setPdfPosition: function(pdfPosition) {
- this.listView.setPdfPosition(pdfPosition)
- }
- };
- PDFListView.Logger = Logger;
- return PDFListView;
- });
|