Text
Text, spans, glyphs, fonts and measurement.
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 }))}
/>