Merge pull request #20064 from calixteman/dict_api

Add few methods to the Dict class in order to simplify the code when writing an annotation
This commit is contained in:
calixteman 2025-07-08 22:16:57 +02:00 committed by GitHub
commit 0e2b59e3d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 222 additions and 166 deletions

View File

@ -361,10 +361,10 @@ class AnnotationFactory {
case AnnotationEditorType.FREETEXT: case AnnotationEditorType.FREETEXT:
if (!baseFontRef) { if (!baseFontRef) {
const baseFont = new Dict(xref); const baseFont = new Dict(xref);
baseFont.set("BaseFont", Name.get("Helvetica")); baseFont.setIfName("BaseFont", "Helvetica");
baseFont.set("Type", Name.get("Font")); baseFont.setIfName("Type", "Font");
baseFont.set("Subtype", Name.get("Type1")); baseFont.setIfName("Subtype", "Type1");
baseFont.set("Encoding", Name.get("WinAnsiEncoding")); baseFont.setIfName("Encoding", "WinAnsiEncoding");
baseFontRef = xref.getNewTemporaryRef(); baseFontRef = xref.getNewTemporaryRef();
changes.put(baseFontRef, { changes.put(baseFontRef, {
data: baseFont, data: baseFont,
@ -571,8 +571,8 @@ function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) {
} }
} }
function getPdfColorArray(color) { function getPdfColorArray(color, defaultValue = null) {
return Array.from(color, c => c / 255); return (color && Array.from(color, c => c / 255)) || defaultValue;
} }
function getQuadPoints(dict, rect) { function getQuadPoints(dict, rect) {
@ -1741,7 +1741,7 @@ class MarkupAnnotation extends Annotation {
const formDict = new Dict(xref); const formDict = new Dict(xref);
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
const appearanceStream = new StringStream(buffer.join(" ")); const appearanceStream = new StringStream(buffer.join(" "));
appearanceStream.dict = appearanceStreamDict; appearanceStream.dict = appearanceStreamDict;
@ -1749,14 +1749,10 @@ class MarkupAnnotation extends Annotation {
const gsDict = new Dict(xref); const gsDict = new Dict(xref);
if (blendMode) { if (blendMode) {
gsDict.set("BM", Name.get(blendMode)); gsDict.setIfName("BM", blendMode);
}
if (typeof strokeAlpha === "number") {
gsDict.set("CA", strokeAlpha);
}
if (typeof fillAlpha === "number") {
gsDict.set("ca", fillAlpha);
} }
gsDict.setIfNumber("CA", strokeAlpha);
gsDict.setIfNumber("ca", fillAlpha);
const stateDict = new Dict(xref); const stateDict = new Dict(xref);
stateDict.set("GS0", gsDict); stateDict.set("GS0", gsDict);
@ -2107,12 +2103,8 @@ class WidgetAnnotation extends Annotation {
if (rotation) { if (rotation) {
mk.set("R", rotation); mk.set("R", rotation);
} }
if (this.borderColor) { mk.setIfArray("BC", getPdfColorArray(this.borderColor));
mk.set("BC", getPdfColorArray(this.borderColor)); mk.setIfArray("BG", getPdfColorArray(this.backgroundColor));
}
if (this.backgroundColor) {
mk.set("BG", getPdfColorArray(this.backgroundColor));
}
return mk.size > 0 ? mk : null; return mk.size > 0 ? mk : null;
} }
@ -2248,7 +2240,7 @@ class WidgetAnnotation extends Annotation {
const resources = this._getSaveFieldResources(xref); const resources = this._getSaveFieldResources(xref);
const appearanceStream = new StringStream(appearance); const appearanceStream = new StringStream(appearance);
const appearanceDict = (appearanceStream.dict = new Dict(xref)); const appearanceDict = (appearanceStream.dict = new Dict(xref));
appearanceDict.set("Subtype", Name.get("Form")); appearanceDict.setIfName("Subtype", "Form");
appearanceDict.set("Resources", resources); appearanceDict.set("Resources", resources);
const bbox = const bbox =
rotation % 180 === 0 rotation % 180 === 0
@ -3272,8 +3264,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
const appearanceStreamDict = new Dict(params.xref); const appearanceStreamDict = new Dict(params.xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", bbox); appearanceStreamDict.set("BBox", bbox);
appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]); appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]);
appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Length", appearance.length);
@ -3460,10 +3452,10 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
get fallbackFontDict() { get fallbackFontDict() {
const dict = new Dict(); const dict = new Dict();
dict.set("BaseFont", Name.get("ZapfDingbats")); dict.setIfName("BaseFont", "ZapfDingbats");
dict.set("Type", Name.get("FallbackType")); dict.setIfName("Type", "FallbackType");
dict.set("Subtype", Name.get("FallbackType")); dict.setIfName("Subtype", "FallbackType");
dict.set("Encoding", Name.get("ZapfDingbatsEncoding")); dict.setIfName("Encoding", "ZapfDingbatsEncoding");
return shadow(this, "fallbackFontDict", dict); return shadow(this, "fallbackFontDict", dict);
} }
@ -3958,8 +3950,8 @@ class FreeTextAnnotation extends MarkupAnnotation {
const { color, fontSize, oldAnnotation, rect, rotation, user, value } = const { color, fontSize, oldAnnotation, rect, rotation, user, value } =
annotation; annotation;
const freetext = oldAnnotation || new Dict(xref); const freetext = oldAnnotation || new Dict(xref);
freetext.set("Type", Name.get("Annot")); freetext.setIfNotExists("Type", Name.get("Annot"));
freetext.set("Subtype", Name.get("FreeText")); freetext.setIfNotExists("Subtype", Name.get("FreeText"));
if (oldAnnotation) { if (oldAnnotation) {
freetext.set("M", `D:${getModificationDate()}`); freetext.set("M", `D:${getModificationDate()}`);
// TODO: We should try to generate a new RC from the content we've. // TODO: We should try to generate a new RC from the content we've.
@ -3968,27 +3960,19 @@ class FreeTextAnnotation extends MarkupAnnotation {
} else { } else {
freetext.set("CreationDate", `D:${getModificationDate()}`); freetext.set("CreationDate", `D:${getModificationDate()}`);
} }
freetext.set("Rect", rect); freetext.setIfArray("Rect", rect);
const da = `/Helv ${fontSize} Tf ${getPdfColor(color, /* isFill */ true)}`; const da = `/Helv ${fontSize} Tf ${getPdfColor(color, /* isFill */ true)}`;
freetext.set("DA", da); freetext.set("DA", da);
freetext.set("Contents", stringToAsciiOrUTF16BE(value)); freetext.setIfDefined("Contents", stringToAsciiOrUTF16BE(value));
freetext.set("F", 4); freetext.setIfNotExists("F", 4);
freetext.set("Border", [0, 0, 0]); freetext.setIfNotExists("Border", [0, 0, 0]);
freetext.set("Rotate", rotation); freetext.setIfNumber("Rotate", rotation);
freetext.setIfDefined("T", stringToAsciiOrUTF16BE(user));
if (user) {
freetext.set("T", stringToAsciiOrUTF16BE(user));
}
if (apRef || ap) { if (apRef || ap) {
const n = new Dict(xref); const n = new Dict(xref);
freetext.set("AP", n); freetext.set("AP", n);
n.set("N", apRef || ap);
if (apRef) {
n.set("N", apRef);
} else {
n.set("N", ap);
}
} }
return freetext; return freetext;
@ -3997,6 +3981,9 @@ class FreeTextAnnotation extends MarkupAnnotation {
static async createNewAppearanceStream(annotation, xref, params) { static async createNewAppearanceStream(annotation, xref, params) {
const { baseFontRef, evaluator, task } = params; const { baseFontRef, evaluator, task } = params;
const { color, fontSize, rect, rotation, value } = annotation; const { color, fontSize, rect, rotation, value } = annotation;
if (!color) {
return null;
}
const resources = new Dict(xref); const resources = new Dict(xref);
const font = new Dict(xref); const font = new Dict(xref);
@ -4005,10 +3992,10 @@ class FreeTextAnnotation extends MarkupAnnotation {
font.set("Helv", baseFontRef); font.set("Helv", baseFontRef);
} else { } else {
const baseFont = new Dict(xref); const baseFont = new Dict(xref);
baseFont.set("BaseFont", Name.get("Helvetica")); baseFont.setIfName("BaseFont", "Helvetica");
baseFont.set("Type", Name.get("Font")); baseFont.setIfName("Type", "Font");
baseFont.set("Subtype", Name.get("Type1")); baseFont.setIfName("Subtype", "Type1");
baseFont.set("Encoding", Name.get("WinAnsiEncoding")); baseFont.setIfName("Encoding", "WinAnsiEncoding");
font.set("Helv", baseFont); font.set("Helv", baseFont);
} }
resources.set("Font", font); resources.set("Font", font);
@ -4110,8 +4097,8 @@ class FreeTextAnnotation extends MarkupAnnotation {
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Resources", resources);
appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]); appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]);
@ -4142,13 +4129,13 @@ class LineAnnotation extends MarkupAnnotation {
if (!this.appearance) { if (!this.appearance) {
// The default stroke color is black. // The default stroke color is black.
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
const interiorColor = getRgbColor(dict.getArray("IC"), null); const interiorColor = getRgbColor(dict.getArray("IC"), null);
// The default fill color is transparent. Setting the fill colour is // The default fill color is transparent. Setting the fill colour is
// necessary if/when we want to add support for non-default line endings. // necessary if/when we want to add support for non-default line endings.
const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; const fillColor = getPdfColorArray(interiorColor);
const fillAlpha = fillColor ? strokeAlpha : null; const fillAlpha = fillColor ? strokeAlpha : null;
const borderWidth = this.borderStyle.width || 1, const borderWidth = this.borderStyle.width || 1,
@ -4202,12 +4189,12 @@ class SquareAnnotation extends MarkupAnnotation {
if (!this.appearance) { if (!this.appearance) {
// The default stroke color is black. // The default stroke color is black.
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
const interiorColor = getRgbColor(dict.getArray("IC"), null); const interiorColor = getRgbColor(dict.getArray("IC"), null);
// The default fill color is transparent. // The default fill color is transparent.
const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; const fillColor = getPdfColorArray(interiorColor);
const fillAlpha = fillColor ? strokeAlpha : null; const fillAlpha = fillColor ? strokeAlpha : null;
if (this.borderStyle.width === 0 && !fillColor) { if (this.borderStyle.width === 0 && !fillColor) {
@ -4249,12 +4236,12 @@ class CircleAnnotation extends MarkupAnnotation {
if (!this.appearance) { if (!this.appearance) {
// The default stroke color is black. // The default stroke color is black.
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
const interiorColor = getRgbColor(dict.getArray("IC"), null); const interiorColor = getRgbColor(dict.getArray("IC"), null);
// The default fill color is transparent. // The default fill color is transparent.
const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; const fillColor = getPdfColorArray(interiorColor);
const fillAlpha = fillColor ? strokeAlpha : null; const fillAlpha = fillColor ? strokeAlpha : null;
if (this.borderStyle.width === 0 && !fillColor) { if (this.borderStyle.width === 0 && !fillColor) {
@ -4334,7 +4321,7 @@ class PolylineAnnotation extends MarkupAnnotation {
if (!this.appearance) { if (!this.appearance) {
// The default stroke color is black. // The default stroke color is black.
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
let fillColor = getRgbColor(dict.getArray("IC"), null); let fillColor = getRgbColor(dict.getArray("IC"), null);
@ -4453,7 +4440,7 @@ class InkAnnotation extends MarkupAnnotation {
if (!this.appearance) { if (!this.appearance) {
// The default stroke color is black. // The default stroke color is black.
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
const borderWidth = this.borderStyle.width || 1, const borderWidth = this.borderStyle.width || 1,
@ -4514,44 +4501,40 @@ class InkAnnotation extends MarkupAnnotation {
user, user,
} = annotation; } = annotation;
const ink = oldAnnotation || new Dict(xref); const ink = oldAnnotation || new Dict(xref);
ink.set("Type", Name.get("Annot")); ink.setIfNotExists("Type", Name.get("Annot"));
ink.set("Subtype", Name.get("Ink")); ink.setIfNotExists("Subtype", Name.get("Ink"));
ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`); ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
ink.set("Rect", rect); ink.setIfArray("Rect", rect);
ink.set("InkList", outlines?.points || paths.points); ink.setIfArray("InkList", outlines?.points || paths?.points);
ink.set("F", 4); ink.setIfNotExists("F", 4);
ink.set("Rotate", rotation); ink.setIfNumber("Rotate", rotation);
ink.setIfDefined("T", stringToAsciiOrUTF16BE(user));
if (user) {
ink.set("T", stringToAsciiOrUTF16BE(user));
}
if (outlines) { if (outlines) {
// Free highlight. // Free highlight.
// There's nothing about this in the spec, but it's used when highlighting // There's nothing about this in the spec, but it's used when highlighting
// in Edge's viewer. Acrobat takes into account this parameter to indicate // in Edge's viewer. Acrobat takes into account this parameter to indicate
// that the Ink is used for highlighting. // that the Ink is used for highlighting.
ink.set("IT", Name.get("InkHighlight")); ink.setIfName("IT", "InkHighlight");
} }
// Line thickness. // Line thickness.
const bs = new Dict(xref); if (thickness > 0) {
ink.set("BS", bs); const bs = new Dict(xref);
bs.set("W", thickness); ink.set("BS", bs);
bs.set("W", thickness);
}
// Color. // Color.
ink.set("C", getPdfColorArray(color)); ink.setIfArray("C", getPdfColorArray(color));
// Opacity. // Opacity.
ink.set("CA", opacity); ink.setIfNumber("CA", opacity);
const n = new Dict(xref); if (ap || apRef) {
ink.set("AP", n); const n = new Dict(xref);
ink.set("AP", n);
if (apRef) { n.set("N", apRef || ap);
n.set("N", apRef);
} else {
n.set("N", ap);
} }
return ink; return ink;
@ -4566,6 +4549,9 @@ class InkAnnotation extends MarkupAnnotation {
); );
} }
const { color, rect, paths, thickness, opacity } = annotation; const { color, rect, paths, thickness, opacity } = annotation;
if (!color) {
return null;
}
const appearanceBuffer = [ const appearanceBuffer = [
`${thickness} w 1 J 1 j`, `${thickness} w 1 J 1 j`,
@ -4606,8 +4592,8 @@ class InkAnnotation extends MarkupAnnotation {
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Length", appearance.length);
@ -4616,7 +4602,7 @@ class InkAnnotation extends MarkupAnnotation {
const extGState = new Dict(xref); const extGState = new Dict(xref);
const r0 = new Dict(xref); const r0 = new Dict(xref);
r0.set("CA", opacity); r0.set("CA", opacity);
r0.set("Type", Name.get("ExtGState")); r0.setIfName("Type", "ExtGState");
extGState.set("R0", r0); extGState.set("R0", r0);
resources.set("ExtGState", extGState); resources.set("ExtGState", extGState);
appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Resources", resources);
@ -4635,6 +4621,9 @@ class InkAnnotation extends MarkupAnnotation {
outlines: { outline }, outlines: { outline },
opacity, opacity,
} = annotation; } = annotation;
if (!color) {
return null;
}
const appearanceBuffer = [ const appearanceBuffer = [
`${getPdfColor(color, /* isFill */ true)}`, `${getPdfColor(color, /* isFill */ true)}`,
"/R0 gs", "/R0 gs",
@ -4662,8 +4651,8 @@ class InkAnnotation extends MarkupAnnotation {
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Length", appearance.length);
@ -4673,11 +4662,11 @@ class InkAnnotation extends MarkupAnnotation {
appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Resources", resources);
const r0 = new Dict(xref); const r0 = new Dict(xref);
extGState.set("R0", r0); extGState.set("R0", r0);
r0.set("BM", Name.get("Multiply")); r0.setIfName("BM", "Multiply");
if (opacity !== 1) { if (opacity !== 1) {
r0.set("ca", opacity); r0.set("ca", opacity);
r0.set("Type", Name.get("ExtGState")); r0.setIfName("Type", "ExtGState");
} }
const ap = new StringStream(appearance); const ap = new StringStream(appearance);
@ -4711,7 +4700,7 @@ class HighlightAnnotation extends MarkupAnnotation {
warn("HighlightAnnotation - ignoring built-in appearance stream."); warn("HighlightAnnotation - ignoring built-in appearance stream.");
} }
// Default color is yellow in Acrobat Reader // Default color is yellow in Acrobat Reader
const fillColor = this.color ? getPdfColorArray(this.color) : [1, 1, 0]; const fillColor = getPdfColorArray(this.color, [1, 1, 0]);
const fillAlpha = dict.get("CA"); const fillAlpha = dict.get("CA");
this._setDefaultAppearance({ this._setDefaultAppearance({
@ -4743,29 +4732,19 @@ class HighlightAnnotation extends MarkupAnnotation {
static createNewDict(annotation, xref, { apRef, ap }) { static createNewDict(annotation, xref, { apRef, ap }) {
const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } = const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } =
annotation; annotation;
const date = `D:${getModificationDate()}`;
const highlight = oldAnnotation || new Dict(xref); const highlight = oldAnnotation || new Dict(xref);
highlight.set("Type", Name.get("Annot")); highlight.setIfNotExists("Type", Name.get("Annot"));
highlight.set("Subtype", Name.get("Highlight")); highlight.setIfNotExists("Subtype", Name.get("Highlight"));
highlight.set( highlight.set(oldAnnotation ? "M" : "CreationDate", date);
oldAnnotation ? "M" : "CreationDate", highlight.setIfArray("Rect", rect);
`D:${getModificationDate()}` highlight.setIfNotExists("F", 4);
); highlight.setIfNotExists("Border", [0, 0, 0]);
highlight.set("CreationDate", `D:${getModificationDate()}`); highlight.setIfNumber("Rotate", rotation);
highlight.set("Rect", rect); highlight.setIfArray("QuadPoints", quadPoints);
highlight.set("F", 4); highlight.setIfArray("C", getPdfColorArray(color));
highlight.set("Border", [0, 0, 0]); highlight.setIfNumber("CA", opacity);
highlight.set("Rotate", rotation); highlight.setIfDefined("T", stringToAsciiOrUTF16BE(user));
highlight.set("QuadPoints", quadPoints);
// Color.
highlight.set("C", getPdfColorArray(color));
// Opacity.
highlight.set("CA", opacity);
if (user) {
highlight.set("T", stringToAsciiOrUTF16BE(user));
}
if (apRef || ap) { if (apRef || ap) {
const n = new Dict(xref); const n = new Dict(xref);
@ -4778,6 +4757,9 @@ class HighlightAnnotation extends MarkupAnnotation {
static async createNewAppearanceStream(annotation, xref, params) { static async createNewAppearanceStream(annotation, xref, params) {
const { color, rect, outlines, opacity } = annotation; const { color, rect, outlines, opacity } = annotation;
if (!color) {
return null;
}
const appearanceBuffer = [ const appearanceBuffer = [
`${getPdfColor(color, /* isFill */ true)}`, `${getPdfColor(color, /* isFill */ true)}`,
@ -4803,8 +4785,8 @@ class HighlightAnnotation extends MarkupAnnotation {
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Length", appearance.length);
@ -4814,11 +4796,11 @@ class HighlightAnnotation extends MarkupAnnotation {
appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Resources", resources);
const r0 = new Dict(xref); const r0 = new Dict(xref);
extGState.set("R0", r0); extGState.set("R0", r0);
r0.set("BM", Name.get("Multiply")); r0.setIfName("BM", "Multiply");
if (opacity !== 1) { if (opacity !== 1) {
r0.set("ca", opacity); r0.set("ca", opacity);
r0.set("Type", Name.get("ExtGState")); r0.setIfName("Type", "ExtGState");
} }
const ap = new StringStream(appearance); const ap = new StringStream(appearance);
@ -4839,9 +4821,7 @@ class UnderlineAnnotation extends MarkupAnnotation {
if (quadPoints) { if (quadPoints) {
if (!this.appearance) { if (!this.appearance) {
// Default color is black // Default color is black
const strokeColor = this.color const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
? getPdfColorArray(this.color)
: [0, 0, 0];
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
// The values 0.571 and 1.3 below corresponds to what Acrobat is doing. // The values 0.571 and 1.3 below corresponds to what Acrobat is doing.
@ -4881,9 +4861,7 @@ class SquigglyAnnotation extends MarkupAnnotation {
if (quadPoints) { if (quadPoints) {
if (!this.appearance) { if (!this.appearance) {
// Default color is black // Default color is black
const strokeColor = this.color const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
? getPdfColorArray(this.color)
: [0, 0, 0];
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
this._setDefaultAppearance({ this._setDefaultAppearance({
@ -4929,9 +4907,7 @@ class StrikeOutAnnotation extends MarkupAnnotation {
if (quadPoints) { if (quadPoints) {
if (!this.appearance) { if (!this.appearance) {
// Default color is black // Default color is black
const strokeColor = this.color const strokeColor = getPdfColorArray(this.color, [0, 0, 0]);
? getPdfColorArray(this.color)
: [0, 0, 0];
const strokeAlpha = dict.get("CA"); const strokeAlpha = dict.get("CA");
this._setDefaultAppearance({ this._setDefaultAppearance({
@ -5030,8 +5006,8 @@ class StampAnnotation extends MarkupAnnotation {
image.set("Type", xobjectName); image.set("Type", xobjectName);
image.set("Subtype", imageName); image.set("Subtype", imageName);
image.set("BitsPerComponent", 8); image.set("BitsPerComponent", 8);
image.set("ColorSpace", Name.get("DeviceRGB")); image.setIfName("ColorSpace", "DeviceRGB");
image.set("Filter", Name.get("DCTDecode")); image.setIfName("Filter", "DCTDecode");
image.set("BBox", [0, 0, width, height]); image.set("BBox", [0, 0, width, height]);
image.set("Width", width); image.set("Width", width);
image.set("Height", height); image.set("Height", height);
@ -5053,7 +5029,7 @@ class StampAnnotation extends MarkupAnnotation {
smask.set("Type", xobjectName); smask.set("Type", xobjectName);
smask.set("Subtype", imageName); smask.set("Subtype", imageName);
smask.set("BitsPerComponent", 8); smask.set("BitsPerComponent", 8);
smask.set("ColorSpace", Name.get("DeviceGray")); smask.setIfName("ColorSpace", "DeviceGray");
smask.set("Width", width); smask.set("Width", width);
smask.set("Height", height); smask.set("Height", height);
@ -5071,31 +5047,21 @@ class StampAnnotation extends MarkupAnnotation {
static createNewDict(annotation, xref, { apRef, ap }) { static createNewDict(annotation, xref, { apRef, ap }) {
const { oldAnnotation, rect, rotation, user } = annotation; const { oldAnnotation, rect, rotation, user } = annotation;
const date = `D:${getModificationDate(annotation.date)}`;
const stamp = oldAnnotation || new Dict(xref); const stamp = oldAnnotation || new Dict(xref);
stamp.set("Type", Name.get("Annot")); stamp.setIfNotExists("Type", Name.get("Annot"));
stamp.set("Subtype", Name.get("Stamp")); stamp.setIfNotExists("Subtype", Name.get("Stamp"));
stamp.set( stamp.set(oldAnnotation ? "M" : "CreationDate", date);
oldAnnotation ? "M" : "CreationDate", stamp.setIfArray("Rect", rect);
`D:${getModificationDate()}` stamp.setIfNotExists("F", 4);
); stamp.setIfNotExists("Border", [0, 0, 0]);
stamp.set("Rect", rect); stamp.setIfNumber("Rotate", rotation);
stamp.set("F", 4); stamp.setIfDefined("T", stringToAsciiOrUTF16BE(user));
stamp.set("Border", [0, 0, 0]);
stamp.set("Rotate", rotation);
if (user) {
stamp.set("T", stringToAsciiOrUTF16BE(user));
}
if (apRef || ap) { if (apRef || ap) {
const n = new Dict(xref); const n = new Dict(xref);
stamp.set("AP", n); stamp.set("AP", n);
n.set("N", apRef || ap);
if (apRef) {
n.set("N", apRef);
} else {
n.set("N", ap);
}
} }
return stamp; return stamp;
@ -5103,6 +5069,9 @@ class StampAnnotation extends MarkupAnnotation {
static async #createNewAppearanceStreamForDrawing(annotation, xref) { static async #createNewAppearanceStreamForDrawing(annotation, xref) {
const { areContours, color, rect, lines, thickness } = annotation; const { areContours, color, rect, lines, thickness } = annotation;
if (!color) {
return null;
}
const appearanceBuffer = [ const appearanceBuffer = [
`${thickness} w 1 J 1 j`, `${thickness} w 1 J 1 j`,
@ -5137,8 +5106,8 @@ class StampAnnotation extends MarkupAnnotation {
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Length", appearance.length);
@ -5167,8 +5136,8 @@ class StampAnnotation extends MarkupAnnotation {
const appearanceStreamDict = new Dict(xref); const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", [0, 0, width, height]); appearanceStreamDict.set("BBox", [0, 0, width, height]);
appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Resources", resources);

View File

@ -679,12 +679,19 @@ function getNewAnnotationsMap(annotationStorage) {
return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null; return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null;
} }
// If the string is null or undefined then it is returned as is.
function stringToAsciiOrUTF16BE(str) { function stringToAsciiOrUTF16BE(str) {
if (str === null || str === undefined) {
return str;
}
return isAscii(str) ? str : stringToUTF16String(str, /* bigEndian = */ true); return isAscii(str) ? str : stringToUTF16String(str, /* bigEndian = */ true);
} }
function isAscii(str) { function isAscii(str) {
return /^[\x00-\x7F]*$/.test(str); if (typeof str !== "string") {
return false;
}
return !str || /^[\x00-\x7F]*$/.test(str);
} }
function stringToUTF16HexString(str) { function stringToUTF16HexString(str) {

View File

@ -267,11 +267,11 @@ class FakeUnicodeFont {
get fontDescriptorRef() { get fontDescriptorRef() {
if (!FakeUnicodeFont._fontDescriptorRef) { if (!FakeUnicodeFont._fontDescriptorRef) {
const fontDescriptor = new Dict(this.xref); const fontDescriptor = new Dict(this.xref);
fontDescriptor.set("Type", Name.get("FontDescriptor")); fontDescriptor.setIfName("Type", "FontDescriptor");
fontDescriptor.set("FontName", this.fontName); fontDescriptor.set("FontName", this.fontName);
fontDescriptor.set("FontFamily", "MyriadPro Regular"); fontDescriptor.set("FontFamily", "MyriadPro Regular");
fontDescriptor.set("FontBBox", [0, 0, 0, 0]); fontDescriptor.set("FontBBox", [0, 0, 0, 0]);
fontDescriptor.set("FontStretch", Name.get("Normal")); fontDescriptor.setIfName("FontStretch", "Normal");
fontDescriptor.set("FontWeight", 400); fontDescriptor.set("FontWeight", 400);
fontDescriptor.set("ItalicAngle", 0); fontDescriptor.set("ItalicAngle", 0);
@ -285,9 +285,9 @@ class FakeUnicodeFont {
get descendantFontRef() { get descendantFontRef() {
const descendantFont = new Dict(this.xref); const descendantFont = new Dict(this.xref);
descendantFont.set("BaseFont", this.fontName); descendantFont.set("BaseFont", this.fontName);
descendantFont.set("Type", Name.get("Font")); descendantFont.setIfName("Type", "Font");
descendantFont.set("Subtype", Name.get("CIDFontType0")); descendantFont.setIfName("Subtype", "CIDFontType0");
descendantFont.set("CIDToGIDMap", Name.get("Identity")); descendantFont.setIfName("CIDToGIDMap", "Identity");
descendantFont.set("FirstChar", this.firstChar); descendantFont.set("FirstChar", this.firstChar);
descendantFont.set("LastChar", this.lastChar); descendantFont.set("LastChar", this.lastChar);
descendantFont.set("FontDescriptor", this.fontDescriptorRef); descendantFont.set("FontDescriptor", this.fontDescriptorRef);
@ -330,11 +330,11 @@ class FakeUnicodeFont {
get baseFontRef() { get baseFontRef() {
const baseFont = new Dict(this.xref); const baseFont = new Dict(this.xref);
baseFont.set("BaseFont", this.fontName); baseFont.set("BaseFont", this.fontName);
baseFont.set("Type", Name.get("Font")); baseFont.setIfName("Type", "Font");
baseFont.set("Subtype", Name.get("Type0")); baseFont.setIfName("Subtype", "Type0");
baseFont.set("Encoding", Name.get("Identity-H")); baseFont.setIfName("Encoding", "Identity-H");
baseFont.set("DescendantFonts", [this.descendantFontRef]); baseFont.set("DescendantFonts", [this.descendantFontRef]);
baseFont.set("ToUnicode", Name.get("Identity-H")); baseFont.setIfName("ToUnicode", "Identity-H");
return this.xref.getNewPersistentRef(baseFont); return this.xref.getNewPersistentRef(baseFont);
} }
@ -463,7 +463,7 @@ class FakeUnicodeFont {
const r0 = new Dict(this.xref); const r0 = new Dict(this.xref);
r0.set("ca", strokeAlpha); r0.set("ca", strokeAlpha);
r0.set("CA", strokeAlpha); r0.set("CA", strokeAlpha);
r0.set("Type", Name.get("ExtGState")); r0.setIfName("Type", "ExtGState");
extGState.set("R0", r0); extGState.set("R0", r0);
resources.set("ExtGState", extGState); resources.set("ExtGState", extGState);
} }
@ -476,8 +476,8 @@ class FakeUnicodeFont {
const appearance = buffer.join("\n"); const appearance = buffer.join("\n");
const appearanceStreamDict = new Dict(this.xref); const appearanceStreamDict = new Dict(this.xref);
appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.setIfName("Subtype", "Form");
appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.setIfName("Type", "XObject");
appearanceStreamDict.set("BBox", [0, 0, w, h]); appearanceStreamDict.set("BBox", [0, 0, w, h]);
appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Length", appearance.length);
appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Resources", resources);

View File

@ -199,6 +199,38 @@ class Dict {
this._map.set(key, value); this._map.set(key, value);
} }
setIfNotExists(key, value) {
if (!this.has(key)) {
this.set(key, value);
}
}
setIfNumber(key, value) {
if (typeof value === "number") {
this.set(key, value);
}
}
setIfArray(key, value) {
if (Array.isArray(value) || ArrayBuffer.isView(value)) {
this.set(key, value);
}
}
setIfDefined(key, value) {
if (value !== undefined && value !== null) {
this.set(key, value);
}
}
setIfName(key, value) {
if (typeof value === "string") {
this.set(key, Name.get(value));
} else if (value instanceof Name) {
this.set(key, value);
}
}
has(key) { has(key) {
return this._map.has(key); return this._map.has(key);
} }

View File

@ -271,7 +271,7 @@ function updateXFA({ xfaData, xfaDatasetsRef, changes, xref }) {
} }
const xfaDataStream = new StringStream(xfaData); const xfaDataStream = new StringStream(xfaData);
xfaDataStream.dict = new Dict(xref); xfaDataStream.dict = new Dict(xref);
xfaDataStream.dict.set("Type", Name.get("EmbeddedFile")); xfaDataStream.dict.setIfName("Type", "EmbeddedFile");
changes.put(xfaDatasetsRef, { changes.put(xfaDatasetsRef, {
data: xfaDataStream, data: xfaDataStream,
@ -382,7 +382,7 @@ function getTrailerDict(xrefInfo, changes, useXrefStream) {
if (useXrefStream) { if (useXrefStream) {
changes.put(refForXrefTable, { data: "" }); changes.put(refForXrefTable, { data: "" });
newXref.set("Size", refForXrefTable.num + 1); newXref.set("Size", refForXrefTable.num + 1);
newXref.set("Type", Name.get("XRef")); newXref.setIfName("Type", "XRef");
} else { } else {
newXref.set("Size", refForXrefTable.num); newXref.set("Size", refForXrefTable.num);
} }

View File

@ -431,6 +431,10 @@ describe("core_utils", function () {
expect(isAscii("hello world in Japanese is こんにちは世界の")).toEqual( expect(isAscii("hello world in Japanese is こんにちは世界の")).toEqual(
false false
); );
expect(isAscii("")).toEqual(true);
expect(isAscii(123)).toEqual(false);
expect(isAscii(null)).toEqual(false);
expect(isAscii(undefined)).toEqual(false);
}); });
}); });

View File

@ -380,6 +380,50 @@ describe("primitives", function () {
"Global font three", "Global font three",
]); ]);
}); });
it("should set the values if they're as expected", function () {
const dict = new Dict();
dict.set("key", "value");
dict.setIfNotExists("key", "new value");
expect(dict.get("key")).toEqual("value");
dict.setIfNotExists("key1", "value");
expect(dict.get("key1")).toEqual("value");
dict.setIfNumber("a", 123);
expect(dict.get("a")).toEqual(123);
dict.setIfNumber("b", "not a number");
expect(dict.has("b")).toBeFalse();
dict.setIfArray("c", [1, 2, 3]);
expect(dict.get("c")).toEqual([1, 2, 3]);
dict.setIfArray("d", new Uint8Array([4, 5, 6]));
expect(dict.get("d")).toEqual(new Uint8Array([4, 5, 6]));
dict.setIfArray("e", "not an array");
expect(dict.has("e")).toBeFalse();
dict.setIfDefined("f", "defined");
expect(dict.get("f")).toEqual("defined");
dict.setIfDefined("g", undefined);
expect(dict.has("g")).toBeFalse();
dict.setIfDefined("h", null);
expect(dict.has("h")).toBeFalse();
dict.setIfName("i", Name.get("name"));
expect(dict.get("i")).toEqual(Name.get("name"));
dict.setIfName("j", "name");
expect(dict.get("j")).toEqual(Name.get("name"));
dict.setIfName("k", 1234);
expect(dict.has("k")).toBeFalse();
});
}); });
describe("Ref", function () { describe("Ref", function () {