Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions FIDELITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ The committed PNGs under `testdata/renders/` back every claim here. Reproduce
them with the commands at the bottom. The measured-vs-Chrome numbers live in
[`bench/REPORT.md`](bench/REPORT.md).

## 2026-10-19 — the "oksvg complex-path defect" of rounds 25, 64 and 79 was three separate defects in oksvg, none of them in this engine; all three are fixed in the fleet fork `go-gfx/oksvg` (v0.2.0), which the engine now uses (patch)

The octocat, the Wikipedia wordmark and tagline, and the MDN wordmark were filed as one third-party defect ("complex multi-subpath `<path>` mis-rendered", suspected to be the fill rule). Rebuilding each against Chrome headless (a standalone Go program, scratch only, pixels off by more than 40 in a channel) showed otherwise:

| | srwiley/oksvg | go-gfx/oksvg v0.2.0 | cause |
|---|---|---|---|
| GitHub octocat (20×20 mark, 600 px) | 24.0% | 0.3% | **arc flags written without separators**: `A9.564 9.564 0 0110 4.844` is flags 0 and 1 then x = 10 (SVG 1.1 §8.3.9); oksvg read `0110` as one number and lost the rest of the path |
| Wikipedia wordmark | 27.7% | 4.2% | **an arc command with several parameter sets** (`a10 10 0 0 1 5 5 8 8 0 0 1 3 3`): every arc was drawn with the first set's radii and flags (fixed in v0.1.0) |
| Wikipedia tagline | 18.9% | 6.8% | the same |

The 4–7% that remains on the two Wikipedia logos is edge anti-aliasing of dense text. The MDN wordmark of round 25 was not re-fetched, so it is not verified; it is a path of the same kind, so the arc fixes are the likely cause.

The fill rule was NOT the cause of any of them. It was a real defect too, found by the same sweep: oksvg ignored `fill-rule`, and rasterx's scanner has one rule (nonzero) and a no-op `SetWinding`. v0.2.0 implements `evenodd` (hand-written star and donuts, with a gradient, opacity and a clip, all under 0.4% off Chrome). None of the 4009 Twemoji graphics changes with it. Also fixed in the fork, with the earlier clip work: `clipPath`/`clip-path`/`clip-rule`.

Not a change to this engine's own code: `svg.go` imports `github.com/go-gfx/oksvg`; the known-gap entries below are annotated.

## 2026-10-18 (round 175) — an ordinary production page (not a crafted one) took tens of seconds to render because of a per-pixel allocation the interface boxing in `image.Image.At()` was hiding, in the `background-repeat` paint path (security audit, patch)

Rounds 166–173 were all about a page's content making the engine do unboundedly MUCH work (deeper recursion, more nodes, more memory). This one is different in kind: the amount of work was bounded and ordinary — a handful of `background-repeat` sections on one real page — but each unit of that work cost far more than it needed to, because of how the pixel data was being read.
Expand Down Expand Up @@ -5458,6 +5474,7 @@ columns.
requires instrumenting go-opentype's own rasteriser directly, outside this
repo. Not fixed here: the font and its rasteriser are both a separate
dependency, not this engine's own code.
- **[FIXED 2026-10-19 in `go-gfx/oksvg` v0.2.0 (arc flags without separators; see the entry at the top) — the next two bullets are kept as history.]**
- **The third-party SVG rasteriser (`github.com/srwiley/oksvg`) mis-renders a
SECOND real-world complex `<path>`**, extending round 25's already-
documented finding (the MDN wordmark) with a concrete new example: GitHub's
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/evanw/esbuild v0.28.2
github.com/go-browserhttp/browserhttp v0.2.0
github.com/go-gfx/gfx v0.35.0
github.com/go-gfx/oksvg v0.1.0
github.com/go-gfx/oksvg v0.2.0
github.com/go-images/images v0.1.0
github.com/go-opentype/fonts v0.12.0
github.com/go-opentype/opentype v0.15.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ github.com/go-crdt/crdt v0.57.0 h1:sNLDTVCEnSb20gm77BUuYav8dzj5krmly/z9O7eRMCo=
github.com/go-crdt/crdt v0.57.0/go.mod h1:zdVTTDLGV5JYAoTJDqCivcAT+/B5gGebDsVd8QNZpVs=
github.com/go-gfx/gfx v0.35.0 h1:AaXMxDMxNB1hROxzx+3EoO06KxsrQUvnw/10olmSgVo=
github.com/go-gfx/gfx v0.35.0/go.mod h1:qIZS9u2+VnBk5CX7o4f/fQ5uD8c+mPFTtAPE85s8V90=
github.com/go-gfx/oksvg v0.1.0 h1:gK9AJ948GoKapOPikT391sD95YVnaNLq2PcvedDGxPo=
github.com/go-gfx/oksvg v0.1.0/go.mod h1:ap7jj7yma4qOcwiAFzPMC9/eODo7Q2nt71DkEZlHA0k=
github.com/go-gfx/oksvg v0.2.0 h1:kuqrSh6aLHy5zM2d2crQf8vP9gnSSmkhsmxOlROnYXo=
github.com/go-gfx/oksvg v0.2.0/go.mod h1:ap7jj7yma4qOcwiAFzPMC9/eODo7Q2nt71DkEZlHA0k=
github.com/go-icons/iconoir v0.3.0 h1:kHG2sU5IsCvtBS1JICuXgfFo5f8UfwY1m7KYMVy97CI=
github.com/go-icons/iconoir v0.3.0/go.mod h1:lPh5OuzVQ+rR4qDPGgAdSRUhnCbHeCAX70HDdwp8/RI=
github.com/go-images/gif v0.2.0 h1:8Xl4Nan+PvyqH4wPre7wJUH61T2eJKOKonFKOdMdpkU=
Expand Down
35 changes: 35 additions & 0 deletions svg_fillrule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) the go-webengine/engine authors.
// SPDX-License-Identifier: BSD-3-Clause

package engine

import (
"image/color"
"testing"
)

// TestRasterizeSVGFillRuleAndArcFlags: go-gfx/oksvg v0.2.0 honours fill-rule,
// and reads arc flags written without separators (the octocat's
// "A9.564 9.564 0 0110 4.844"), which srwiley/oksvg did not.
func TestRasterizeSVGFillRuleAndArcFlags(t *testing.T) {
alpha := func(src string, x, y int) uint8 {
img, ok := RasterizeSVG([]byte(src), 100, 100, "")
if !ok {
t.Fatal("not rasterised")
}
return color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA).A
}
donut := func(rule string) string {
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path fill="#000"` + rule + ` d="M10 10H90V90H10Z M30 30H70V70H30Z"/></svg>`
}
if a := alpha(donut(` fill-rule="evenodd"`), 50, 50); a != 0 {
t.Errorf("evenodd: the hole is filled (alpha %d)", a)
}
if a := alpha(donut(""), 50, 50); a != 255 {
t.Errorf("nonzero, same direction: the hole is not filled (alpha %d)", a)
}
arc := `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M0 50A50 50 0 0110 0L10 50Z"/></svg>`
if a := alpha(arc, 5, 40); a != 255 {
t.Errorf("a path with compact arc flags is cut short (alpha %d)", a)
}
}
Loading