Merge pull request #12034 from Snuffleupagus/Function-local-cache-3

Add local caching of `Function`s, by reference, in the `PDFFunctionFactory` (issue 2541)
This commit is contained in:
Tim van der Meij 2020-07-04 12:05:15 +02:00 committed by GitHub
commit 29548ad498
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 29 deletions

View File

@ -535,7 +535,7 @@ class ColorSpace {
const name = xref.fetchIfRef(cs[1]); const name = xref.fetchIfRef(cs[1]);
numComps = Array.isArray(name) ? name.length : 1; numComps = Array.isArray(name) ? name.length : 1;
alt = this.parseToIR(cs[2], xref, resources, pdfFunctionFactory); alt = this.parseToIR(cs[2], xref, resources, pdfFunctionFactory);
const tintFn = pdfFunctionFactory.create(xref.fetchIfRef(cs[3])); const tintFn = pdfFunctionFactory.create(cs[3]);
return ["AlternateCS", numComps, alt, tintFn]; return ["AlternateCS", numComps, alt, tintFn];
case "Lab": case "Lab":
params = xref.fetchIfRef(cs[1]); params = xref.fetchIfRef(cs[1]);

View File

@ -53,7 +53,6 @@ import { calculateMD5 } from "./crypto.js";
import { Linearization } from "./parser.js"; import { Linearization } from "./parser.js";
import { OperatorList } from "./operator_list.js"; import { OperatorList } from "./operator_list.js";
import { PartialEvaluator } from "./evaluator.js"; import { PartialEvaluator } from "./evaluator.js";
import { PDFFunctionFactory } from "./function.js";
const DEFAULT_USER_UNIT = 1.0; const DEFAULT_USER_UNIT = 1.0;
const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792]; const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
@ -75,7 +74,6 @@ class Page {
fontCache, fontCache,
builtInCMapCache, builtInCMapCache,
globalImageCache, globalImageCache,
pdfFunctionFactory,
}) { }) {
this.pdfManager = pdfManager; this.pdfManager = pdfManager;
this.pageIndex = pageIndex; this.pageIndex = pageIndex;
@ -85,7 +83,6 @@ class Page {
this.fontCache = fontCache; this.fontCache = fontCache;
this.builtInCMapCache = builtInCMapCache; this.builtInCMapCache = builtInCMapCache;
this.globalImageCache = globalImageCache; this.globalImageCache = globalImageCache;
this.pdfFunctionFactory = pdfFunctionFactory;
this.evaluatorOptions = pdfManager.evaluatorOptions; this.evaluatorOptions = pdfManager.evaluatorOptions;
this.resourcesPromise = null; this.resourcesPromise = null;
@ -265,7 +262,6 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
pdfFunctionFactory: this.pdfFunctionFactory,
}); });
const dataPromises = Promise.all([contentStreamPromise, resourcesPromise]); const dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
@ -359,7 +355,6 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
pdfFunctionFactory: this.pdfFunctionFactory,
}); });
return partialEvaluator.getTextContent({ return partialEvaluator.getTextContent({
@ -508,11 +503,6 @@ class PDFDocument {
this.pdfManager = pdfManager; this.pdfManager = pdfManager;
this.stream = stream; this.stream = stream;
this.xref = new XRef(stream, pdfManager); this.xref = new XRef(stream, pdfManager);
this.pdfFunctionFactory = new PDFFunctionFactory({
xref: this.xref,
isEvalSupported: pdfManager.evaluatorOptions.isEvalSupported,
});
this._pagePromises = []; this._pagePromises = [];
} }
@ -821,7 +811,6 @@ class PDFDocument {
fontCache: catalog.fontCache, fontCache: catalog.fontCache,
builtInCMapCache: catalog.builtInCMapCache, builtInCMapCache: catalog.builtInCMapCache,
globalImageCache: catalog.globalImageCache, globalImageCache: catalog.globalImageCache,
pdfFunctionFactory: this.pdfFunctionFactory,
}); });
})); }));
} }

View File

