[api-minor] Add support for ViewerPreferences in the API (issue 10736)

Please see the specification, https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf#M11.9.12864.1Heading.71.Viewer.Preferences

Furthermore, note that this patch *only* adds API support and unit-tests but does not attempt to integrate e.g. the `ViewerPreferences -> Direction` property into the viewer (which would be necessary to address issue 10736).
The reason for this is that it's not entirely clear to me exactly if/how that could be implemented; e.g. would it be as simple as setting the `dir` attribute on the `viewerContainer` DOM element, or will it be more complicated?
There's also the question of how the `ViewerPreferences -> Direction` value interacts with the `PageMode`, and this will generally require a fair bit of manual testing. Since the direction of the *entire* viewer depends on the browser locale, there's also a somewhat open question regarding what default value to use for different locales.
Finally, if the viewer supports `ViewerPreferences -> Direction` then I'm assuming that it will be necessary to allow users to override the default value, which will require (most likely) new `SecondaryToolbar` buttons and icons for those etc.

Hence this patch only lays the necessary foundation for eventually addressing issue 10736, but defers the actual implementation until later. (Time permitting, I'll try to look into the viewer part later.)
This commit is contained in:
Jonas Jenwald 2019-04-14 13:13:59 +02:00
parent 8bbae79832
commit 311bac3ebb
4 changed files with 176 additions and 7 deletions

View File

@ -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;

View File

@ -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');
});

View File

@ -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.
@ -2246,8 +2254,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);
}

View File

@ -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'));