Conversation
The distance grids and the scratch arrays used by the distance transform are
sized from the canvas dimension, but a glyph that overflows the canvas is
clipped to `size - buffer`, which makes its padded bounding box up to
`size + buffer` wide and tall. edt1d then reads and writes past the end of
f/v/z, and the bottom and right edges of such a glyph come out zeroed:
128 159 223 255 0 0 0 128 159 223 255 255 255 255
0 0 0 0 0 0 0 -> 128 159 223 223 223 223 223
0 0 0 0 0 0 0 120 146 159 159 159 159 159
Name the clip bound `maxGlyphDim` and derive the grid dimension from it, so
the two can't drift apart, and size every array for the largest padded glyph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
OX Security reviewed this pull request — nothing to fix.
Branch |
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.
draw()clips an overflowing glyph tosize - buffer, so its padded box reachessize + buffer— onebufferwider than the canvas. The grids and thef/v/zscratch arrays are sized fromsize, soedt1druns past their end and the clipped edges come out zeroed.This is a regression of #29, reintroduced in e83b8c3 when
gridSizewas folded intosize. The two aren't interchangeable: the grid is alwayssize + buffer, because the clip bound is itself derived fromsize— growing the canvas grows the largest glyph by the same amount.Repro —
{fontSize: 20, buffer: 2}(28px canvas), a glyph filling it, padded box 30×30:Only glyphs whose bounding box exceeds
size - 2 * bufferare affected, so which ones depends on the renderer and the font — it comes down to how large a bounding box the font reports relative tofontSize. In Chrome/macOSsans-serifat the defaults it is Arabic presentation forms: 2.0% of U+FB50–FDFF atfontSize: 24and 4.9% at 48, up to 3.5% of a glyph's bytes. Latin, Greek, CJK, kana, Hangul and emoji all come out clean there. Under node-canvas the same code corrupts ~55% of the emoji block instead, because emoji measure 1.33em there against Chrome's 1.13em.Either way it matches the note on #29 that this "happens to be correct for almost any glyph (thus we didn't notice the problem)".
Fix — name the clip bound
maxGlyphDimand derive the grid dimension from it, so the two can't drift apart again. Output is byte-identical for every glyph that wasn't already corrupted (565 renders across 5 option sets). Costs ~3.6 KB per instance at the defaults (the two grids go from 36² to 39²).Test — rasterizes a canvas-filling square through stubbed
measureText/fillText, so it needs no particular font. Asserts the padded glyph fits every array and that the SDF stays symmetric. Fails onmain.