Add support for PageLabels in the viewer

This patch implements the page label functionality in a similar way as Adobe Reader.
For documents with page labels, if a non-existent page label is entered we'll try to fallback to the page number instead.
The patch also includes a preference (`disablePageLabels`), to make it easy to opt-out of using page labels if the user/implementor so wishes.

The way that `get/set currentPageLabel` is implemented in `PDFViewer`, is as wrappers for the corresponding `get/set currentPageNumber` functions, since that seemed like the cleanest solution.
The page labels are purposely *only* added to the page controls in the viewer UI, and not stored in e.g. the `ViewHistory`. Since doing so would mean adding unnecessary code complexity, without any real added value, and would also mean delaying the inital loading of PDF documents.

Note that this patch will ignore page labels if they are identical to standard page numbering, since in this case displaying the page labels adds no value (but only UI noise). The reason for handling this case specially, is that in practice a surprising number of PDF files include "pointless" page labels.
This commit is contained in:
Jonas Jenwald 2016-01-23 13:56:56 +01:00
parent 23ec02bb93
commit f461fd64aa
7 changed files with 137 additions and 11 deletions

View File

@ -89,6 +89,10 @@
],
"default": 0
},
"disablePageLabels": {
"type": "boolean",
"default": false
},
"disableTelemetry": {
"title": "Disable telemetry",
"type": "boolean",

View File

@ -20,9 +20,13 @@ next_label=Next
# LOCALIZATION NOTE (page.title): The tooltip for the pageNumber input.
page.title=Page
# LOCALIZATION NOTE (page_of): "{{pageCount}}" will be replaced by a number
# LOCALIZATION NOTE (of_pages): "{{pagesCount}}" will be replaced by a number
# representing the total number of pages in the document.
page_of=of {{pageCount}}
of_pages=of {{pagesCount}}
# LOCALIZATION NOTE (page_of_pages): "{{pageNumber}}" and "{{pagesCount}}"
# will be replaced by a number representing the currently visible page,
# respectively a number representing the total number of pages in the document.
+page_of_pages=({{pageNumber}} of {{pagesCount}})
zoom_out.title=Zoom Out
zoom_out_label=Zoom Out

View File

@ -20,9 +20,13 @@ next_label=Nästa
# LOCALIZATION NOTE (page.title): The tooltip for the pageNumber input.
page.title=Sida
# LOCALIZATION NOTE (page_of): "{{pageCount}}" will be replaced by a number
# LOCALIZATION NOTE (of_pages): "{{pagesCount}}" will be replaced by a number
# representing the total number of pages in the document.
page_of=av {{pageCount}}
of_pages=av {{pagesCount}}
# LOCALIZATION NOTE (page_of_pages): "{{pageNumber}}" and "{{pagesCount}}"
# will be replaced by a number representing the currently active visible page,
# respectively a number representing the total number of pages in the document.
page_of_pages=({{pageNumber}} av {{pagesCount}})
zoom_out.title=Zooma ut
zoom_out_label=Zooma ut

View File

@ -178,10 +178,12 @@ var PDFViewerApplication = {
preferencePdfBugEnabled: false,
preferenceShowPreviousViewOnLoad: true,
preferenceDefaultZoomValue: '',
preferenceDisablePageLabels: false,
isViewerEmbedded: (window.parent !== window),
url: '',
baseUrl: '',
externalServices: DefaultExernalServices,
hasPageLabels: false,
// called once when the document is loaded
initialize: function pdfViewInitialize(appConfig) {
@ -380,6 +382,9 @@ var PDFViewerApplication = {
// before the various viewer components are initialized.
self.pdfViewer.renderInteractiveForms = value;
}),
Preferences.get('disablePageLabels').then(function resolved(value) {
self.preferenceDisablePageLabels = value;
}),
// TODO move more preferences and other async stuff here
]).catch(function (reason) { });
@ -567,6 +572,7 @@ var PDFViewerApplication = {
}
this.store = null;
this.isInitialViewSet = false;
this.hasPageLabels = false;
this.pdfSidebar.reset();
this.pdfOutlineViewer.reset();
@ -879,7 +885,8 @@ var PDFViewerApplication = {
this.pageRotation = 0;
this.pdfThumbnailViewer.setDocument(pdfDocument);
var pdfThumbnailViewer = this.pdfThumbnailViewer;
pdfThumbnailViewer.setDocument(pdfDocument);
firstPagePromise.then(function(pdfPage) {
downloadedPromise.then(function () {
@ -959,6 +966,33 @@ var PDFViewerApplication = {
});
});
pdfDocument.getPageLabels().then(function (labels) {
if (!labels || self.preferenceDisablePageLabels) {
return;
}
var i = 0, numLabels = labels.length;
if (numLabels !== self.pagesCount) {
console.error('The number of Page Labels does not match ' +
'the number of pages in the document.');
return;
}
// Ignore page labels that correspond to standard page numbering.
while (i < numLabels && labels[i] === (i + 1).toString()) {
i++;
}
if (i === numLabels) {
return;
}
pdfViewer.setPageLabels(labels);
pdfThumbnailViewer.setPageLabels(labels);
self.hasPageLabels = true;
self._updateUIToolbar({
resetNumPages: true,
});
});
pagesPromise.then(function() {
if (self.supportsPrinting) {
pdfDocument.getJavaScript().then(function(javaScript) {
@ -1186,6 +1220,7 @@ var PDFViewerApplication = {
/**
* @typedef UpdateUIToolbarParameters
* @property {number} pageNumber
* @property {string} pageLabel
* @property {string} scaleValue
* @property {number} scale
* @property {boolean} resetNumPages
@ -1226,11 +1261,25 @@ var PDFViewerApplication = {
var pagesCount = this.pagesCount;
if (resetNumPages) {
toolbarConfig.numPages.textContent =
mozL10n.get('page_of', { pageCount: pagesCount }, 'of {{pageCount}}');
if (this.hasPageLabels) {
toolbarConfig.pageNumber.type = 'text';
} else {
toolbarConfig.pageNumber.type = 'number';
toolbarConfig.numPages.textContent = mozL10n.get('of_pages',
{ pagesCount: pagesCount }, 'of {{pagesCount}}');
}
toolbarConfig.pageNumber.max = pagesCount;
}
toolbarConfig.pageNumber.value = pageNumber;
if (this.hasPageLabels) {
toolbarConfig.pageNumber.value = params.pageLabel ||
this.pdfViewer.currentPageLabel;
toolbarConfig.numPages.textContent = mozL10n.get('page_of_pages',
{ pageNumber: pageNumber, pagesCount: pagesCount },
'({{pageNumber}} of {{pagesCount}})');
} else {
toolbarConfig.pageNumber.value = pageNumber;
}
toolbarConfig.previous.disabled = (pageNumber <= 1);
toolbarConfig.next.disabled = (pageNumber >= pagesCount);
@ -1495,11 +1544,13 @@ function webViewerInitialized() {
});
appConfig.toolbar.pageNumber.addEventListener('change', function() {
PDFViewerApplication.page = (this.value | 0);
var pdfViewer = PDFViewerApplication.pdfViewer;
pdfViewer.currentPageLabel = this.value;
// Ensure that the page number input displays the correct value, even if the
// value entered by the user was invalid (e.g. a floating point number).
if (this.value !== PDFViewerApplication.page.toString()) {
if (this.value !== pdfViewer.currentPageNumber.toString() &&
this.value !== pdfViewer.currentPageLabel) {
PDFViewerApplication._updateUIToolbar({});
}
});
@ -1930,6 +1981,7 @@ function webViewerPageChanging(e) {
PDFViewerApplication._updateUIToolbar({
pageNumber: page,
pageLabel: e.pageLabel,
});
if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {

View File

@ -13,5 +13,6 @@
"useOnlyCssZoom": false,
"externalLinkTarget": 0,
"enhanceTextSelection": false,
"renderInteractiveForms": false
"renderInteractiveForms": false,
"disablePageLabels": false
}

View File

@ -133,6 +133,7 @@ var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() {
*/
_resetView: function PDFThumbnailViewer_resetView() {
this.thumbnails = [];
this._pageLabels = null;
this._pagesRotation = 0;
this._pagesRequests = [];
@ -179,6 +180,24 @@ var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() {
}
},
/**
* @param {Array|null} labels
*/
setPageLabels: function PDFThumbnailViewer_setPageLabels(labels) {
if (!this.pdfDocument) {
return;
}
if (!labels) {
this._pageLabels = null;
} else if (!(labels instanceof Array &&
this.pdfDocument.numPages === labels.length)) {
this._pageLabels = null;
console.error('PDFThumbnailViewer_setPageLabels: Invalid page labels.');
} else {
this._pageLabels = labels;
}
},
/**
* @param {PDFThumbnailView} thumbView
* @returns {PDFPage}

View File

@ -211,6 +211,7 @@ var PDFViewer = (function pdfViewer() {
var arg = {
source: this,
pageNumber: val,
pageLabel: this._pageLabels && this._pageLabels[val - 1],
};
this._currentPageNumber = val;
this.eventBus.dispatch('pagechanging', arg);
@ -221,6 +222,28 @@ var PDFViewer = (function pdfViewer() {
}
},
/**
* @returns {string|null} Returns the current page label,
* or `null` if no page labels exist.
*/
get currentPageLabel() {
return this._pageLabels && this._pageLabels[this._currentPageNumber - 1];
},
/**
* @param {string} val - The page label.
*/
set currentPageLabel(val) {
var pageNumber = val | 0; // Fallback page number.
if (this._pageLabels) {
var i = this._pageLabels.indexOf(val);
if (i >= 0) {
pageNumber = i + 1;
}
}
this.currentPageNumber = pageNumber;
},
/**
* @returns {number}
*/
@ -414,11 +437,30 @@ var PDFViewer = (function pdfViewer() {
}.bind(this));
},
/**
* @param {Array|null} labels
*/
setPageLabels: function PDFViewer_setPageLabels(labels) {
if (!this.pdfDocument) {
return;
}
if (!labels) {
this._pageLabels = null;
} else if (!(labels instanceof Array &&
this.pdfDocument.numPages === labels.length)) {
this._pageLabels = null;
console.error('PDFViewer_setPageLabels: Invalid page labels.');
} else {
this._pageLabels = labels;
}
},
_resetView: function () {
this._pages = [];
this._currentPageNumber = 1;
this._currentScale = UNKNOWN_SCALE;
this._currentScaleValue = null;
this._pageLabels = null;
this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
this._location = null;
this._pagesRotation = 0;