Add Polygons#normalizeWinding for non-zero fill consumers#17
Open
MasKusuno wants to merge 1 commit into
Open
Conversation
Adds a `normalizeWinding(direction?)` method to the `Polygons` class that flips the vertex order of each contour so all contours share a single winding orientation. KAGE pushes each stroke as an independent closed polygon without normalizing winding, which is invisible under evenodd filling but produces white-out artefacts at stroke intersections under non-zero filling — the default for both SVG `<path>` (when no fill-rule is set) and TrueType `glyf`. Calling this method before passing the polygons to such a renderer ensures overlapping strokes render as a single filled shape. The new `WindingDirection` type alias is also exported. The method defaults to `"cw"`, which matches the convention used by TrueType `glyf` outer contours when coordinates are flipped to a y-up system. Refs kurgm#15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR addresses #15 by adding an opt-in
normalizeWindingmethod to thePolygonsclass. It flips the vertex order of each contour so all contours share a single winding orientation, eliminating the white-out artefacts that mixed winding produces under non-zero filling.Background
Polygons.pushaccumulates each rendered stroke as an independent closed polygon, but the engine never normalizes the polygons' winding directions. This is invisible at the small render sizes used by the GlyphWiki web viewer withevenoddfilling, but becomes prominent when the output is fed into a font (TrueTypeglyftable) or scaled SVG<path>without an explicitfill-rule=\"evenodd\". The intersection of two overlapping strokes ends up unfilled — the inner overlap area is treated as a hole.API
In KAGE's y-axis-down coordinate system:
Why default
"cw"?When KAGE polygons are flipped to a y-up coordinate system (e.g. when emitting a TrueType
glyf),"cw"in y-down corresponds to"ccw"in y-up — which is the orientation TrueType uses for outer contours. Sopolygons.normalizeWinding()with the default produces glyf-ready polygons after the y-flip. SVG callers using non-zero filling can pick either direction; the default is fine there too.Why this is non-breaking
push/generateSVG/generateEPSare unchanged.arrayfield are untouched.WindingDirectionis a new exported type; existing imports are unaffected.Tests
test/index.jsgains aPolygons#normalizeWindingblock covering:"cw"normalization makes every polygon's signed area>= 0."ccw"makes every polygon's signed area<= 0.Existing tests continue to pass.
Real-world usage
We hit this while building a free-license PUA font from GlyphWiki dumps. Without normalization, characters with dense overlapping strokes (e.g. CJK Unified kanji with crossbars over verticals) leave white gashes at the crossings. With
polygons.normalizeWinding()called once before emitting theglyftable, the artefacts disappear cleanly.Note
The implementation lives entirely on the consumer side of the polygon list — it doesn't touch the stroke generation pipeline or the existing
pushvalidation logic. So the engine's render output is unchanged unless callers explicitly opt in.Refs #15