Donner SVG main
0.8.0 · 382bd0190.8.0 main
SVG editor and embeddable C⁠+⁠+⁠20 engine.
Loading...
Searching...
No Matches
resvg-test-suite: Custom Golden Overrides

Status: Reference (current as of 2026-08-18)

When Donner's rendering of a resvg-test-suite case is correct but the upstream golden is not the right thing to compare against, the test pins Params::WithGoldenOverride("donner/svg/renderer/testdata/golden/resvg-<name>.png") so it compares against a Donner-blessed golden instead. This doc is the catalog of every such override and the reason it exists.

There are two distinct reasons an override is justified:

  1. resvg's golden is wrong per spec — Donner renders to the spec, resvg doesn't. These are genuine upstream golden bugs.
  2. Blessed minor diff — Donner and resvg both render acceptably, but there's a small, well-understood difference (sub-pixel char advance, anti-aliasing on a curved baseline, a font-backend difference) where pinning a Donner golden is cleaner than carrying a per-test pixel threshold.

Custom goldens live in donner/svg/renderer/testdata/golden/resvg-<test-name>.png, named with the new (post-Great-Rename) test stem. The rationale for each is the .withReason("…") argument chained onto the override in resvg_test_suite.cc — grep the test name there to find it inline.

Naming note: these used the old a-* / e-* convention before the suite upgrade (0022). The four originally documented here mapped forward as: a-filter-013/a-filter-015 → drop-shadow-function-mm-values/drop-shadow-function-em-values (now parked, see below); e-marker-045/e-marker-051 (closed-shape / cusp marker direction) were superseded upstream and retired — the surviving marker override is orient=auto-on-M-C-C-4.

Invalid filter URL reference qualifications

