Skip to content

Commit 23b622a

Browse files
committed
fix(text): snap inline-icon drawn rects to whole render-target pixels
Icon regions placed at fractional pen positions through the anchor math, so 1:1-sized art (a 16px emote at :16) sampled between texels and rendered soft. Snap the drawn rect in the flush, gated on the node's pixel-snap mode (bit-7 clear -- the same condition the engine's glyphs snap under): position rounds to whole pen px, and SIZE rounds independently of position so a :16 icon is exactly 16px, never 15/17 from the two edges rounding apart. Floors at 1px. An integral pen rect survives to the screen because pen px = render px, the fsLeft/fsBottom terms cancel exactly through the anchor round-trip (the engine adds the same rect corner back at resolve), and the pool's width-convergence multiplier is linear.
1 parent f806f17 commit 23b622a

1 file changed

Lines changed: 32 additions & 5 deletions

File tree

src/text/InlineTexture.cpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,10 @@ void FlushLayout(void *layout) {
16671667
}
16681668
const float ox = Game::Read<float>(n, Offsets::OFF_TEXT_NODE_ORIGIN_X);
16691669
const float oy = Game::Read<float>(n, Offsets::OFF_TEXT_NODE_ORIGIN_Y);
1670+
// Same pixel-snap mode the emitter honours (bit-7 clear) — gates
1671+
// the drawn-rect snap in the placement loop below.
1672+
const bool snapNode =
1673+
(Game::Read<uint32_t>(n, Offsets::OFF_TEXT_NODE_FLAGS) & 0x80u) == 0;
16701674
// The fs rect, read HERE in the same flush as the icon coords — a
16711675
// coherent snapshot. Placements are stored FS-RELATIVE: an
16721676
// apply-time rect read raced the chat relayout (SetText invalidates
@@ -1706,14 +1710,37 @@ void FlushLayout(void *layout) {
17061710
// that — the retail look.
17071711
const float cy = r.y + r.fontH * g_centerFrac + oy + g_vBias - r.offsetY;
17081712
if (K.x > 1.0f && K.y > 1.0f && fs != nullptr && fsRectValid) {
1709-
const float rx = cx + g_regionCalX;
1710-
const float ry = cy + g_regionCalY;
1713+
float rx = cx + g_regionCalX;
1714+
float y0 = cy + g_regionCalY - r.h * 0.5f;
1715+
float w = r.w, h = r.h;
1716+
if (snapNode) {
1717+
// Land the drawn rect on WHOLE render-target pixels,
1718+
// like the engine's own glyphs (rounded origin +
1719+
// truncated advances). Pen px = render px, the fsLeft/
1720+
// fsBottom terms cancel exactly through the anchor
1721+
// round-trip (the engine adds the same rect corner
1722+
// back at resolve), and the pool's width convergence
1723+
// preserves the linear factor — so an integral pen
1724+
// rect lands integral on screen: 1:1-sized icons
1725+
// sample texel centres (crisp) instead of blending
1726+
// four neighbours (soft). Size snaps independently of
1727+
// position so a :16 icon is EXACTLY 16px, never 15/17
1728+
// from the two edges rounding apart.
1729+
rx = std::floor(rx + 0.5f);
1730+
y0 = std::floor(y0 + 0.5f);
1731+
w = std::floor(w + 0.5f);
1732+
h = std::floor(h + 0.5f);
1733+
if (w < 1.0f)
1734+
w = 1.0f;
1735+
if (h < 1.0f)
1736+
h = 1.0f;
1737+
}
17111738
Text::InlineTexturePool::Placement p;
17121739
p.path = r.path;
17131740
p.x0 = rx / K.x - fsLeft;
1714-
p.y0 = (ry - r.h * 0.5f) / K.y - fsBottom;
1715-
p.x1 = (rx + r.w) / K.x - fsLeft;
1716-
p.y1 = (ry + r.h * 0.5f) / K.y - fsBottom;
1741+
p.y0 = y0 / K.y - fsBottom;
1742+
p.x1 = (rx + w) / K.x - fsLeft;
1743+
p.y1 = (y0 + h) / K.y - fsBottom;
17171744
p.color = r.color;
17181745
p.u0 = r.u0;
17191746
p.v0 = r.v0;

0 commit comments

Comments
 (0)