[font] resolve the initial viewport of SVG glyphs#265
Conversation
The SVG documents of some fonts, such as Noto Color Emoji, do not specify a viewBox attribute on their root element. The OpenType specification defines the initial viewport of an SVG glyph document as the em square (head.unitsPerEm), so renderers relying on the document alone ended up with an empty viewport and rendered nothing. Expose the resolved viewport as a new ViewBox field on GlyphSVG, computed from the root <svg> element attributes: the viewBox attribute if present, then the width and height attributes, then the em square. The root element is scanned with a small dedicated parser rather than encoding/xml, to avoid its binary size overhead in this core package. Updates go-text#191 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the font package’s OT-SVG support by resolving an SVG glyph document’s initial viewport and exposing it to callers, ensuring SVG glyphs without an explicit viewBox can still be rendered correctly (per the OpenType spec’s em-square default).
Changes:
- Add
SVGViewBoxand logic to resolve the initial viewport from root<svg>attributes (with spec-compliant fallback to(0,0,upem,upem)). - Extend
GlyphSVGto include the resolvedViewBox, and plumbupeminto the SVG glyph data path. - Add unit/integration tests covering viewport resolution and a real font fixture.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
font/svg.go |
Implements viewBox/width/height parsing and a lightweight root <svg> attribute scanner to resolve the initial viewport. |
font/renderer.go |
Extends GlyphSVG with ViewBox and computes it when extracting SVG glyph data. |
font/svg_test.go |
Adds tests for SVGViewBox resolution and verifies GlyphDataSVG(...).ViewBox on a fixture font. |
font/export_test.go |
Exposes the internal svgViewBox helper to tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Reject the non-finite number forms accepted by strconv.ParseFloat
("NaN", "Inf", ...) in viewBox, width and height attribute values,
falling back to the em-square default instead of propagating them.
Ignore brackets and '>' inside quoted literals and comments when
skipping a doctype internal subset, so that contents such as
<!ENTITY foo "]>"> do not end the scan early.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
andydotxyz
left a comment
There was a problem hiding this comment.
Nice one thanks, I can't see anything beyond the Copilot fixes
benoitkugler
left a comment
There was a problem hiding this comment.
This is a nice addition, thank you !
I would rather use the standard library encoding/xml : I would rather not have our own copy implementation and I note we already import it in fontscan to support fontconfig files.
I'm not sure I understand the need for
func SVGDocViewBox(doc []byte, upem uint16) SVGViewBox , could you double check it ?
This is needed to test an unexported function If this solution (exporting an API only for testing) is not conventional, I'm fine to change the way. What do you think? |
Sure. |
Replace the dedicated XML prolog scanner with encoding/xml to read the root element attributes. This removes hand-written handling of the byte order mark, XML declaration, comments and doctype internal subsets, delegating it to the standard library. Using a proper XML parser also makes the element namespace available, so the root is now required to be an svg element in the SVG namespace (a missing namespace is tolerated, a foreign one is rejected). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@benoitkugler all done. Please take a look |
Thanks for the modifications. I think we could avoid the |
Move the test to package font, matching the rest of the package, and call svgViewBox directly instead of through an export bridge. This removes the export_test.go file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Done. |
benoitkugler
left a comment
There was a problem hiding this comment.
Thank you, this looks great to me !
What
Adds a
ViewBoxfield toGlyphSVG, exposing the initial viewport of an SVG glyph document. It is resolved from the root<svg>element attributes with the following cascade:viewBoxattribute, if present and valid;width/heightattributes (the fallback FreeType's reference OT-SVG port also uses);(0, 0, upem, upem), as required by the OpenType specification:Why
Updates #191: Noto Color Emoji's SVG documents have no
viewBoxattribute, so consumers relying on the document alone (e.g. go-text/render via oksvg) see a 0×0 viewport and render nothing. The library is the natural place to resolve the spec-defined default, since it knowsunitsPerEm.Notes for reviewers
encoding/xml: importingencoding/xmlinto the corefontpackage measured ~175 KB of binary size overhead, a real cost for wasm-type builds, while only the root element's attributes are needed. The scanner handles BOM, XML declaration, comments, doctype (including internal subset), namespace-prefixed roots and both quote styles; any malformed input degrades to the em-square default rather than an error. Happy to switch to anxml.Decodertoken loop if you prefer stdlib robustness over the size cost —svgRootAttributeskeeps the same signature either way.NotoColorEmoji-Regular.ttffrom the issue: the 🍣 glyph now reportsViewBox {0, 0, 1024, 1024}(upem = 1024). The integration test usestoys/chromacheck-svg.ttf, whose document also lacks aviewBox.GlyphSVG.ViewBoxinstead of oksvg's parsed (empty) one, and place the SVG origin at the baseline (the documents use font units, y-down, origin at baseline).Authored by Claude (Claude Code), on behalf of @hajimehoshi.
🤖 Generated with Claude Code