diff --git a/src/core/pattern.js b/src/core/pattern.js index 68042e58d..6059fdfcf 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -163,19 +163,6 @@ class RadialAxialShading extends BaseShading { [extendStart, extendEnd] = extendArr; } - if ( - this.shadingType === ShadingType.RADIAL && - (!extendStart || !extendEnd) - ) { - // Radial gradient only currently works if either circle is fully within - // the other circle. - const [x1, y1, r1, x2, y2, r2] = this.coordsArr; - const distance = Math.hypot(x1 - x2, y1 - y2); - if (r1 <= r2 + distance && r2 <= r1 + distance) { - warn("Unsupported radial gradient."); - } - } - this.extendStart = extendStart; this.extendEnd = extendEnd; diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index d460de365..5a8da30a5 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -83,6 +83,23 @@ class RadialAxialShadingPattern extends BaseShadingPattern { return this._type === "radial"; } + // Returns true when the smaller circle's center (p0 when r0 ≤ r1) lies + // outside the larger circle. In that case the canvas radial gradient picks + // t > 1 solutions for points inside the outer circle and maps them to the + // transparent stop we append for extendEnd=false, making the gradient + // invisible. A two-pass draw (reversed first, normal on top) fixes this + // (see #20851). + _isCircleCenterOutside() { + if (!this.isRadial() || this._r0 > this._r1) { + return false; + } + const dist = Math.hypot( + this._p0[0] - this._p1[0], + this._p0[1] - this._p1[1] + ); + return dist > this._r1; + } + _createGradient(ctx, transform = null) { let grad; let firstPoint = this._p0; @@ -125,6 +142,41 @@ class RadialAxialShadingPattern extends BaseShadingPattern { return grad; } + _createReversedGradient(ctx, transform = null) { + // Swapped circles: (p1, r1) → (p0, r0), with color stops reversed. + let firstPoint = this._p1; + let secondPoint = this._p0; + if (transform) { + firstPoint = firstPoint.slice(); + secondPoint = secondPoint.slice(); + Util.applyTransform(firstPoint, transform); + Util.applyTransform(secondPoint, transform); + } + let r0 = this._r1; + let r1 = this._r0; + if (transform) { + const scale = new Float32Array(2); + Util.singularValueDecompose2dScale(transform, scale); + r0 *= scale[0]; + r1 *= scale[0]; + } + const grad = ctx.createRadialGradient( + firstPoint[0], + firstPoint[1], + r0, + secondPoint[0], + secondPoint[1], + r1 + ); + const reversedStops = this._colorStops + .map(([t, c]) => [1 - t, c]) + .reverse(); + for (const [t, c] of reversedStops) { + grad.addColorStop(t, c); + } + return grad; + } + getPattern(ctx, owner, inverse, pathType) { let pattern; if (pathType === PathType.STROKE || pathType === PathType.FILL) { @@ -193,6 +245,10 @@ class RadialAxialShadingPattern extends BaseShadingPattern { } applyBoundingBox(tmpCtx, this._bbox); + if (this._isCircleCenterOutside()) { + tmpCtx.fillStyle = this._createReversedGradient(tmpCtx); + tmpCtx.fill(); + } tmpCtx.fillStyle = this._createGradient(tmpCtx); tmpCtx.fill(); @@ -203,6 +259,15 @@ class RadialAxialShadingPattern extends BaseShadingPattern { // Shading fills are applied relative to the current matrix which is also // how canvas gradients work, so there's no need to do anything special // here. + if (this._isCircleCenterOutside()) { + // Draw the reversed gradient first so the normal gradient can + // correctly overlay it (see _isCircleCenterOutside for details). + ctx.save(); + applyBoundingBox(ctx, this._bbox); + ctx.fillStyle = this._createReversedGradient(ctx); + ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + ctx.restore(); + } applyBoundingBox(ctx, this._bbox); pattern = this._createGradient(ctx); } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 3f85ddfda..8e4b0a508 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -883,3 +883,4 @@ !nested_outline.pdf !form_two_pages.pdf !outlines_se.pdf +!radial_gradients.pdf diff --git a/test/pdfs/radial_gradients.pdf b/test/pdfs/radial_gradients.pdf new file mode 100644 index 000000000..edbf57481 --- /dev/null +++ b/test/pdfs/radial_gradients.pdf @@ -0,0 +1,698 @@ +%PDF-1.4 +%âãÏÓ +1 0 obj +<< /Type /Catalog /Pages 2 0 R >> +endobj +2 0 obj +<< /Type /Pages /Kids [3 0 R 4 0 R 5 0 R 6 0 R 7 0 R] /Count 5 >> +endobj +3 0 obj +<< /Type /Page + /Parent 2 0 R + /MediaBox [0 0 595 842] + /Contents 38 0 R + /Resources << + /Shading << /Sh1 13 0 R /Sh2 14 0 R /Sh3 15 0 R /Sh4 16 0 R >> + /Font << /F1 37 0 R >> + >> +>> +endobj +4 0 obj +<< /Type /Page + /Parent 2 0 R + /MediaBox [0 0 595 842] + /Contents 39 0 R + /Resources << + /Shading << /Sh5 17 0 R /Sh6 18 0 R /Sh7 19 0 R /Sh8 20 0 R >> + /Font << /F1 37 0 R >> + >> +>> +endobj +5 0 obj +<< /Type /Page + /Parent 2 0 R + /MediaBox [0 0 595 842] + /Contents 40 0 R + /Resources << + /Shading << /Sh9 21 0 R /Sh10 22 0 R /Sh11 23 0 R /Sh12 24 0 R >> + /Font << /F1 37 0 R >> + >> +>> +endobj +6 0 obj +<< /Type /Page + /Parent 2 0 R + /MediaBox [0 0 595 842] + /Contents 41 0 R + /Resources << + /Shading << /Sh13 25 0 R /Sh14 26 0 R /Sh15 27 0 R /Sh16 28 0 R /Sh17 29 0 R /Sh18 30 0 R /Sh19 31 0 R /Sh20 32 0 R >> + /Font << /F1 37 0 R >> + >> +>> +endobj +7 0 obj +<< /Type /Page + /Parent 2 0 R + /MediaBox [0 0 595 842] + /Contents 42 0 R + /Resources << + /Shading << /Sh21 25 0 R /Sh22 26 0 R /Sh23 27 0 R /Sh24 28 0 R /Sh25 33 0 R /Sh26 34 0 R /Sh27 35 0 R /Sh28 36 0 R >> + /Font << /F1 37 0 R >> + >> +>> +endobj +8 0 obj +<< /FunctionType 2 /Domain [0 1] /C0 [1 0 0] /C1 [1 1 0] /N 1 >> +endobj +9 0 obj +<< /FunctionType 2 /Domain [0 1] /C0 [1 1 0] /C1 [0 1 0] /N 1 >> +endobj +10 0 obj +<< /FunctionType 2 /Domain [0 1] /C0 [0 1 0] /C1 [0 1 1] /N 1 >> +endobj +11 0 obj +<< /FunctionType 2 /Domain [0 1] /C0 [0 1 1] /C1 [0 0 1] /N 1 >> +endobj +12 0 obj +<< /FunctionType 3 /Domain [0 1] /Functions [8 0 R 9 0 R 10 0 R 11 0 R] /Bounds [0.25 0.5 0.75] /Encode [0 1 0 1 0 1 0 1] >> +endobj +13 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [155 637 15 155 637 115] /Function 12 0 R /Extend [true true] >> +endobj +14 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [375 637 40 505 637 40] /Function 12 0 R /Extend [true true] >> +endobj +15 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [125 237 50 185 237 95] /Function 12 0 R /Extend [true true] >> +endobj +16 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [395 237 65 475 237 65] /Function 12 0 R /Extend [true true] >> +endobj +17 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [155 637 0 155 637 110] /Function 12 0 R /Extend [true true] >> +endobj +18 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [400 637 0 440 637 100] /Function 12 0 R /Extend [true true] >> +endobj +19 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [265 237 0 155 237 110] /Function 12 0 R /Extend [true true] >> +endobj +20 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [540 237 0 440 237 80] /Function 12 0 R /Extend [true true] >> +endobj +21 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [125 637 20 155 637 90] /Function 12 0 R /Extend [true true] >> +endobj +22 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [410 637 20 440 637 90] /Function 12 0 R /Extend [true false] >> +endobj +23 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [125 237 20 155 237 90] /Function 12 0 R /Extend [false true] >> +endobj +24 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [410 237 20 440 237 90] /Function 12 0 R /Extend [false false] >> +endobj +25 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [133 689 15 153 689 70] /Function 12 0 R /Extend [true true] >> +endobj +26 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [133 489 15 153 489 70] /Function 12 0 R /Extend [true false] >> +endobj +27 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [133 289 15 153 289 70] /Function 12 0 R /Extend [false true] >> +endobj +28 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [133 89 15 153 89 70] /Function 12 0 R /Extend [false false] >> +endobj +29 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [521 689 0 431 689 60] /Function 12 0 R /Extend [true true] >> +endobj +30 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [521 489 0 431 489 60] /Function 12 0 R /Extend [true false] >> +endobj +31 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [521 289 0 431 289 60] /Function 12 0 R /Extend [false true] >> +endobj +32 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [521 89 0 431 89 60] /Function 12 0 R /Extend [false false] >> +endobj +33 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [511 689 25 431 689 60] /Function 12 0 R /Extend [true true] >> +endobj +34 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [511 489 25 431 489 60] /Function 12 0 R /Extend [true false] >> +endobj +35 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [511 289 25 431 289 60] /Function 12 0 R /Extend [false true] >> +endobj +36 0 obj +<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [511 89 25 431 89 60] /Function 12 0 R /Extend [false false] >> +endobj +37 0 obj +<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> +endobj +38 0 obj +<< /Length 799 >> +stream +BT +/F1 11 Tf +20 825 Td +(Radial Gradients with 5 Color Stops - Four Circle Configurations) Tj +ET +1 g +30 462 250 350 re f +q +31 463 248 316 re W n +/Sh1 sh +Q +0 G 0.5 w +30 462 250 350 re S +30 780 m 280 780 l S +0 g +BT +/F1 9 Tf +35 791 Td +(Contained circles (one inside the other)) Tj +ET +1 g +315 462 250 350 re f +q +316 463 248 316 re W n +/Sh2 sh +Q +0 G 0.5 w +315 462 250 350 re S +315 780 m 565 780 l S +0 g +BT +/F1 9 Tf +320 791 Td +(Disjoint circles) Tj +ET +1 g +30 62 250 350 re f +q +31 63 248 316 re W n +/Sh3 sh +Q +0 G 0.5 w +30 62 250 350 re S +30 380 m 280 380 l S +0 g +BT +/F1 9 Tf +35 391 Td +(Intersecting: focal centre inside) Tj +ET +1 g +315 62 250 350 re f +q +316 63 248 316 re W n +/Sh4 sh +Q +0 G 0.5 w +315 62 250 350 re S +315 380 m 565 380 l S +0 g +BT +/F1 9 Tf +320 391 Td +(Intersecting: both centres outside) Tj +ET + +endstream +endobj +39 0 obj +<< /Length 780 >> +stream +BT +/F1 11 Tf +20 825 Td +(Radial Gradients with 5 Color Stops - One Radius = 0) Tj +ET +1 g +30 462 250 350 re f +q +31 463 248 316 re W n +/Sh5 sh +Q +0 G 0.5 w +30 462 250 350 re S +30 780 m 280 780 l S +0 g +BT +/F1 9 Tf +35 791 Td +(r=0 at centre (concentric point)) Tj +ET +1 g +315 462 250 350 re f +q +316 463 248 316 re W n +/Sh6 sh +Q +0 G 0.5 w +315 462 250 350 re S +315 780 m 565 780 l S +0 g +BT +/F1 9 Tf +320 791 Td +(r=0 inside circle (off-centre)) Tj +ET +1 g +30 62 250 350 re f +q +31 63 248 316 re W n +/Sh7 sh +Q +0 G 0.5 w +30 62 250 350 re S +30 380 m 280 380 l S +0 g +BT +/F1 9 Tf +35 391 Td +(r=0 on boundary of circle) Tj +ET +1 g +315 62 250 350 re f +q +316 63 248 316 re W n +/Sh8 sh +Q +0 G 0.5 w +315 62 250 350 re S +315 380 m 565 380 l S +0 g +BT +/F1 9 Tf +320 391 Td +(r=0 outside circle (disjoint)) Tj +ET + +endstream +endobj +40 0 obj +<< /Length 837 >> +stream +BT +/F1 11 Tf +20 825 Td +(Radial Gradients - Extend Variations (same geometry, off-centre focal)) Tj +ET +1 g +30 462 250 350 re f +q +31 463 248 316 re W n +/Sh9 sh +Q +0 G 0.5 w +30 462 250 350 re S +30 780 m 280 780 l S +0 g +BT +/F1 9 Tf +35 791 Td +(Extend [true true ] - fills both sides) Tj +ET +1 g +315 462 250 350 re f +q +316 463 248 316 re W n +/Sh10 sh +Q +0 G 0.5 w +315 462 250 350 re S +315 780 m 565 780 l S +0 g +BT +/F1 9 Tf +320 791 Td +(Extend [true false] - fills inward only) Tj +ET +1 g +30 62 250 350 re f +q +31 63 248 316 re W n +/Sh11 sh +Q +0 G 0.5 w +30 62 250 350 re S +30 380 m 280 380 l S +0 g +BT +/F1 9 Tf +35 391 Td +(Extend [false true ] - fills outward only) Tj +ET +1 g +315 62 250 350 re f +q +316 63 248 316 re W n +/Sh12 sh +Q +0 G 0.5 w +315 62 250 350 re S +315 380 m 565 380 l S +0 g +BT +/F1 9 Tf +320 391 Td +(Extend [false false] - cone only) Tj +ET + +endstream +endobj +41 0 obj +<< /Length 1578 >> +stream +BT +/F1 11 Tf +20 825 Td +(Extend variations: normal (col 1) vs r0=0 point outside (col 2)) Tj +ET +BT +/F1 9 Tf +25 800 Td +(Normal: p0 inside p1, r0=15, r1=70) Tj +ET +BT +/F1 9 Tf +303 800 Td +(r0=0 point outside: d=90 > r1=60) Tj +ET +1 g +20 610 267 190 re f +q +21 611 265 156 re W n +/Sh13 sh +Q +0 G 0.5 w +20 610 267 190 re S +20 768 m 287 768 l S +0 g +BT +/F1 9 Tf +25 779 Td +(Normal [true true ]) Tj +ET +1 g +298 610 267 190 re f +q +299 611 265 156 re W n +/Sh17 sh +Q +0 G 0.5 w +298 610 267 190 re S +298 768 m 565 768 l S +0 g +BT +/F1 9 Tf +303 779 Td +(r=0 outside [true true ]) Tj +ET +1 g +20 410 267 190 re f +q +21 411 265 156 re W n +/Sh14 sh +Q +0 G 0.5 w +20 410 267 190 re S +20 568 m 287 568 l S +0 g +BT +/F1 9 Tf +25 579 Td +(Normal [true false]) Tj +ET +1 g +298 410 267 190 re f +q +299 411 265 156 re W n +/Sh18 sh +Q +0 G 0.5 w +298 410 267 190 re S +298 568 m 565 568 l S +0 g +BT +/F1 9 Tf +303 579 Td +(r=0 outside [true false]) Tj +ET +1 g +20 210 267 190 re f +q +21 211 265 156 re W n +/Sh15 sh +Q +0 G 0.5 w +20 210 267 190 re S +20 368 m 287 368 l S +0 g +BT +/F1 9 Tf +25 379 Td +(Normal [false true ]) Tj +ET +1 g +298 210 267 190 re f +q +299 211 265 156 re W n +/Sh19 sh +Q +0 G 0.5 w +298 210 267 190 re S +298 368 m 565 368 l S +0 g +BT +/F1 9 Tf +303 379 Td +(r=0 outside [false true ]) Tj +ET +1 g +20 10 267 190 re f +q +21 11 265 156 re W n +/Sh16 sh +Q +0 G 0.5 w +20 10 267 190 re S +20 168 m 287 168 l S +0 g +BT +/F1 9 Tf +25 179 Td +(Normal [false false]) Tj +ET +1 g +298 10 267 190 re f +q +299 11 265 156 re W n +/Sh20 sh +Q +0 G 0.5 w +298 10 267 190 re S +298 168 m 565 168 l S +0 g +BT +/F1 9 Tf +303 179 Td +(r=0 outside [false false]) Tj +ET + +endstream +endobj +42 0 obj +<< /Length 1590 >> +stream +BT +/F1 11 Tf +20 825 Td +(Extend variations: normal (col 1) vs r0=25 outside (col 2)) Tj +ET +BT +/F1 9 Tf +25 800 Td +(Normal: p0 inside p1, r0=15, r1=70) Tj +ET +BT +/F1 9 Tf +303 800 Td +(r0=25 outside: d=80 > r1=60, intersecting) Tj +ET +1 g +20 610 267 190 re f +q +21 611 265 156 re W n +/Sh21 sh +Q +0 G 0.5 w +20 610 267 190 re S +20 768 m 287 768 l S +0 g +BT +/F1 9 Tf +25 779 Td +(Normal [true true ]) Tj +ET +1 g +298 610 267 190 re f +q +299 611 265 156 re W n +/Sh25 sh +Q +0 G 0.5 w +298 610 267 190 re S +298 768 m 565 768 l S +0 g +BT +/F1 9 Tf +303 779 Td +(r0=25 outside [true true ]) Tj +ET +1 g +20 410 267 190 re f +q +21 411 265 156 re W n +/Sh22 sh +Q +0 G 0.5 w +20 410 267 190 re S +20 568 m 287 568 l S +0 g +BT +/F1 9 Tf +25 579 Td +(Normal [true false]) Tj +ET +1 g +298 410 267 190 re f +q +299 411 265 156 re W n +/Sh26 sh +Q +0 G 0.5 w +298 410 267 190 re S +298 568 m 565 568 l S +0 g +BT +/F1 9 Tf +303 579 Td +(r0=25 outside [true false]) Tj +ET +1 g +20 210 267 190 re f +q +21 211 265 156 re W n +/Sh23 sh +Q +0 G 0.5 w +20 210 267 190 re S +20 368 m 287 368 l S +0 g +BT +/F1 9 Tf +25 379 Td +(Normal [false true ]) Tj +ET +1 g +298 210 267 190 re f +q +299 211 265 156 re W n +/Sh27 sh +Q +0 G 0.5 w +298 210 267 190 re S +298 368 m 565 368 l S +0 g +BT +/F1 9 Tf +303 379 Td +(r0=25 outside [false true ]) Tj +ET +1 g +20 10 267 190 re f +q +21 11 265 156 re W n +/Sh24 sh +Q +0 G 0.5 w +20 10 267 190 re S +20 168 m 287 168 l S +0 g +BT +/F1 9 Tf +25 179 Td +(Normal [false false]) Tj +ET +1 g +298 10 267 190 re f +q +299 11 265 156 re W n +/Sh28 sh +Q +0 G 0.5 w +298 10 267 190 re S +298 168 m 565 168 l S +0 g +BT +/F1 9 Tf +303 179 Td +(r0=25 outside [false false]) Tj +ET + +endstream +endobj +xref +0 43 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000145 00000 n +0000000369 00000 n +0000000593 00000 n +0000000822 00000 n +0000001132 00000 n +0000001442 00000 n +0000001522 00000 n +0000001602 00000 n +0000001683 00000 n +0000001764 00000 n +0000001905 00000 n +0000002035 00000 n +0000002164 00000 n +0000002293 00000 n +0000002422 00000 n +0000002551 00000 n +0000002680 00000 n +0000002809 00000 n +0000002937 00000 n +0000003066 00000 n +0000003196 00000 n +0000003326 00000 n +0000003457 00000 n +0000003586 00000 n +0000003716 00000 n +0000003846 00000 n +0000003975 00000 n +0000004103 00000 n +0000004232 00000 n +0000004361 00000 n +0000004489 00000 n +0000004618 00000 n +0000004748 00000 n +0000004878 00000 n +0000005007 00000 n +0000005078 00000 n +0000005929 00000 n +0000006761 00000 n +0000007650 00000 n +0000009281 00000 n +trailer +<< /Size 43 /Root 1 0 R >> +startxref +10924 +%%EOF diff --git a/test/test_manifest.json b/test/test_manifest.json index 9060f2481..96245b451 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -13079,7 +13079,11 @@ "rounds": 1, "type": "extract", "includePages": [0, 2, 12], - "pageMapping": { "1": 1, "3": 2, "13": 3 } + "pageMapping": { + "1": 1, + "3": 2, + "13": 3 + } }, { "id": "bug900822-encrypted-extract_0", @@ -13088,7 +13092,9 @@ "rounds": 1, "type": "extract", "includePages": [0], - "pageMapping": { "1": 1 } + "pageMapping": { + "1": 1 + } }, { "id": "xfa_bug1998843", @@ -13985,5 +13991,12 @@ "md5": "7072f6763bf2f0d6df14d5fc86962c5a", "rounds": 1, "type": "eq" + }, + { + "id": "radial_gradients", + "file": "pdfs/radial_gradients.pdf", + "md5": "80e8bed66b83928698f008c33de47edd", + "rounds": 1, + "type": "eq" } ]