From 63b37b4371624d0787203630cb5db484d014b532 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 7 Jul 2025 20:40:16 +0200 Subject: [PATCH] Add few methods to the Dict class in order to simplify the code when writing an annotation --- src/core/annotation.js | 271 +++++++++++++++------------------ src/core/core_utils.js | 9 +- src/core/default_appearance.js | 24 +-- src/core/primitives.js | 32 ++++ src/core/writer.js | 4 +- test/unit/core_utils_spec.js | 4 + test/unit/primitives_spec.js | 44 ++++++ 7 files changed, 222 insertions(+), 166 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 06eeff126..3bc5dbdcf 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -361,10 +361,10 @@ class AnnotationFactory { case AnnotationEditorType.FREETEXT: if (!baseFontRef) { const baseFont = new Dict(xref); - baseFont.set("BaseFont", Name.get("Helvetica")); - baseFont.set("Type", Name.get("Font")); - baseFont.set("Subtype", Name.get("Type1")); - baseFont.set("Encoding", Name.get("WinAnsiEncoding")); + baseFont.setIfName("BaseFont", "Helvetica"); + baseFont.setIfName("Type", "Font"); + baseFont.setIfName("Subtype", "Type1"); + baseFont.setIfName("Encoding", "WinAnsiEncoding"); baseFontRef = xref.getNewTemporaryRef(); changes.put(baseFontRef, { data: baseFont, @@ -571,8 +571,8 @@ function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) { } } -function getPdfColorArray(color) { - return Array.from(color, c => c / 255); +function getPdfColorArray(color, defaultValue = null) { + return (color && Array.from(color, c => c / 255)) || defaultValue; } function getQuadPoints(dict, rect) { @@ -1741,7 +1741,7 @@ class MarkupAnnotation extends Annotation { const formDict = new Dict(xref); const appearanceStreamDict = new Dict(xref); - appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.setIfName("Subtype", "Form"); const appearanceStream = new StringStream(buffer.join(" ")); appearanceStream.dict = appearanceStreamDict; @@ -1749,14 +1749,10 @@ class MarkupAnnotation extends Annotation { const gsDict = new Dict(xref); if (blendMode) { - gsDict.set("BM", Name.get(blendMode)); - } - if (typeof strokeAlpha === "number") { - gsDict.set("CA", strokeAlpha); - } - if (typeof fillAlpha === "number") { - gsDict.set("ca", fillAlpha); + gsDict.setIfName("BM", blendMode); } + gsDict.setIfNumber("CA", strokeAlpha); + gsDict.setIfNumber("ca", fillAlpha); const stateDict = new Dict(xref); stateDict.set("GS0", gsDict); @@ -2107,12 +2103,8 @@ class WidgetAnnotation extends Annotation { if (rotation) { mk.set("R", rotation); } - if (this.borderColor) { - mk.set("BC", getPdfColorArray(this.borderColor)); - } - if (this.backgroundColor) { - mk.set("BG", getPdfColorArray(this.backgroundColor)); - } + mk.setIfArray("BC", getPdfColorArray(this.borderColor)); + mk.setIfArray("BG", getPdfColorArray(this.backgroundColor)); return mk.size > 0 ? mk : null; } @@ -2248,7 +2240,7 @@ class WidgetAnnotation extends Annotation { const resources = this._getSaveFieldResources(xref); const appearanceStream = new StringStream(appearance); const appearanceDict = (appearanceStream.dict = new Dict(xref)); - appearanceDict.set("Subtype", Name.get("Form")); + appearanceDict.setIfName("Subtype", "Form"); appearanceDict.set("Resources", resources); const bbox = rotation % 180 === 0 @@ -3272,8 +3264,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { const appearanceStreamDict = new Dict(params.xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", bbox); appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]); appearanceStreamDict.set("Length", appearance.length); @@ -3460,10 +3452,10 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { get fallbackFontDict() { const dict = new Dict(); - dict.set("BaseFont", Name.get("ZapfDingbats")); - dict.set("Type", Name.get("FallbackType")); - dict.set("Subtype", Name.get("FallbackType")); - dict.set("Encoding", Name.get("ZapfDingbatsEncoding")); + dict.setIfName("BaseFont", "ZapfDingbats"); + dict.setIfName("Type", "FallbackType"); + dict.setIfName("Subtype", "FallbackType"); + dict.setIfName("Encoding", "ZapfDingbatsEncoding"); return shadow(this, "fallbackFontDict", dict); } @@ -3958,8 +3950,8 @@ class FreeTextAnnotation extends MarkupAnnotation { const { color, fontSize, oldAnnotation, rect, rotation, user, value } = annotation; const freetext = oldAnnotation || new Dict(xref); - freetext.set("Type", Name.get("Annot")); - freetext.set("Subtype", Name.get("FreeText")); + freetext.setIfNotExists("Type", Name.get("Annot")); + freetext.setIfNotExists("Subtype", Name.get("FreeText")); if (oldAnnotation) { freetext.set("M", `D:${getModificationDate()}`); // TODO: We should try to generate a new RC from the content we've. @@ -3968,27 +3960,19 @@ class FreeTextAnnotation extends MarkupAnnotation { } else { freetext.set("CreationDate", `D:${getModificationDate()}`); } - freetext.set("Rect", rect); + freetext.setIfArray("Rect", rect); const da = `/Helv ${fontSize} Tf ${getPdfColor(color, /* isFill */ true)}`; freetext.set("DA", da); - freetext.set("Contents", stringToAsciiOrUTF16BE(value)); - freetext.set("F", 4); - freetext.set("Border", [0, 0, 0]); - freetext.set("Rotate", rotation); - - if (user) { - freetext.set("T", stringToAsciiOrUTF16BE(user)); - } + freetext.setIfDefined("Contents", stringToAsciiOrUTF16BE(value)); + freetext.setIfNotExists("F", 4); + freetext.setIfNotExists("Border", [0, 0, 0]); + freetext.setIfNumber("Rotate", rotation); + freetext.setIfDefined("T", stringToAsciiOrUTF16BE(user)); if (apRef || ap) { const n = new Dict(xref); freetext.set("AP", n); - - if (apRef) { - n.set("N", apRef); - } else { - n.set("N", ap); - } + n.set("N", apRef || ap); } return freetext; @@ -3997,6 +3981,9 @@ class FreeTextAnnotation extends MarkupAnnotation { static async createNewAppearanceStream(annotation, xref, params) { const { baseFontRef, evaluator, task } = params; const { color, fontSize, rect, rotation, value } = annotation; + if (!color) { + return null; + } const resources = new Dict(xref); const font = new Dict(xref); @@ -4005,10 +3992,10 @@ class FreeTextAnnotation extends MarkupAnnotation { font.set("Helv", baseFontRef); } else { const baseFont = new Dict(xref); - baseFont.set("BaseFont", Name.get("Helvetica")); - baseFont.set("Type", Name.get("Font")); - baseFont.set("Subtype", Name.get("Type1")); - baseFont.set("Encoding", Name.get("WinAnsiEncoding")); + baseFont.setIfName("BaseFont", "Helvetica"); + baseFont.setIfName("Type", "Font"); + baseFont.setIfName("Subtype", "Type1"); + baseFont.setIfName("Encoding", "WinAnsiEncoding"); font.set("Helv", baseFont); } resources.set("Font", font); @@ -4110,8 +4097,8 @@ class FreeTextAnnotation extends MarkupAnnotation { const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]); @@ -4142,13 +4129,13 @@ class LineAnnotation extends MarkupAnnotation { if (!this.appearance) { // 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 interiorColor = getRgbColor(dict.getArray("IC"), null); // The default fill color is transparent. Setting the fill colour is // 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 borderWidth = this.borderStyle.width || 1, @@ -4202,12 +4189,12 @@ class SquareAnnotation extends MarkupAnnotation { if (!this.appearance) { // 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 interiorColor = getRgbColor(dict.getArray("IC"), null); // The default fill color is transparent. - const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; + const fillColor = getPdfColorArray(interiorColor); const fillAlpha = fillColor ? strokeAlpha : null; if (this.borderStyle.width === 0 && !fillColor) { @@ -4249,12 +4236,12 @@ class CircleAnnotation extends MarkupAnnotation { if (!this.appearance) { // 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 interiorColor = getRgbColor(dict.getArray("IC"), null); // The default fill color is transparent. - const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; + const fillColor = getPdfColorArray(interiorColor); const fillAlpha = fillColor ? strokeAlpha : null; if (this.borderStyle.width === 0 && !fillColor) { @@ -4334,7 +4321,7 @@ class PolylineAnnotation extends MarkupAnnotation { if (!this.appearance) { // 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 borderWidth = this.borderStyle.width || 1, @@ -4433,7 +4420,7 @@ class InkAnnotation extends MarkupAnnotation { if (!this.appearance) { // 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 borderWidth = this.borderStyle.width || 1, @@ -4494,44 +4481,40 @@ class InkAnnotation extends MarkupAnnotation { user, } = annotation; const ink = oldAnnotation || new Dict(xref); - ink.set("Type", Name.get("Annot")); - ink.set("Subtype", Name.get("Ink")); + ink.setIfNotExists("Type", Name.get("Annot")); + ink.setIfNotExists("Subtype", Name.get("Ink")); ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`); - ink.set("Rect", rect); - ink.set("InkList", outlines?.points || paths.points); - ink.set("F", 4); - ink.set("Rotate", rotation); - - if (user) { - ink.set("T", stringToAsciiOrUTF16BE(user)); - } + ink.setIfArray("Rect", rect); + ink.setIfArray("InkList", outlines?.points || paths?.points); + ink.setIfNotExists("F", 4); + ink.setIfNumber("Rotate", rotation); + ink.setIfDefined("T", stringToAsciiOrUTF16BE(user)); if (outlines) { // Free highlight. // 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 // that the Ink is used for highlighting. - ink.set("IT", Name.get("InkHighlight")); + ink.setIfName("IT", "InkHighlight"); } // Line thickness. - const bs = new Dict(xref); - ink.set("BS", bs); - bs.set("W", thickness); + if (thickness > 0) { + const bs = new Dict(xref); + ink.set("BS", bs); + bs.set("W", thickness); + } // Color. - ink.set("C", getPdfColorArray(color)); + ink.setIfArray("C", getPdfColorArray(color)); // Opacity. - ink.set("CA", opacity); + ink.setIfNumber("CA", opacity); - const n = new Dict(xref); - ink.set("AP", n); - - if (apRef) { - n.set("N", apRef); - } else { - n.set("N", ap); + if (ap || apRef) { + const n = new Dict(xref); + ink.set("AP", n); + n.set("N", apRef || ap); } return ink; @@ -4546,6 +4529,9 @@ class InkAnnotation extends MarkupAnnotation { ); } const { color, rect, paths, thickness, opacity } = annotation; + if (!color) { + return null; + } const appearanceBuffer = [ `${thickness} w 1 J 1 j`, @@ -4586,8 +4572,8 @@ class InkAnnotation extends MarkupAnnotation { const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Length", appearance.length); @@ -4596,7 +4582,7 @@ class InkAnnotation extends MarkupAnnotation { const extGState = new Dict(xref); const r0 = new Dict(xref); r0.set("CA", opacity); - r0.set("Type", Name.get("ExtGState")); + r0.setIfName("Type", "ExtGState"); extGState.set("R0", r0); resources.set("ExtGState", extGState); appearanceStreamDict.set("Resources", resources); @@ -4615,6 +4601,9 @@ class InkAnnotation extends MarkupAnnotation { outlines: { outline }, opacity, } = annotation; + if (!color) { + return null; + } const appearanceBuffer = [ `${getPdfColor(color, /* isFill */ true)}`, "/R0 gs", @@ -4642,8 +4631,8 @@ class InkAnnotation extends MarkupAnnotation { const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Length", appearance.length); @@ -4653,11 +4642,11 @@ class InkAnnotation extends MarkupAnnotation { appearanceStreamDict.set("Resources", resources); const r0 = new Dict(xref); extGState.set("R0", r0); - r0.set("BM", Name.get("Multiply")); + r0.setIfName("BM", "Multiply"); if (opacity !== 1) { r0.set("ca", opacity); - r0.set("Type", Name.get("ExtGState")); + r0.setIfName("Type", "ExtGState"); } const ap = new StringStream(appearance); @@ -4691,7 +4680,7 @@ class HighlightAnnotation extends MarkupAnnotation { warn("HighlightAnnotation - ignoring built-in appearance stream."); } // 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"); this._setDefaultAppearance({ @@ -4723,29 +4712,19 @@ class HighlightAnnotation extends MarkupAnnotation { static createNewDict(annotation, xref, { apRef, ap }) { const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } = annotation; + const date = `D:${getModificationDate()}`; const highlight = oldAnnotation || new Dict(xref); - highlight.set("Type", Name.get("Annot")); - highlight.set("Subtype", Name.get("Highlight")); - highlight.set( - oldAnnotation ? "M" : "CreationDate", - `D:${getModificationDate()}` - ); - highlight.set("CreationDate", `D:${getModificationDate()}`); - highlight.set("Rect", rect); - highlight.set("F", 4); - highlight.set("Border", [0, 0, 0]); - highlight.set("Rotate", rotation); - highlight.set("QuadPoints", quadPoints); - - // Color. - highlight.set("C", getPdfColorArray(color)); - - // Opacity. - highlight.set("CA", opacity); - - if (user) { - highlight.set("T", stringToAsciiOrUTF16BE(user)); - } + highlight.setIfNotExists("Type", Name.get("Annot")); + highlight.setIfNotExists("Subtype", Name.get("Highlight")); + highlight.set(oldAnnotation ? "M" : "CreationDate", date); + highlight.setIfArray("Rect", rect); + highlight.setIfNotExists("F", 4); + highlight.setIfNotExists("Border", [0, 0, 0]); + highlight.setIfNumber("Rotate", rotation); + highlight.setIfArray("QuadPoints", quadPoints); + highlight.setIfArray("C", getPdfColorArray(color)); + highlight.setIfNumber("CA", opacity); + highlight.setIfDefined("T", stringToAsciiOrUTF16BE(user)); if (apRef || ap) { const n = new Dict(xref); @@ -4758,6 +4737,9 @@ class HighlightAnnotation extends MarkupAnnotation { static async createNewAppearanceStream(annotation, xref, params) { const { color, rect, outlines, opacity } = annotation; + if (!color) { + return null; + } const appearanceBuffer = [ `${getPdfColor(color, /* isFill */ true)}`, @@ -4783,8 +4765,8 @@ class HighlightAnnotation extends MarkupAnnotation { const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Length", appearance.length); @@ -4794,11 +4776,11 @@ class HighlightAnnotation extends MarkupAnnotation { appearanceStreamDict.set("Resources", resources); const r0 = new Dict(xref); extGState.set("R0", r0); - r0.set("BM", Name.get("Multiply")); + r0.setIfName("BM", "Multiply"); if (opacity !== 1) { r0.set("ca", opacity); - r0.set("Type", Name.get("ExtGState")); + r0.setIfName("Type", "ExtGState"); } const ap = new StringStream(appearance); @@ -4819,9 +4801,7 @@ class UnderlineAnnotation extends MarkupAnnotation { if (quadPoints) { if (!this.appearance) { // Default 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"); // The values 0.571 and 1.3 below corresponds to what Acrobat is doing. @@ -4861,9 +4841,7 @@ class SquigglyAnnotation extends MarkupAnnotation { if (quadPoints) { if (!this.appearance) { // Default 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"); this._setDefaultAppearance({ @@ -4909,9 +4887,7 @@ class StrikeOutAnnotation extends MarkupAnnotation { if (quadPoints) { if (!this.appearance) { // Default 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"); this._setDefaultAppearance({ @@ -5010,8 +4986,8 @@ class StampAnnotation extends MarkupAnnotation { image.set("Type", xobjectName); image.set("Subtype", imageName); image.set("BitsPerComponent", 8); - image.set("ColorSpace", Name.get("DeviceRGB")); - image.set("Filter", Name.get("DCTDecode")); + image.setIfName("ColorSpace", "DeviceRGB"); + image.setIfName("Filter", "DCTDecode"); image.set("BBox", [0, 0, width, height]); image.set("Width", width); image.set("Height", height); @@ -5033,7 +5009,7 @@ class StampAnnotation extends MarkupAnnotation { smask.set("Type", xobjectName); smask.set("Subtype", imageName); smask.set("BitsPerComponent", 8); - smask.set("ColorSpace", Name.get("DeviceGray")); + smask.setIfName("ColorSpace", "DeviceGray"); smask.set("Width", width); smask.set("Height", height); @@ -5051,31 +5027,21 @@ class StampAnnotation extends MarkupAnnotation { static createNewDict(annotation, xref, { apRef, ap }) { const { oldAnnotation, rect, rotation, user } = annotation; + const date = `D:${getModificationDate(annotation.date)}`; const stamp = oldAnnotation || new Dict(xref); - stamp.set("Type", Name.get("Annot")); - stamp.set("Subtype", Name.get("Stamp")); - stamp.set( - oldAnnotation ? "M" : "CreationDate", - `D:${getModificationDate()}` - ); - stamp.set("Rect", rect); - stamp.set("F", 4); - stamp.set("Border", [0, 0, 0]); - stamp.set("Rotate", rotation); - - if (user) { - stamp.set("T", stringToAsciiOrUTF16BE(user)); - } + stamp.setIfNotExists("Type", Name.get("Annot")); + stamp.setIfNotExists("Subtype", Name.get("Stamp")); + stamp.set(oldAnnotation ? "M" : "CreationDate", date); + stamp.setIfArray("Rect", rect); + stamp.setIfNotExists("F", 4); + stamp.setIfNotExists("Border", [0, 0, 0]); + stamp.setIfNumber("Rotate", rotation); + stamp.setIfDefined("T", stringToAsciiOrUTF16BE(user)); if (apRef || ap) { const n = new Dict(xref); stamp.set("AP", n); - - if (apRef) { - n.set("N", apRef); - } else { - n.set("N", ap); - } + n.set("N", apRef || ap); } return stamp; @@ -5083,6 +5049,9 @@ class StampAnnotation extends MarkupAnnotation { static async #createNewAppearanceStreamForDrawing(annotation, xref) { const { areContours, color, rect, lines, thickness } = annotation; + if (!color) { + return null; + } const appearanceBuffer = [ `${thickness} w 1 J 1 j`, @@ -5117,8 +5086,8 @@ class StampAnnotation extends MarkupAnnotation { const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Length", appearance.length); @@ -5147,8 +5116,8 @@ class StampAnnotation extends MarkupAnnotation { const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", [0, 0, width, height]); appearanceStreamDict.set("Resources", resources); diff --git a/src/core/core_utils.js b/src/core/core_utils.js index b91cb375c..c5f3ed987 100644 --- a/src/core/core_utils.js +++ b/src/core/core_utils.js @@ -679,12 +679,19 @@ function getNewAnnotationsMap(annotationStorage) { return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null; } +// If the string is null or undefined then it is returned as is. function stringToAsciiOrUTF16BE(str) { + if (str === null || str === undefined) { + return str; + } return isAscii(str) ? str : stringToUTF16String(str, /* bigEndian = */ true); } function isAscii(str) { - return /^[\x00-\x7F]*$/.test(str); + if (typeof str !== "string") { + return false; + } + return !str || /^[\x00-\x7F]*$/.test(str); } function stringToUTF16HexString(str) { diff --git a/src/core/default_appearance.js b/src/core/default_appearance.js index b05ac5065..ad49f187e 100644 --- a/src/core/default_appearance.js +++ b/src/core/default_appearance.js @@ -267,11 +267,11 @@ class FakeUnicodeFont { get fontDescriptorRef() { if (!FakeUnicodeFont._fontDescriptorRef) { const fontDescriptor = new Dict(this.xref); - fontDescriptor.set("Type", Name.get("FontDescriptor")); + fontDescriptor.setIfName("Type", "FontDescriptor"); fontDescriptor.set("FontName", this.fontName); fontDescriptor.set("FontFamily", "MyriadPro Regular"); fontDescriptor.set("FontBBox", [0, 0, 0, 0]); - fontDescriptor.set("FontStretch", Name.get("Normal")); + fontDescriptor.setIfName("FontStretch", "Normal"); fontDescriptor.set("FontWeight", 400); fontDescriptor.set("ItalicAngle", 0); @@ -285,9 +285,9 @@ class FakeUnicodeFont { get descendantFontRef() { const descendantFont = new Dict(this.xref); descendantFont.set("BaseFont", this.fontName); - descendantFont.set("Type", Name.get("Font")); - descendantFont.set("Subtype", Name.get("CIDFontType0")); - descendantFont.set("CIDToGIDMap", Name.get("Identity")); + descendantFont.setIfName("Type", "Font"); + descendantFont.setIfName("Subtype", "CIDFontType0"); + descendantFont.setIfName("CIDToGIDMap", "Identity"); descendantFont.set("FirstChar", this.firstChar); descendantFont.set("LastChar", this.lastChar); descendantFont.set("FontDescriptor", this.fontDescriptorRef); @@ -330,11 +330,11 @@ class FakeUnicodeFont { get baseFontRef() { const baseFont = new Dict(this.xref); baseFont.set("BaseFont", this.fontName); - baseFont.set("Type", Name.get("Font")); - baseFont.set("Subtype", Name.get("Type0")); - baseFont.set("Encoding", Name.get("Identity-H")); + baseFont.setIfName("Type", "Font"); + baseFont.setIfName("Subtype", "Type0"); + baseFont.setIfName("Encoding", "Identity-H"); baseFont.set("DescendantFonts", [this.descendantFontRef]); - baseFont.set("ToUnicode", Name.get("Identity-H")); + baseFont.setIfName("ToUnicode", "Identity-H"); return this.xref.getNewPersistentRef(baseFont); } @@ -463,7 +463,7 @@ class FakeUnicodeFont { const r0 = new Dict(this.xref); r0.set("ca", strokeAlpha); r0.set("CA", strokeAlpha); - r0.set("Type", Name.get("ExtGState")); + r0.setIfName("Type", "ExtGState"); extGState.set("R0", r0); resources.set("ExtGState", extGState); } @@ -476,8 +476,8 @@ class FakeUnicodeFont { const appearance = buffer.join("\n"); const appearanceStreamDict = new Dict(this.xref); - appearanceStreamDict.set("Subtype", Name.get("Form")); - appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.setIfName("Subtype", "Form"); + appearanceStreamDict.setIfName("Type", "XObject"); appearanceStreamDict.set("BBox", [0, 0, w, h]); appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Resources", resources); diff --git a/src/core/primitives.js b/src/core/primitives.js index c48a34eb7..aa79c24c3 100644 --- a/src/core/primitives.js +++ b/src/core/primitives.js @@ -199,6 +199,38 @@ class Dict { 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) { return this._map.has(key); } diff --git a/src/core/writer.js b/src/core/writer.js index 40a06483f..bf66226a2 100644 --- a/src/core/writer.js +++ b/src/core/writer.js @@ -271,7 +271,7 @@ function updateXFA({ xfaData, xfaDatasetsRef, changes, xref }) { } const xfaDataStream = new StringStream(xfaData); xfaDataStream.dict = new Dict(xref); - xfaDataStream.dict.set("Type", Name.get("EmbeddedFile")); + xfaDataStream.dict.setIfName("Type", "EmbeddedFile"); changes.put(xfaDatasetsRef, { data: xfaDataStream, @@ -382,7 +382,7 @@ function getTrailerDict(xrefInfo, changes, useXrefStream) { if (useXrefStream) { changes.put(refForXrefTable, { data: "" }); newXref.set("Size", refForXrefTable.num + 1); - newXref.set("Type", Name.get("XRef")); + newXref.setIfName("Type", "XRef"); } else { newXref.set("Size", refForXrefTable.num); } diff --git a/test/unit/core_utils_spec.js b/test/unit/core_utils_spec.js index 2e0fd8fa9..402796de5 100644 --- a/test/unit/core_utils_spec.js +++ b/test/unit/core_utils_spec.js @@ -431,6 +431,10 @@ describe("core_utils", function () { expect(isAscii("hello world in Japanese is こんにちは世界の")).toEqual( false ); + expect(isAscii("")).toEqual(true); + expect(isAscii(123)).toEqual(false); + expect(isAscii(null)).toEqual(false); + expect(isAscii(undefined)).toEqual(false); }); }); diff --git a/test/unit/primitives_spec.js b/test/unit/primitives_spec.js index 00c491004..04c92009b 100644 --- a/test/unit/primitives_spec.js +++ b/test/unit/primitives_spec.js @@ -380,6 +380,50 @@ describe("primitives", function () { "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 () {