diff --git a/src/core/pattern.js b/src/core/pattern.js index 3c49df32c..ec9b42605 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -157,10 +157,9 @@ class RadialAxialShading extends BaseShading { const fnObj = dict.getRaw("Function"); const fn = pdfFunctionFactory.createFromArray(fnObj); - // 10 samples seems good enough for now, but probably won't work - // if there are sharp color changes. Ideally, we would implement - // the spec faithfully and add lossless optimizations. - const NUMBER_OF_SAMPLES = 10; + // Use lcm(1,2,3,4,5,6,7,8,10) = 840 (including 9 increases this to 2520) + // to catch evenly spaced stops. oeis.org/A003418 + const NUMBER_OF_SAMPLES = 840; const step = (t1 - t0) / NUMBER_OF_SAMPLES; const colorStops = (this.colorStops = []); @@ -176,13 +175,80 @@ class RadialAxialShading extends BaseShading { const color = new Float32Array(cs.numComps), ratio = new Float32Array(1); let rgbColor; - for (let i = 0; i <= NUMBER_OF_SAMPLES; i++) { + + let iBase = 0; + ratio[0] = t0; + fn(ratio, 0, color, 0); + let rgbBase = cs.getRgb(color, 0); + const cssColorBase = Util.makeHexColor(rgbBase[0], rgbBase[1], rgbBase[2]); + colorStops.push([0, cssColorBase]); + + let iPrev = 1; + ratio[0] = t0 + step; + fn(ratio, 0, color, 0); + let rgbPrev = cs.getRgb(color, 0); + + // Slopes are rise / run. + // A max slope is from the least value the base component could have been + // to the greatest value the current component could have been. + // A min slope is from the greatest value the base component could have been + // to the least value the current component could have been. + // Each component could have been rounded up to .5 from its original value + // so the conservative deltas are +-1 (+-.5 for base and -+.5 for current). + + // The run is iPrev - iBase = 1, so omitted. + let maxSlopeR = rgbPrev[0] - rgbBase[0] + 1; + let maxSlopeG = rgbPrev[1] - rgbBase[1] + 1; + let maxSlopeB = rgbPrev[2] - rgbBase[2] + 1; + let minSlopeR = rgbPrev[0] - rgbBase[0] - 1; + let minSlopeG = rgbPrev[1] - rgbBase[1] - 1; + let minSlopeB = rgbPrev[2] - rgbBase[2] - 1; + + for (let i = 2; i < NUMBER_OF_SAMPLES; i++) { ratio[0] = t0 + i * step; fn(ratio, 0, color, 0); rgbColor = cs.getRgb(color, 0); - const cssColor = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]); - colorStops.push([i / NUMBER_OF_SAMPLES, cssColor]); + + // Keep going if the maximum minimum slope <= the minimum maximum slope. + // Otherwise add a rgbPrev color stop and make it the new base. + + const run = i - iBase; + maxSlopeR = Math.min(maxSlopeR, (rgbColor[0] - rgbBase[0] + 1) / run); + maxSlopeG = Math.min(maxSlopeG, (rgbColor[1] - rgbBase[1] + 1) / run); + maxSlopeB = Math.min(maxSlopeB, (rgbColor[2] - rgbBase[2] + 1) / run); + minSlopeR = Math.max(minSlopeR, (rgbColor[0] - rgbBase[0] - 1) / run); + minSlopeG = Math.max(minSlopeG, (rgbColor[1] - rgbBase[1] - 1) / run); + minSlopeB = Math.max(minSlopeB, (rgbColor[2] - rgbBase[2] - 1) / run); + + const slopesExist = + minSlopeR <= maxSlopeR && + minSlopeG <= maxSlopeG && + minSlopeB <= maxSlopeB; + + if (!slopesExist) { + const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]); + colorStops.push([iPrev / NUMBER_OF_SAMPLES, cssColor]); + + // TODO: When fn frequency is high (iPrev - iBase === 1 twice in a row), + // send the color space and function to do the sampling display side. + + // The run is i - iPrev = 1, so omitted. + maxSlopeR = rgbColor[0] - rgbPrev[0] + 1; + maxSlopeG = rgbColor[1] - rgbPrev[1] + 1; + maxSlopeB = rgbColor[2] - rgbPrev[2] + 1; + minSlopeR = rgbColor[0] - rgbPrev[0] - 1; + minSlopeG = rgbColor[1] - rgbPrev[1] - 1; + minSlopeB = rgbColor[2] - rgbPrev[2] - 1; + + iBase = iPrev; + rgbBase = rgbPrev; + } + + iPrev = i; + rgbPrev = rgbColor; } + const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]); + colorStops.push([1, cssColor]); let background = "transparent"; if (dict.has("Background")) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index c59d30dbf..e4d9b49b2 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -345,6 +345,7 @@ !issue12810.pdf !bug866395.pdf !issue12010_reduced.pdf +!issue10572.pdf !issue11718_reduced.pdf !bug1027533.pdf !bug1028735.pdf @@ -534,6 +535,7 @@ !issue15012.pdf !issue15150.pdf !poppler-395-0-fuzzed.pdf +!issue14165.pdf !GHOSTSCRIPT-698804-1-fuzzed.pdf !issue14814.pdf !poppler-91414-0-53.pdf diff --git a/test/pdfs/issue10572.pdf b/test/pdfs/issue10572.pdf new file mode 100644 index 000000000..5ff34d707 --- /dev/null +++ b/test/pdfs/issue10572.pdf @@ -0,0 +1,374 @@ +%PDF-1.5 +% +%QDF-1.0 + +%% Original object ID: 20 0 +1 0 obj +<< + /Pages 3 0 R + /Type /Catalog +>> +endobj + +%% Original object ID: 19 0 +2 0 obj +<< + /Creator (cairo 1.9.5 \(http://cairographics.org\)) + /Producer (cairo 1.9.5 \(http://cairographics.org\)) +>> +endobj + +%% Original object ID: 1 0 +3 0 obj +<< + /Count 1 + /Kids [ + 4 0 R + ] + /Type /Pages +>> +endobj + +%% Page 1 +%% Original object ID: 7 0 +4 0 obj +<< + /Contents 5 0 R + /Group << + /CS /DeviceRGB + /S /Transparency + /Type /Group + >> + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 3 0 R + /Resources 7 0 R + /Type /Page +>> +endobj + +%% Contents for page 1 +%% Original object ID: 3 0 +5 0 obj +<< + /Length 6 0 R +>> +stream +q +Q q +54 738 504 -661.699 re W n +1 1 1 rg /a0 gs +54 738 504 -661.699 re f +Q q +61 716 225 -450 re W n +q /Pattern cs /p5 scn /a0 gs +61 716 225 -450 re f +Q +Q q +54 738 504 -661.699 re W n +0 0 0 RG /a0 gs +1 w +0 J +0 j +[] 0.0 d +10 M 60.5 716.5 226 -451 re S +56 736 424 -23 re W n +Q q +Q +endstream +endobj + +6 0 obj +279 +endobj + +%% Original object ID: 2 0 +7 0 obj +<< + /ExtGState << + /a0 << + /CA 1 + /ca 1 + >> + >> + /Pattern << + /p5 9 0 R + >> +>> +endobj + +%% Original object ID: 6 0 +8 0 obj +<< +>> +endobj + +%% Original object ID: 5 0 +9 0 obj +<< + /Matrix [ + 1 + 0 + 0 + -1 + 61 + 716 + ] + /PatternType 2 + /Shading << + /ColorSpace /DeviceRGB + /Coords [ + 112.5 + -900 + 112.5 + 900 + ] + /Domain [ + -6 + 6 + ] + /Extend [ + false + false + ] + /Function 13 0 R + /ShadingType 2 + >> + /Type /Pattern +>> +endobj + +%% Original object ID: 18 0 +10 0 obj +<< +>> +endobj + +%% Original object ID: 15 0 +11 0 obj +<< + /Length 12 0 R +>> +stream +endstream +endobj + +12 0 obj +0 +endobj + +%% Original object ID: 12 0 +13 0 obj +<< + /Bounds [ + -5 + -4 + -3 + -2 + -1 + 0 + 1 + 2 + 3 + 4 + 5 + ] + /Domain [ + -6 + 6 + ] + /Encode [ + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + 0 + 1 + ] + /FunctionType 3 + /Functions [ + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + 15 0 R + ] +>> +endobj + +%% Original object ID: 17 0 +14 0 obj +<< +>> +endobj + +%% Original object ID: 11 0 +15 0 obj +<< + /Bounds [ + 0.5 + 0.5 + ] + /Domain [ + 0 + 1 + ] + /Encode [ + 0 + 1 + 0 + 1 + 0 + 1 + ] + /FunctionType 3 + /Functions [ + 18 0 R + 19 0 R + 20 0 R + ] +>> +endobj + +%% Original object ID: 13 0 +16 0 obj +<< + /Length1 9948 + /Length 17 0 R +>> +stream +endstream +endobj + +%QDF: ignore_newline +17 0 obj +0 +endobj + +%% Original object ID: 8 0 +18 0 obj +<< + /C0 [ + 0 + 1 + 0 + ] + /C1 [ + 0 + 1 + 0 + ] + /Domain [ + 0 + 1 + ] + /FunctionType 2 + /N 1 +>> +endobj + +%% Original object ID: 9 0 +19 0 obj +<< + /C0 [ + 0 + 1 + 0 + ] + /C1 [ + 0 + 0 + 1 + ] + /Domain [ + 0 + 1 + ] + /FunctionType 2 + /N 1 +>> +endobj + +%% Original object ID: 10 0 +20 0 obj +<< + /C0 [ + 0 + 0 + 1 + ] + /C1 [ + 0 + 0 + 1 + ] + /Domain [ + 0 + 1 + ] + /FunctionType 2 + /N 1 +>> +endobj + +xref +0 21 +0000000000 65535 f +0000000053 00000 n +0000000135 00000 n +0000000293 00000 n +0000000402 00000 n +0000000661 00000 n +0000000995 00000 n +0000001042 00000 n +0000001187 00000 n +0000001236 00000 n +0000001608 00000 n +0000001659 00000 n +0000001716 00000 n +0000001763 00000 n +0000002259 00000 n +0000002310 00000 n +0000002543 00000 n +0000002637 00000 n +0000002683 00000 n +0000002846 00000 n +0000003010 00000 n +trailer << + /Info 2 0 R + /Root 1 0 R + /Size 21 + /ID [<68de518de1319b4dc1c1bfbdee95b02c><68de518de1319b4dc1c1bfbdee95b02c>] +>> +startxref +3146 +%%EOF diff --git a/test/pdfs/issue14165.pdf b/test/pdfs/issue14165.pdf new file mode 100644 index 000000000..9d963ee44 --- /dev/null +++ b/test/pdfs/issue14165.pdf @@ -0,0 +1,336 @@ +%PDF-1.5 +% +%QDF-1.0 + +%% Original object ID: 1 0 +1 0 obj +<< + /Lang (de-DE) + /MarkInfo << + /Marked true + >> + /Pages 3 0 R + /StructTreeRoot 5 0 R + /Type /Catalog +>> +endobj + +%% Original object ID: 11 0 +2 0 obj +<< + /Author (Kinski, Andreas) + /CreationDate (D:20210930092306+02'00') + /Creator + /ModDate (D:20210930092306+02'00') + /Producer +>> +endobj + +%% Original object ID: 2 0 +3 0 obj +<< + /Count 1 + /Kids [ + 13 0 R + ] + /Type /Pages +>> +endobj + +4 0 obj +<< + /Type /ObjStm + /Length 1320 + /N 8 + /First 110 +>> +stream +5 0 +6 171 +7 531 +8 628 +9 779 +10 908 +11 999 +12 1135 +%% Object stream: object 5, index 0; original object ID: 12 +<< + /K [ + 8 0 R + ] + /ParentTree 7 0 R + /ParentTreeNextKey 1 + /RoleMap 6 0 R + /Type /StructTreeRoot +>> +%% Object stream: object 6, index 1; original object ID: 13 +<< + /Annotation /Sect + /Artifact /Sect + /Chart /Sect + /Chartsheet /Part + /Diagram /Figure + /Dialogsheet /Part + /Endnote /Note + /Footer /Sect + /Footnote /Note + /Header /Sect + /InlineShape /Sect + /Macrosheet /Part + /Slide /Part + /Textbox /Sect + /Workbook /Document + /Worksheet /Part +>> +%% Object stream: object 7, index 2; original object ID: 14 +<< + /Nums [ + 0 + 10 0 R + ] +>> +%% Object stream: object 8, index 3; original object ID: 15 +<< + /K [ + 9 0 R + 11 0 R + 12 0 R + ] + /P 5 0 R + /S /Part + /Type /StructElem +>> +%% Object stream: object 9, index 4; original object ID: 16 +<< + /K 0 + /P 8 0 R + /Pg 13 0 R + /S /Span + /Type /StructElem +>> +%% Object stream: object 10, index 5; original object ID: 17 +[ + 9 0 R + 11 0 R + 12 0 R +] +%% Object stream: object 11, index 6; original object ID: 19 +<< + /K [ + 1 + ] + /P 8 0 R + /Pg 13 0 R + /S /P + /Type /StructElem +>> +%% Object stream: object 12, index 7; original object ID: 20 +<< + /K [ + 2 + ] + /P 8 0 R + /Pg 13 0 R + /S /P + /Type /StructElem +>> +endstream +endobj + +%% Page 1 +%% Original object ID: 3 0 +13 0 obj +<< + /Contents 14 0 R + /Group << + /CS /DeviceRGB + /S /Transparency + /Type /Group + >> + /MediaBox [ + 0 + 0 + 1190.52 + 841.92 + ] + /Parent 3 0 R + /Resources << + /ExtGState << + /GS10 16 0 R + /GS5 17 0 R + >> + /Pattern << + /P7 19 0 R + >> + /ProcSet [ + /PDF + /Text + /ImageB + /ImageC + /ImageI + ] + >> + /StructParents 0 + /Tabs /S + /Type /Page +>> +endobj + +%% Contents for page 1 +%% Original object ID: 4 0 +14 0 obj +<< + /Length 15 0 R +>> +stream + /P <> BDC q +0 0.000061035 1190.52 841.92 re +W* n +/GS5 gs +/Pattern cs /P7 scn +0 -0.22998 1191.1 842.15 re +f* +Q + EMC /P <> BDC q +0.00001774 0 1190.52 841.92 re +W* n +BT +Q + EMC +endstream +endobj + +%QDF: ignore_newline +15 0 obj +193 +endobj + +%% Original object ID: 10 0 +16 0 obj +<< + /BM /Normal + /CA 1 + /Type /ExtGState +>> +endobj + +%% Original object ID: 5 0 +17 0 obj +<< + /BM /Normal + /Type /ExtGState + /ca 1 +>> +endobj + +%QDF: ignore_newline +18 0 obj +552604 +endobj + +%% Original object ID: 7 0 +19 0 obj +<< + /PatternType 2 + /Shading << + /ColorSpace /DeviceRGB + /Coords [ + 595.56 + 888 + 595.56 + -33.6 + ] + /Extend [ + true + true + ] + /Function 22 0 R + /ShadingType 2 + >> +>> +endobj + +%QDF: ignore_newline +20 0 obj +552604 +endobj + +%QDF: ignore_newline +21 0 obj +552604 +endobj + +%% Original object ID: 6 0 +22 0 obj +<< + /BitsPerSample 8 + /Decode [ + 0 + 1 + 0 + 1 + 0 + 1 + ] + /Domain [ + 0 + 1 + ] + /Encode [ + 0 + 5119 + ] + /FunctionType 0 + /Order 1 + /Range [ + 0 + 1 + 0 + 1 + 0 + 1 + ] + /Size [ + 5120 + ] + /Length 23 0 R +>> +stream +~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}|||||||||{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvvvvvwwwwwwwwwwwwxxxxxxxxxxxxyyyyyyyyyzzzzzzzzz{{{{{{{{{|||||||||}}}}}}}}}~~~~~~ +endstream +endobj + +%QDF: ignore_newline +23 0 obj +15360 +endobj + +%QDF: ignore_newline +24 0 obj +552604 +endobj + +%QDF: ignore_newline +25 0 obj +552604 +endobj + +26 0 obj +<< + /Type /XRef + /Length 108 + /W [ 1 2 1 ] + /Info 2 0 R + /Root 1 0 R + /Size 27 + /ID [<1f73712092d451469489d26e4b30a56f>] +>> +stream +44}$ +   M   0 1 ^ JJK"K: +endstream +endobj + +startxref +19258 +%%EOF diff --git a/test/test_manifest.json b/test/test_manifest.json index a542ec06e..d0cc9933d 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -5407,6 +5407,12 @@ "enableXfa": true, "type": "eq" }, + { "id": "issue14165", + "file": "pdfs/issue14165.pdf", + "md5": "917850af2a387475b34b00010200897d", + "rounds": 1, + "type": "eq" + }, { "id": "scorecard_reduced", "file": "pdfs/scorecard_reduced.pdf", "md5": "aa8ed0827092c963eea64adb718a3806", @@ -6282,6 +6288,12 @@ } } }, + { "id": "issue10572", + "file": "pdfs/issue10572.pdf", + "md5": "48ad69ed106338b3c6845fc7101488b2", + "rounds": 1, + "type": "eq" + }, { "id": "issue11931", "file": "pdfs/issue11931.pdf", "md5": "9ea233037992e1f10280420a49e72845",