@lalalandsynth I gave your screenshot to Claude:
Short version: the number you hand a JUCE/HISE Font is not the same quantity Figma calls "font size," and HISE's drawText doesn't lay glyphs out the same way Figma does. Those are two separate problems, and your three screenshots are actually showing both of them at once.
Why the size number is different (the 16-vs-12 clue)
Figma's font size is the em / point size — the classic typographic size. JUCE's plain Font(float) constructor does not take that. Historically it takes the font's total height (ascent + descent), which for most typefaces is larger than the em by roughly 1.2–1.35×. Your own result nails it: 16 in HISE ≈ 12 in Figma is a 1.333 ratio, which is exactly that height-to-em factor for your font. So when you type "12" into a HISE paint routine, you're really asking for a glyph whose height is 12px, which is a visibly bigger letter than a 12px-em Figma glyph.
The fix is to size by point height instead of raw height:
Newer JUCE / HISE: Font(FontOptions().withPointHeight(12.0f)), or someFont.withPointHeight(12.0f).
If you only have the old API, multiply your Figma px by the font's em-to-height factor (≈1.33 here) — but withPointHeight is the correct, font-independent way.
That single change is why your third screenshot ("make a text label and set it to 16") looks better and different: a real Label/TextLayout path applies proper metrics, whereas your hand-rolled paint routine was fighting the height/em confusion.
Why it drifts in the middle even when the ends line up
The accumulating spread in screenshot 1 (matches at the left, red creeps wider across the word) and the "fits end-to-end but wrong in the middle" in screenshot 2 are a layout/kerning problem, not a size problem. When you scaled the whole string to make the endpoints match, you corrected the total width but not the individual glyph advances — so every interior letter sits at a slightly wrong x. That happens because:
Your paint routine is almost certainly stepping glyph-by-glyph and summing advance widths (or letting JUCE's older glyph path do it) with no kerning / no HarfBuzz shaping, while Figma shapes the run with full kerning and fractional positioning. The per-pair differences add up left-to-right, which is exactly the linear drift you see.
Subpixel rounding: if positions are being rounded to integer pixels per glyph, the error accumulates too.
Fix: don't advance glyphs yourself. Use a juce::GlyphArrangement / TextLayout (or Label / drawText) so the same shaping and kerning is applied, rather than a manual per-character loop. In JUCE 8 the text backend switched to HarfBuzz shaping, which gets much closer to what a design tool does — worth knowing if HISE's JUCE version matters here.
The thing that's easy to miss: is HISE even using your font?
You labeled red as "paint routine font (not label)." If the exact TTF isn't loaded as a typeface and you're selecting it by name, JUCE will silently fall back to a default face with completely different metrics — which would also produce width drift and a size mismatch. Make sure the paint routine is drawing with the same embedded typeface (loaded via the loaded font / Typeface::createSystemTypefaceFor on the actual TTF bytes), not just Font("SomeName", ...). A name lookup that fails is the single most common cause of "my custom font is subtly wrong in JUCE."
What I'd do, in order
Confirm the paint routine is drawing the actual embedded TTF, not a name-matched fallback.
Switch sizing to withPointHeight() so the HISE number equals the Figma px number.
Replace any manual glyph-advance loop with GlyphArrangement/drawText so kerning and fractional positioning match.
Do those three and the red should sit on the black without the mid-word drift — and you won't need the 1.33 fudge anymore.
If you paste the actual HISE paint routine (the LAF/drawText snippet and how the font is created/loaded), I can point at the exact line that's causing each effect.

