shapeText function

List<ShapedGlyph> shapeText(
  1. String text,
  2. TextStyle style,
  3. TtfFont font, {
  4. String fontKey = '',
})

Converts text into a list of ShapedGlyph objects using font.

Handles:

  • Surrogate pairs (via String.runes)
  • TextStyle.letterSpacing added to every glyph's advance
  • TextStyle.wordSpacing added to the advance of U+0020 SPACE
  • Pair kerning via TtfFont.getKerning

Glyphs for unmapped code points are represented by the font's .notdef glyph (ID 0) so the advance width is still accounted for.

Implementation

List<ShapedGlyph> shapeText(String text, TextStyle style, TtfFont font,
    {String fontKey = ''}) {
  final double fontSize = style._fontSize ?? 14.0;
  final double letterSpacing = style._letterSpacing ?? 0.0;
  final double wordSpacing = style._wordSpacing ?? 0.0;

  // Resolve colour (bit 1 of encoded[0]).
  Color color = const Color(0xFF000000);
  if ((style._encoded[0] & (1 << 1)) != 0) {
    color = Color(style._encoded[1]);
  }

  // Resolve text decorations (bit 2 of encoded[0] → mask in encoded[2]).
  final int decorationMask =
      (style._encoded[0] & (1 << 2)) != 0 ? style._encoded[2] : 0;

  // Resolve decoration colour (bit 3 of encoded[0] → ARGB in encoded[3]).
  final Color? decorationColor =
      (style._encoded[0] & (1 << 3)) != 0 ? Color(style._encoded[3]) : null;

  // Resolve shadows from the direct _shadows field.
  final List<Shadow>? shadows =
      (style._shadows != null && style._shadows.isNotEmpty)
          ? style._shadows
          : null;

  final List<ShapedGlyph> result = [];

  for (final codePoint in text.runes) {
    // Newlines have zero advance and no ink; they are handled by the layout
    // engine as hard line breaks.
    if (codePoint == 0x0A) {
      result.add(ShapedGlyph(
        codePoint: codePoint,
        glyphId: 0,
        font: font,
        fontSize: fontSize,
        advance: 0,
        color: color,
        decorationMask: decorationMask,
        decorationColor: decorationColor,
        shadows: shadows,
        fontKey: fontKey,
      ));
      continue;
    }

    // Map code point → glyph ID; fall back to .notdef (ID 0).
    final int glyphId = font.getGlyphId(codePoint) ?? 0;

    // Base advance from font hmtx table.
    double advance = font.getAdvanceWidth(glyphId, fontSize);

    // Pair kerning with the previous non-newline glyph.
    if (result.isNotEmpty && !result.last.isNewline) {
      advance += font.getKerning(result.last.glyphId, glyphId, fontSize);
    }

    // letterSpacing is added after every glyph.
    advance += letterSpacing;

    // wordSpacing is added for U+0020 SPACE.
    if (codePoint == 0x0020) {
      advance += wordSpacing;
    }

    result.add(ShapedGlyph(
      codePoint: codePoint,
      glyphId: glyphId,
      font: font,
      fontSize: fontSize,
      advance: advance,
      color: color,
      decorationMask: decorationMask,
      decorationColor: decorationColor,
      shadows: shadows,
      fontKey: fontKey,
    ));
  }

  return result;
}