Skip to content

Commit

Permalink
Improve (local) caching of parsed ColorSpaces (PR 12001 follow-up)
Browse files Browse the repository at this point in the history
This patch contains the following *notable* improvements:
 - Changes the `ColorSpace.parse` call-sites to, where possible, pass in a reference rather than actual ColorSpace data (necessary for the next point).
 - Adds (local) caching of `ColorSpace`s by `Ref`, when applicable, in addition the caching by name. This (generally) improves `ColorSpace` caching for e.g. the SMask code-paths.
 - Extends the (local) `ColorSpace` caching to also apply when handling Images and Patterns, thus further reducing unneeded re-parsing.
 - Adds a new `ColorSpace.parseAsync` method, almost identical to the existing `ColorSpace.parse` one, but returning a Promise instead (this simplifies some code in the `PartialEvaluator`).
  • Loading branch information
Snuffleupagus committed Jun 18, 2020
1 parent 0b44389 commit c2e25f0
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 45 deletions.
111 changes: 108 additions & 3 deletions src/core/colorspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
unreachable,
warn,
} from "../shared/util.js";
import { isDict, isName, isStream } from "./primitives.js";
import { isDict, isName, isStream, Name, Ref } from "./primitives.js";
import { MissingDataException } from "./core_utils.js";

/**
* Resizes an RGB image with 3 components.
Expand Down Expand Up @@ -259,9 +260,113 @@ class ColorSpace {
return shadow(this, "usesZeroToOneRange", true);
}

static parse({ cs, xref, resources = null, pdfFunctionFactory }) {
/**
* @private
*/
static _cache(cacheKey, xref, localColorSpaceCache, parsedColorSpace) {
if (!localColorSpaceCache) {
throw new Error(
'ColorSpace._cache - expected "localColorSpaceCache" argument.'
);
}
if (!parsedColorSpace) {
throw new Error(
'ColorSpace._cache - expected "parsedColorSpace" argument.'
);
}
let csName, csRef;
if (cacheKey instanceof Ref) {
csRef = cacheKey;

// If parsing succeeded, we know that this call cannot throw.
cacheKey = xref.fetch(cacheKey);
}
if (cacheKey instanceof Name) {
csName = cacheKey.name;
}
if (csName || csRef) {
localColorSpaceCache.set(csName, csRef, parsedColorSpace);
}
}

static getCached(cacheKey, xref, localColorSpaceCache) {
if (!localColorSpaceCache) {
throw new Error(
'ColorSpace.getCached - expected "localColorSpaceCache" argument.'
);
}
if (cacheKey instanceof Ref) {
const localColorSpace = localColorSpaceCache.getByRef(cacheKey);
if (localColorSpace) {
return localColorSpace;
}

try {
cacheKey = xref.fetch(cacheKey);
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
// Any errors should be handled during parsing, rather than here.
}
}
if (cacheKey instanceof Name) {
const localColorSpace = localColorSpaceCache.getByName(cacheKey.name);
if (localColorSpace) {
return localColorSpace;
}
}
return null;
}

static async parseAsync({
cs,
xref,
resources = null,
pdfFunctionFactory,
localColorSpaceCache,
}) {
if (
typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")
) {
assert(
!this.getCached(cs, xref, localColorSpaceCache),
"Expected `ColorSpace.getCached` to have been manually checked " +
"before calling `ColorSpace.parseAsync`."
);
}
const IR = this.parseToIR(cs, xref, resources, pdfFunctionFactory);
return this.fromIR(IR);
const parsedColorSpace = this.fromIR(IR);

// Attempt to cache the parsed ColorSpace, by name and/or reference.
if (localColorSpaceCache) {
this._cache(cs, xref, localColorSpaceCache, parsedColorSpace);
}
return parsedColorSpace;
}

static parse({
cs,
xref,
resources = null,
pdfFunctionFactory,
localColorSpaceCache,
}) {
if (localColorSpaceCache) {
const cachedColorSpace = this.getCached(cs, xref, localColorSpaceCache);
if (cachedColorSpace) {
return cachedColorSpace;
}
}
const IR = this.parseToIR(cs, xref, resources, pdfFunctionFactory);
const parsedColorSpace = this.fromIR(IR);

// Attempt to cache the parsed ColorSpace, by name and/or reference.
if (localColorSpaceCache) {
this._cache(cs, xref, localColorSpaceCache, parsedColorSpace);
}
return parsedColorSpace;
}

