#9518

FreeType: opt-in HarfBuzz shaping (RenderShapedText) for GSUB ligatures

Opens-celleswants to merge intofrom

Summary

Add an opt-in ImGuiFreeType::RenderShapedText API, guarded by IMGUI_ENABLE_HARFBUZZ_SHAPING, that shapes a text cluster with HarfBuzz and renders the resulting (possibly ligature) glyphs. This forms GSUB ligatures -- ZWJ families (U+1F468 U+200D U+1F469 ... -> 👨‍👩‍👧‍👦) and regional-indicator flags (U+1F1EB U+1F1F7 -> 🇫🇷) -- that ImFont::RenderText cannot, because RenderText is codepoint-by-codepoint and never consults the font's GSUB table.

Why opt-in, not in RenderText

Text shaping is a historically sensitive topic (see #3903, #6186, etc.). This PR takes the least intrusive approach: a separate RenderShapedText function that renderers needing ligatures call explicitly. The existing RenderText hot path is untouched -- zero risk for the standard text rendering path. Consumers that don't call RenderShapedText see no change.

What (all in misc/freetype/imgui_freetype.cpp + .h, guarded by IMGUI_ENABLE_HARFBUZZ_SHAPING)

  • An hb_font_t* per FT_Face (hb_ft_font_create in InitFont, destroyed in CloseFont).
  • A per-(src,baked) shaped-glyph cache (ImVector<ImFontGlyph> + ImGuiStorage glyph-index -> cache slot) for ligature glyphs that have no Unicode codepoint and so cannot live in ImFontBaked's codepoint-indexed Glyphs/IndexLookup tables.
  • ImGui_ImplFreeType_BakeGlyphByID: bake a glyph by its FT glyph index (skipping FT_Get_Char_Index) on demand, pack+blit into the atlas, set UVs/Colored, cache it.
  • ImGuiFreeType::RenderShapedText: hb_shape the cluster, then for each shaped glyph bake-by-ID and emit a quad with the shaped advance, mirroring RenderText's mid-frame texture-growth retry (cmd_count / goto begin).

The shaped-glyph cache lives in FontSrcBakedData (the FreeType backend's per-baked storage), not in the public ImFontBaked struct -- so no public struct ABI change.

Build

IMGUI_ENABLE_HARFBUZZ_SHAPING requires HarfBuzz headers + lib to be available when defined. imgui itself doesn't add a build-system target for HarfBuzz (imgui ships as sources); the consumer's build system links HarfBuzz when the flag is set (e.g. the cimgui-pack CMake option, see the companion PR JuliaImGui/cimgui-pack#20). When the flag is not defined, the shaping code is absent and the build is identical to today's FreeType builder.

Verification

Compiled with and without IMGUI_ENABLE_HARFBUZZ_SHAPING (clang, FreeType 2.13/2.14 + HarfBuzz 8.5/14.3):

  • with: imgui_freetype.cpp compiles, RenderShapedText symbol present;
  • without: identical object to the current FreeType builder (shaping code excluded by the guard).

A downstream TUI cell-grid renderer (ManyUI / ManyUICImGui, ImGui 1.92 docking via cimgui-pack, with the companion FreeType option PR) renders 👨‍👩‍👧‍👦 as a single family ligature and 🇫🇷 as a single flag glyph using RenderShapedText, where AddText (codepoint-by-codepoint) renders them as 4 separate people / "FR" letters.

Companion PRs:

  • ocornut/imgui#9517 -- FreeType color glyph (BGRA) fix (independent, complementary: that PR fixes color rendering, this PR adds shaping).
  • JuliaImGui/cimgui-pack#20 -- CMake option CIMGUI_ENABLE_FREETYPE + IMGUI_USE_WCHAR32 (the build-side enablement).

AI-assisted.