Merge pull request #4374 from yurydelendik/dictgetall

Doesn't traverse cyclic references in Dict.getAll; reduces empty-Dict GC
This commit is contained in:
Brendan Dahl 2014-04-08 10:43:42 -07:00
commit 608c6cea5a
7 changed files with 93 additions and 18 deletions

View File

@ -68,7 +68,7 @@ var Page = (function PageClosure() {
// present, but can be empty. Some document omit it still. In this case
// return an empty dictionary:
if (value === undefined) {
value = new Dict();
value = Dict.empty;
}
return shadow(this, 'resources', value);
},

View File

@ -48,6 +48,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return false;
}
var processed = Object.create(null);
if (resources.objId) {
processed[resources.objId] = true;
}
var nodes = [resources];
while (nodes.length) {
var node = nodes.shift();
@ -75,10 +80,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
continue;
}
var xResources = xObject.dict.get('Resources');
// Only add the resource if it's different from the current one,
// otherwise we can get stuck in an infinite loop.
if (isDict(xResources) && xResources !== node) {
// Checking objId to detect an infinite loop.
if (isDict(xResources) &&
(!xResources.objId || !processed[xResources.objId])) {
nodes.push(xResources);
if (xResources.objId) {
processed[xResources.objId] = true;
}
}
}
}
@ -466,9 +474,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList = (operatorList || new OperatorList());
resources = (resources || new Dict());
var xobjs = (resources.get('XObject') || new Dict());
var patterns = (resources.get('Pattern') || new Dict());
resources = (resources || Dict.empty);
var xobjs = (resources.get('XObject') || Dict.empty);
var patterns = (resources.get('Pattern') || Dict.empty);
var preprocessor = new EvaluatorPreprocessor(stream, xref);
if (evaluatorState) {
preprocessor.setState(evaluatorState);
@ -659,7 +667,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return self.loadFont(fontName, fontRef, xref, resources, null);
}
resources = (xref.fetchIfRef(resources) || new Dict());
resources = (xref.fetchIfRef(resources) || Dict.empty);
// The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
var xobjs = null;
var xobjsCache = {};
@ -753,7 +761,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
if (!xobjs) {
xobjs = (resources.get('XObject') || new Dict());
xobjs = (resources.get('XObject') || Dict.empty);
}
var name = args[0].name;
@ -1147,7 +1155,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (type.name == 'Type3') {
// FontDescriptor is only required for Type3 fonts when the document
// is a tagged pdf. Create a barbebones one to get by.
descriptor = new Dict();
descriptor = new Dict(null);
descriptor.set('FontName', Name.get(type.name));
} else {
// Before PDF 1.5 if the font was one of the base 14 fonts, having a

View File

@ -62,11 +62,30 @@ var Dict = (function DictClosure() {
return nonSerializable; // creating closure on some variable
};
var GETALL_DICTIONARY_TYPES_WHITELIST = {
'Background': true,
'ExtGState': true,
'Halftone': true,
'Layout': true,
'Mask': true,
'Pagination': true,
'Printing': true
};
function isRecursionAllowedFor(dict) {
if (!isName(dict.Type)) {
return true;
}
var dictType = dict.Type.name;
return GETALL_DICTIONARY_TYPES_WHITELIST[dictType] === true;
}
// xref is optional
function Dict(xref) {
// Map should only be used internally, use functions below to access.
this.map = Object.create(null);
this.xref = xref;
this.objId = null;
this.__nonSerializable__ = nonSerializable; // disable cloning of the Dict
}
@ -130,10 +149,51 @@ var Dict = (function DictClosure() {
// creates new map and dereferences all Refs
getAll: function Dict_getAll() {
var all = {};
var all = Object.create(null);
var queue = null;
for (var key in this.map) {
var obj = this.get(key);
all[key] = (obj instanceof Dict ? obj.getAll() : obj);
if (obj instanceof Dict) {
if (isRecursionAllowedFor(obj)) {
(queue || (queue = [])).push({target: all, key: key, obj: obj});
} else {
all[key] = this.getRaw(key);
}
} else {
all[key] = obj;
}
}
if (!queue) {
return all;
}
// trying to take cyclic references into the account
var processed = Object.create(null);
while (queue.length > 0) {
var item = queue.shift();
var itemObj = item.obj;
var objId = itemObj.objId;
if (objId && objId in processed) {
item.target[item.key] = processed[objId];
continue;
}
var dereferenced = Object.create(null);
for (var key in itemObj.map) {
var obj = itemObj.get(key);
if (obj instanceof Dict) {
if (isRecursionAllowedFor(obj)) {
queue.push({target: dereferenced, key: key, obj: obj});
} else {
dereferenced[key] = itemObj.getRaw(key);
}
} else {
dereferenced[key] = obj;
}
}
if (objId) {
processed[objId] = dereferenced;
}
item.target[item.key] = dereferenced;
}
return all;
},
@ -153,6 +213,8 @@ var Dict = (function DictClosure() {
}
};
Dict.empty = new Dict(null);
return Dict;
})();
@ -1061,10 +1123,15 @@ var XRef = (function XRefClosure() {
}
if (xrefEntry.uncompressed) {
return this.fetchUncompressed(ref, xrefEntry, suppressEncryption);
xrefEntry = this.fetchUncompressed(ref, xrefEntry, suppressEncryption);
} else {
return this.fetchCompressed(xrefEntry, suppressEncryption);
xrefEntry = this.fetchCompressed(xrefEntry, suppressEncryption);
}
if (isDict(xrefEntry)) {
xrefEntry.objId = 'R' + ref.num + '.' + ref.gen;
}
return xrefEntry;
},
fetchUncompressed: function XRef_fetchUncompressed(ref, xrefEntry,

View File

@ -131,7 +131,7 @@ var Parser = (function ParserClosure() {
var stream = lexer.stream;
// parse dictionary
var dict = new Dict();
var dict = new Dict(null);
while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
if (!isName(this.buf1)) {
error('Dictionary key must be a name object');

View File

@ -1744,7 +1744,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
this.str = str;
this.dict = str.dict;
params = params || new Dict();
params = params || Dict.empty;
this.encoding = params.get('K') || 0;
this.eoline = params.get('EndOfLine') || false;

View File

@ -378,7 +378,7 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() {
var fieldType = Util.getInheritableProperty(dict, 'FT');
data.fieldType = isName(fieldType) ? fieldType.name : '';
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0;
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || new Dict();
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
// Building the full field name by collecting the field and
// its ancestors 'T' data and joining them using '.'.

View File

@ -249,7 +249,7 @@ function readFontIndexData(aStream, aIsByte) {
}
var Type2Parser = function type2Parser(aFilePath) {
var font = new Dict();
var font = new Dict(null);
var xhr = new XMLHttpRequest();
xhr.open('GET', aFilePath, false);