Skip to content

Fix create_text_sprite atlas sharing, anchors, position, and empty text - #2915

Merged
pvcraven merged 2 commits into
developmentfrom
fix/create-text-sprite
Oct 5, 2026
Merged

pvcraven merged 2 commits into
developmentfrom
fix/create-text-sprite

Conversation

@pvcraven

@pvcraven pvcraven commented Oct 5, 2026

Copy link
Copy Markdown
Member

Summary

First of the text rendering fixes from the review. arcade.create_text_sprite() had four bugs, all reproduced on development before fixing.

1. Sprites with the same text shared one atlas image

The texture was created with Texture.create_empty(text, size), using the text as its name, which is also the atlas key. So a second sprite with the same text reused the first one's atlas region, and render_into drew over it:

  • The first sprite's image changed. A red 20pt "Same" turned blue after a blue 40pt "Same" was created.
  • The second sprite's 117×65 texture was squeezed into the first sprite's 58×32 region, so it drew stretched.

Fix: each texture gets a unique name (create_text_sprite_<uuid>). create_empty documents its name as "the unique name for this texture". The atlas already frees an image when its texture is garbage collected (through its finalizers), so this doesn't leak atlas space.

2. anchor_x="center" and "right" clipped the text

Before drawing into the texture, only y was shifted to bring the text to (0, 0). With a center or right anchor, the text's left edge is negative, so it was drawn partly or completely outside the texture. For "Hello World" at 20pt, measured as total alpha in the texture:

anchor_x Before After
left 285,564 285,564
center 137,710 (half) 285,561
right 0 (empty) 285,564

Fix: shift x as well.

3. The sprite was placed too high

The sprite's center was read from the Text after the shift, which put it about one text height too high: 14 px for 12pt text, and 47 px for 40pt. Fix: use the bounds from before the shift.

The sprite now covers the same area as an arcade.Text(text, 0, 0, anchor_y="baseline", ...) with the same arguments. Rendered side by side at the same position, their pixels match within 1 px, for every anchor, size, multiline and centered multiline case I tried. The 1 px is a faint edge column on multiline text; text sprites draw edges slightly lighter because of a separate blending issue from the review.

4. An empty string raised ValueError

The docstring says text "can be an empty string", but that made a 0×0 texture and raised ValueError: height and width must be > 0. The texture is now at least 1×1, giving a transparent sprite.

Also, the texture size is rounded up instead of truncated, so a fractional text width can't clip the last column. Widths are whole pixels on Windows, so this changes nothing here.

Behavior change

Sprites from create_text_sprite are now positioned differently: lower by the text's height, and anchored like Text. Code that positions the sprite itself afterwards, which is the usual case, isn't affected. No examples or docs in the repo use create_text_sprite.

Tests

New tests in tests/unit/text/test_text_sprite.py:

  • test_position_matches_text (6 cases: default, center and right anchors, 40pt, multiline, centered multiline): the sprite's bounds equal the Text object's bounds. It compares against arcade's own Text bounds, so it doesn't depend on which fonts are installed (CI is Linux).
  • test_anchor_draws_all_text (left, center, right): the anchored sprite's image has as much ink as the left-anchored one. Each image is read right away, so this test doesn't depend on fix 1.
  • test_same_text_different_style: each texture gets an atlas region of its own size, and creating a blue sprite doesn't change the red one's pixels.
  • test_empty_text: a transparent texture of at least 1×1.

On development, 10 of these fail: all six position cases, the center and right anchors, the shared image, and the empty string. Only the left-anchor case and the existing loose test_create cases pass there.

Full suite on pyglet 3.0.dev11: 1472 passed. The 3 failures are the render tests that only fail on my machine. Ruff is clean, and mypy reports no errors in arcade/text.py.

🤖 Generated with Claude Code

Paul V Craven and others added 2 commits October 5, 2026 15:57
- The texture was created with the text as its atlas name, so sprites
  with the same text shared one atlas image. Creating a second one in
  another color or size redrew the shared image (changing the first
  sprite) into the first sprite's region (stretching the second). Give
  each texture a unique name; the atlas frees it when the texture is
  garbage collected.
- Only y was shifted before drawing into the texture, so with
  anchor_x="center" or "right" the text started at a negative x and was
  partly or completely clipped. Shift x too.
- The sprite's center was read from the Text after shifting it, which
  put the sprite too high. Use the bounds from before the shift, so the
  sprite covers the same area as an equivalent Text object.
- An empty string made a 0x0 texture and raised ValueError. Make the
  texture at least 1x1.
- Round the texture size up instead of truncating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pvcraven
pvcraven merged commit 1e73c78 into development Oct 5, 2026
7 checks passed
@pvcraven
pvcraven deleted the fix/create-text-sprite branch October 5, 2026 21:40
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.

1 participant