Implement a single getInheritableProperty
utility function
This function combines the logic of two separate methods into one. The loop limit is also a good thing to have for the calls in `src/core/annotation.js`. Moreover, since this is important functionality, a set of unit tests and documentation is added.
This commit is contained in:
parent
4e5eb59a33
commit
f308d73d40
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AnnotationBorderStyleType, AnnotationFieldFlag, AnnotationFlag,
|
AnnotationBorderStyleType, AnnotationFieldFlag, AnnotationFlag,
|
||||||
AnnotationType, OPS, stringToBytes, stringToPDFString, Util, warn
|
AnnotationType, getInheritableProperty, OPS, stringToBytes, stringToPDFString,
|
||||||
|
Util, warn
|
||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
import { Catalog, FileSpec, ObjectLoader } from './obj';
|
import { Catalog, FileSpec, ObjectLoader } from './obj';
|
||||||
import { Dict, isDict, isName, isRef, isStream } from './primitives';
|
import { Dict, isDict, isName, isRef, isStream } from './primitives';
|
||||||
@ -60,7 +61,7 @@ class AnnotationFactory {
|
|||||||
return new TextAnnotation(parameters);
|
return new TextAnnotation(parameters);
|
||||||
|
|
||||||
case 'Widget':
|
case 'Widget':
|
||||||
let fieldType = Util.getInheritableProperty(dict, 'FT');
|
let fieldType = getInheritableProperty({ dict, key: 'FT', });
|
||||||
fieldType = isName(fieldType) ? fieldType.name : null;
|
fieldType = isName(fieldType) ? fieldType.name : null;
|
||||||
|
|
||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
@ -580,15 +581,16 @@ class WidgetAnnotation extends Annotation {
|
|||||||
|
|
||||||
data.annotationType = AnnotationType.WIDGET;
|
data.annotationType = AnnotationType.WIDGET;
|
||||||
data.fieldName = this._constructFieldName(dict);
|
data.fieldName = this._constructFieldName(dict);
|
||||||
data.fieldValue = Util.getInheritableProperty(dict, 'V',
|
data.fieldValue = getInheritableProperty({ dict, key: 'V',
|
||||||
/* getArray = */ true);
|
getArray: true, });
|
||||||
data.alternativeText = stringToPDFString(dict.get('TU') || '');
|
data.alternativeText = stringToPDFString(dict.get('TU') || '');
|
||||||
data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
|
data.defaultAppearance = getInheritableProperty({ dict, key: 'DA', }) || '';
|
||||||
let fieldType = Util.getInheritableProperty(dict, 'FT');
|
let fieldType = getInheritableProperty({ dict, key: 'FT', });
|
||||||
data.fieldType = isName(fieldType) ? fieldType.name : null;
|
data.fieldType = isName(fieldType) ? fieldType.name : null;
|
||||||
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
|
this.fieldResources = getInheritableProperty({ dict, key: 'DR', }) ||
|
||||||
|
Dict.empty;
|
||||||
|
|
||||||
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff');
|
data.fieldFlags = getInheritableProperty({ dict, key: 'Ff', });
|
||||||
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
|
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
|
||||||
data.fieldFlags = 0;
|
data.fieldFlags = 0;
|
||||||
}
|
}
|
||||||
@ -675,18 +677,20 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
|||||||
constructor(params) {
|
constructor(params) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
|
const dict = params.dict;
|
||||||
|
|
||||||
// The field value is always a string.
|
// The field value is always a string.
|
||||||
this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
|
this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
|
||||||
|
|
||||||
// Determine the alignment of text in the field.
|
// Determine the alignment of text in the field.
|
||||||
let alignment = Util.getInheritableProperty(params.dict, 'Q');
|
let alignment = getInheritableProperty({ dict, key: 'Q', });
|
||||||
if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) {
|
if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) {
|
||||||
alignment = null;
|
alignment = null;
|
||||||
}
|
}
|
||||||
this.data.textAlignment = alignment;
|
this.data.textAlignment = alignment;
|
||||||
|
|
||||||
// Determine the maximum length of text in the field.
|
// Determine the maximum length of text in the field.
|
||||||
let maximumLength = Util.getInheritableProperty(params.dict, 'MaxLen');
|
let maximumLength = getInheritableProperty({ dict, key: 'MaxLen', });
|
||||||
if (!Number.isInteger(maximumLength) || maximumLength < 0) {
|
if (!Number.isInteger(maximumLength) || maximumLength < 0) {
|
||||||
maximumLength = null;
|
maximumLength = null;
|
||||||
}
|
}
|
||||||
@ -814,7 +818,7 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
|
|||||||
// inherit the options from a parent annotation (issue 8094).
|
// inherit the options from a parent annotation (issue 8094).
|
||||||
this.data.options = [];
|
this.data.options = [];
|
||||||
|
|
||||||
let options = Util.getInheritableProperty(params.dict, 'Opt');
|
let options = getInheritableProperty({ dict: params.dict, key: 'Opt', });
|
||||||
if (Array.isArray(options)) {
|
if (Array.isArray(options)) {
|
||||||
let xref = params.xref;
|
let xref = params.xref;
|
||||||
for (let i = 0, ii = options.length; i < ii; i++) {
|
for (let i = 0, ii = options.length; i < ii; i++) {
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
import { Catalog, ObjectLoader, XRef } from './obj';
|
import { Catalog, ObjectLoader, XRef } from './obj';
|
||||||
import { Dict, isDict, isName, isStream } from './primitives';
|
import { Dict, isDict, isName, isStream } from './primitives';
|
||||||
import {
|
import {
|
||||||
info, isArrayBuffer, isNum, isSpace, isString, MissingDataException, OPS,
|
getInheritableProperty, info, isArrayBuffer, isNum, isSpace, isString,
|
||||||
shadow, stringToBytes, stringToPDFString, Util, warn
|
MissingDataException, OPS, shadow, stringToBytes, stringToPDFString, Util
|
||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
import { NullStream, Stream, StreamsSequenceStream } from './stream';
|
import { NullStream, Stream, StreamsSequenceStream } from './stream';
|
||||||
import { AnnotationFactory } from './annotation';
|
import { AnnotationFactory } from './annotation';
|
||||||
@ -62,33 +62,19 @@ var Page = (function PageClosure() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Page.prototype = {
|
Page.prototype = {
|
||||||
getInheritedPageProp: function Page_getInheritedPageProp(key, getArray) {
|
/**
|
||||||
var dict = this.pageDict, valueArray = null, loopCount = 0;
|
* @private
|
||||||
var MAX_LOOP_COUNT = 100;
|
*/
|
||||||
getArray = getArray || false;
|
_getInheritableProperty(key, getArray = false) {
|
||||||
// Always walk up the entire parent chain, to be able to find
|
let value = getInheritableProperty({ dict: this.pageDict, key, getArray,
|
||||||
// e.g. \Resources placed on multiple levels of the tree.
|
stopWhenFound: false, });
|
||||||
while (dict) {
|
if (!Array.isArray(value)) {
|
||||||
var value = getArray ? dict.getArray(key) : dict.get(key);
|
return value;
|
||||||
if (value !== undefined) {
|
|
||||||
if (!valueArray) {
|
|
||||||
valueArray = [];
|
|
||||||
}
|
|
||||||
valueArray.push(value);
|
|
||||||
}
|
|
||||||
if (++loopCount > MAX_LOOP_COUNT) {
|
|
||||||
warn('getInheritedPageProp: maximum loop count exceeded for ' + key);
|
|
||||||
return valueArray ? valueArray[0] : undefined;
|
|
||||||
}
|
|
||||||
dict = dict.get('Parent');
|
|
||||||
}
|
}
|
||||||
if (!valueArray) {
|
if (value.length === 1 || !isDict(value[0])) {
|
||||||
return undefined;
|
return value[0];
|
||||||
}
|
}
|
||||||
if (valueArray.length === 1 || !isDict(valueArray[0])) {
|
return Dict.merge(this.xref, value);
|
||||||
return valueArray[0];
|
|
||||||
}
|
|
||||||
return Dict.merge(this.xref, valueArray);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get content() {
|
get content() {
|
||||||
@ -100,11 +86,12 @@ var Page = (function PageClosure() {
|
|||||||
// present, but can be empty. Some document omit it still, in this case
|
// present, but can be empty. Some document omit it still, in this case
|
||||||
// we return an empty dictionary.
|
// we return an empty dictionary.
|
||||||
return shadow(this, 'resources',
|
return shadow(this, 'resources',
|
||||||
this.getInheritedPageProp('Resources') || Dict.empty);
|
this._getInheritableProperty('Resources') || Dict.empty);
|
||||||
},
|
},
|
||||||
|
|
||||||
get mediaBox() {
|
get mediaBox() {
|
||||||
var mediaBox = this.getInheritedPageProp('MediaBox', true);
|
var mediaBox = this._getInheritableProperty('MediaBox',
|
||||||
|
/* getArray = */ true);
|
||||||
// Reset invalid media box to letter size.
|
// Reset invalid media box to letter size.
|
||||||
if (!Array.isArray(mediaBox) || mediaBox.length !== 4) {
|
if (!Array.isArray(mediaBox) || mediaBox.length !== 4) {
|
||||||
return shadow(this, 'mediaBox', LETTER_SIZE_MEDIABOX);
|
return shadow(this, 'mediaBox', LETTER_SIZE_MEDIABOX);
|
||||||
@ -113,7 +100,8 @@ var Page = (function PageClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get cropBox() {
|
get cropBox() {
|
||||||
var cropBox = this.getInheritedPageProp('CropBox', true);
|
var cropBox = this._getInheritableProperty('CropBox',
|
||||||
|
/* getArray = */ true);
|
||||||
// Reset invalid crop box to media box.
|
// Reset invalid crop box to media box.
|
||||||
if (!Array.isArray(cropBox) || cropBox.length !== 4) {
|
if (!Array.isArray(cropBox) || cropBox.length !== 4) {
|
||||||
return shadow(this, 'cropBox', this.mediaBox);
|
return shadow(this, 'cropBox', this.mediaBox);
|
||||||
@ -143,7 +131,7 @@ var Page = (function PageClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get rotate() {
|
get rotate() {
|
||||||
var rotate = this.getInheritedPageProp('Rotate') || 0;
|
var rotate = this._getInheritableProperty('Rotate') || 0;
|
||||||
// Normalize rotation so it's a multiple of 90 and between 0 and 270
|
// Normalize rotation so it's a multiple of 90 and between 0 and 270
|
||||||
if (rotate % 90 !== 0) {
|
if (rotate % 90 !== 0) {
|
||||||
rotate = 0;
|
rotate = 0;
|
||||||
@ -180,7 +168,7 @@ var Page = (function PageClosure() {
|
|||||||
|
|
||||||
loadResources: function Page_loadResources(keys) {
|
loadResources: function Page_loadResources(keys) {
|
||||||
if (!this.resourcesPromise) {
|
if (!this.resourcesPromise) {
|
||||||
// TODO: add async getInheritedPageProp and remove this.
|
// TODO: add async `_getInheritableProperty` and remove this.
|
||||||
this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
|
this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
|
||||||
}
|
}
|
||||||
return this.resourcesPromise.then(() => {
|
return this.resourcesPromise.then(() => {
|
||||||
@ -316,7 +304,7 @@ var Page = (function PageClosure() {
|
|||||||
|
|
||||||
get annotations() {
|
get annotations() {
|
||||||
var annotations = [];
|
var annotations = [];
|
||||||
var annotationRefs = this.getInheritedPageProp('Annots') || [];
|
var annotationRefs = this._getInheritableProperty('Annots') || [];
|
||||||
for (var i = 0, n = annotationRefs.length; i < n; ++i) {
|
for (var i = 0, n = annotationRefs.length; i < n; ++i) {
|
||||||
var annotationRef = annotationRefs[i];
|
var annotationRef = annotationRefs[i];
|
||||||
var annotation = AnnotationFactory.create(this.xref, annotationRef,
|
var annotation = AnnotationFactory.create(this.xref, annotationRef,
|
||||||
|
@ -644,6 +644,53 @@ function isEvalSupported() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of an inheritable property.
|
||||||
|
*
|
||||||
|
* If the PDF specification explicitly lists a property in a dictionary as
|
||||||
|
* inheritable, then the value of the property may be present in the dictionary
|
||||||
|
* itself or in one or more parents of the dictionary.
|
||||||
|
*
|
||||||
|
* If the key is not found in the tree, `undefined` is returned. Otherwise,
|
||||||
|
* the value for the key is returned or, if `stopWhenFound` is `false`, a list
|
||||||
|
* of values is returned. To avoid infinite loops, the traversal is stopped when
|
||||||
|
* the loop limit is reached.
|
||||||
|
*
|
||||||
|
* @param {Dict} dict - Dictionary from where to start the traversal.
|
||||||
|
* @param {string} key - The key of the property to find the value for.
|
||||||
|
* @param {boolean} getArray - Whether or not the value should be fetched as an
|
||||||
|
* array. The default value is `false`.
|
||||||
|
* @param {boolean} stopWhenFound - Whether or not to stop the traversal when
|
||||||
|
* the key is found. If set to `false`, we always walk up the entire parent
|
||||||
|
* chain, for example to be able to find `\Resources` placed on multiple
|
||||||
|
* levels of the tree. The default value is `true`.
|
||||||
|
*/
|
||||||
|
function getInheritableProperty({ dict, key, getArray = false,
|
||||||
|
stopWhenFound = true, }) {
|
||||||
|
const LOOP_LIMIT = 100;
|
||||||
|
let loopCount = 0;
|
||||||
|
let values;
|
||||||
|
|
||||||
|
while (dict) {
|
||||||
|
const value = getArray ? dict.getArray(key) : dict.get(key);
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (stopWhenFound) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (!values) {
|
||||||
|
values = [];
|
||||||
|
}
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
if (++loopCount > LOOP_LIMIT) {
|
||||||
|
warn(`getInheritableProperty: maximum loop count exceeded for "${key}"`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dict = dict.get('Parent');
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
|
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
|
||||||
|
|
||||||
var Util = (function UtilClosure() {
|
var Util = (function UtilClosure() {
|
||||||
@ -853,17 +900,6 @@ var Util = (function UtilClosure() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Util.getInheritableProperty =
|
|
||||||
function Util_getInheritableProperty(dict, name, getArray) {
|
|
||||||
while (dict && !dict.has(name)) {
|
|
||||||
dict = dict.get('Parent');
|
|
||||||
}
|
|
||||||
if (!dict) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return getArray ? dict.getArray(name) : dict.get(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
Util.inherit = function Util_inherit(sub, base, prototype) {
|
Util.inherit = function Util_inherit(sub, base, prototype) {
|
||||||
sub.prototype = Object.create(base.prototype);
|
sub.prototype = Object.create(base.prototype);
|
||||||
sub.prototype.constructor = sub;
|
sub.prototype.constructor = sub;
|
||||||
@ -1609,6 +1645,7 @@ export {
|
|||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
createObjectURL,
|
createObjectURL,
|
||||||
deprecated,
|
deprecated,
|
||||||
|
getInheritableProperty,
|
||||||
getLookupTableFactory,
|
getLookupTableFactory,
|
||||||
getVerbosityLevel,
|
getVerbosityLevel,
|
||||||
info,
|
info,
|
||||||
|
@ -14,9 +14,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bytesToString, isArrayBuffer, isBool, isEmptyObj, isNum, isSpace, isString,
|
bytesToString, getInheritableProperty, isArrayBuffer, isBool, isEmptyObj,
|
||||||
log2, ReadableStream, removeNullCharacters, stringToBytes, stringToPDFString
|
isNum, isSpace, isString, log2, ReadableStream, removeNullCharacters,
|
||||||
|
stringToBytes, stringToPDFString
|
||||||
} from '../../src/shared/util';
|
} from '../../src/shared/util';
|
||||||
|
import { Dict, Ref } from '../../src/core/primitives';
|
||||||
|
import { XRefMock } from './test_utils';
|
||||||
|
|
||||||
describe('util', function() {
|
describe('util', function() {
|
||||||
describe('bytesToString', function() {
|
describe('bytesToString', function() {
|
||||||
@ -50,6 +53,106 @@ describe('util', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getInheritableProperty', function() {
|
||||||
|
it('handles non-dictionary arguments', function() {
|
||||||
|
expect(getInheritableProperty({ dict: null, key: 'foo', }))
|
||||||
|
.toEqual(undefined);
|
||||||
|
expect(getInheritableProperty({ dict: undefined, key: 'foo', }))
|
||||||
|
.toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles dictionaries that do not contain the property', function() {
|
||||||
|
// Empty dictionary.
|
||||||
|
const emptyDict = new Dict();
|
||||||
|
expect(getInheritableProperty({ dict: emptyDict, key: 'foo', }))
|
||||||
|
.toEqual(undefined);
|
||||||
|
|
||||||
|
// Filled dictionary with a different property.
|
||||||
|
const filledDict = new Dict();
|
||||||
|
filledDict.set('bar', 'baz');
|
||||||
|
expect(getInheritableProperty({ dict: filledDict, key: 'foo', }))
|
||||||
|
.toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches the property if it is not inherited', function() {
|
||||||
|
const ref = new Ref(10, 0);
|
||||||
|
const xref = new XRefMock([{ ref, data: 'quux', }]);
|
||||||
|
const dict = new Dict(xref);
|
||||||
|
|
||||||
|
// Regular values should be fetched.
|
||||||
|
dict.set('foo', 'bar');
|
||||||
|
expect(getInheritableProperty({ dict, key: 'foo', })).toEqual('bar');
|
||||||
|
|
||||||
|
// Array value should be fetched (with references resolved).
|
||||||
|
dict.set('baz', ['qux', ref]);
|
||||||
|
expect(getInheritableProperty({ dict, key: 'baz', getArray: true, }))
|
||||||
|
.toEqual(['qux', 'quux']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches the property if it is inherited and present on one level',
|
||||||
|
function() {
|
||||||
|
const ref = new Ref(10, 0);
|
||||||
|
const xref = new XRefMock([{ ref, data: 'quux', }]);
|
||||||
|
const firstDict = new Dict(xref);
|
||||||
|
const secondDict = new Dict(xref);
|
||||||
|
firstDict.set('Parent', secondDict);
|
||||||
|
|
||||||
|
// Regular values should be fetched.
|
||||||
|
secondDict.set('foo', 'bar');
|
||||||
|
expect(getInheritableProperty({ dict: firstDict, key: 'foo', }))
|
||||||
|
.toEqual('bar');
|
||||||
|
|
||||||
|
// Array value should be fetched (with references resolved).
|
||||||
|
secondDict.set('baz', ['qux', ref]);
|
||||||
|
expect(getInheritableProperty({ dict: firstDict, key: 'baz',
|
||||||
|
getArray: true, }))
|
||||||
|
.toEqual(['qux', 'quux']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches the property if it is inherited and present on multiple levels',
|
||||||
|
function() {
|
||||||
|
const ref = new Ref(10, 0);
|
||||||
|
const xref = new XRefMock([{ ref, data: 'quux', }]);
|
||||||
|
const firstDict = new Dict(xref);
|
||||||
|
const secondDict = new Dict(xref);
|
||||||
|
firstDict.set('Parent', secondDict);
|
||||||
|
|
||||||
|
// Regular values should be fetched.
|
||||||
|
firstDict.set('foo', 'bar1');
|
||||||
|
secondDict.set('foo', 'bar2');
|
||||||
|
expect(getInheritableProperty({ dict: firstDict, key: 'foo', }))
|
||||||
|
.toEqual('bar1');
|
||||||
|
expect(getInheritableProperty({ dict: firstDict, key: 'foo',
|
||||||
|
getArray: false, stopWhenFound: false, }))
|
||||||
|
.toEqual(['bar1', 'bar2']);
|
||||||
|
|
||||||
|
// Array value should be fetched (with references resolved).
|
||||||
|
firstDict.set('baz', ['qux1', ref]);
|
||||||
|
secondDict.set('baz', ['qux2', ref]);
|
||||||
|
expect(getInheritableProperty({ dict: firstDict, key: 'baz',
|
||||||
|
getArray: true, stopWhenFound: false, }))
|
||||||
|
.toEqual([['qux1', 'quux'], ['qux2', 'quux']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stops searching when the loop limit is reached', function() {
|
||||||
|
const dict = new Dict();
|
||||||
|
let currentDict = dict;
|
||||||
|
let parentDict = null;
|
||||||
|
for (let i = 0; i < 150; i++) { // Exceeds the loop limit of 100.
|
||||||
|
parentDict = new Dict();
|
||||||
|
currentDict.set('Parent', parentDict);
|
||||||
|
currentDict = parentDict;
|
||||||
|
}
|
||||||
|
parentDict.set('foo', 'bar'); // Never found because of loop limit.
|
||||||
|
expect(getInheritableProperty({ dict, key: 'foo', })).toEqual(undefined);
|
||||||
|
|
||||||
|
dict.set('foo', 'baz');
|
||||||
|
expect(getInheritableProperty({ dict, key: 'foo', getArray: false,
|
||||||
|
stopWhenFound: false, }))
|
||||||
|
.toEqual(['baz']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('isArrayBuffer', function() {
|
describe('isArrayBuffer', function() {
|
||||||
it('handles array buffer values', function() {
|
it('handles array buffer values', function() {
|
||||||
expect(isArrayBuffer(new ArrayBuffer(0))).toEqual(true);
|
expect(isArrayBuffer(new ArrayBuffer(0))).toEqual(true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user