diff --git a/src/core/obj.js b/src/core/obj.js index 434b5831e..696c007bd 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -14,9 +14,10 @@ */ import { - bytesToString, createPromiseCapability, createValidAbsoluteUrl, FormatError, - info, InvalidPDFException, isBool, isNum, isString, PermissionFlag, shadow, - stringToPDFString, stringToUTF8String, unreachable, warn + assert, bytesToString, createPromiseCapability, createValidAbsoluteUrl, + FormatError, info, InvalidPDFException, isBool, isNum, isString, + PermissionFlag, shadow, stringToPDFString, stringToUTF8String, unreachable, + warn } from '../shared/util'; import { Dict, isCmd, isDict, isName, isRef, isRefsEqual, isStream, Ref, RefSet, @@ -415,6 +416,136 @@ class Catalog { return shadow(this, 'pageMode', pageMode); } + get viewerPreferences() { + const ViewerPreferencesValidators = { + HideToolbar: isBool, + HideMenubar: isBool, + HideWindowUI: isBool, + FitWindow: isBool, + CenterWindow: isBool, + DisplayDocTitle: isBool, + NonFullScreenPageMode: isName, + Direction: isName, + ViewArea: isName, + ViewClip: isName, + PrintArea: isName, + PrintClip: isName, + PrintScaling: isName, + Duplex: isName, + PickTrayByPDFSize: isBool, + PrintPageRange: Array.isArray, + NumCopies: Number.isInteger, + }; + + const obj = this.catDict.get('ViewerPreferences'); + const prefs = Object.create(null); + + if (isDict(obj)) { + for (const key in ViewerPreferencesValidators) { + if (!obj.has(key)) { + continue; + } + const value = obj.get(key); + // Make sure the (standard) value conforms to the specification. + if (!ViewerPreferencesValidators[key](value)) { + info(`Bad value in ViewerPreferences for "${key}".`); + continue; + } + let prefValue; + + switch (key) { + case 'NonFullScreenPageMode': + switch (value.name) { + case 'UseNone': + case 'UseOutlines': + case 'UseThumbs': + case 'UseOC': + prefValue = value.name; + break; + default: + prefValue = 'UseNone'; + } + break; + case 'Direction': + switch (value.name) { + case 'L2R': + case 'R2L': + prefValue = value.name; + break; + default: + prefValue = 'L2R'; + } + break; + case 'ViewArea': + case 'ViewClip': + case 'PrintArea': + case 'PrintClip': + switch (value.name) { + case 'MediaBox': + case 'CropBox': + case 'BleedBox': + case 'TrimBox': + case 'ArtBox': + prefValue = value.name; + break; + default: + prefValue = 'CropBox'; + } + break; + case 'PrintScaling': + switch (value.name) { + case 'None': + case 'AppDefault': + prefValue = value.name; + break; + default: + prefValue = 'AppDefault'; + } + break; + case 'Duplex': + switch (value.name) { + case 'Simplex': + case 'DuplexFlipShortEdge': + case 'DuplexFlipLongEdge': + prefValue = value.name; + break; + default: + prefValue = 'None'; + } + break; + case 'PrintPageRange': + const length = value.length; + if (length % 2 !== 0) { // The number of elements must be even. + break; + } + const isValid = value.every((page, i, arr) => { + return (Number.isInteger(page) && page > 0) && + (i === 0 || page >= arr[i - 1]) && page <= this.numPages; + }); + if (isValid) { + prefValue = value; + } + break; + case 'NumCopies': + if (value > 0) { + prefValue = value; + } + break; + default: + assert(typeof value === 'boolean'); + prefValue = value; + } + + if (prefValue !== undefined) { + prefs[key] = prefValue; + } else { + info(`Bad value in ViewerPreferences for "${key}".`); + } + } + } + return shadow(this, 'viewerPreferences', prefs); + } + get openActionDestination() { const obj = this.catDict.get('OpenAction'); let openActionDest = null; diff --git a/src/core/worker.js b/src/core/worker.js index c1684c0fc..fee45edaf 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -525,7 +525,11 @@ var WorkerMessageHandler = { return pdfManager.ensureCatalog('pageMode'); }); - handler.on('getOpenActionDestination', function(data) { + handler.on('GetViewerPreferences', function(data) { + return pdfManager.ensureCatalog('viewerPreferences'); + }); + + handler.on('GetOpenActionDestination', function(data) { return pdfManager.ensureCatalog('openActionDestination'); }); diff --git a/src/display/api.js b/src/display/api.js index 95fe99f42..358ff57b2 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -674,6 +674,14 @@ class PDFDocumentProxy { return this._transport.getPageMode(); } + /** + * @return {Promise} A promise that is resolved with an {Object} containing + * the viewer preferences. + */ + getViewerPreferences() { + return this._transport.getViewerPreferences(); + } + /** * @return {Promise} A promise that is resolved with an {Array} containing the * destination, or `null` when no open action is present in the PDF file. @@ -2247,8 +2255,12 @@ class WorkerTransport { return this.messageHandler.sendWithPromise('GetPageMode', null); } + getViewerPreferences() { + return this.messageHandler.sendWithPromise('GetViewerPreferences', null); + } + getOpenActionDestination() { - return this.messageHandler.sendWithPromise('getOpenActionDestination', + return this.messageHandler.sendWithPromise('GetOpenActionDestination', null); } diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index f0cb6d6b8..de132ddba 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -18,8 +18,9 @@ import { NodeFileReaderFactory, TEST_PDFS_PATH } from './test_utils'; import { - createPromiseCapability, FontType, InvalidPDFException, MissingPDFException, - OPS, PasswordException, PasswordResponses, PermissionFlag, StreamType + createPromiseCapability, FontType, InvalidPDFException, isEmptyObj, + MissingPDFException, OPS, PasswordException, PasswordResponses, + PermissionFlag, StreamType } from '../../src/shared/util'; import { DOMCanvasFactory, RenderingCancelledException, StatTimer @@ -646,6 +647,27 @@ describe('api', function() { }).catch(done.fail); }); + it('gets default viewer preferences', function(done) { + var loadingTask = getDocument(buildGetDocumentParams('tracemonkey.pdf')); + + loadingTask.promise.then(function(pdfDocument) { + return pdfDocument.getViewerPreferences(); + }).then(function(prefs) { + expect(typeof prefs === 'object' && prefs !== null && + isEmptyObj(prefs)).toEqual(true); + + loadingTask.destroy().then(done); + }).catch(done.fail); + }); + it('gets non-default viewer preferences', function(done) { + doc.getViewerPreferences().then(function(prefs) { + expect(prefs).toEqual({ + Direction: 'L2R', + }); + done(); + }).catch(done.fail); + }); + it('gets default open action destination', function(done) { var loadingTask = getDocument(buildGetDocumentParams('tracemonkey.pdf'));