Text

Text, spans, glyphs, fonts and measurement.

App.tsx
1import { Canvas, Rect, Span, Text, measureText } from 'react-native-tgfx'
2
3const words = 'Lay out, measure, then draw.'
4const style = { fontSize: 64, width: 340 }
5const box = measureText(words, style)
6
7<Canvas style={{ flex: 1 }}>
8 <Rect x={40} y={140 + box.ascent}
9 width={box.width} height={box.height}
10 style="stroke" color="#7c3aed" />
11 <Text {...style} x={40} y={140}>
12 <Span text="Lay out, " color="#0ea5e9" />
13 <Span text="measure, " color="#ec4899" />
14 <Span text="then draw." color="#71717a" />
15 </Text>
16</Canvas>
9:41
import { Canvas, Text } from "react-native-tgfx";

<Canvas style={{ width: 300, height: 140 }}>
  <Text text="Hello" x={20} y={60} fontSize={28} color="#f5f7fa" />
</Canvas>;

TextProps · TextStyle · FontWeight

y is the baseline. A width wraps the text.

Spans

Span (SpanProps) styles a run inside one paragraph.

<Text x={0} y={40} width={220} fontSize={18}>
  <Span text="Total " />
  <Span text="4 500" fontWeight={700} color="#d98a4a" />
  <Span text=" credits" />
</Text>

Fonts

The fonts prop on Canvas loads fonts before the children render:

<Canvas
  fonts={{
    "PT Serif": [
      require("./PT_Serif-Web-Regular.ttf"),
      require("./PT_Serif-Web-Bold.ttf"),
    ],
  }}
>
  <Text fontFamily="PT Serif" x={16} y={40} text="ready when you see me" />
</Canvas>

A failed load warns and renders anyway.

useFonts returns readiness. loadFont and loadFonts work outside React. registeredFontFamilies lists registered families.

const ready = useFonts({
  "PT Serif": [
    require("./PT_Serif-Web-Regular.ttf"),
    require("./PT_Serif-Web-Bold.ttf"),
  ],
});

Each entry is a FontSource. Use useFontsState to see load errors:

const { ready, error } = useFontsState({
  "PT Serif": [require("./PT_Serif-Web-Regular.ttf")],
});

Measurement

measureText returns TextMeasurement with per-line LineMeasurement.

const box = measureText("measured", { fontSize: 22, fontFamily: "PT Serif" });
const { lines } = measureText(label, { fontSize: 18, width: 200 });

Bidi and complex scripts

Complex scripts and mixed-direction text are shaped and reordered.

<Text x={0} y={40} text="مرحبا بالعالم" fontSize={24} fontFamily="Noto Naskh Arabic" />
<Text x={0} y={80} text="ab مرحبا cd" fontSize={24} fontFamily="Noto Naskh Arabic" />

The first strong character sets the direction. Right-to-left text still needs textAlign="right".

A script still needs a face that covers it:

await loadFonts({
  "Noto Naskh Arabic": [require("./NotoNaskhArabic-Regular.ttf")],
});

Otherwise glyphs draw blank, with a warning.

Direction marks U+200E, U+200F and U+061C work. Embedding, override and isolate controls, bracket pairing (N0) and per-line direction (P1) do not.

Hit testing

glyphPositionAtCoordinate maps a point to an insertion point. rectsForRange maps a byte range to boxes. Subtract the <Text> x/y from the touch point first.

const style = { fontSize: 17, fontFamily: "PT Serif", width: 320 };

const at = glyphPositionAtCoordinate(text, style, touch.x - x, touch.y - y);
// at.index is a UTF-8 byte offset; at.leading says which half of the glyph it
// landed on, and at.line which line it resolved to after clamping.

const boxes = rectsForRange(text, style, selection.start, selection.end);
// One box per line the range crosses; a one-glyph range is that glyph's bounds.

Not every offset has a box. Fall back to the glyph before it:

const at = rectsForRange(text, style, caret, caret + 1)[0];
const before =
  at == null ? rectsForRange(text, style, caret - 1, caret)[0] : undefined;
const x = at ? at.x : before ? before.x + before.width : 0;
const rule = at ?? before;

Each call runs layout, like measureText, so avoid it every frame. rectsForRange returns TextBox.

Offsets are UTF-8 bytes, like startIndex on LineMeasurement. They match JS string indices only for ASCII. TextBox.endIndex is exclusive.

{
  boxes.map((box, i) => (
    <Rect
      key={i}
      x={box.x}
      y={box.y}
      width={box.width}
      height={box.height}
      color="#cfe4ff"
    />
  ));
}

Text as a path

textToPath returns text as SVG path data.

const outline = textToPath('outlined', { fontSize: 44, fontFamily: 'PT Serif' })

<Group transform={[{ translateX: 8 }, { translateY: 60 }]}>
  <Path path={outline} color="#d98a4a" style="stroke" strokeWidth={2} />
</Group>

TextPath lays text along a path.

Glyphs

glyphIds plus Glyphs place each glyph yourself (PlacedGlyph).

const style = { fontSize: 32, fontFamily: 'PT Serif' }
const ids = glyphIds('wave', style)

<Glyphs
  x={20}
  y={60}
  {...style}
  glyphs={ids.map((id, i) => ({ id, x: i * 22, y: Math.sin(i) * 8 }))}
/>

On this page