Fix the rendering of the radial gradient when a center is outside of the other circle and there's no extend

It fixes #20851.
This commit is contained in:
Calixte Denizet 2026-03-13 10:20:55 +01:00
parent b7698d617d
commit c610f44952
5 changed files with 779 additions and 15 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -883,3 +883,4 @@
!nested_outline.pdf
!form_two_pages.pdf
!outlines_se.pdf
!radial_gradients.pdf

View File

@ -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

View File

@ -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"
}
]