From 66341cb3d7290a41b04067c72dcd0fd943ea29e2 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Tue, 21 Jul 2026 22:14:14 +0000 Subject: [PATCH 68/71] fix(macos): restore dropped glyphs (w, ~) in the GL 2.0 backend GLRenderTarget::renderTile() sized its quad directly from tile.targetSize, unlike SoftwareRenderTarget::blitAtlasTile() which falls back to tile.bitmapSize when targetSize is zero. TextRenderer::restrictToTileSize() zeroes targetSize as an implicit sentinel meaning "this tile was cropped; use bitmapSize instead" whenever a glyph's rasterized bitmap overflows the fixed-width atlas tile (the monospace advance metric, with zero headroom) -- e.g. 'w' and '~' in a hinted Menlo, both wide/generous-overshoot glyphs prone to overflowing their advance width. With a zeroed targetSize, the GL quad collapsed to zero width/height and glyph draws nothing, leaving a blank but still correctly pen-advanced cell -- the letter's data was never wrong (confirmed by the user: copy-pasting the on-screen text elsewhere restored it correctly), only its GL-specific render was silently skipped. The CPU backend never had this bug because blitAtlasTile already guards it. Fixed by adding the same firstNonZero(targetSize, bitmapSize) fallback GLRenderTarget's sibling already uses. No test harness exists yet for either render target (both need AppKit/OpenGL, not headlessly testable as currently structured), so no test added -- matches the existing state of this file. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LzTmVgP2ruMz987VJ78k77 --- src/contour_macos/GLRenderTarget.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/contour_macos/GLRenderTarget.cpp b/src/contour_macos/GLRenderTarget.cpp index f4a518e5..a6cc2c69 100644 --- a/src/contour_macos/GLRenderTarget.cpp +++ b/src/contour_macos/GLRenderTarget.cpp @@ -39,6 +39,15 @@ namespace return static_cast(unbox(s.height)); } + // TextRenderer::restrictToTileSize() zeroes targetSize as an implicit sentinel meaning "use + // bitmapSize (the already-cropped, always-nonzero size) instead" for any glyph whose rasterized + // bitmap overflowed the fixed-width atlas tile (e.g. 'w'/'~' in a hinted monospace font, wider + // than the advance metric the tile size is set from). SoftwareRenderTarget::blitAtlasTile + // already falls back this way; renderTile() below must match it, or a zeroed targetSize collapses + // the emitted quad to zero width/height and the glyph draws nothing -- a blank but correctly + // pen-advanced gap, since the CPU backend's blit isn't in this code path to mask it. + [[nodiscard]] int firstNonZero(int a, int b) noexcept { return a != 0 ? a : b; } + // GLSL 1.20 (OpenGL 2.0). A single program draws every quad: solid fills, glyph tiles and // image quads. The selector uniform picks how the sampled texel becomes the output color, // matching text.frag and SoftwareRenderTarget exactly. @@ -290,8 +299,10 @@ void GLRenderTarget::renderTile(RenderTile tile) Quad q; q.x = static_cast(tile.x.value); q.y = static_cast(tile.y.value); - q.w = static_cast(widthOf(tile.targetSize)); - q.h = static_cast(heightOf(tile.targetSize)); + q.w = static_cast(firstNonZero(static_cast(widthOf(tile.targetSize)), + static_cast(widthOf(tile.bitmapSize)))); + q.h = static_cast(firstNonZero(static_cast(heightOf(tile.targetSize)), + static_cast(heightOf(tile.bitmapSize)))); q.u0 = tile.normalizedLocation.x; q.v0 = tile.normalizedLocation.y; q.u1 = tile.normalizedLocation.x + tile.normalizedLocation.width;