Filter Effects 1 specifies that a missing or non-filter URL disables the complete filter chain, leaving the object unfiltered (published definition, editor's draft). Two historical PNGs encode different behavior:

  • filters/filter/invalid-FuncIRI.svg: the PNG makes the referenced rectangle transparent.
  • filters/filter-functions/one-invalid-url-in-list.svg: the PNG retains the surrounding grayscale and opacity functions; its center is RGBA (80,126,100,127).

Both files are reference-disagreement skips against those PNGs. Independently authored pixel tests in FilterReferenceStages_tests.cc require unchanged visible leaf and group geometry for missing and non-filter references, including functions on both sides of a bad URL. No replacement golden or enlarged tolerance is used. Empty valid filters are covered separately and remain transparent.

Blur at a clipping filter region

filters/filter/complex-order-and-xlink-href.svg filters a rect through a filter that inherits its blur and height through xlink:href and sets its own x, so the filter region cuts the blur's input at its left and bottom edges. The vendored reference now is resvg v0.49.0's 300x300 rendering; the snapshot's reference had been flattened onto white (see third_party/resvg-test-suite/BUILD.bazel). Against it, Donner's region geometry and its free blur edges match. The remaining pixels (1,275 on TinySkia; Geode adds 3 frame-corner pixels) lie within 5 px of the two clipped edges: resvg approximates the blur with five box passes over a region-sized buffer, and each pass drops what spreads past the buffer, so the first row and column inside read alpha 84. Donner keeps that spill and reads 143, close to the 144 of a single Gaussian of the clipped input. The row is a reference-disagreement skip; a raised pixel budget would keep the rest of the case compared, but needs a maintainer decision. RendererRegressionTests.FilterHrefRegionClipMatchesClippedSourceBlurredWithRoom requires the clipped render to equal the visible part of the rect blurred with room around it.

Active overrides (37)

resvg golden is wrong per spec

Category Test Reason (withReason)
filters/feSpotLight complex-transform resvg bug: SpotLight Y
filters/feImage svg We render higher quality
filters/feSpecularLighting with-fePointLight resvg golden
painting/context with-pattern-objectBoundingBox-in-use resvg golden bakes fractional-tile rounding
painting/marker orient=auto-on-M-C-C-4 Pre-existing rendering diff (stroke/AA), not cusp-related
painting/marker with-an-image-child We (correctly) render the image child
painting/paint-order on-tspan Preserve cross-span kerning across a paint-only tspan boundary
text/font-size named-value Donner uses CSS Fonts Level 4
text/font-size named-value-without-a-parent Donner uses CSS Fonts Level 4

named-value{,-without-a-parent} reflect CSS Fonts Level 4's revised handling of named font sizes (xx-small … xx-large), which differs from resvg's older table.

with-pattern-objectBoundingBox-in-use runs against a committed Donner render (resvg-with-pattern-objectBoundingBox-in-use.png), shared by both backends (TinyGolden and GeodeGolden pass at the default budget). Evidence that resvg's golden is the artifact: resvg rasterizes pattern tiles at a rounded integer pixel size, and this test's context bounding box yields a fractional 56.57px on-screen tile that resvg draws as 57px, stretching the tile period by ~0.8%. Donner repeats the tile at the exact period. A numeric grid fit confirms it: Donner's render fits an ideal diamond grid anchored at the computed context box (31.82, -116.67), 226.27² with score 1.0, while resvg's golden fits no ideal grid (best score 0.899 — its grid drifts, consistent with per-tile rounding). The integer-tile pattern goldens in the same category (with-pattern-in-use, with-pattern-and-transform-in-use, with-pattern-on-marker, whose tiles land on exact pixel sizes) match Donner exactly, isolating the discrepancy to the fractional-tile case. Revisit if resvg regenerates goldens without the rounding.

Allowed nearest-neighbor reference differences

CSS Images permits an implementation-selected edge-preserving algorithm for crisp-edges; deprecated optimizeSpeed maps to that value. An alternate nearest sampling grid does not establish that the upstream reference violates the specification.

Category Test Reason
painting/image-rendering on-feImage Nearest sampling at destination pixel centers

The project reference retains all vendored reference pixels outside the image primitive. Inside, the decoded 64 x 64 RGBA source is sampled with source_index = floor((destination_index + 0.5) * 64 / destination_extent). The default filter region expands the (40, 40, 120, 120) object box by 10% on each side, producing (70, 70, 360, 360) after the 500 / 200 viewport scale. All 129,600 image-region pixels follow this independent geometric construction. The separate optimizeSpeed fixture compares against its resvg v0.49.0 rendering, which TinySkia matches. Geode stays disabled for it: at 3.75 device pixels per texel, every second texel boundary falls on a pixel center, and Geode's interpolated texture coordinate lands just below it where tiny-skia's float inverse transform lands on it, so Geode picks the previous texel. All 564 differing pixels are inside the image; this is a sampling-position rounding difference, not missing SVG support.

Backend-difference goldens (text)

These render correctly but differ from resvg because Donner's text stack (and the simple-vs-text-full split) shapes/composes differently. The golden is whichever Donner backend the test runs under.

Category Test Reason
text/text complex-graphemes-and-coordinates-list Simple text can't compose combining marks
text/text compound-emojis-and-coordinates-list Emoji bitmap scaling differs from the golden
text/text rotate-on-Arabic Arabic shaping requires text-full
text/text x-and-y-with-multiple-values-and-arabic-text Arabic shaping; vertical-axis AA diff not the focus

Blessed minor diffs (textPath / text-decoration)

The text/textPath family carries the bulk of the overrides: sub-pixel character advance and anti-aliasing differences along curved baselines, which differ between the simple and text-full backends. All are .withReason("Minor char") unless noted.

Category Test Reason
text/textPath dy-with-tiny-coordinates AA + minor char advance diffs (text vs text-full)
text/textPath m-L-Z-path Minor char
text/textPath mixed-children-1 AA diffs
text/textPath nested Minor char
text/textPath path-with-ClosePath Minor char
text/textPath simple-case Minor char
text/textPath startOffset=-100 Minor char
text/textPath startOffset=10percent Minor char
text/textPath startOffset=30 Minor char
text/textPath startOffset=5mm Minor char
text/textPath tspan-with-absolute-position Minor char
text/textPath tspan-with-relative-position Minor char
text/textPath two-paths Minor char
text/textPath very-long-text AA diffs
text/textPath with-baseline-shift Minor char
text/textPath with-coordinates-on-text Minor char
text/textPath with-coordinates-on-textPath Minor char
text/textPath with-rotate Minor char
text/textPath with-text-anchor (no reason string)
text/textPath with-transform-on-a-referenced-path Minor char
text/textPath with-transform-outside-a-referenced-path Minor char
text/textPath with-underline Minor char
text/text-decoration indirect (no reason string)

Two entries (textPath/with-text-anchor, text-decoration/indirect) carry no withReason. They should either get one or migrate to a per-test threshold; the "Minor char" cluster is the precedent. Tracked in 0021.

Parked goldens (2) - filters/filter-functions triage

resvg-drop-shadow-function-mm-values.png and resvg-drop-shadow-function-em-values.png are the migrated successors of the old a-filter-013 / a-filter-015 drop-shadow blur-halo cases: Donner's three-pass box-blur approximation (correct per CSS Filter Effects L1 §8 / SVG §15.9) produces a different halo shape than resvg's blur, so a Donner golden is required.

They are currently unreferenced. The filters/filter-functions category is active, with 26 of 43 files comparing on both backends and 17 explicit Triage: skips. No project-owned oracle has been accepted for the two unit-based drop-shadow cases yet. Keep both files until normative specification/browser triage decides whether each mismatch is a Donner defect, a vendored-oracle difference, or a justified project golden. Do not delete them as "orphans".

Removed goldens (cleanup, 2026-05-15)

Three goldens added by #515 were never referenced by any override and were removed as dead data (recoverable from git history if a real override need surfaces):

  • resvg-em-on-the-root-element.png
  • resvg-ex-on-the-root-element.png
  • resvg-percent-value-without-a-parent.png

The corresponding text/font-size tests pass against the upstream golden — only named-value{,-without-a-parent} need an override. After this cleanup the golden/ dir holds exactly 39 resvg-*.png: 37 active + the 2 parked drop-shadow goldens above.

Maintenance

  • Adding an override: place resvg-<new-stem>.png in donner/svg/renderer/testdata/golden/, add Params::WithGoldenOverride(…) .withReason("…") in resvg_test_suite.cc, and add a row here. The golden/*.png glob in testdata/BUILD.bazel picks it up automatically.
  • Never overwrite a golden without explicit approval; these images encode deliberate spec and quality judgments.
  • When a Skip'd gap in 0021 is fixed, check whether it needs an override here too.