5 changed files with 521 additions and 412 deletions
@ -0,0 +1,314 @@
@@ -0,0 +1,314 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/* globals mozL10n, RenderingStates, Promise */ |
||||
|
||||
'use strict'; |
||||
|
||||
var THUMBNAIL_WIDTH = 98; // px
|
||||
var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px
|
||||
|
||||
/** |
||||
* @typedef {Object} PDFThumbnailViewOptions |
||||
* @property {HTMLDivElement} container - The viewer element. |
||||
* @property {number} id - The thumbnail's unique ID (normally its number). |
||||
* @property {PageViewport} defaultViewport - The page viewport. |
||||
* @property {IPDFLinkService} linkService - The navigation/linking service. |
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object. |
||||
*/ |
||||
|
||||
/** |
||||
* @class |
||||
* @implements {IRenderableView} |
||||
*/ |
||||
var PDFThumbnailView = (function PDFThumbnailViewClosure() { |
||||
function getTempCanvas(width, height) { |
||||
var tempCanvas = PDFThumbnailView.tempImageCache; |
||||
if (!tempCanvas) { |
||||
tempCanvas = document.createElement('canvas'); |
||||
PDFThumbnailView.tempImageCache = tempCanvas; |
||||
} |
||||
tempCanvas.width = width; |
||||
tempCanvas.height = height; |
||||
|
||||
// Since this is a temporary canvas, we need to fill the canvas with a white
|
||||
// background ourselves. |_getPageDrawContext| uses CSS rules for this.
|
||||
var ctx = tempCanvas.getContext('2d'); |
||||
ctx.save(); |
||||
ctx.fillStyle = 'rgb(255, 255, 255)'; |
||||
ctx.fillRect(0, 0, width, height); |
||||
ctx.restore(); |
||||
return tempCanvas; |
||||
} |
||||
|
||||
/** |
||||
* @constructs PDFThumbnailView |
||||
* @param {PDFThumbnailViewOptions} options |
||||
*/ |
||||
function PDFThumbnailView(options) { |
||||
var container = options.container; |
||||
var id = options.id; |
||||
var defaultViewport = options.defaultViewport; |
||||
var linkService = options.linkService; |
||||
var renderingQueue = options.renderingQueue; |
||||
|
||||
this.id = id; |
||||
this.renderingId = 'thumbnail' + id; |
||||
|
||||
this.pdfPage = null; |
||||
this.rotation = 0; |
||||
this.viewport = defaultViewport; |
||||
this.pdfPageRotate = defaultViewport.rotation; |
||||
|
||||
this.linkService = linkService; |
||||
this.renderingQueue = renderingQueue; |
||||
|
||||
this.hasImage = false; |
||||
this.resume = null; |
||||
this.renderingState = RenderingStates.INITIAL; |
||||
|
||||
this.pageWidth = this.viewport.width; |
||||
this.pageHeight = this.viewport.height; |
||||
this.pageRatio = this.pageWidth / this.pageHeight; |
||||
|
||||
this.canvasWidth = THUMBNAIL_WIDTH; |
||||
this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; |
||||
this.scale = this.canvasWidth / this.pageWidth; |
||||
|
||||
var anchor = document.createElement('a'); |
||||
anchor.href = linkService.getAnchorUrl('#page=' + id); |
||||
anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); |
||||
anchor.onclick = function stopNavigation() { |
||||
linkService.page = id; |
||||
return false; |
||||
}; |
||||
|
||||
var div = document.createElement('div'); |
||||
div.id = 'thumbnailContainer' + id; |
||||
div.className = 'thumbnail'; |
||||
this.el = div; // TODO: replace 'el' property usage.
|
||||
this.div = div; |
||||
|
||||
if (id === 1) { |
||||
// Highlight the thumbnail of the first page when no page number is
|
||||
// specified (or exists in cache) when the document is loaded.
|
||||
div.classList.add('selected'); |
||||
} |
||||
|
||||
var ring = document.createElement('div'); |
||||
ring.className = 'thumbnailSelectionRing'; |
||||
var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; |
||||
ring.style.width = this.canvasWidth + borderAdjustment + 'px'; |
||||
ring.style.height = this.canvasHeight + borderAdjustment + 'px'; |
||||
this.ring = ring; |
||||
|
||||
div.appendChild(ring); |
||||
anchor.appendChild(div); |
||||
container.appendChild(anchor); |
||||
} |
||||
|
||||
PDFThumbnailView.prototype = { |
||||
setPdfPage: function PDFThumbnailView_setPdfPage(pdfPage) { |
||||
this.pdfPage = pdfPage; |
||||
this.pdfPageRotate = pdfPage.rotate; |
||||
var totalRotation = (this.rotation + this.pdfPageRotate) % 360; |
||||
this.viewport = pdfPage.getViewport(1, totalRotation); |
||||
this.reset(); |
||||
}, |
||||
|
||||
reset: function PDFThumbnailView_reset() { |
||||
if (this.renderTask) { |
||||
this.renderTask.cancel(); |
||||
} |
||||
this.hasImage = false; |
||||
this.resume = null; |
||||
this.renderingState = RenderingStates.INITIAL; |
||||
|
||||
this.pageWidth = this.viewport.width; |
||||
this.pageHeight = this.viewport.height; |
||||
this.pageRatio = this.pageWidth / this.pageHeight; |
||||
|
||||
this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; |
||||
this.scale = (this.canvasWidth / this.pageWidth); |
||||
|
||||
this.div.removeAttribute('data-loaded'); |
||||
var ring = this.ring; |
||||
var childNodes = ring.childNodes; |
||||
for (var i = childNodes.length - 1; i >= 0; i--) { |
||||
ring.removeChild(childNodes[i]); |
||||
} |
||||
var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; |
||||
ring.style.width = this.canvasWidth + borderAdjustment + 'px'; |
||||
ring.style.height = this.canvasHeight + borderAdjustment + 'px'; |
||||
|
||||
if (this.canvas) { |
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
this.canvas.width = 0; |
||||
this.canvas.height = 0; |
||||
delete this.canvas; |
||||
} |
||||
}, |
||||
|
||||
update: function PDFThumbnailView_update(rotation) { |
||||
if (typeof rotation !== 'undefined') { |
||||
this.rotation = rotation; |
||||
} |
||||
var totalRotation = (this.rotation + this.pdfPageRotate) % 360; |
||||
this.viewport = this.viewport.clone({ |
||||
scale: 1, |
||||
rotation: totalRotation |
||||
}); |
||||
this.reset(); |
||||
}, |
||||
|
||||
/** |
||||
* @private |
||||
*/ |
||||
_getPageDrawContext: function PDFThumbnailView_getPageDrawContext() { |
||||
var canvas = document.createElement('canvas'); |
||||
canvas.id = this.renderingId; |
||||
|
||||
canvas.width = this.canvasWidth; |
||||
canvas.height = this.canvasHeight; |
||||
canvas.className = 'thumbnailImage'; |
||||
canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', |
||||
{page: this.id}, 'Thumbnail of Page {{page}}')); |
||||
|
||||
this.canvas = canvas; |
||||
this.div.setAttribute('data-loaded', true); |
||||
this.ring.appendChild(canvas); |
||||
|
||||
return canvas.getContext('2d'); |
||||
}, |
||||
|
||||
draw: function PDFThumbnailView_draw() { |
||||
if (this.renderingState !== RenderingStates.INITIAL) { |
||||
console.error('Must be in new state before drawing'); |
||||
} |
||||
if (this.hasImage) { |
||||
return Promise.resolve(undefined); |
||||
} |
||||
this.hasImage = true; |
||||
this.renderingState = RenderingStates.RUNNING; |
||||
|
||||
var resolveRenderPromise, rejectRenderPromise; |
||||
var promise = new Promise(function (resolve, reject) { |
||||
resolveRenderPromise = resolve; |
||||
rejectRenderPromise = reject; |
||||
}); |
||||
|
||||
var self = this; |
||||
function thumbnailDrawCallback(error) { |
||||
// The renderTask may have been replaced by a new one, so only remove
|
||||
// the reference to the renderTask if it matches the one that is
|
||||
// triggering this callback.
|
||||
if (renderTask === self.renderTask) { |
||||
self.renderTask = null; |
||||
} |
||||
if (error === 'cancelled') { |
||||
rejectRenderPromise(error); |
||||
return; |
||||
} |
||||
self.renderingState = RenderingStates.FINISHED; |
||||
|
||||
if (!error) { |
||||
resolveRenderPromise(undefined); |
||||
} else { |
||||
rejectRenderPromise(error); |
||||
} |
||||
} |
||||
|
||||
var ctx = this._getPageDrawContext(); |
||||
var drawViewport = this.viewport.clone({ scale: this.scale }); |
||||
var renderContinueCallback = function renderContinueCallback(cont) { |
||||
if (!self.renderingQueue.isHighestPriority(self)) { |
||||
self.renderingState = RenderingStates.PAUSED; |
||||
self.resume = function resumeCallback() { |
||||
self.renderingState = RenderingStates.RUNNING; |
||||
cont(); |
||||
}; |
||||
return; |
||||
} |
||||
cont(); |
||||
}; |
||||
|
||||
var renderContext = { |
||||
canvasContext: ctx, |
||||
viewport: drawViewport, |
||||
continueCallback: renderContinueCallback |
||||
}; |
||||
var renderTask = this.renderTask = this.pdfPage.render(renderContext); |
||||
|
||||
renderTask.promise.then( |
||||
function pdfPageRenderCallback() { |
||||
thumbnailDrawCallback(null); |
||||
}, |
||||
function pdfPageRenderError(error) { |
||||
thumbnailDrawCallback(error); |
||||
} |
||||
); |
||||
return promise; |
||||
}, |
||||
|
||||
setImage: function PDFThumbnailView_setImage(pageView) { |
||||
var img = pageView.canvas; |
||||
if (this.hasImage || !img) { |
||||
return; |
||||
} |
||||
if (!this.pdfPage) { |
||||
this.setPdfPage(pageView.pdfPage); |
||||
} |
||||
this.hasImage = true; |
||||
this.renderingState = RenderingStates.FINISHED; |
||||
|
||||
var ctx = this._getPageDrawContext(); |
||||
var canvas = ctx.canvas; |
||||
|
||||
if (img.width <= 2 * canvas.width) { |
||||
ctx.drawImage(img, 0, 0, img.width, img.height, |
||||
0, 0, canvas.width, canvas.height); |
||||
return; |
||||
} |
||||
// drawImage does an awful job of rescaling the image, doing it gradually.
|
||||
var MAX_NUM_SCALING_STEPS = 3; |
||||
var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; |
||||
var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; |
||||
var reducedImage = getTempCanvas(reducedWidth, reducedHeight); |
||||
var reducedImageCtx = reducedImage.getContext('2d'); |
||||
|
||||
while (reducedWidth > img.width || reducedHeight > img.height) { |
||||
reducedWidth >>= 1; |
||||
reducedHeight >>= 1; |
||||
} |
||||
reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, |
||||
0, 0, reducedWidth, reducedHeight); |
||||
while (reducedWidth > 2 * canvas.width) { |
||||
reducedImageCtx.drawImage(reducedImage, |
||||
0, 0, reducedWidth, reducedHeight, |
||||
0, 0, reducedWidth >> 1, reducedHeight >> 1); |
||||
reducedWidth >>= 1; |
||||
reducedHeight >>= 1; |
||||
} |
||||
ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, |
||||
0, 0, canvas.width, canvas.height); |
||||
} |
||||
}; |
||||
|
||||
return PDFThumbnailView; |
||||
})(); |
||||
|
||||
PDFThumbnailView.tempImageCache = null; |
@ -0,0 +1,204 @@
@@ -0,0 +1,204 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/* globals watchScroll, getVisibleElements, scrollIntoView, PDFThumbnailView, |
||||
Promise */ |
||||
|
||||
'use strict'; |
||||
|
||||
var THUMBNAIL_SCROLL_MARGIN = -19; |
||||
|
||||
//#include pdf_thumbnail_view.js
|
||||
|
||||
/** |
||||
* @typedef {Object} PDFThumbnailViewerOptions |
||||
* @property {HTMLDivElement} container - The container for the thumbnail |
||||
* elements. |
||||
* @property {IPDFLinkService} linkService - The navigation/linking service. |
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object. |
||||
*/ |
||||
|
||||
/** |
||||
* Simple viewer control to display thumbnails for pages. |
||||
* @class |
||||
* @implements {IRenderableView} |
||||
*/ |
||||
var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() { |
||||
/** |
||||
* @constructs PDFThumbnailViewer |
||||
* @param {PDFThumbnailViewerOptions} options |
||||
*/ |
||||
function PDFThumbnailViewer(options) { |
||||
this.container = options.container; |
||||
this.renderingQueue = options.renderingQueue; |
||||
this.linkService = options.linkService; |
||||
|
||||
this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this)); |
||||
this._resetView(); |
||||
} |
||||
|
||||
PDFThumbnailViewer.prototype = { |
||||
/** |
||||
* @private |
||||
*/ |
||||
_scrollUpdated: function PDFThumbnailViewer_scrollUpdated() { |
||||
this.renderingQueue.renderHighestPriority(); |
||||
}, |
||||
|
||||
getThumbnail: function PDFThumbnailViewer_getThumbnail(index) { |
||||
return this.thumbnails[index]; |
||||
}, |
||||
|
||||
/** |
||||
* @private |
||||
*/ |
||||
_getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() { |
||||
return getVisibleElements(this.container, this.thumbnails); |
||||
}, |
||||
|
||||
scrollThumbnailIntoView: |
||||
function PDFThumbnailViewer_scrollThumbnailIntoView(page) { |
||||
var selected = document.querySelector('.thumbnail.selected'); |
||||
if (selected) { |
||||
selected.classList.remove('selected'); |
||||
} |
||||
var thumbnail = document.getElementById('thumbnailContainer' + page); |
||||
thumbnail.classList.add('selected'); |
||||
var visibleThumbs = this._getVisibleThumbs(); |
||||
var numVisibleThumbs = visibleThumbs.views.length; |
||||
|
||||
// If the thumbnail isn't currently visible, scroll it into view.
|
||||
if (numVisibleThumbs > 0) { |
||||
var first = visibleThumbs.first.id; |
||||
// Account for only one thumbnail being visible.
|
||||
var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first); |
||||
if (page <= first || page >= last) { |
||||
scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN }); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
get pagesRotation() { |
||||
return this._pagesRotation; |
||||
}, |
||||
|
||||
set pagesRotation(rotation) { |
||||
this._pagesRotation = rotation; |
||||
for (var i = 0, l = this.thumbnails.length; i < l; i++) { |
||||
var thumb = this.thumbnails[i]; |
||||
thumb.update(rotation); |
||||
} |
||||
}, |
||||
|
||||
cleanup: function PDFThumbnailViewer_cleanup() { |
||||
var tempCanvas = PDFThumbnailView.tempImageCache; |
||||
if (tempCanvas) { |
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
tempCanvas.width = 0; |
||||
tempCanvas.height = 0; |
||||
} |
||||
PDFThumbnailView.tempImageCache = null; |
||||
}, |
||||
|
||||
/** |
||||
* @private |
||||
*/ |
||||
_resetView: function PDFThumbnailViewer_resetView() { |
||||
this.thumbnails = []; |
||||
this._pagesRotation = 0; |
||||
this._pagesRequests = []; |
||||
}, |
||||
|
||||
setDocument: function PDFThumbnailViewer_setDocument(pdfDocument) { |
||||
if (this.pdfDocument) { |
||||
// cleanup of the elements and views
|
||||
var thumbsView = this.container; |
||||
while (thumbsView.hasChildNodes()) { |
||||
thumbsView.removeChild(thumbsView.lastChild); |
||||
} |
||||
this._resetView(); |
||||
} |
||||
|
||||
this.pdfDocument = pdfDocument; |
||||
if (!pdfDocument) { |
||||
return Promise.resolve(); |
||||
} |
||||
|
||||
return pdfDocument.getPage(1).then(function (firstPage) { |
||||
var pagesCount = pdfDocument.numPages; |
||||
var viewport = firstPage.getViewport(1.0); |
||||
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { |
||||
var thumbnail = new PDFThumbnailView({ |
||||
container: this.container, |
||||
id: pageNum, |
||||
defaultViewport: viewport.clone(), |
||||
linkService: this.linkService, |
||||
renderingQueue: this.renderingQueue |
||||
}); |
||||
this.thumbnails.push(thumbnail); |
||||
} |
||||
}.bind(this)); |
||||
}, |
||||
|
||||
/** |
||||
* @param {PDFPageView} pageView |
||||
* @returns {PDFPage} |
||||
* @private |
||||
*/ |
||||
_ensurePdfPageLoaded: |
||||
function PDFThumbnailViewer_ensurePdfPageLoaded(thumbView) { |
||||
if (thumbView.pdfPage) { |
||||
return Promise.resolve(thumbView.pdfPage); |
||||
} |
||||
var pageNumber = thumbView.id; |
||||
if (this._pagesRequests[pageNumber]) { |
||||
return this._pagesRequests[pageNumber]; |
||||
} |
||||
var promise = this.pdfDocument.getPage(pageNumber).then( |
||||
function (pdfPage) { |
||||
thumbView.setPdfPage(pdfPage); |
||||
this._pagesRequests[pageNumber] = null; |
||||
return pdfPage; |
||||
}.bind(this)); |
||||
this._pagesRequests[pageNumber] = promise; |
||||
return promise; |
||||
}, |
||||
|
||||
ensureThumbnailVisible: |
||||
function PDFThumbnailViewer_ensureThumbnailVisible(page) { |
||||
// Ensure that the thumbnail of the current page is visible
|
||||
// when switching from another view.
|
||||
scrollIntoView(document.getElementById('thumbnailContainer' + page)); |
||||
}, |
||||
|
||||
forceRendering: function () { |
||||
var visibleThumbs = this._getVisibleThumbs(); |
||||
var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, |
||||
this.thumbnails, |
||||
this.scroll.down); |
||||
if (thumbView) { |
||||
this._ensurePdfPageLoaded(thumbView).then(function () { |
||||
this.renderingQueue.renderView(thumbView); |
||||
}.bind(this)); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
return PDFThumbnailViewer; |
||||
})(); |
@ -1,410 +0,0 @@
@@ -1,410 +0,0 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
||||
/* Copyright 2012 Mozilla Foundation |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/* globals mozL10n, RenderingStates, Promise, scrollIntoView, |
||||
watchScroll, getVisibleElements */ |
||||
|
||||
'use strict'; |
||||
|
||||
var THUMBNAIL_SCROLL_MARGIN = -19; |
||||
var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; |
||||
|
||||
/** |
||||
* @constructor |
||||
* @param container |
||||
* @param id |
||||
* @param defaultViewport |
||||
* @param linkService |
||||
* @param renderingQueue |
||||
* |
||||
* @implements {IRenderableView} |
||||
*/ |
||||
var ThumbnailView = function thumbnailView(container, id, defaultViewport, |
||||
linkService, renderingQueue) { |
||||
var anchor = document.createElement('a'); |
||||
anchor.href = linkService.getAnchorUrl('#page=' + id); |
||||
anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); |
||||
anchor.onclick = function stopNavigation() { |
||||
linkService.page = id; |
||||
return false; |
||||
}; |
||||
|
||||
this.pdfPage = undefined; |
||||
this.viewport = defaultViewport; |
||||
this.pdfPageRotate = defaultViewport.rotation; |
||||
|
||||
this.rotation = 0; |
||||
this.pageWidth = this.viewport.width; |
||||
this.pageHeight = this.viewport.height; |
||||
this.pageRatio = this.pageWidth / this.pageHeight; |
||||
this.id = id; |
||||
this.renderingId = 'thumbnail' + id; |
||||
|
||||
this.canvasWidth = 98; |
||||
this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; |
||||
this.scale = this.canvasWidth / this.pageWidth; |
||||
|
||||
var div = this.el = document.createElement('div'); |
||||
div.id = 'thumbnailContainer' + id; |
||||
div.className = 'thumbnail'; |
||||
|
||||
if (id === 1) { |
||||
// Highlight the thumbnail of the first page when no page number is
|
||||
// specified (or exists in cache) when the document is loaded.
|
||||
div.classList.add('selected'); |
||||
} |
||||
|
||||
var ring = document.createElement('div'); |
||||
ring.className = 'thumbnailSelectionRing'; |
||||
ring.style.width = this.canvasWidth + 2 * THUMBNAIL_CANVAS_BORDER_WIDTH + |
||||
'px'; |
||||
ring.style.height = this.canvasHeight + 2 * THUMBNAIL_CANVAS_BORDER_WIDTH + |
||||
'px'; |
||||
|
||||
div.appendChild(ring); |
||||
anchor.appendChild(div); |
||||
container.appendChild(anchor); |
||||
|
||||
this.hasImage = false; |
||||
this.renderingState = RenderingStates.INITIAL; |
||||
this.renderingQueue = renderingQueue; |
||||
|
||||
this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) { |
||||
this.pdfPage = pdfPage; |
||||
this.pdfPageRotate = pdfPage.rotate; |
||||
var totalRotation = (this.rotation + this.pdfPageRotate) % 360; |
||||
this.viewport = pdfPage.getViewport(1, totalRotation); |
||||
this.update(); |
||||
}; |
||||
|
||||
this.update = function thumbnailViewUpdate(rotation) { |
||||
if (rotation !== undefined) { |
||||
this.rotation = rotation; |
||||
} |
||||
var totalRotation = (this.rotation + this.pdfPageRotate) % 360; |
||||
this.viewport = this.viewport.clone({ |
||||
scale: 1, |
||||
rotation: totalRotation |
||||
}); |
||||
this.pageWidth = this.viewport.width; |
||||
this.pageHeight = this.viewport.height; |
||||
this.pageRatio = this.pageWidth / this.pageHeight; |
||||
|
||||
this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; |
||||
this.scale = (this.canvasWidth / this.pageWidth); |
||||
|
||||
div.removeAttribute('data-loaded'); |
||||
ring.textContent = ''; |
||||
ring.style.width = this.canvasWidth + 2 * THUMBNAIL_CANVAS_BORDER_WIDTH + |
||||
'px'; |
||||
ring.style.height = this.canvasHeight + 2 * THUMBNAIL_CANVAS_BORDER_WIDTH + |
||||
'px'; |
||||
|
||||
this.hasImage = false; |
||||
this.renderingState = RenderingStates.INITIAL; |
||||
this.resume = null; |
||||
}; |
||||
|
||||
this.getPageDrawContext = function thumbnailViewGetPageDrawContext() { |
||||
var canvas = document.createElement('canvas'); |
||||
canvas.id = 'thumbnail' + id; |
||||
|
||||
canvas.width = this.canvasWidth; |
||||
canvas.height = this.canvasHeight; |
||||
canvas.className = 'thumbnailImage'; |
||||
canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', |
||||
{page: id}, 'Thumbnail of Page {{page}}')); |
||||
|
||||
div.setAttribute('data-loaded', true); |
||||
|
||||
ring.appendChild(canvas); |
||||
|
||||
return canvas.getContext('2d'); |
||||
}; |
||||
|
||||
this.drawingRequired = function thumbnailViewDrawingRequired() { |
||||
return !this.hasImage; |
||||
}; |
||||
|
||||
this.draw = function thumbnailViewDraw() { |
||||
if (this.renderingState !== RenderingStates.INITIAL) { |
||||
console.error('Must be in new state before drawing'); |
||||
} |
||||
|
||||
this.renderingState = RenderingStates.RUNNING; |
||||
if (this.hasImage) { |
||||
return Promise.resolve(undefined); |
||||
} |
||||
|
||||
var resolveRenderPromise, rejectRenderPromise; |
||||
var promise = new Promise(function (resolve, reject) { |
||||
resolveRenderPromise = resolve; |
||||
rejectRenderPromise = reject; |
||||
}); |
||||
|
||||
var self = this; |
||||
var ctx = this.getPageDrawContext(); |
||||
var drawViewport = this.viewport.clone({ scale: this.scale }); |
||||
var renderContext = { |
||||
canvasContext: ctx, |
||||
viewport: drawViewport, |
||||
continueCallback: function(cont) { |
||||
if (!self.renderingQueue.isHighestPriority(self)) { |
||||
self.renderingState = RenderingStates.PAUSED; |
||||
self.resume = function() { |
||||
self.renderingState = RenderingStates.RUNNING; |
||||
cont(); |
||||
}; |
||||
return; |
||||
} |
||||
cont(); |
||||
} |
||||
}; |
||||
this.pdfPage.render(renderContext).promise.then( |
||||
function pdfPageRenderCallback() { |
||||
self.renderingState = RenderingStates.FINISHED; |
||||
resolveRenderPromise(undefined); |
||||
}, |
||||
function pdfPageRenderError(error) { |
||||
self.renderingState = RenderingStates.FINISHED; |
||||
rejectRenderPromise(error); |
||||
} |
||||
); |
||||
this.hasImage = true; |
||||
return promise; |
||||
}; |
||||
|
||||
function getTempCanvas(width, height) { |
||||
var tempCanvas = ThumbnailView.tempImageCache; |
||||
if (!tempCanvas) { |
||||
tempCanvas = document.createElement('canvas'); |
||||
ThumbnailView.tempImageCache = tempCanvas; |
||||
} |
||||
tempCanvas.width = width; |
||||
tempCanvas.height = height; |
||||
|
||||
// Since this is a temporary canvas, we need to fill
|
||||
// the canvas with a white background ourselves.
|
||||
// |getPageDrawContext| uses CSS rules for this.
|
||||
var ctx = tempCanvas.getContext('2d'); |
||||
ctx.save(); |
||||
ctx.fillStyle = 'rgb(255, 255, 255)'; |
||||
ctx.fillRect(0, 0, width, height); |
||||
ctx.restore(); |
||||
return tempCanvas; |
||||
} |
||||
|
||||
this.setImage = function thumbnailViewSetImage(pageView) { |
||||
var img = pageView.canvas; |
||||
if (this.hasImage || !img) { |
||||
return; |
||||
} |
||||
if (!this.pdfPage) { |
||||
this.setPdfPage(pageView.pdfPage); |
||||
} |
||||
this.renderingState = RenderingStates.FINISHED; |
||||
var ctx = this.getPageDrawContext(); |
||||
var canvas = ctx.canvas; |
||||
|
||||
if (img.width <= 2 * canvas.width) { |
||||
ctx.drawImage(img, 0, 0, img.width, img.height, |
||||
0, 0, canvas.width, canvas.height); |
||||
} else { |
||||
// drawImage does an awful job of rescaling the image, doing it gradually
|
||||
var MAX_NUM_SCALING_STEPS = 3; |
||||
var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; |
||||
var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; |
||||
var reducedImage = getTempCanvas(reducedWidth, reducedHeight); |
||||
var reducedImageCtx = reducedImage.getContext('2d'); |
||||
|
||||
while (reducedWidth > img.width || reducedHeight > img.height) { |
||||
reducedWidth >>= 1; |
||||
reducedHeight >>= 1; |
||||
} |
||||
reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, |
||||
0, 0, reducedWidth, reducedHeight); |
||||
while (reducedWidth > 2 * canvas.width) { |
||||
reducedImageCtx.drawImage(reducedImage, |
||||
0, 0, reducedWidth, reducedHeight, |
||||
0, 0, reducedWidth >> 1, reducedHeight >> 1); |
||||
reducedWidth >>= 1; |
||||
reducedHeight >>= 1; |
||||
} |
||||
ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, |
||||
0, 0, canvas.width, canvas.height); |
||||
} |
||||
|
||||
this.hasImage = true; |
||||
}; |
||||
}; |
||||
|
||||
ThumbnailView.tempImageCache = null; |
||||
|
||||
/** |
||||
* @typedef {Object} PDFThumbnailViewerOptions |
||||
* @property {HTMLDivElement} container - The container for the thumbs elements. |
||||
* @property {IPDFLinkService} linkService - The navigation/linking service. |
||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object. |
||||
*/ |
||||
|
||||
/** |
||||
* Simple viewer control to display thumbs for pages. |
||||
* @class |
||||
*/ |
||||
var PDFThumbnailViewer = (function pdfThumbnailViewer() { |
||||
/** |
||||
* @constructs |
||||
* @param {PDFThumbnailViewerOptions} options |
||||
*/ |
||||
function PDFThumbnailViewer(options) { |
||||
this.container = options.container; |
||||
this.renderingQueue = options.renderingQueue; |
||||
this.linkService = options.linkService; |
||||
|
||||
this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this)); |
||||
this._resetView(); |
||||
} |
||||
|
||||
PDFThumbnailViewer.prototype = { |
||||
_scrollUpdated: function PDFThumbnailViewer_scrollUpdated() { |
||||
this.renderingQueue.renderHighestPriority(); |
||||
}, |
||||
|
||||
getThumbnail: function PDFThumbnailViewer_getThumbnail(index) { |
||||
return this.thumbnails[index]; |
||||
}, |
||||
|
||||
_getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() { |
||||
return getVisibleElements(this.container, this.thumbnails); |
||||
}, |
||||
|
||||
scrollThumbnailIntoView: function (page) { |
||||
var selected = document.querySelector('.thumbnail.selected'); |
||||
if (selected) { |
||||
selected.classList.remove('selected'); |
||||
} |
||||
var thumbnail = document.getElementById('thumbnailContainer' + page); |
||||
thumbnail.classList.add('selected'); |
||||
var visibleThumbs = this._getVisibleThumbs(); |
||||
var numVisibleThumbs = visibleThumbs.views.length; |
||||
|
||||
// If the thumbnail isn't currently visible, scroll it into view.
|
||||
if (numVisibleThumbs > 0) { |
||||
var first = visibleThumbs.first.id; |
||||
// Account for only one thumbnail being visible.
|
||||
var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first); |
||||
if (page <= first || page >= last) { |
||||
scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN }); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
get pagesRotation() { |
||||
return this._pagesRotation; |
||||
}, |
||||
|
||||
set pagesRotation(rotation) { |
||||
this._pagesRotation = rotation; |
||||
for (var i = 0, l = this.thumbnails.length; i < l; i++) { |
||||
var thumb = this.thumbnails[i]; |
||||
thumb.update(rotation); |
||||
} |
||||
}, |
||||
|
||||
cleanup: function PDFThumbnailViewer_cleanup() { |
||||
ThumbnailView.tempImageCache = null; |
||||
}, |
||||
|
||||
_resetView: function () { |
||||
this.thumbnails = []; |
||||
this._pagesRotation = 0; |
||||
this._pagesRequests = []; |
||||
}, |
||||
|
||||
setDocument: function (pdfDocument) { |
||||
if (this.pdfDocument) { |
||||
// cleanup of the elements and views
|
||||
var thumbsView = this.container; |
||||
while (thumbsView.hasChildNodes()) { |
||||
thumbsView.removeChild(thumbsView.lastChild); |
||||
} |
||||
this._resetView(); |
||||
} |
||||
|
||||
this.pdfDocument = pdfDocument; |
||||
if (!pdfDocument) { |
||||
return Promise.resolve(); |
||||
} |
||||
|
||||
return pdfDocument.getPage(1).then(function (firstPage) { |
||||
var pagesCount = pdfDocument.numPages; |
||||
var viewport = firstPage.getViewport(1.0); |
||||
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { |
||||
var thumbnail = new ThumbnailView(this.container, pageNum, |
||||
viewport.clone(), this.linkService, |
||||
this.renderingQueue); |
||||
this.thumbnails.push(thumbnail); |
||||
} |
||||
}.bind(this)); |
||||
}, |
||||
|
||||
/** |
||||
* @param {PDFPageView} pageView |
||||
* @returns {PDFPage} |
||||
* @private |
||||
*/ |
||||
_ensurePdfPageLoaded: function (thumbView) { |
||||
if (thumbView.pdfPage) { |
||||
return Promise.resolve(thumbView.pdfPage); |
||||
} |
||||
var pageNumber = thumbView.id; |
||||
if (this._pagesRequests[pageNumber]) { |
||||
return this._pagesRequests[pageNumber]; |
||||
} |
||||
var promise = this.pdfDocument.getPage(pageNumber).then( |
||||
function (pdfPage) { |
||||
thumbView.setPdfPage(pdfPage); |
||||
this._pagesRequests[pageNumber] = null; |
||||
return pdfPage; |
||||
}.bind(this)); |
||||
this._pagesRequests[pageNumber] = promise; |
||||
return promise; |
||||
}, |
||||
|
||||
ensureThumbnailVisible: |
||||
function PDFThumbnailViewer_ensureThumbnailVisible(page) { |
||||
// Ensure that the thumbnail of the current page is visible
|
||||
// when switching from another view.
|
||||
scrollIntoView(document.getElementById('thumbnailContainer' + page)); |
||||
}, |
||||
|
||||
forceRendering: function () { |
||||
var visibleThumbs = this._getVisibleThumbs(); |
||||
var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, |
||||
this.thumbnails, |
||||
this.scroll.down); |
||||
if (thumbView) { |
||||
this._ensurePdfPageLoaded(thumbView).then(function () { |
||||
this.renderingQueue.renderView(thumbView); |
||||
}.bind(this)); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
return PDFThumbnailViewer; |
||||
})(); |
Loading…
Reference in new issue