Skip to content

Commit

Permalink
Refactors PDFLinkService.
Browse files Browse the repository at this point in the history
  • Loading branch information
yurydelendik committed Apr 18, 2015
1 parent e9f7f55 commit b2a57c9
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 234 deletions.
6 changes: 6 additions & 0 deletions web/interfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ IPDFLinkService.prototype = {
* @param {string} action
*/
executeNamedAction: function (action) {},

/**
* @param {number} pageNum - page number.
* @param {Object} pageRef - reference to the page.
*/
cachePageRef: function (pageNum, pageRef) {},
};

/**
Expand Down
298 changes: 298 additions & 0 deletions web/pdf_link_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2015 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 PDFViewer, PDFHistory, Promise, parseQueryString */

'use strict';

/**
* Performs navigation functions inside PDF, such as opening specified page,
* or destination.
* @class
* @implements {IPDFLinkService}
*/
var PDFLinkService = (function () {
/**
* @constructs PDFLinkService
* @param {PDFLinkServiceOptions} options
*/
function PDFLinkService() {
this.pdfDocument = null;
this.pdfViewer = null;
this.pdfHistory = null;

this._pagesRefCache = null;
}

PDFLinkService.prototype = {
setDocument: function (pdfDocument) {
this.pdfDocument = pdfDocument;
this._pagesRefCache = Object.create(null);
},

setViewer: function (pdfViewer) {
this.pdfViewer = pdfViewer;
},

setHistory: function (pdfHistory) {
this.pdfHistory = pdfHistory;
},

get pagesCount() {
return this.pdfDocument.numPages;
},

/**
* @returns {number}
*/
get page() {
return this.pdfViewer.currentPageNumber;
},
/**
* @param {number} value
*/
set page(value) {
this.pdfViewer.currentPageNumber = value;
},
/**
* @param dest - The PDF destination object.
*/
navigateTo: function (dest) {
var destString = '';
var self = this;

var goToDestination = function(destRef) {
// dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
var pageNumber = destRef instanceof Object ?
self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
(destRef + 1);
if (pageNumber) {
if (pageNumber > self.pagesCount) {
pageNumber = self.pagesCount;
}
self.pdfViewer.scrollPageIntoView(pageNumber, dest);

// Update the browsing history.
self.pdfHistory.push({
dest: dest,
hash: destString,
page: pageNumber
});
} else {
self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
var pageNum = pageIndex + 1;
var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
self._pagesRefCache[cacheKey] = pageNum;
goToDestination(destRef);
});
}
};

var destinationPromise;
if (typeof dest === 'string') {
destString = dest;
destinationPromise = this.pdfDocument.getDestination(dest);
} else {
destinationPromise = Promise.resolve(dest);
}
destinationPromise.then(function(destination) {
dest = destination;
if (!(destination instanceof Array)) {
return; // invalid destination
}
goToDestination(destination[0]);
});
},

/**
* @param dest - The PDF destination object.
* @returns {string} The hyperlink to the PDF object.
*/
getDestinationHash: function (dest) {
if (typeof dest === 'string') {
return this.getAnchorUrl('#' + escape(dest));
}
if (dest instanceof Array) {
var destRef = dest[0]; // see navigateTo method for dest format
var pageNumber = destRef instanceof Object ?
this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
(destRef + 1);
if (pageNumber) {
var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
var destKind = dest[1];
if (typeof destKind === 'object' && 'name' in destKind &&
destKind.name === 'XYZ') {
var scale = (dest[4] || this.pdfViewer.currentScaleValue);
var scaleNumber = parseFloat(scale);
if (scaleNumber) {
scale = scaleNumber * 100;
}
pdfOpenParams += '&zoom=' + scale;
if (dest[2] || dest[3]) {
pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
}
}
return pdfOpenParams;
}
}
return '';
},

/**
* Prefix the full url on anchor links to make sure that links are resolved
* relative to the current URL instead of the one defined in <base href>.
* @param {String} anchor The anchor hash, including the #.
* @returns {string} The hyperlink to the PDF object.
*/
getAnchorUrl: function (anchor) {
//#if (GENERIC || B2G)
return anchor;
//#endif
//#if (FIREFOX || MOZCENTRAL)
// return this.url.split('#')[0] + anchor;
//#endif
//#if CHROME
// return location.href.split('#')[0] + anchor;
//#endif
},

/**
* @param {string} hash
*/
setHash: function (hash) {
if (hash.indexOf('=') >= 0) {
var params = parseQueryString(hash);
// borrowing syntax from "Parameters for Opening PDF Files"
if ('nameddest' in params) {
this.pdfHistory.updateNextHashParam(params.nameddest);
this.navigateTo(params.nameddest);
return;
}
var pageNumber, dest;
if ('page' in params) {
pageNumber = (params.page | 0) || 1;
}
if ('zoom' in params) {
// Build the destination array.
var zoomArgs = params.zoom.split(','); // scale,left,top
var zoomArg = zoomArgs[0];
var zoomArgNumber = parseFloat(zoomArg);

if (zoomArg.indexOf('Fit') === -1) {
// If the zoomArg is a number, it has to get divided by 100. If it's
// a string, it should stay as it is.
dest = [null, { name: 'XYZ' },
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
(zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
} else {
if (zoomArg === 'Fit' || zoomArg === 'FitB') {
dest = [null, { name: zoomArg }];
} else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
(zoomArg === 'FitV' || zoomArg === 'FitBV')) {
dest = [null, { name: zoomArg },
zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
} else if (zoomArg === 'FitR') {
if (zoomArgs.length !== 5) {
console.error('pdfViewSetHash: ' +
'Not enough parameters for \'FitR\'.');
} else {
dest = [null, { name: zoomArg },
(zoomArgs[1] | 0), (zoomArgs[2] | 0),
(zoomArgs[3] | 0), (zoomArgs[4] | 0)];
}
} else {
console.error('pdfViewSetHash: \'' + zoomArg +
'\' is not a valid zoom value.');
}
}
}
if (dest) {
this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
} else if (pageNumber) {
this.page = pageNumber; // simple page
}
if ('pagemode' in params) {
if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
params.pagemode === 'attachments') {
this.switchSidebarView((params.pagemode === 'bookmarks' ?
'outline' : params.pagemode), true);
} else if (params.pagemode === 'none' && this.sidebarOpen) {
document.getElementById('sidebarToggle').click();
}
}
} else if (/^\d+$/.test(hash)) { // page number
this.page = hash;
} else { // named destination
this.pdfHistory.updateNextHashParam(unescape(hash));
this.navigateTo(unescape(hash));
}
},

/**
* @param {string} action
*/
executeNamedAction: function (action) {
// See PDF reference, table 8.45 - Named action
switch (action) {
case 'GoBack':
this.pdfHistory.back();
break;

case 'GoForward':
this.pdfHistory.forward();
break;

case 'NextPage':
this.page++;
break;

case 'PrevPage':
this.page--;
break;

case 'LastPage':
this.page = this.pagesCount;
break;

case 'FirstPage':
this.page = 1;
break;

default:
break; // No action according to spec
}

var event = document.createEvent('CustomEvent');
event.initCustomEvent('namedaction', true, true, {
action: action
});
this.pdfViewer.container.dispatchEvent(event);

},

/**
* @param {number} pageNum - page number.
* @param {Object} pageRef - reference to the page.
*/
cachePageRef: function (pageNum, pageRef) {
var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
this._pagesRefCache[refStr] = pageNum;
}
};

return PDFLinkService;
})();
4 changes: 3 additions & 1 deletion web/pdf_viewer.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/
/*jshint globalstrict: false */
/* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder,
/* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService,
DefaultTextLayerFactory, AnnotationsLayerBuilder,
DefaultAnnotationsLayerFactory, getFileName */

