Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 81 additions & 18 deletions src/core/colorspace_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class ColorSpaceUtils {
}

try {
parsedCS = this.#parse(cs, options);
parsedCS = this.#parse(cs, options, /* topLevel = */ true);
} catch (ex) {
if (asyncIfNotCached && !(ex instanceof MissingDataException)) {
return Promise.reject(ex);
Expand Down Expand Up @@ -124,39 +124,92 @@ class ColorSpaceUtils {
return parsedCS;
}

static #parse(cs, options) {
const { xref, resources, pdfFunctionFactory, globalColorSpaceCache } =
options;
/**
* NOTE: This method should *only* be invoked from `this.#parse`,
* when parsing "default" ColorSpaces (i.e. /DefaultGray, /DefaultRGB,
* and /DefaultCMYK).
*/
static #defaultParse(cs, options, deviceCS) {
const { globalColorSpaceCache } = options;
let csRef, parsedCS;

// Check if the ColorSpace is cached first, to avoid re-parsing it.
if (cs instanceof Ref) {
csRef = cs;

const cachedCS = globalColorSpaceCache.getByRef(csRef);
if (cachedCS) {
return cachedCS;
}
}
try {
parsedCS = this.#parse(cs, options);
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`Cannot parse default ColorSpace: "${ex}".`);
return deviceCS;
}

// The default ColorSpace must be compatible with the original one.
if (parsedCS.numComps !== deviceCS.numComps) {
warn(
"Incorrect number of components in default ColorSpace, " +
`expected "${deviceCS.numComps}" and got "${parsedCS.numComps}".`
);
return deviceCS;
}

// Only cache the parsed ColorSpace globally, by reference.
if (csRef) {
globalColorSpaceCache.set(/* name = */ null, csRef, parsedCS);
}
return parsedCS;
}

static #parse(cs, options, topLevel = false) {
const { xref, pdfFunctionFactory, globalColorSpaceCache } = options;

cs = xref.fetchIfRef(cs);
if (cs instanceof Name) {
switch (cs.name) {
case "G":
case "DeviceGray":
case "DeviceGray": {
const defaultCS = topLevel && this.#getResCS("DefaultGray", options);
if (defaultCS) {
return this.#defaultParse(defaultCS, options, this.gray);
}
return this.gray;
}
case "RGB":
case "DeviceRGB":
case "DeviceRGB": {
const defaultCS = topLevel && this.#getResCS("DefaultRGB", options);
if (defaultCS) {
return this.#defaultParse(defaultCS, options, this.rgb);
}
return this.rgb;
}
case "DeviceRGBA":
return this.rgba;
case "CMYK":
case "DeviceCMYK":
case "DeviceCMYK": {
const defaultCS = topLevel && this.#getResCS("DefaultCMYK", options);
if (defaultCS) {
return this.#subParse(defaultCS, options, this.cmyk);
}
return this.cmyk;
}
case "Pattern":
return new PatternCS(/* baseCS = */ null);
default:
if (resources instanceof Dict) {
const colorSpaces = resources.get("ColorSpace");
if (colorSpaces instanceof Dict) {
const resourcesCS = colorSpaces.get(cs.name);
if (resourcesCS) {
if (resourcesCS instanceof Name) {
return this.#parse(resourcesCS, options);
}
cs = resourcesCS;
break;
}
const resourcesCS = xref.fetchIfRef(this.#getResCS(cs.name, options));
if (resourcesCS) {
if (resourcesCS instanceof Name) {
return this.#parse(resourcesCS, options);
}
cs = resourcesCS;
break;
}
// Fallback to the default gray color space.
warn(`Unrecognized ColorSpace: ${cs.name}`);
Expand Down Expand Up @@ -276,6 +329,16 @@ class ColorSpaceUtils {
return this.gray;
}

static #getResCS(name, { resources }) {
if (resources instanceof Dict) {
const colorSpaces = resources.get("ColorSpace");
if (colorSpaces instanceof Dict) {
return colorSpaces.getRaw(name) ?? null;
}
}
return null;
}

static get gray() {
return shadow(this, "gray", new DeviceGrayCS());
}
Expand Down
40 changes: 27 additions & 13 deletions src/core/default_appearance.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class AppearanceStreamEvaluator extends EvaluatorPreprocessor {
fontColor: /* black = */ new Uint8ClampedArray(3),
fillColorSpace: ColorSpaceUtils.gray,
};
let breakLoop = false;
let breakLoop = false,
cs = null;
const stack = [];

try {
Expand Down Expand Up @@ -157,27 +158,29 @@ class AppearanceStreamEvaluator extends EvaluatorPreprocessor {
}
break;
case OPS.setFillColorSpace:
result.fillColorSpace = ColorSpaceUtils.parse({
cs: args[0],
xref: this.xref,
resources: this.resources,
pdfFunctionFactory: this._pdfFunctionFactory,
globalColorSpaceCache: this.globalColorSpaceCache,
localColorSpaceCache: this._localColorSpaceCache,
});
result.fillColorSpace = this.#parseColorSpace(args[0]);
break;
case OPS.setFillColor:
const cs = result.fillColorSpace;
cs = result.fillColorSpace;
cs.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillRGBColor:
ColorSpaceUtils.rgb.getRgbItem(args, 0, result.fontColor, 0);
cs = result.fillColorSpace = this.#parseColorSpace(
Name.get("DeviceRGB")
);
cs.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillGray:
ColorSpaceUtils.gray.getRgbItem(args, 0, result.fontColor, 0);
cs = result.fillColorSpace = this.#parseColorSpace(
Name.get("DeviceGray")
);
cs.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillCMYKColor:
ColorSpaceUtils.cmyk.getRgbItem(args, 0, result.fontColor, 0);
cs = result.fillColorSpace = this.#parseColorSpace(
Name.get("DeviceCMYK")
);
cs.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.showText:
case OPS.showSpacedText:
Expand Down Expand Up @@ -208,6 +211,17 @@ class AppearanceStreamEvaluator extends EvaluatorPreprocessor {
});
return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory);
}

#parseColorSpace(cs) {
return ColorSpaceUtils.parse({
cs,
xref: this.xref,
resources: this.resources,
pdfFunctionFactory: this._pdfFunctionFactory,
globalColorSpaceCache: this.globalColorSpaceCache,
localColorSpaceCache: this._localColorSpaceCache,
});
}
}

// Parse appearance stream to extract font and color information.
Expand Down
Loading