/**
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* <p>This module was extracted from the PdfJs previewer plugin that was originally a Share Extras
* (http://share-extras.github.io/) project and later integrated into Alfresco Share 5.0. It was
* created exclusively to support the [Aikau PdfJs module]{@link module:alfresco/preview/PdfJs/PdfJs}.</p>
* <p>This module is responsible for rendering the overall view of a PDF document where each individual
* page is an instance of a [DocumentPage]{@link module:alfresco/preview/PdfJs/DocumentPage}. It is used
* to render both the main view and the thumbnail view in the PDF previewer plugin.</p>
*
* @module alfresco/preview/PdfJs/DocumentView
* @extends dijit/_WidgetBase
* @mixes module:alfresco/core/Core
* @author Dave Draper
* @author Will Abson
* @auther Peter Löfgren
* @author Kevin Roast
*/
define(["dojo/_base/declare",
"dijit/_WidgetBase",
"alfresco/core/Core",
"alfresco/preview/PdfJs/DocumentPage",
"alfresco/preview/PdfJs/PdfJsConstants",
"dojo/_base/lang",
"dojo/on",
"dojo/dom-geometry",
"dojo/dom-class",
"dojo/dom-style",
"jquery"],
function(declare, _WidgetBase, AlfCore, DocumentPage, PdfJsConstants, lang, on, domGeom, domClass, domStyle, $) {
return declare([_WidgetBase, AlfCore], {
/**
* Currently active page
*
* @instance
* @default
*/
activePage : null,
/**
* Name of last scale to be auto-selected or selected by the user. This is the value which will be persisted when the
* document is unloaded and used to set up the same view the next time it is loaded.
*
* @instance
* @default
*/
lastScale : null,
/**
* Counter for viewer scroll events - incremented on event, decremented some time later. Rendering will occur only when counter reaches zero.
*
* @instance
* @type {int}
* @default
*/
renderOnScrollZero : 0,
/**
*
* @instance
*/
postCreate: function alfresco_preview_PdfJs_DocumentView__postCreate() {
this.viewer = this.domNode;
var computedStyle = domStyle.getComputedStyle(this.viewer);
this.viewerRegion = domGeom.getContentBox(this.viewer, computedStyle);
this.currentScale = this.currentScale || PdfJsConstants.K_UNKNOWN_SCALE;
this.name = this.name || "";
// Used for setupRenderLayoutTimer in TextLayerbuilder
this.lastScroll = 0;
var self = this;
// TODO: Replace with Dojo equivilant...
this.viewer.addEventListener("scroll", function()
{
self.lastScroll = Date.now();
}, false);
this.addScrollListener();
this.pages = [];
if (this.pagesToAdd)
{
this.addPages(this.pagesToAdd);
}
},
/**
* Add a single page from a PDF document to this view
*
* @instance
*/
addPage: function alfresco_preview_PdfJs_DocumentView__addPage(id, content) {
var page = new DocumentPage(id, content, this, {}, this.pdfJsPlugin);
this.pages.push(page);
},
/**
* Add pages from a PDF document to this view
*
* @instance
*/
addPages: function alfresco_preview_PdfJs_DocumentView__addPages(pages) {
for ( var i = 0; i < pages.length; i++)
{
var page = pages[i];
this.addPage(i + 1, page);
}
},
/**
* Render page containers and set their sizes. This does not render the page content itself, neither canvas or text layers.
*
* @instance
*/
render : function alfresco_preview_PdfJs_DocumentView__render() {
// Render each page (not canvas or text layers)
for ( var i = 0; i < this.pages.length; i++)
{
this.alfLog("log", "Rendering " + this.name + " page container " + (i+1));
this.pages[i].render();
}
// Set scale, if not already set
if (this.currentScale === PdfJsConstants.K_UNKNOWN_SCALE)
{
// Scale was not initialized: invalid bookmark or scale was not specified.
// Setting the default one.
this.setScale(this.parseScale(this.defaultScale));
}
else
{
this.alignRows();
}
},
/**
* Remove all existing canvas content
*
* @instance
*/
reset : function alfresco_preview_PdfJs_DocumentView__reset() {
// Remove all the existing canvas elements
for ( var i = 0; i < this.pages.length; i++)
{
this.pages[i].reset();
}
// Now redefine the row margins
this.alignRows();
},
/**
* Centre the rows of pages horizontally within their parent viewer element by adding the correct amount of left padding
*
* @instance
*/
alignRows : function alfresco_preview_PdfJs_DocumentView__alignRows() {
var rowPos = -1, rowWidth = 0, largestRow = 0, scrollY = $(document).scrollTop();
if (this.pageLayout === "multi")
{
domStyle.set(this.viewer, "padding-left", "0px");
for (var i = 0; i < this.pages.length; i++)
{
var page = this.pages[i],
container = page.container,
containerBounds = container.getBoundingClientRect(),
vpos = containerBounds.top + scrollY - page.parent.viewerRegion.t,
marginLeft = parseInt(domStyle.get(container, "margin-left"), 10);
// If multi-page mode is on, we need to add custom extra margin to the LHS of the 1st item in the row to make it centred
if (vpos !== rowPos)
{
rowWidth = marginLeft; // Rather than start from zero assume equal right padding on last row item
}
rowWidth += containerBounds.width + marginLeft;
largestRow = Math.max(largestRow, rowWidth);
rowPos = vpos;
}
domStyle.set(this.viewer, "padding-left", Math.floor((this.viewer.clientWidth - largestRow) / 2) + "px");
}
},
/**
* Render pages in the visible area of the viewer (or near it) given the current scroll position
*
* @instance
*/
renderVisiblePages : function alfresco_preview_PdfJs_DocumentView__renderVisiblePages() {
// region may not be populated properly if the div was hidden
var computedStyle = domStyle.getComputedStyle(this.viewer);
this.viewerRegion = domGeom.getContentBox(this.viewer, computedStyle);
// this.viewerRegion = Dom.getRegion(this.viewer);
var vheight = this.viewerRegion.h, vtop = this.viewerRegion.t;
this.alfLog("log", "Render " + this.name + " visible pages: viewer height " + this.viewerRegion.h + "px");
// Render visible pages
for (var i = 0; i < this.pages.length; i++)
{
var page = this.pages[i];
if (!page.canvas)
{
var pregion = page.container.getBoundingClientRect(),
top = pregion.top - vtop,
bottom = top + pregion.height,
vicinity = 1.5;
// WA - improve algorithm for selecting which pages to render, based on the following criteria
// Page top is above the viewer top edge, bottom below the bottom edge OR
// Bottom is within half the viewer height of the top edge OR
// Top is within half the viewer height of the bottom edge
if (top < 0 && 0 < bottom ||
-vheight * vicinity < bottom && bottom < vheight ||
0 < top && top < vheight * (vicinity + 1))
{
this.alfLog("log", "Rendering " + this.name + " page " + (i+1) + " content (page top:" + top + ", bottom:" + bottom + ")");
page.renderContent();
}
}
}
},
/**
* Scroll the viewer to the given page number
*
* @instance
* @param n {int} Number of the page to scroll to, 1 or greater.
*/
scrollTo : function alfresco_preview_PdfJs_DocumentView__scrollTo(n, offsetY) {
var newPos = this.pages[n - 1].getVPos(),
firstPos = this.pages[0].getVPos();
this.alfLog("log", "Scrolling " + this.name + " to page " + n +
". New page top is " + newPos + "px" +
". First page top is " + firstPos + "px");
var scrollTop = newPos - firstPos;
if (offsetY)
{
scrollTop += offsetY;
}
this.alfLog("log", "Old scrollTop was " + this.viewer.scrollTop + "px");
this.alfLog("log", "Set scrollTop to " + scrollTop + "px");
this.alfLog("log", "scrollTop offsetY " + offsetY);
this.viewer.scrollTop = scrollTop;
this.pageNum = n;
// Render visible pages
this.renderVisiblePages();
},
/**
* Set the scale of the view and remove all previously-rendered document content
*
* @instance
* @param value {float} numerical scale value
*/
setScale : function alfresco_preview_PdfJs_DocumentView__setScale(value) {
if (value === this.currentScale)
{
return;
}
this.alfLog("log", "Scale is now " + value);
this.currentScale = value;
// Remove all the existing canvas elements
this.reset();
// Now redefine the row margins
this.alignRows();
},
/**
* Calculate page zoom level based on the supplied value. Recognises numerical values and special string constants, e.g. 'page-fit'.
* Normally used in conjunction with setScale(), since this method does not set the current value.
*
* @instance
* @return {float} Numerical scale value
*/
parseScale : function alfresco_preview_PdfJs_DocumentView__parseScale(value) {
/*jshint maxcomplexity:false,maxstatements:false*/
var scale = parseFloat(value);
if (scale)
{
this.lastScale = value;
return scale;
}
if (this.pages.length !== 0)
{
var currentPage = this.pages[0],
container = currentPage.container,
hmargin = parseInt(domStyle.get(container, "margin-left"), 10) + parseInt(domStyle.get(container, "margin-right"), 10),
vmargin = parseInt(domStyle.get(container, "margin-top"), 10),
contentWidth = parseInt(currentPage.content.pageInfo.view[2], 10),
contentHeight = parseInt(currentPage.content.pageInfo.view[3], 10),
rotation = currentPage.content.pageInfo.rotate,
clientWidth = this.fullscreen ? window.screen.width : this.viewer.clientWidth - 1, // allow an extra pixel in width otherwise 2-up view wraps
clientHeight = this.fullscreen ? window.screen.height : this.viewer.clientHeight;
this.alfLog("log", "Client height: " + this.viewer.clientHeight);
if (rotation === 90 || rotation === 270)
{
var temp = contentWidth;
contentWidth = contentHeight;
contentHeight = temp;
}
var pageWidthScale, pageHeightScale;
switch (value)
{
case PdfJsConstants.ZOOM_LEVEL_PAGE_WIDTH:
pageWidthScale = (clientWidth - hmargin * 2) / contentWidth;
scale = pageWidthScale;
break;
case PdfJsConstants.ZOOM_LEVEL_TWO_PAGE_WIDTH:
pageWidthScale = (clientWidth - hmargin * 3) / contentWidth;
scale = pageWidthScale / 2;
break;
case PdfJsConstants.ZOOM_LEVEL_PAGE_HEIGHT:
pageHeightScale = (clientHeight - vmargin * 2) / contentHeight;
scale = pageHeightScale;
break;
case PdfJsConstants.ZOOM_LEVEL_PAGE_FIT:
pageWidthScale = (clientWidth - hmargin*2) / contentWidth;
pageHeightScale = (clientHeight - vmargin*2) / contentHeight;
scale = Math.min(pageWidthScale, pageHeightScale);
break;
case PdfJsConstants.ZOOM_LEVEL_TWO_PAGE_FIT:
pageWidthScale = (clientWidth - hmargin*3) / contentWidth;
pageHeightScale = (clientHeight - vmargin*2) / contentHeight;
scale = Math.min(pageWidthScale / 2, pageHeightScale);
break;
case PdfJsConstants.ZOOM_LEVEL_AUTO:
var tpf = this.parseScale(PdfJsConstants.ZOOM_LEVEL_TWO_PAGE_FIT),
opf = this.parseScale(PdfJsConstants.ZOOM_LEVEL_PAGE_FIT),
opw = this.parseScale(PdfJsConstants.ZOOM_LEVEL_PAGE_WIDTH),
tpw = this.parseScale(PdfJsConstants.ZOOM_LEVEL_TWO_PAGE_WIDTH),
minScale = this.autoMinScale,
maxScale = this.autoMaxScale;
if (tpf > minScale && this.numPages > 1)
{
scale = tpf;
}
else if (opf > minScale)
{
scale = opf;
}
else if (tpw > minScale && this.numPages > 1)
{
scale = tpw;
}
else if (opw > minScale)
{
scale = opw;
}
else
{
scale = minScale;
}
// Make sure that the page is not zoomed in *too* far.
// A limit of 125% max zoom is the default for the main view.
if (maxScale)
{
scale = Math.min(scale, maxScale);
}
break;
default:
throw "Unrecognised zoom level '" + value + "'";
}
}
else
{
throw "Unrecognised zoom level - no pages";
}
this.lastScale = value;
return Math.abs(scale); // Make sure of positive value!
},
/**
* Return the number of the page (1 or greater) that should be considered the 'current' page given the scroll position.
*
* @instance
* @returns {int} Number of the current page, 1 or greater
*/
getScrolledPageNumber : function alfresco_preview_PdfJs_DocumentView__getScrolledPageNumber() {
// Calculate new page number
for (var i = 0; i < this.pages.length; i++)
{
var page = this.pages[i],
vpos = page.getVPos();
if (vpos + parseInt(page.container.style.height, 10) / 2 > 0)
{
return i + 1;
}
}
return this.pages.length;
},
/**
* Set the currently-active page number
*
* @instance
*/
setActivePage : function alfresco_preview_PdfJs_DocumentView__setActivePage(n) {
if (this.activePage)
{
domClass.remove(this.activePage.container, "activePage");
}
domClass.add(this.pages[n - 1].container, "activePage");
this.activePage = this.pages[n - 1];
},
/**
*
*
* @instance
*/
onResize: function onResize() {
// TODO viewerRegion should be populated by an event?
var computedStyle = domStyle.getComputedStyle(this.viewer);
this.viewerRegion = domGeom.getContentBox(this.viewer, computedStyle);
},
/**
* Sets up a scroll event listener on the main viewer window. This allows rendering and publication
* of state to be performed as the user scrolls through the PDF.
*
* @instance
*/
addScrollListener: function alfresco_preview_PdfJs_DocumentView__addScrollListener() {
this.viewerScrollEventListener = on(this.viewer, "scroll", lang.hitch(this, this.onScrollEvent));
},
/**
* Removes the scroll event listener created by the [addScrollListener]
* {@link module:alfresco/preview/PdfJs/DocumentView#addScrollListener} function.
*
* @instance
*/
removeScrollListener: function alfresco_preview_PdfJs_DocumentView__addScrollListener() {
if (this.viewerScrollEventListener)
{
this.viewerScrollEventListener.remove();
}
},
/**
* This function handles scroll events and calls the [onScroll]
* {@link module:alfresco/preview/PdfJs/DocumentView#onScroll} if a short timeout is reached
* before another scroll event occurs. This is done to prevent to many scroll publications
* being generated unnecessarily.
*
* @instance
* @param {event} evt The scroll event object.
*/
onScrollEvent: function alfresco_preview_PdfJs_DocumentView__onScrollEvent(/*jshint unused:false*/ evt) {
// this.renderOnScrollZero++;
if (this.scrollEventTimeout)
{
clearTimeout(this.scrollEventTimeout);
}
// Set timeout to call onScroll if another scroll event isn't detected very soon...
this.scrollEventTimeout = setTimeout(lang.hitch(this, this.onScroll), 50);
},
/**
* Event handler for scroll event within the view area this will publish on a topic that
* the [PdfJs plugin]{@link module:alfresco/preview/PdfJs/PdfJs} should subscribe to so that
* it can update the active page and set the current page number.
*
* @instance
*/
onScroll: function alfresco_preview_PdfJs_DocumentView__onScroll(/*jshint unused:false*/ evt) {
// Render visible pages
this.renderVisiblePages();
this.alfPublish(PdfJsConstants.VIEWER_SCROLL_TOPIC, {});
}
});
});