Skip to content

feat(svg): gradient paints end-to-end — fills and strokes as native PDF shadings#179

Merged
DemchaAV merged 2 commits into
developfrom
feat/svg-gradient-paints
Jun 12, 2026
Merged

feat(svg): gradient paints end-to-end — fills and strokes as native PDF shadings#179
DemchaAV merged 2 commits into
developfrom
feat/svg-gradient-paints

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Jun 12, 2026

Copy link
Copy Markdown
Owner

the SVG surface (beta) now renders linearGradient / radialGradient natively, on fills and strokes — all four GraphCompose brand logos parse and render 1:1.

Engine

  • DocumentPaint.LinearAxis / RadialCircle — endpoint-exact gradient forms (the angle/farthest-corner forms can't carry SVG's explicit extents).
  • PathNode + PathBuilder: fill(paint) / strokePaint(paint); solid paints normalise to the flat-colour path in PathDefinition (mirrors ShapeDefinition), so non-gradient documents stay byte-identical (guarded by test).
  • Gradient fill: clip-to-path + axial/radial shading. Gradient stroke: shading-pattern stroking colour (/Pattern CS, type 2). Found and fixed a PDFBox serialization trap: nested shading/function dicts get promoted to indirect objects the writer never emits — inlineDeep pins the freshly built subtree direct (test asserts the colour function survives reload).

Reader

  • url(#id) on fill/stroke with SVG inheritance; userSpaceOnUse (through the element's accumulated affine — matches browsers) and objectBoundingBox (through the shape's bbox) units; gradientTransform; % offsets; multi-stop; one href hop (Inkscape/Figma split definitions). New SvgGradients (file-size rule).
  • Loud failures, never wrong renders: unknown id, non-pad spreadMethod, stop-opacity, focal radials.

Ergonomics (from the «костыль» review)

  • SvgIcon#node(width) — node form with a tight box for layer anchors; addSvgIcon delegates; the triplicated stack-building loop in examples is gone.
  • Rows accept ShapeContainerNode directly (same atomic overlay composite as the already-allowed LayerStackNode) — the wrap-in-section ceremony is gone from gallery + catalog.

Verification: 1301 tests, BUILD SUCCESS (+14 new: axis mapping incl. y-flip, bbox units, href, gradientTransform, radial, error matrix, pattern+function survival, mini-mark e2e). All four assets/*.svg brand logos render correctly (gradient G-outline, gradient dots, wordmarks) — proof render attached to the thread.

Stacked on #177; merge after it in the queue.

…es as native PDF shadings

- DocumentPaint gains endpoint-exact LinearAxis and RadialCircle forms
- PathNode/PathBuilder grow fill(paint) + strokePaint(paint); solid paints
  normalise to the flat-colour path (byte-identical non-gradient output)
- gradient fills clip to the path and emit axial/radial shadings; gradient
  strokes ride a shading-pattern stroking colour (inlined subtree - nested
  dicts would dangle as null indirect refs after save)
- SvgIconReader resolves url(#id) gradients: userSpaceOnUse and
  objectBoundingBox units, gradientTransform, percent offsets, multi-stop,
  one href hop; loud failures for focal/spreadMethod/stop-opacity
- SvgIcon#node(width) node form; addSvgIcon delegates; rows accept
  ShapeContainerNode (same atomic overlay composite as LayerStackNode)
- examples simplified onto icon.node(); vector-path gains a gradient block
return userSpace ? defaultFraction * viewportSize : defaultFraction;
}
if (v.endsWith("%")) {
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
return userSpace ? fraction * viewportSize : fraction;
}
return Double.parseDouble(v);
return userSpace ? defaultFraction * diagonal : defaultFraction;
}
if (v.endsWith("%")) {
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
return userSpace ? fraction * diagonal : fraction;
}
return Double.parseDouble(v);
}
String v = value.trim();
if (v.endsWith("%")) {
return Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
if (v.endsWith("%")) {
return Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
}
return Double.parseDouble(v);
Base automatically changed from feat/feature-catalog-v18-blocks to develop June 12, 2026 14:28
@DemchaAV DemchaAV merged commit 4bf11b1 into develop Jun 12, 2026
11 checks passed
@DemchaAV DemchaAV deleted the feat/svg-gradient-paints branch June 12, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants