Skip to content

[font] resolve the initial viewport of SVG glyphs#265

Open
hajimehoshi wants to merge 4 commits into
go-text:mainfrom
hajimehoshi:claude/stoic-goldberg-f9b94d
Open

[font] resolve the initial viewport of SVG glyphs#265
hajimehoshi wants to merge 4 commits into
go-text:mainfrom
hajimehoshi:claude/stoic-goldberg-f9b94d

Conversation

@hajimehoshi

Copy link
Copy Markdown
Contributor

What

Adds a ViewBox field to GlyphSVG, exposing the initial viewport of an SVG glyph document. It is resolved from the root <svg> element attributes with the following cascade:

  1. the viewBox attribute, if present and valid;
  2. the width/height attributes (the fallback FreeType's reference OT-SVG port also uses);
  3. the em square (0, 0, upem, upem), as required by the OpenType specification:

The size of the initial viewport for the SVG document is the em square: height and width both equal to head.unitsPerEm.

Why

Updates #191: Noto Color Emoji's SVG documents have no viewBox attribute, 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 knows unitsPerEm.

Notes for reviewers

  • The root element is scanned with a small dedicated parser instead of encoding/xml: importing encoding/xml into the core font package 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 an xml.Decoder token loop if you prefer stdlib robustness over the size cost — svgRootAttributes keeps the same signature either way.
  • The element is matched by its local name only; the namespace binding is intentionally not verified (a check could only reject broken fonts, and would yield the same em-square fallback anyway).
  • Verified against the actual NotoColorEmoji-Regular.ttf from the issue: the 🍣 glyph now reports ViewBox {0, 0, 1024, 1024} (upem = 1024). The integration test uses toys/chromacheck-svg.ttf, whose document also lacks a viewBox.
  • A companion change in go-text/render is still needed for the issue's user-visible fix: use GlyphSVG.ViewBox instead 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

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>
Copilot AI review requested due to automatic review settings July 3, 2026 17:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 SVGViewBox and logic to resolve the initial viewport from root <svg> attributes (with spec-compliant fallback to (0,0,upem,upem)).
  • Extend GlyphSVG to include the resolved ViewBox, and plumb upem into 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.

Comment thread font/svg.go
Comment thread font/svg.go Outdated
Comment thread font/svg.go Outdated
Comment thread font/svg_test.go
Comment thread font/svg_test.go Outdated
Comment thread font/renderer.go Outdated
Comment thread font/renderer.go
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 andydotxyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one thanks, I can't see anything beyond the Copilot fixes

@benoitkugler benoitkugler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ?

@hajimehoshi

hajimehoshi commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

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 svgViewBox from an independent test package (font_test). I now think this should be renamed to SVGViewBox. EDIT: This name is intentionally SVGDocViewBox to avoid a conflict with the existing SVGViewBox.

If this solution (exporting an API only for testing) is not conventional, I'm fine to change the way. What do you think?

@hajimehoshi

hajimehoshi commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

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.

Sure. As the first comment says, this would increase the binary size by 175 KB. EDIT: As this is already imported, there is no problem!

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

Copy link
Copy Markdown
Contributor Author

@benoitkugler all done. Please take a look

@benoitkugler

Copy link
Copy Markdown
Contributor

@benoitkugler all done. Please take a look

Thanks for the modifications. I think we could avoid the export_test.go file if we use package font instead of package font_test in svg_test.go

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

Copy link
Copy Markdown
Contributor Author

Thanks for the modifications. I think we could avoid the export_test.go file if we use package font instead of package font_test in svg_test.go

Done.

@benoitkugler benoitkugler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this looks great to me !

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.

4 participants