static fromIR(IR) {
Expand Down
84 changes: 51 additions & 33 deletions src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,12 +408,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
groupOptions.isolated = group.get("I") || false;
groupOptions.knockout = group.get("K") || false;
if (group.has("CS")) {
const cs = group.get("CS");
const cs = group.getRaw("CS");

const localColorSpace =
cs instanceof Name && localColorSpaceCache.getByName(cs.name);
if (localColorSpace) {
colorSpace = localColorSpace;
const cachedColorSpace = ColorSpace.getCached(
cs,
this.xref,
localColorSpaceCache
);
if (cachedColorSpace) {
colorSpace = cachedColorSpace;
} else {
colorSpace = await this.parseColorSpace({
cs,
Expand Down Expand Up @@ -480,6 +483,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList,
cacheKey,
localImageCache,
localColorSpaceCache,
}) {
var dict = image.dict;
const imageRef = dict.objId;
Expand Down Expand Up @@ -546,6 +550,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
image,
isInline,
pdfFunctionFactory: this.pdfFunctionFactory,
localColorSpaceCache,
});
// We force the use of RGBA_32BPP images here, because we can't handle
// any other kind.
Expand Down Expand Up @@ -582,6 +587,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
image,
isInline,
pdfFunctionFactory: this.pdfFunctionFactory,
localColorSpaceCache,
})
.then(imageObj => {
imgData = imageObj.createImageData(/* forceRGBA = */ false);
Expand Down Expand Up @@ -1132,19 +1138,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},

parseColorSpace({ cs, resources, localColorSpaceCache }) {
return new Promise(resolve => {
const parsedColorSpace = ColorSpace.parse({
cs,
xref: this.xref,
resources,
pdfFunctionFactory: this.pdfFunctionFactory,
});

const csName = cs instanceof Name ? cs.name : null;
if (csName) {
localColorSpaceCache.set(csName, /* ref = */ null, parsedColorSpace);
}
resolve(parsedColorSpace);
return ColorSpace.parseAsync({
cs,
xref: this.xref,
resources,
pdfFunctionFactory: this.pdfFunctionFactory,
localColorSpaceCache,
}).catch(reason => {
if (reason instanceof AbortException) {
return null;
Expand All @@ -1162,7 +1161,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
});
},

async handleColorN(operatorList, fn, args, cs, patterns, resources, task) {
async handleColorN(
operatorList,
fn,
args,
cs,
patterns,
resources,
task,
localColorSpaceCache
) {
// compile tiling patterns
var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors
Expand Down Expand Up @@ -1191,7 +1199,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.xref,
resources,
this.handler,
this.pdfFunctionFactory
this.pdfFunctionFactory,
localColorSpaceCache
);
operatorList.addOp(fn, pattern.getIR());
return undefined;
Expand Down Expand Up @@ -1349,6 +1358,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList,
cacheKey: name,
localImageCache,
localColorSpaceCache,
})
.then(resolveXObject, rejectXObject);
return;
Expand Down Expand Up @@ -1422,6 +1432,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList,
cacheKey,
localImageCache,
localColorSpaceCache,
})
);
return;
Expand Down Expand Up @@ -1480,11 +1491,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break;

case OPS.setFillColorSpace: {
const localColorSpace =
args[0] instanceof Name &&
localColorSpaceCache.getByName(args[0].name);
if (localColorSpace) {
stateManager.state.fillColorSpace = localColorSpace;
const cachedColorSpace = ColorSpace.getCached(
args[0],
xref,
localColorSpaceCache
);
if (cachedColorSpace) {
stateManager.state.fillColorSpace = cachedColorSpace;
continue;
}

Expand All @@ -1504,11 +1517,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return;
}
case OPS.setStrokeColorSpace: {
const localColorSpace =
args[0] instanceof Name &&
localColorSpaceCache.getByName(args[0].name);
if (localColorSpace) {
stateManager.state.strokeColorSpace = localColorSpace;
const cachedColorSpace = ColorSpace.getCached(
args[0],
xref,
localColorSpaceCache
);
if (cachedColorSpace) {
stateManager.state.strokeColorSpace = cachedColorSpace;
continue;
}

Expand Down Expand Up @@ -1576,7 +1591,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs,
patterns,
resources,
task
task,
localColorSpaceCache
)
);
return;
Expand All @@ -1595,7 +1611,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs,
patterns,
resources,
task
task,
localColorSpaceCache
)
);
return;
Expand All @@ -1621,7 +1638,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
xref,
resources,
self.handler,
self.pdfFunctionFactory
self.pdfFunctionFactory,
localColorSpaceCache
);
var patternIR = shadingFill.getIR();
args = [patternIR];
Expand Down
8 changes: 7 additions & 1 deletion src/core/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ var PDFImage = (function PDFImageClosure() {
mask = null,
isMask = false,
pdfFunctionFactory,
localColorSpaceCache,
}) {
this.image = image;
var dict = image.dict;
Expand Down Expand Up @@ -159,7 +160,7 @@ var PDFImage = (function PDFImageClosure() {
this.bpc = bitsPerComponent;

if (!this.imageMask) {
var colorSpace = dict.get("ColorSpace", "CS");
let colorSpace = dict.getRaw("ColorSpace") || dict.getRaw("CS");
if (!colorSpace) {
info("JPX images (which do not require color spaces)");
switch (image.numComps) {
Expand All @@ -184,6 +185,7 @@ var PDFImage = (function PDFImageClosure() {
xref,
resources: isInline ? res : null,
pdfFunctionFactory,
localColorSpaceCache,
});
this.numComps = this.colorSpace.numComps;
}
Expand Down Expand Up @@ -220,6 +222,7 @@ var PDFImage = (function PDFImageClosure() {
image: smask,
isInline,
pdfFunctionFactory,
localColorSpaceCache,
});
} else if (mask) {
if (isStream(mask)) {
Expand All @@ -235,6 +238,7 @@ var PDFImage = (function PDFImageClosure() {
isInline,
isMask: true,
pdfFunctionFactory,
localColorSpaceCache,
});
}
} else {
Expand All @@ -253,6 +257,7 @@ var PDFImage = (function PDFImageClosure() {
image,
isInline = false,
pdfFunctionFactory,
localColorSpaceCache,
}) {
const imageData = image;
let smaskData = null;
Expand All @@ -279,6 +284,7 @@ var PDFImage = (function PDFImageClosure() {
smask: smaskData,
mask: maskData,
pdfFunctionFactory,
localColorSpaceCache,
});
};

Expand Down
Loading

0 comments on commit c2e25f0

Please sign in to comment.