Expand All @@ -28,10 +28,12 @@ if (typeof PDFJS === 'undefined') {
'use strict';

//#include ui_utils.js
//#include pdf_link_service.js
//#include pdf_viewer.js

PDFJS.PDFViewer = PDFViewer;
PDFJS.PDFPageView = PDFPageView;
PDFJS.PDFLinkService = PDFLinkService;
PDFJS.TextLayerBuilder = TextLayerBuilder;
PDFJS.DefaultTextLayerFactory = DefaultTextLayerFactory;
PDFJS.AnnotationsLayerBuilder = AnnotationsLayerBuilder;
Expand Down
11 changes: 8 additions & 3 deletions web/pdf_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ var PDFViewer = (function pdfViewer() {
}

var pagesCount = pdfDocument.numPages;
var pagesRefMap = this.pagesRefMap = {};
var self = this;

var resolvePagesPromise;
Expand Down Expand Up @@ -280,6 +279,8 @@ var PDFViewer = (function pdfViewer() {
this.pages.push(pageView);
}

var linkService = this.linkService;

// Fetch all the pages since the viewport is needed before printing
// starts to create the correct size canvas. Wait until one page is
// rendered so we don't tie up too many resources early on.
Expand All @@ -292,8 +293,7 @@ var PDFViewer = (function pdfViewer() {
if (!pageView.pdfPage) {
pageView.setPdfPage(pdfPage);
}
var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
pagesRefMap[refStr] = pageNum;
linkService.cachePageRef(pageNum, pdfPage.ref);
getPagesLeft--;
if (!getPagesLeft) {
resolvePagesPromise();
Expand Down Expand Up @@ -783,6 +783,11 @@ var SimpleLinkService = (function SimpleLinkServiceClosure() {
* @param {string} action
*/
executeNamedAction: function (action) {},
/**
* @param {number} pageNum - page number.
* @param {Object} pageRef - reference to the page.
*/
cachePageRef: function (pageNum, pageRef) {},
};
return SimpleLinkService;
})();
15 changes: 15 additions & 0 deletions web/ui_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ function watchScroll(viewAreaElement, callback) {
return state;
}

/**
* Helper function to parse query string (e.g. ?param1=value&parm2=...).
*/
function parseQueryString(query) {
var parts = query.split('&');
var params = {};
for (var i = 0, ii = parts.length; i < ii; ++i) {
var param = parts[i].split('=');
var key = param[0].toLowerCase();
var value = param.length > 1 ? param[1] : null;
params[decodeURIComponent(key)] = decodeURIComponent(value);
}
return params;
}

/**
* Use binary search to find the index of the first item in a given array which
* passes a given condition. The items are expected to be sorted in the sense
Expand Down
Loading

0 comments on commit b2a57c9

Please sign in to comment.