@ -26,6 +26,7 @@ import {
isNum, isNum,
isString, isString,
OPS, OPS,
shadow,
stringToPDFString, stringToPDFString,
TextRenderingMode, TextRenderingMode,
UNSUPPORTED_FEATURES, UNSUPPORTED_FEATURES,
@ -72,6 +73,7 @@ import {
getSymbolsFonts, getSymbolsFonts,
} from "./standard_fonts.js"; } from "./standard_fonts.js";
import { getTilingPatternIR, Pattern } from "./pattern.js"; import { getTilingPatternIR, Pattern } from "./pattern.js";
import { isPDFFunction, PDFFunctionFactory } from "./function.js";
import { Lexer, Parser } from "./parser.js"; import { Lexer, Parser } from "./parser.js";
import { LocalColorSpaceCache, LocalImageCache } from "./image_utils.js"; import { LocalColorSpaceCache, LocalImageCache } from "./image_utils.js";
import { bidi } from "./bidi.js"; import { bidi } from "./bidi.js";
@ -79,7 +81,6 @@ import { ColorSpace } from "./colorspace.js";
import { DecodeStream } from "./stream.js"; import { DecodeStream } from "./stream.js";
import { getGlyphsUnicode } from "./glyphlist.js"; import { getGlyphsUnicode } from "./glyphlist.js";
import { getMetrics } from "./metrics.js"; import { getMetrics } from "./metrics.js";
import { isPDFFunction } from "./function.js";
import { MurmurHash3_64 } from "./murmurhash3.js"; import { MurmurHash3_64 } from "./murmurhash3.js";
import { OperatorList } from "./operator_list.js"; import { OperatorList } from "./operator_list.js";
import { PDFImage } from "./image.js"; import { PDFImage } from "./image.js";
@ -103,7 +104,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
builtInCMapCache, builtInCMapCache,
globalImageCache, globalImageCache,
options = null, options = null,
pdfFunctionFactory,
}) { }) {
this.xref = xref; this.xref = xref;
this.handler = handler; this.handler = handler;
@ -113,7 +113,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.builtInCMapCache = builtInCMapCache; this.builtInCMapCache = builtInCMapCache;
this.globalImageCache = globalImageCache; this.globalImageCache = globalImageCache;
this.options = options || DefaultPartialEvaluatorOptions; this.options = options || DefaultPartialEvaluatorOptions;
this.pdfFunctionFactory = pdfFunctionFactory;
this.parsingType3Font = false; this.parsingType3Font = false;
this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this); this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this);
@ -207,6 +206,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
SHADING_PATTERN = 2; SHADING_PATTERN = 2;
PartialEvaluator.prototype = { PartialEvaluator.prototype = {
/**
* Since Functions are only cached (locally) by reference, we can share one
* `PDFFunctionFactory` instance within this `PartialEvaluator` instance.
*/
get _pdfFunctionFactory() {
const pdfFunctionFactory = new PDFFunctionFactory({
xref: this.xref,
isEvalSupported: this.options.isEvalSupported,
});
return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory);
},
clone(newOptions = DefaultPartialEvaluatorOptions) { clone(newOptions = DefaultPartialEvaluatorOptions) {
var newEvaluator = Object.create(this); var newEvaluator = Object.create(this);
newEvaluator.options = newOptions; newEvaluator.options = newOptions;
@ -552,7 +563,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
res: resources, res: resources,
image, image,
isInline, isInline,
pdfFunctionFactory: this.pdfFunctionFactory, pdfFunctionFactory: this._pdfFunctionFactory,
localColorSpaceCache, localColorSpaceCache,
}); });
// We force the use of RGBA_32BPP images here, because we can't handle // We force the use of RGBA_32BPP images here, because we can't handle
@ -589,7 +600,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
res: resources, res: resources,
image, image,
isInline, isInline,
pdfFunctionFactory: this.pdfFunctionFactory, pdfFunctionFactory: this._pdfFunctionFactory,
localColorSpaceCache, localColorSpaceCache,
}) })
.then(imageObj => { .then(imageObj => {
@ -651,7 +662,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// we will build a map of integer values in range 0..255 to be fast. // we will build a map of integer values in range 0..255 to be fast.
var transferObj = smask.get("TR"); var transferObj = smask.get("TR");
if (isPDFFunction(transferObj)) { if (isPDFFunction(transferObj)) {
const transferFn = this.pdfFunctionFactory.create(transferObj); const transferFn = this._pdfFunctionFactory.create(transferObj);
var transferMap = new Uint8Array(256); var transferMap = new Uint8Array(256);
var tmp = new Float32Array(1); var tmp = new Float32Array(1);
for (var i = 0; i < 256; i++) { for (var i = 0; i < 256; i++) {
@ -1145,7 +1156,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs, cs,
xref: this.xref, xref: this.xref,
resources, resources,
pdfFunctionFactory: this.pdfFunctionFactory, pdfFunctionFactory: this._pdfFunctionFactory,
localColorSpaceCache, localColorSpaceCache,
}).catch(reason => { }).catch(reason => {
if (reason instanceof AbortException) { if (reason instanceof AbortException) {
@ -1202,7 +1213,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.xref, this.xref,
resources, resources,
this.handler, this.handler,
this.pdfFunctionFactory, this._pdfFunctionFactory,
localColorSpaceCache localColorSpaceCache
); );
operatorList.addOp(fn, pattern.getIR()); operatorList.addOp(fn, pattern.getIR());
@ -1641,7 +1652,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
xref, xref,
resources, resources,
self.handler, self.handler,
self.pdfFunctionFactory, self._pdfFunctionFactory,
localColorSpaceCache localColorSpaceCache
); );
var patternIR = shadingFill.getIR(); var patternIR = shadingFill.getIR();

View File

@ -13,6 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Dict, isDict, isStream, Ref } from "./primitives.js";
import { import {
FormatError, FormatError,
info, info,
@ -20,29 +21,94 @@ import {
IsEvalSupportedCached, IsEvalSupportedCached,
unreachable, unreachable,
} from "../shared/util.js"; } from "../shared/util.js";
import { isDict, isStream } from "./primitives.js";
import { PostScriptLexer, PostScriptParser } from "./ps_parser.js"; import { PostScriptLexer, PostScriptParser } from "./ps_parser.js";
import { LocalFunctionCache } from "./image_utils.js";
class PDFFunctionFactory { class PDFFunctionFactory {
constructor({ xref, isEvalSupported = true }) { constructor({ xref, isEvalSupported = true }) {
this.xref = xref; this.xref = xref;
this.isEvalSupported = isEvalSupported !== false; this.isEvalSupported = isEvalSupported !== false;
this._localFunctionCache = null; // Initialized lazily.
} }
create(fn) { create(fn) {
return PDFFunction.parse({ const cachedFunction = this.getCached(fn);
if (cachedFunction) {
return cachedFunction;
}
const parsedFunction = PDFFunction.parse({
xref: this.xref, xref: this.xref,
isEvalSupported: this.isEvalSupported, isEvalSupported: this.isEvalSupported,
fn, fn: fn instanceof Ref ? this.xref.fetch(fn) : fn,
}); });
// Attempt to cache the parsed Function, by reference.
this._cache(fn, parsedFunction);
return parsedFunction;
} }
createFromArray(fnObj) { createFromArray(fnObj) {
return PDFFunction.parseArray({ const cachedFunction = this.getCached(fnObj);
if (cachedFunction) {
return cachedFunction;
}
const parsedFunction = PDFFunction.parseArray({
xref: this.xref, xref: this.xref,
isEvalSupported: this.isEvalSupported, isEvalSupported: this.isEvalSupported,
fnObj, fnObj: fnObj instanceof Ref ? this.xref.fetch(fnObj) : fnObj,
}); });
// Attempt to cache the parsed Function, by reference.
this._cache(fnObj, parsedFunction);
return parsedFunction;
}
getCached(cacheKey) {
let fnRef;
if (cacheKey instanceof Ref) {
fnRef = cacheKey;
} else if (cacheKey instanceof Dict) {
fnRef = cacheKey.objId;
} else if (isStream(cacheKey)) {
fnRef = cacheKey.dict && cacheKey.dict.objId;
}
if (fnRef) {
if (!this._localFunctionCache) {
this._localFunctionCache = new LocalFunctionCache();
}
const localFunction = this._localFunctionCache.getByRef(fnRef);
if (localFunction) {
return localFunction;
}
}
return null;
}
/**
* @private
*/
_cache(cacheKey, parsedFunction) {
if (!parsedFunction) {
throw new Error(
'PDFFunctionFactory._cache - expected "parsedFunction" argument.'
);
}
let fnRef;
if (cacheKey instanceof Ref) {
fnRef = cacheKey;
} else if (cacheKey instanceof Dict) {
fnRef = cacheKey.objId;
} else if (isStream(cacheKey)) {
fnRef = cacheKey.dict && cacheKey.dict.objId;
}
if (fnRef) {
if (!this._localFunctionCache) {
this._localFunctionCache = new LocalFunctionCache();
}
this._localFunctionCache.set(/* name = */ null, fnRef, parsedFunction);
}
} }
} }

View File

@ -91,6 +91,22 @@ class LocalColorSpaceCache extends BaseLocalCache {
} }
} }
class LocalFunctionCache extends BaseLocalCache {
getByName(name) {
unreachable("Should not call `getByName` method.");
}
set(name = null, ref, data) {
if (!ref) {
throw new Error('LocalFunctionCache.set - expected "ref" argument.');
}
if (this._imageCache.has(ref)) {
return;
}
this._imageCache.put(ref, data);
}
}
class GlobalImageCache { class GlobalImageCache {
static get NUM_PAGES_THRESHOLD() { static get NUM_PAGES_THRESHOLD() {
return shadow(this, "NUM_PAGES_THRESHOLD", 2); return shadow(this, "NUM_PAGES_THRESHOLD", 2);
@ -184,4 +200,9 @@ class GlobalImageCache {
} }
} }
export { LocalImageCache, LocalColorSpaceCache, GlobalImageCache }; export {
LocalImageCache,
LocalColorSpaceCache,
LocalFunctionCache,
GlobalImageCache,
};

View File

@ -178,7 +178,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
this.extendStart = extendStart; this.extendStart = extendStart;
this.extendEnd = extendEnd; this.extendEnd = extendEnd;
var fnObj = dict.get("Function"); var fnObj = dict.getRaw("Function");
var fn = pdfFunctionFactory.createFromArray(fnObj); var fn = pdfFunctionFactory.createFromArray(fnObj);
// 10 samples seems good enough for now, but probably won't work // 10 samples seems good enough for now, but probably won't work
@ -878,7 +878,7 @@ Shadings.Mesh = (function MeshClosure() {
? cs.getRgb(dict.get("Background"), 0) ? cs.getRgb(dict.get("Background"), 0)
: null; : null;
var fnObj = dict.get("Function"); var fnObj = dict.getRaw("Function");
var fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null; var fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null;
this.coords = []; this.coords = [];