1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-22 16:18:08 +02:00

Always prefer abbreviated keys, over full ones, when doing any dictionary lookups (issue 14256)

Note that issue 14256 was specifically about *inline* images, please refer to:
 - https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#G7.1852045
 - https://www.pdfa.org/safedocs-unearths-pdf-inline-image-issue/
 - https://pdf-issues.pdfa.org/32000-2-2020/clause08.html#H8.9.7

However, during review of the initial PR in https://github.com/mozilla/pdf.js/pull/14257#issuecomment-964469710, it was suggested that we instead do this *unconditionally for all* dictionary lookups.
In addition to re-ordering the existing call-sites in the `src/core`-code, and adding non-PRODUCTION/TESTING asserts to catch future errors, for consistency a number of existing `if`/`switch`-blocks were re-factored to also check the abbreviated keys first.
This commit is contained in:
Jonas Jenwald 2021-11-09 22:39:21 +01:00
parent 4ee906adf4
commit ea1c348c67
12 changed files with 730 additions and 104 deletions

View file

@ -379,14 +379,14 @@ class ColorSpace {
cs = xref.fetchIfRef(cs);
if (isName(cs)) {
switch (cs.name) {
case "DeviceGray":
case "G":
case "DeviceGray":
return this.singletons.gray;
case "DeviceRGB":
case "RGB":
case "DeviceRGB":
return this.singletons.rgb;
case "DeviceCMYK":
case "CMYK":
case "DeviceCMYK":
return this.singletons.cmyk;
case "Pattern":
return new PatternCS(/* baseCS = */ null);
@ -417,14 +417,14 @@ class ColorSpace {
let params, numComps, baseCS, whitePoint, blackPoint, gamma;
switch (mode) {
case "DeviceGray":
case "G":
case "DeviceGray":
return this.singletons.gray;
case "DeviceRGB":
case "RGB":
case "DeviceRGB":
return this.singletons.rgb;
case "DeviceCMYK":
case "CMYK":
case "DeviceCMYK":
return this.singletons.cmyk;
case "CalGray":
params = xref.fetchIfRef(cs[1]);
@ -467,8 +467,8 @@ class ColorSpace {
baseCS = this._parse(baseCS, xref, resources, pdfFunctionFactory);
}
return new PatternCS(baseCS);
case "Indexed":
case "I":
case "Indexed":
baseCS = this._parse(cs[1], xref, resources, pdfFunctionFactory);
const hiVal = xref.fetchIfRef(cs[2]) + 1;
const lookup = xref.fetchIfRef(cs[3]);

View file

@ -580,8 +580,8 @@ class PartialEvaluator {
}) {
const dict = image.dict;
const imageRef = dict.objId;
const w = dict.get("Width", "W");
const h = dict.get("Height", "H");
const w = dict.get("W", "Width");
const h = dict.get("H", "Height");
if (!(w && isNum(w)) || !(h && isNum(h))) {
warn("Image dimensions are missing, or not numbers.");
@ -604,8 +604,8 @@ class PartialEvaluator {
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
const imageMask = dict.get("ImageMask", "IM") || false;
const interpolate = dict.get("Interpolate", "I");
const imageMask = dict.get("IM", "ImageMask") || false;
const interpolate = dict.get("I", "Interpolate");
let imgData, args;
if (imageMask) {
// This depends on a tmpCanvas being filled with the
@ -613,20 +613,17 @@ class PartialEvaluator {
// data can't be done here. Instead of creating a
// complete PDFImage, only read the information needed
// for later.
const width = dict.get("Width", "W");
const height = dict.get("Height", "H");
const bitStrideLength = (width + 7) >> 3;
const bitStrideLength = (w + 7) >> 3;
const imgArray = image.getBytes(
bitStrideLength * height,
bitStrideLength * h,
/* forceClamped = */ true
);
const decode = dict.getArray("Decode", "D");
const decode = dict.getArray("D", "Decode");
imgData = PDFImage.createMask({
imgArray,
width,
height,
width: w,
height: h,
imageIsFromDecodeStream: image instanceof DecodeStream,
inverseDecode: !!decode && decode[0] > 0,
interpolate,
@ -648,7 +645,7 @@ class PartialEvaluator {
return;
}
const softMask = dict.get("SMask", "SM") || false;
const softMask = dict.get("SM", "SMask") || false;
const mask = dict.get("Mask") || false;
const SMALL_IMAGE_DIMENSIONS = 200;

View file

@ -93,7 +93,7 @@ class PDFImage {
this.image = image;
const dict = image.dict;
const filter = dict.get("Filter");
const filter = dict.get("F", "Filter");
if (isName(filter)) {
switch (filter.name) {
case "JPXDecode":
@ -114,8 +114,8 @@ class PDFImage {
}
// TODO cache rendered images?
let width = dict.get("Width", "W");
let height = dict.get("Height", "H");
let width = dict.get("W", "Width");
let height = dict.get("H", "Height");
if (
Number.isInteger(image.width) &&
@ -139,13 +139,13 @@ class PDFImage {
this.width = width;
this.height = height;
this.interpolate = dict.get("Interpolate", "I");
this.imageMask = dict.get("ImageMask", "IM") || false;
this.interpolate = dict.get("I", "Interpolate");
this.imageMask = dict.get("IM", "ImageMask") || false;
this.matte = dict.get("Matte") || false;
let bitsPerComponent = image.bitsPerComponent;
if (!bitsPerComponent) {
bitsPerComponent = dict.get("BitsPerComponent", "BPC");
bitsPerComponent = dict.get("BPC", "BitsPerComponent");
if (!bitsPerComponent) {
if (this.imageMask) {
bitsPerComponent = 1;
@ -159,7 +159,7 @@ class PDFImage {
this.bpc = bitsPerComponent;
if (!this.imageMask) {
let colorSpace = dict.getRaw("ColorSpace") || dict.getRaw("CS");
let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace");
if (!colorSpace) {
info("JPX images (which do not require color spaces)");
switch (image.numComps) {
@ -174,8 +174,7 @@ class PDFImage {
break;
default:
throw new Error(
`JPX images with ${image.numComps} ` +
"color components not supported."
`JPX images with ${image.numComps} color components not supported.`
);
}
}
@ -189,7 +188,7 @@ class PDFImage {
this.numComps = this.colorSpace.numComps;
}
this.decode = dict.getArray("Decode", "D");
this.decode = dict.getArray("D", "Decode");
this.needsDecode = false;
if (
this.decode &&
@ -226,7 +225,7 @@ class PDFImage {
} else if (mask) {
if (isStream(mask)) {
const maskDict = mask.dict,
imageMask = maskDict.get("ImageMask", "IM");
imageMask = maskDict.get("IM", "ImageMask");
if (!imageMask) {
warn("Ignoring /Mask in image without /ImageMask.");
} else {

View file

@ -62,9 +62,9 @@ class JpegStream extends DecodeStream {
};
// Checking if values need to be transformed before conversion.
const decodeArr = this.dict.getArray("Decode", "D");
const decodeArr = this.dict.getArray("D", "Decode");
if (this.forceRGB && Array.isArray(decodeArr)) {
const bitsPerComponent = this.dict.get("BitsPerComponent") || 8;
const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
const decodeArrLength = decodeArr.length;
const transform = new Int32Array(decodeArrLength);
let transformNeeded = false;

View file

@ -513,7 +513,7 @@ class Parser {
}
// Extract the name of the first (i.e. the current) image filter.
const filter = dict.get("Filter", "F");
const filter = dict.get("F", "Filter");
let filterName;
if (isName(filter)) {
filterName = filter.name;
@ -527,14 +527,21 @@ class Parser {
// Parse image stream.
const startPos = stream.pos;
let length;
if (filterName === "DCTDecode" || filterName === "DCT") {
length = this.findDCTDecodeInlineStreamEnd(stream);
} else if (filterName === "ASCII85Decode" || filterName === "A85") {
length = this.findASCII85DecodeInlineStreamEnd(stream);
} else if (filterName === "ASCIIHexDecode" || filterName === "AHx") {
length = this.findASCIIHexDecodeInlineStreamEnd(stream);
} else {
length = this.findDefaultInlineStreamEnd(stream);
switch (filterName) {
case "DCT":
case "DCTDecode":
length = this.findDCTDecodeInlineStreamEnd(stream);
break;
case "A85":
case "ASCII85Decode":
length = this.findASCII85DecodeInlineStreamEnd(stream);
break;
case "AHx":
case "ASCIIHexDecode":
length = this.findASCIIHexDecodeInlineStreamEnd(stream);
break;
default:
length = this.findDefaultInlineStreamEnd(stream);
}
let imageStream = stream.makeSubStream(startPos, length, dict);
@ -694,15 +701,12 @@ class Parser {
}
filter(stream, dict, length) {
let filter = dict.get("Filter", "F");
let params = dict.get("DecodeParms", "DP");
let filter = dict.get("F", "Filter");
let params = dict.get("DP", "DecodeParms");
if (isName(filter)) {
if (Array.isArray(params)) {
warn(
"/DecodeParms should not contain an Array, " +
"when /Filter contains a Name."
);
warn("/DecodeParms should not be an Array, when /Filter is a Name.");
}
return this.makeFilter(stream, filter.name, length, params);
}
@ -740,59 +744,60 @@ class Parser {
try {
const xrefStreamStats = this.xref.stats.streamTypes;
if (name === "FlateDecode" || name === "Fl") {
xrefStreamStats[StreamType.FLATE] = true;
if (params) {
return new PredictorStream(
new FlateStream(stream, maybeLength),
maybeLength,
params
);
}
return new FlateStream(stream, maybeLength);
}
if (name === "LZWDecode" || name === "LZW") {
xrefStreamStats[StreamType.LZW] = true;
let earlyChange = 1;
if (params) {
if (params.has("EarlyChange")) {
earlyChange = params.get("EarlyChange");
switch (name) {
case "Fl":
case "FlateDecode":
xrefStreamStats[StreamType.FLATE] = true;
if (params) {
return new PredictorStream(
new FlateStream(stream, maybeLength),
maybeLength,
params
);
}
return new PredictorStream(
new LZWStream(stream, maybeLength, earlyChange),
maybeLength,
params
);
}
return new LZWStream(stream, maybeLength, earlyChange);
}
if (name === "DCTDecode" || name === "DCT") {
xrefStreamStats[StreamType.DCT] = true;
return new JpegStream(stream, maybeLength, params);
}
if (name === "JPXDecode" || name === "JPX") {
xrefStreamStats[StreamType.JPX] = true;
return new JpxStream(stream, maybeLength, params);
}
if (name === "ASCII85Decode" || name === "A85") {
xrefStreamStats[StreamType.A85] = true;
return new Ascii85Stream(stream, maybeLength);
}
if (name === "ASCIIHexDecode" || name === "AHx") {
xrefStreamStats[StreamType.AHX] = true;
return new AsciiHexStream(stream, maybeLength);
}
if (name === "CCITTFaxDecode" || name === "CCF") {
xrefStreamStats[StreamType.CCF] = true;
return new CCITTFaxStream(stream, maybeLength, params);
}
if (name === "RunLengthDecode" || name === "RL") {
xrefStreamStats[StreamType.RLX] = true;
return new RunLengthStream(stream, maybeLength);
}
if (name === "JBIG2Decode") {
xrefStreamStats[StreamType.JBIG] = true;
return new Jbig2Stream(stream, maybeLength, params);
return new FlateStream(stream, maybeLength);
case "LZW":
case "LZWDecode":
xrefStreamStats[StreamType.LZW] = true;
let earlyChange = 1;
if (params) {
if (params.has("EarlyChange")) {
earlyChange = params.get("EarlyChange");
}
return new PredictorStream(
new LZWStream(stream, maybeLength, earlyChange),
maybeLength,
params
);
}
return new LZWStream(stream, maybeLength, earlyChange);
case "DCT":
case "DCTDecode":
xrefStreamStats[StreamType.DCT] = true;
return new JpegStream(stream, maybeLength, params);
case "JPX":
case "JPXDecode":
xrefStreamStats[StreamType.JPX] = true;
return new JpxStream(stream, maybeLength, params);
case "A85":
case "ASCII85Decode":
xrefStreamStats[StreamType.A85] = true;
return new Ascii85Stream(stream, maybeLength);
case "AHx":
case "ASCIIHexDecode":
xrefStreamStats[StreamType.AHX] = true;
return new AsciiHexStream(stream, maybeLength);
case "CCF":
case "CCITTFaxDecode":
xrefStreamStats[StreamType.CCF] = true;
return new CCITTFaxStream(stream, maybeLength, params);
case "RL":
case "RunLengthDecode":
xrefStreamStats[StreamType.RLX] = true;
return new RunLengthStream(stream, maybeLength);
case "JBIG2Decode":
xrefStreamStats[StreamType.JBIG] = true;
return new Jbig2Stream(stream, maybeLength, params);
}
warn(`Filter "${name}" is not supported.`);
return stream;

View file

@ -117,7 +117,7 @@ class RadialAxialShading extends BaseShading {
this.coordsArr = dict.getArray("Coords");
this.shadingType = dict.get("ShadingType");
const cs = ColorSpace.parse({
cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
xref,
resources,
pdfFunctionFactory,
@ -415,7 +415,7 @@ class MeshShading extends BaseShading {
this.bbox = null;
}
const cs = ColorSpace.parse({
cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
xref,
resources,
pdfFunctionFactory,

View file

@ -43,7 +43,7 @@ class PredictorStream extends DecodeStream {
this.dict = str.dict;
const colors = (this.colors = params.get("Colors") || 1);
const bits = (this.bits = params.get("BitsPerComponent") || 8);
const bits = (this.bits = params.get("BPC", "BitsPerComponent") || 8);
const columns = (this.columns = params.get("Columns") || 1);
this.pixBytes = (colors * bits + 7) >> 3;

View file

@ -90,8 +90,22 @@ class Dict {
get(key1, key2, key3) {
let value = this._map[key1];
if (value === undefined && key2 !== undefined) {
if (
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")) &&
key2.length < key1.length
) {
unreachable("Dict.get: Expected keys to be ordered by length.");
}
value = this._map[key2];
if (value === undefined && key3 !== undefined) {
if (
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")) &&
key3.length < key2.length
) {
unreachable("Dict.get: Expected keys to be ordered by length.");
}
value = this._map[key3];
}
}
@ -105,8 +119,22 @@ class Dict {
async getAsync(key1, key2, key3) {
let value = this._map[key1];
if (value === undefined && key2 !== undefined) {
if (
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")) &&
key2.length < key1.length
) {
unreachable("Dict.getAsync: Expected keys to be ordered by length.");
}
value = this._map[key2];
if (value === undefined && key3 !== undefined) {
if (
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")) &&
key3.length < key2.length
) {
unreachable("Dict.getAsync: Expected keys to be ordered by length.");
}
value = this._map[key3];
}
}
@ -120,8 +148,22 @@ class Dict {
getArray(key1, key2, key3) {
let value = this._map[key1];
if (value === undefined && key2 !== undefined) {
if (
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")) &&
key2.length < key1.length
) {
unreachable("Dict.getArray: Expected keys to be ordered by length.");
}
value = this._map[key2];
if (value === undefined && key3 !== undefined) {
if (
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("!PRODUCTION || TESTING")) &&
key3.length < key2.length
) {
unreachable("Dict.getArray: Expected keys to be ordered by length.");
}
value = this._map[key3];
}
}