A text was drawn into a texture as tall as Pixi measures the string
"|ÉqÅM". Whatever reached further, like the "g" of Manrope (0.015 em
below the "q"), was cut off at the bottom. The blue frame around a
text was only estimated from the font size and the number of letters,
so it was far too wide for most texts.
- textPicture.ts takes the height of a line from the font itself
(fontBoundingBoxAscent and fontBoundingBoxDescent, as a browser
does). Pixi draws with a margin, and the picture is cut to that box
afterwards. Where Pixi's own box is larger, Pixi's box wins.
- Both raster paths (linear and nearest) use the cut.
- The height no longer depends on the letters typed, so a text does
not jump while it is edited.
- bounds.ts asks a measurer that textPicture.ts registers
(setTextMeasure). Selection, picking in a running game, framing the
camera, the drop preview and culling all get the measured size.
The footprint functions take the font file for this.
- Unit tests in node have no canvas and keep the estimate
(estimatedTextSize).
- Texts get taller: Source Sans 3 at 48 px is 68 instead of 55 px,
Manrope is 1.366 em instead of 1.25 em. The glyphs stay in place,
the centre moves by less than half a pixel. A shader on a text
gets a taller UV rectangle.
Tests:
- Gate on melektaus-atx green: 307 files, 4762 tests.
- 5 new unit tests for textBox, textCut and the measurer hook.
- e2e 30: 26 checks, 30f-iii new. The texture is 68 high, the font
box is 68, the frame equals the drawn picture (237x68).
- Counter test: with Pixi's old box the texture is 55 high and
30f-iii is red.
- Not checked in the browser: the Manrope caption of the user and
texts with several lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A text could not wear a shader, and a pixel font was always smoothed.
Pixi's Text takes no custom shader. It also shares one texture between
texts of the same content and style, and the filter is not part of that
key, so two equal texts with different filters would overwrite each
other.
- TextPicture rasters a text into a texture of its own and draws it
like a sprite. Shader, time animation and edge mode work without a
special path. A new text, font or size rasters again and keeps the
shader.
- The old texture is dropped only after the shader holds the new one.
Before, Pixi warned about a destroyed texture that was still bound.
- A text has a Shader row with the shader's property rows below, as a
sprite has. Overrides, saving, animation and the Properties node
(type Text) know them. A shader parameter named like an own row of
the text loses to that row, as on the particle source.
- The font asset has a Filter row, Linear or Nearest. The row existed
three times already (texture, vector art, pixel art) and is now one
helper filterRow.
- Nearest rasters the text 8 times finer and cuts every 8x8 block to
one texel: at least half covered is full, else empty. Chrome on
Windows smooths canvas text sideways through DirectWrite whatever
filter we set later. The cut removes those half pixels on every
system, and the texture stays one texel per world unit.
Found on the way:
- On a font that is no pixel font thin strokes break apart under
Nearest. For a pixel font at its own size this does not matter.
Not done:
- The preview in the Shader tab does not take a selected text as its
picture, it shows the test picture then.
Tests:
- ./check.sh: 189 files, 2759 tests, CHECK EXIT 0.
- text-shader.test.ts with 10 tests: rows and name precedence, a
clamped override, save and load, animation, the font filter, and
sharpCoverage on a smoothed edge and at the texture border.
- e2e 30 has 7 new checks: red glyphs through the shader picked with
the mouse, the shader survives a new text, red in the game, soft
edges under Linear, clean 4x4 blocks under Nearest, and half covered
texels (Linear 238, Nearest 0).
- Full local run before the Nearest cut: all passed, 1543 checks.
GM_STEPS=1,30 on the final bundle: all passed, 32 checks. The sum
1544 in the README is counted from these two runs, not measured.
- Counter-checks fail as expected: in the browser no shader on a text
fails 30h-30j, an always linear sampler fails the Nearest check, the
old 1:1 raster leaves 107 half covered texels and fails 30k-ii.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>