Merge remote-tracking branch 'origin/master' into claude/issue-51

# Conflicts:
#	project.cpp
This commit is contained in:
catbot 2026-06-16 15:43:11 +00:00
commit e4f8b057f3
8 changed files with 396 additions and 42 deletions

View file

@ -48,39 +48,79 @@ std::uint32_t UIRenderer::ShapeText(Font& font, float pxSize,
std::string_view utf8,
std::array<float,4> color,
GlyphItem* out, std::uint32_t outCapacity,
float* outAdvance) {
float* outAdvance,
TextAlign align) {
if (fontAtlas == nullptr) {
std::println("UIRenderer::ShapeText: no FontAtlas (set fontAtlas before Initialize)");
std::abort();
}
const float scale = pxSize / FontAtlas::kBaseSize;
float cursor = x;
std::uint32_t written = 0;
// Look up (or build) the origin-relative shaped run. It is stored with
// the pen starting at x=0, baseline=0 so a cache hit only needs the
// translate below. The whole string is shaped (no outCapacity limit) so
// a later call with a larger buffer still gets the full run.
ShapedRunKey key{&font, pxSize, color, std::string(utf8)};
auto it = shapedRuns_.find(key);
if (it == shapedRuns_.end()) {
// Miss: shape from scratch. Ensure() each glyph so brand-new ones
// rasterise into the atlas and mark it dirty for the next upload —
// a hit skips this, which is safe because atlas entries are never
// evicted once placed.
const float scale = pxSize / FontAtlas::kBaseSize;
ShapedRun run;
float cursor = 0.0f;
std::size_t i = 0;
while (i < utf8.size()) {
std::uint32_t cp = DecodeUtf8(utf8, i);
if (cp == 0) break;
if (cp == '\n') { continue; }
std::size_t i = 0;
while (i < utf8.size() && written < outCapacity) {
std::uint32_t cp = DecodeUtf8(utf8, i);
if (cp == 0) break;
if (cp == '\n') { continue; }
fontAtlas->Ensure(font, cp);
const Glyph* g = fontAtlas->Lookup(font, cp);
if (g == nullptr) continue;
fontAtlas->Ensure(font, cp);
const Glyph* g = fontAtlas->Lookup(font, cp);
if (g == nullptr) continue;
if (g->w > 0 && g->h > 0) {
GlyphItem& gi = out[written++];
gi.x = cursor + g->xoff * scale;
gi.y = baselineY + g->yoff * scale;
gi.w = g->w * scale;
gi.h = g->h * scale;
gi.u0 = g->u0; gi.v0 = g->v0;
gi.u1 = g->u1; gi.v1 = g->v1;
gi.r = color[0]; gi.g = color[1]; gi.b = color[2]; gi.a = color[3];
if (g->w > 0 && g->h > 0) {
GlyphItem gi;
gi.x = cursor + g->xoff * scale;
gi.y = g->yoff * scale;
gi.w = g->w * scale;
gi.h = g->h * scale;
gi.u0 = g->u0; gi.v0 = g->v0;
gi.u1 = g->u1; gi.v1 = g->v1;
gi.r = color[0]; gi.g = color[1]; gi.b = color[2]; gi.a = color[3];
run.glyphs.push_back(gi);
}
cursor += g->advance * scale;
}
cursor += g->advance * scale;
run.advance = cursor;
if (shapedRuns_.size() >= kMaxShapedRuns) shapedRuns_.clear();
it = shapedRuns_.emplace(std::move(key), std::move(run)).first;
}
if (outAdvance) *outAdvance = cursor - x;
const ShapedRun& run = it->second;
if (outAdvance) *outAdvance = run.advance;
// Fold the alignment shift into the translate so callers don't need a
// second pass over the glyphs.
const float alignShift = (align == TextAlign::Center) ? -run.advance * 0.5f
: (align == TextAlign::Right) ? -run.advance
: 0.0f;
const float tx = x + alignShift;
std::uint32_t written = 0;
for (const GlyphItem& src : run.glyphs) {
if (written >= outCapacity) break;
GlyphItem& gi = out[written++];
gi = src;
gi.x += tx;
gi.y += baselineY;
}
return written;
}
void UIRenderer::InvalidateFont(const Font& font) noexcept {
std::erase_if(shapedRuns_, [&font](const auto& kv) {
return kv.first.font == &font;
});
}

View file

@ -58,17 +58,12 @@ namespace {
GlyphItem* writePos = buf.glyphs + before;
float baseline = centerY + fontSize * 0.32f;
float advance = 0.0f;
// ShapeText folds the -advance/2 centering shift into its own pass.
std::uint32_t n = buf.renderer->ShapeText(
font, fontSize, centerX, baseline,
label, color, writePos, cap, &advance
label, color, writePos, cap, &advance, TextAlign::Center
);
*buf.glyphCount = before + n;
// Center: shift each glyph's x by -advance/2.
float shift = -advance * 0.5f;
for (std::uint32_t i = 0; i < n; ++i) {
writePos[i].x += shift;
}
return advance;
}
}
@ -202,16 +197,12 @@ float Crafter::DrawText(UIBuffer& buf, std::string_view text,
GlyphItem* writePos = buf.glyphs + before;
float advance = 0.0f;
// Alignment is applied inside ShapeText (folded into its single pass).
std::uint32_t n = buf.renderer->ShapeText(
font, fontSize, x, baselineY,
text, color, writePos, cap, &advance
text, color, writePos, cap, &advance, align
);
*buf.glyphCount = before + n;
if (align != TextAlign::Left && n > 0) {
float shift = (align == TextAlign::Center) ? -advance * 0.5f : -advance;
for (std::uint32_t i = 0; i < n; ++i) writePos[i].x += shift;
}
return advance;
}