From acfb420250705a0798d35371e588ad329b866795 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 28 Aug 2020 01:05:33 +0200 Subject: [PATCH] Add support, in `Dict.merge`, for merging of "sub"-dictionaries This allows for merging of dictionaries one level deeper than previously. This could be useful e.g. for /Resources dictionaries, where you want to e.g. merge their respective /Font dictionaries (and other) together rather than picking just the first one. --- src/core/document.js | 2 +- src/core/evaluator.js | 6 ++-- src/core/primitives.js | 55 +++++++++++++++++++++++++++++----- src/shared/compatibility.js | 9 ++++++ test/unit/primitives_spec.js | 57 ++++++++++++++++++++++++++++++++---- 5 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/core/document.js b/src/core/document.js index cd459f6f7f62f5..82275b291d5f5e 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -114,7 +114,7 @@ class Page { if (value.length === 1 || !isDict(value[0])) { return value[0]; } - return Dict.merge(this.xref, value); + return Dict.merge({ xref: this.xref, dictArray: value }); } get content() { diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 45de604b1424aa..ee03d436bdcd4c 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -726,8 +726,10 @@ class PartialEvaluator { const tilingOpList = new OperatorList(); // Merge the available resources, to prevent issues when the patternDict // is missing some /Resources entries (fixes issue6541.pdf). - const resourcesArray = [patternDict.get("Resources"), resources]; - const patternResources = Dict.merge(this.xref, resourcesArray); + const patternResources = Dict.merge({ + xref: this.xref, + dictArray: [patternDict.get("Resources"), resources], + }); return this.getOperatorList({ stream: pattern, diff --git a/src/core/primitives.js b/src/core/primitives.js index eb53ccb82c42d2..39eddf9844ab00 100644 --- a/src/core/primitives.js +++ b/src/core/primitives.js @@ -173,22 +173,61 @@ var Dict = (function DictClosure() { Dict.empty = new Dict(null); - Dict.merge = function (xref, dictArray) { + Dict.merge = function ({ xref, dictArray, mergeSubDicts = false }) { const mergedDict = new Dict(xref); - for (let i = 0, ii = dictArray.length; i < ii; i++) { - const dict = dictArray[i]; - if (!isDict(dict)) { + if (!mergeSubDicts) { + for (const dict of dictArray) { + if (!(dict instanceof Dict)) { + continue; + } + for (const [key, value] of Object.entries(dict._map)) { + if (mergedDict._map[key] === undefined) { + mergedDict._map[key] = value; + } + } + } + return mergedDict.size > 0 ? mergedDict : Dict.empty; + } + const properties = new Map(); + + for (const dict of dictArray) { + if (!(dict instanceof Dict)) { continue; } - for (const keyName in dict._map) { - if (mergedDict._map[keyName] !== undefined) { + for (const [key, value] of Object.entries(dict._map)) { + let property = properties.get(key); + if (property === undefined) { + property = []; + properties.set(key, property); + } + property.push(value); + } + } + for (const [name, values] of properties) { + if (values.length === 1 || !(values[0] instanceof Dict)) { + mergedDict._map[name] = values[0]; + continue; + } + const subDict = new Dict(xref); + + for (const dict of values) { + if (!(dict instanceof Dict)) { continue; } - mergedDict._map[keyName] = dict._map[keyName]; + for (const [key, value] of Object.entries(dict._map)) { + if (subDict._map[key] === undefined) { + subDict._map[key] = value; + } + } + } + if (subDict.size > 0) { + mergedDict._map[name] = subDict; } } - return mergedDict; + properties.clear(); + + return mergedDict.size > 0 ? mergedDict : Dict.empty; }; return Dict; diff --git a/src/shared/compatibility.js b/src/shared/compatibility.js index 1f8697fd2c4aaa..195e86ef7ade3e 100644 --- a/src/shared/compatibility.js +++ b/src/shared/compatibility.js @@ -390,4 +390,13 @@ if ( } Object.values = require("core-js/es/object/values.js"); })(); + + // Provides support for Object.entires in legacy browsers. + // Support: IE, Chrome<54 + (function checkObjectValues() { + if (Object.entries) { + return; + } + Object.entries = require("core-js/es/object/entries.js"); + })(); } diff --git a/test/unit/primitives_spec.js b/test/unit/primitives_spec.js index 7f3e375b7984be..7ceb19e3811dd0 100644 --- a/test/unit/primitives_spec.js +++ b/test/unit/primitives_spec.js @@ -317,16 +317,63 @@ describe("primitives", function () { const fontFileDict = new Dict(); fontFileDict.set("FontFile", "Type1 font file"); - const mergedDict = Dict.merge(null, [ - dictWithManyKeys, - dictWithSizeKey, - fontFileDict, - ]); + const mergedDict = Dict.merge({ + xref: null, + dictArray: [dictWithManyKeys, dictWithSizeKey, fontFileDict], + }); const mergedKeys = mergedDict.getKeys(); expect(mergedKeys.sort()).toEqual(expectedKeys); expect(mergedDict.get("FontFile")).toEqual(testFontFile); }); + + it("should correctly merge sub-dictionaries", function () { + const localFontDict = new Dict(); + localFontDict.set("F1", "Local font one"); + + const globalFontDict = new Dict(); + globalFontDict.set("F1", "Global font one"); + globalFontDict.set("F2", "Global font two"); + globalFontDict.set("F3", "Global font three"); + + const localDict = new Dict(); + localDict.set("Font", localFontDict); + + const globalDict = new Dict(); + globalDict.set("Font", globalFontDict); + + const mergedDict = Dict.merge({ + xref: null, + dictArray: [localDict, globalDict], + }); + const mergedSubDict = Dict.merge({ + xref: null, + dictArray: [localDict, globalDict], + mergeSubDicts: true, + }); + + const mergedFontDict = mergedDict.get("Font"); + const mergedSubFontDict = mergedSubDict.get("Font"); + + expect(mergedFontDict instanceof Dict).toEqual(true); + expect(mergedSubFontDict instanceof Dict).toEqual(true); + + const mergedFontDictKeys = mergedFontDict.getKeys(); + const mergedSubFontDictKeys = mergedSubFontDict.getKeys(); + + expect(mergedFontDictKeys).toEqual(["F1"]); + expect(mergedSubFontDictKeys).toEqual(["F1", "F2", "F3"]); + + const mergedFontDictValues = mergedFontDict.getRawValues(); + const mergedSubFontDictValues = mergedSubFontDict.getRawValues(); + + expect(mergedFontDictValues).toEqual(["Local font one"]); + expect(mergedSubFontDictValues).toEqual([ + "Local font one", + "Global font two", + "Global font three", + ]); + }); }); describe("Ref", function () {