From 31f081ae173497af1c6a6fd6064703bc9e64c622 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Wed, 26 Mar 2014 09:07:38 -0500 Subject: [PATCH] Doesn't traverse cyclic references in Dict.getAll; reduces empty-Dict garbage --- src/core/core.js | 2 +- src/core/evaluator.js | 26 +++++++++----- src/core/obj.js | 75 ++++++++++++++++++++++++++++++++++++--- src/core/parser.js | 2 +- src/core/stream.js | 2 +- src/shared/annotation.js | 2 +- src/shared/fonts_utils.js | 2 +- 7 files changed, 93 insertions(+), 18 deletions(-) diff --git a/src/core/core.js b/src/core/core.js index 8ac43c834..7138ce091 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -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); }, diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 84a618a3e..d8ce942d7 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -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 diff --git a/src/core/obj.js b/src/core/obj.js index f9f197b1e..ab4bb04c7 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -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, diff --git a/src/core/parser.js b/src/core/parser.js index aff46087a..acdb46bfc 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -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'); diff --git a/src/core/stream.js b/src/core/stream.js index 0776c43fd..b2580f985 100644 --- a/src/core/stream.js +++ b/src/core/stream.js @@ -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; diff --git a/src/shared/annotation.js b/src/shared/annotation.js index 4d34edf50..0f48f0f3c 100644 --- a/src/shared/annotation.js +++ b/src/shared/annotation.js @@ -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 '.'. diff --git a/src/shared/fonts_utils.js b/src/shared/fonts_utils.js index bc2659ab5..a14d92106 100644 --- a/src/shared/fonts_utils.js +++ b/src/shared/fonts_utils.js @@ -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);