diff --git a/src/core/annotation.js b/src/core/annotation.js index 5fe3d8654..09a178e0c 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -199,8 +199,13 @@ function getQuadPoints(dict, rect) { const y = quadPoints[j + 1]; // The quadpoints should be ignored if any coordinate in the array - // lies outside the region specified by the rectangle. - if (x < rect[0] || x > rect[2] || y < rect[1] || y > rect[3]) { + // lies outside the region specified by the rectangle. The rectangle + // can be `null` for markup annotations since their rectangle may be + // incorrect (fixes bug 1538111). + if ( + rect !== null && + (x < rect[0] || x > rect[2] || y < rect[1] || y > rect[3]) + ) { return null; } quadPointsLists[i].push({ x, y }); @@ -791,6 +796,68 @@ class MarkupAnnotation extends Annotation { setCreationDate(creationDate) { this.creationDate = isString(creationDate) ? creationDate : null; } + + _setDefaultAppearance({ + xref, + extra, + strokeColor, + fillColor, + blendMode, + pointsCallback, + }) { + let minX = Number.MAX_VALUE; + let minY = Number.MAX_VALUE; + let maxX = Number.MIN_VALUE; + let maxY = Number.MIN_VALUE; + + const buffer = ["q"]; + if (extra) { + buffer.push(extra); + } + if (strokeColor) { + buffer.push(`${strokeColor[0]} ${strokeColor[1]} ${strokeColor[2]} RG`); + } + if (fillColor) { + buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`); + } + + for (const points of this.data.quadPoints) { + const [mX, MX, mY, MY] = pointsCallback(buffer, points); + minX = Math.min(minX, mX); + maxX = Math.max(maxX, MX); + minY = Math.min(minY, mY); + maxY = Math.max(maxY, MY); + } + buffer.push("Q"); + + const formDict = new Dict(xref); + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("Subtype", Name.get("Form")); + + const appearanceStream = new StringStream(buffer.join(" ")); + appearanceStream.dict = appearanceStreamDict; + formDict.set("Fm0", appearanceStream); + + const gsDict = new Dict(xref); + if (blendMode) { + gsDict.set("BM", Name.get(blendMode)); + } + + const stateDict = new Dict(xref); + stateDict.set("GS0", gsDict); + + const resources = new Dict(xref); + resources.set("ExtGState", stateDict); + resources.set("XObject", formDict); + + const appearanceDict = new Dict(xref); + appearanceDict.set("Resources", resources); + const bbox = (this.data.rect = [minX, minY, maxX, maxY]); + appearanceDict.set("BBox", bbox); + + this.appearance = new StringStream("/GS0 gs /Fm0 Do"); + this.appearance.dict = appearanceDict; + } } class WidgetAnnotation extends Annotation { @@ -1854,9 +1921,28 @@ class HighlightAnnotation extends MarkupAnnotation { this.data.annotationType = AnnotationType.HIGHLIGHT; - const quadPoints = getQuadPoints(parameters.dict, this.rectangle); + const quadPoints = getQuadPoints(parameters.dict, null); if (quadPoints) { this.data.quadPoints = quadPoints; + if (!this.appearance) { + // Default color is yellow in Acrobat Reader + const fillColor = this.color + ? Array.from(this.color).map(c => c / 255) + : [1, 1, 0]; + this._setDefaultAppearance({ + xref: parameters.xref, + fillColor, + blendMode: "Multiply", + pointsCallback: (buffer, points) => { + buffer.push(`${points[0].x} ${points[0].y} m`); + buffer.push(`${points[1].x} ${points[1].y} l`); + buffer.push(`${points[3].x} ${points[3].y} l`); + buffer.push(`${points[2].x} ${points[2].y} l`); + buffer.push("f"); + return [points[0].x, points[1].x, points[3].y, points[1].y]; + }, + }); + } } } } @@ -1867,9 +1953,26 @@ class UnderlineAnnotation extends MarkupAnnotation { this.data.annotationType = AnnotationType.UNDERLINE; - const quadPoints = getQuadPoints(parameters.dict, this.rectangle); + const quadPoints = getQuadPoints(parameters.dict, null); if (quadPoints) { this.data.quadPoints = quadPoints; + if (!this.appearance) { + // Default color is black + const strokeColor = this.color + ? Array.from(this.color).map(c => c / 255) + : [0, 0, 0]; + this._setDefaultAppearance({ + xref: parameters.xref, + extra: "[] 0 d 1 w", + strokeColor, + pointsCallback: (buffer, points) => { + buffer.push(`${points[2].x} ${points[2].y} m`); + buffer.push(`${points[3].x} ${points[3].y} l`); + buffer.push("S"); + return [points[0].x, points[1].x, points[3].y, points[1].y]; + }, + }); + } } } } @@ -1880,9 +1983,35 @@ class SquigglyAnnotation extends MarkupAnnotation { this.data.annotationType = AnnotationType.SQUIGGLY; - const quadPoints = getQuadPoints(parameters.dict, this.rectangle); + const quadPoints = getQuadPoints(parameters.dict, null); if (quadPoints) { this.data.quadPoints = quadPoints; + if (!this.appearance) { + // Default color is black + const strokeColor = this.color + ? Array.from(this.color).map(c => c / 255) + : [0, 0, 0]; + this._setDefaultAppearance({ + xref: parameters.xref, + extra: "[] 0 d 1 w", + strokeColor, + pointsCallback: (buffer, points) => { + const dy = (points[0].y - points[2].y) / 6; + let shift = dy; + let x = points[2].x; + const y = points[2].y; + const xEnd = points[3].x; + buffer.push(`${x} ${y + shift} m`); + do { + x += 2; + shift = shift === 0 ? dy : 0; + buffer.push(`${x} ${y + shift} l`); + } while (x < xEnd); + buffer.push("S"); + return [points[2].x, xEnd, y - 2 * dy, y + 2 * dy]; + }, + }); + } } } } @@ -1893,9 +2022,32 @@ class StrikeOutAnnotation extends MarkupAnnotation { this.data.annotationType = AnnotationType.STRIKEOUT; - const quadPoints = getQuadPoints(parameters.dict, this.rectangle); + const quadPoints = getQuadPoints(parameters.dict, null); if (quadPoints) { this.data.quadPoints = quadPoints; + if (!this.appearance) { + // Default color is black + const strokeColor = this.color + ? Array.from(this.color).map(c => c / 255) + : [0, 0, 0]; + this._setDefaultAppearance({ + xref: parameters.xref, + extra: "[] 0 d 1 w", + strokeColor, + pointsCallback: (buffer, points) => { + buffer.push( + `${(points[0].x + points[2].x) / 2}` + + ` ${(points[0].y + points[2].y) / 2} m` + ); + buffer.push( + `${(points[1].x + points[3].x) / 2}` + + ` ${(points[1].y + points[3].y) / 2} l` + ); + buffer.push("S"); + return [points[0].x, points[1].x, points[3].y, points[1].y]; + }, + }); + } } } } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 3da0e6cab..3172b863a 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -113,6 +113,7 @@ !bug1245391_reduced.pdf !bug1252420.pdf !bug1513120_reduced.pdf +!bug1538111.pdf !bug1552113.pdf !issue9949.pdf !bug1308536.pdf @@ -359,10 +360,14 @@ !issue9278.pdf !annotation-text-without-popup.pdf !annotation-underline.pdf +!annotation-underline-without-appearance.pdf !issue269_2.pdf !annotation-strikeout.pdf +!annotation-strikeout-without-appearance.pdf !annotation-squiggly.pdf +!annotation-squiggly-without-appearance.pdf !annotation-highlight.pdf +!annotation-highlight-without-appearance.pdf !annotation-freetext.pdf !annotation-line.pdf !annotation-square-circle.pdf diff --git a/test/pdfs/annotation-highlight-without-appearance.pdf b/test/pdfs/annotation-highlight-without-appearance.pdf new file mode 100644 index 000000000..007493362 Binary files /dev/null and b/test/pdfs/annotation-highlight-without-appearance.pdf differ diff --git a/test/pdfs/annotation-squiggly-without-appearance.pdf b/test/pdfs/annotation-squiggly-without-appearance.pdf new file mode 100644 index 000000000..203efebb1 Binary files /dev/null and b/test/pdfs/annotation-squiggly-without-appearance.pdf differ diff --git a/test/pdfs/annotation-strikeout-without-appearance.pdf b/test/pdfs/annotation-strikeout-without-appearance.pdf new file mode 100644 index 000000000..b8c8cab3c Binary files /dev/null and b/test/pdfs/annotation-strikeout-without-appearance.pdf differ diff --git a/test/pdfs/annotation-underline-without-appearance.pdf b/test/pdfs/annotation-underline-without-appearance.pdf new file mode 100644 index 000000000..bb7526dc5 Binary files /dev/null and b/test/pdfs/annotation-underline-without-appearance.pdf differ diff --git a/test/pdfs/bug1538111.pdf b/test/pdfs/bug1538111.pdf new file mode 100644 index 000000000..deac036e1 --- /dev/null +++ b/test/pdfs/bug1538111.pdf @@ -0,0 +1,108 @@ +%PDF-1.7 +% ò¤ô + +1 0 obj << + /Type /Catalog + /Pages 2 0 R +>> +endobj + +2 0 obj << + /Type /Pages + /Count 1 + /Kids [ 3 0 R ] +>> +endobj + +3 0 obj << + /Type /Page + /Parent 2 0 R + /MediaBox [0 0 595 842] + /Annots [ + 4 0 R + 5 0 R + 6 0 R + 7 0 R + ] + /Tabs /R +>> +endobj + +4 0 obj << + /Type /Annot + /Subtype /Highlight + /Rect [ 108 602 506 640 ] + /NM (Hilight-1) + /F 4 + /QuadPoints [ + 107.7896 639.9486 505.8939 639.9486 107.7896 629.1634 505.8939 629.1634 + 107.7896 626.2871 505.8939 626.2871 107.7896 615.5011 505.8939 615.5011 + 107.7896 612.6248 380.1389 612.6248 107.7896 601.8397 380.1389 601.8397 + ] + /C [ 1 1 0 ] + /Contents () +>> +endobj + +5 0 obj << + /Type /Annot + /Subtype /Underline + /Rect [ 108 552 506 590 ] + /NM (Underline-1) + /F 4 + /QuadPoints [ + 107.7896 589.9486 505.8939 589.9486 107.7896 579.1634 505.8939 579.1634 + 107.7896 576.2871 505.8939 576.2871 107.7896 565.5011 505.8939 565.5011 + 107.7896 562.6248 380.1389 562.6248 107.7896 551.8397 380.1389 551.8397 + ] + /C [ 0 0 0 ] + /Contents () +>> +endobj + +6 0 obj << + /Type /Annot + /Subtype /Squiggly + /Rect [ 108 502 506 540 ] + /NM (Squiggly-1) + /F 4 + /QuadPoints [ + 107.7896 539.9486 505.8939 539.9486 107.7896 529.1634 505.8939 529.1634 + 107.7896 526.2871 505.8939 526.2871 107.7896 515.5011 505.8939 515.5011 + 107.7896 512.6248 380.1389 512.6248 107.7896 501.8397 380.1389 501.8397 + ] + /C [ 0 0 0 ] + /Contents () +>> +endobj + +7 0 obj << + /Type /Annot + /Subtype /StrikeOut + /Rect [ 108 452 506 490 ] + /NM (StrikeOut-1) + /F 4 + /QuadPoints [ + 107.7896 489.9486 505.8939 489.9486 107.7896 479.1634 505.8939 479.1634 + 107.7896 476.2871 505.8939 476.2871 107.7896 465.5011 505.8939 465.5011 + 107.7896 462.6248 380.1389 462.6248 107.7896 451.8397 380.1389 451.8397 + ] + /C [ 0 0 0 ] + /Contents () +>> +endobj + +xref +0 8 +0000000000 65535 f +0000000016 00000 n +0000000070 00000 n +0000000136 00000 n +0000000281 00000 n +0000000671 00000 n +0000001063 00000 n +0000001453 00000 n +trailer<< /Root 1 0 R /Size 8 >> +startxref +1845 +%%EOF diff --git a/test/test_manifest.json b/test/test_manifest.json index 273378359..a2b1d8752 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -764,6 +764,13 @@ "annotations": true, "about": "Annotation with (unsupported) file:// URL." }, + { "id": "bug1538111", + "file": "pdfs/bug1538111.pdf", + "md5": "3f3635cfc25d132fb1054042e520e297", + "rounds": 1, + "annotations": true, + "type": "eq" + }, { "id": "bug1552113", "file": "pdfs/bug1552113.pdf", "md5": "dafb7ba1328e8deaab2e3619c94bf974", @@ -4378,6 +4385,13 @@ "type": "eq", "annotations": true }, + { "id": "annotation-underline-without-appearance", + "file": "pdfs/annotation-underline-without-appearance.pdf", + "md5": "dd5be5e9a8e6bdbf67c175ca170f7cb7", + "rounds": 1, + "annotations": true, + "type": "eq" + }, { "id": "annotation-strikeout", "file": "pdfs/annotation-strikeout.pdf", "md5": "6624e6b5bedd2f2855b6ab12bbf93c57", @@ -4385,6 +4399,13 @@ "type": "eq", "annotations": true }, + { "id": "annotation-strikeout-without-appearance", + "file": "pdfs/annotation-strikeout-without-appearance.pdf", + "md5": "1dc751ab83e8deb3094bfc580289b097", + "rounds": 1, + "annotations": true, + "type": "eq" + }, { "id": "annotation-squiggly", "file": "pdfs/annotation-squiggly.pdf", "md5": "38661e731ac6c525af5894d2d20c6e71", @@ -4392,6 +4413,13 @@ "type": "eq", "annotations": true }, + { "id": "annotation-squiggly-without-appearance", + "file": "pdfs/annotation-squiggly-without-appearance.pdf", + "md5": "6546f22a06a5e51d0e835c677cdbc705", + "rounds": 1, + "annotations": true, + "type": "eq" + }, { "id": "annotation-highlight", "file": "pdfs/annotation-highlight.pdf", "md5": "e13e198e3a69c32dc9ebdc704d3105e1", @@ -4399,6 +4427,13 @@ "type": "eq", "annotations": true }, + { "id": "annotation-highlight-without-appearance", + "file": "pdfs/annotation-highlight-without-appearance.pdf", + "md5": "a1f2811324fa1ff0c9f1778697413dad", + "rounds": 1, + "annotations": true, + "type": "eq" + }, { "id": "annotation-freetext", "file": "pdfs/annotation-freetext.pdf", "md5": "6ca19ce632ead3aed08f22e588510e2f",