UNPKG

@shopify/react-native-skia

Version:

High-performance React Native Graphics using Skia

146 lines (132 loc) 3.87 kB
import type { SkRSXform, SkTextBlob, SkPoint, SkFont, } from "../../../skia/types"; import type { DrawingContext, TextBlobProps, TextPathProps, TextProps, } from "../../types"; import { NodeType } from "../../types"; import { processPath } from "../datatypes"; import type { GlyphsProps } from "../../types/Drawings"; import { JsiDrawingNode } from "../DrawingNode"; import type { NodeContext } from "../Node"; export class TextNode extends JsiDrawingNode<TextProps, SkFont | null> { constructor(ctx: NodeContext, props: TextProps) { super(ctx, NodeType.Text, props); } protected deriveProps() { const { font } = this.props; if (!font) { return null; } return font; } draw({ canvas, paint }: DrawingContext) { const { text, x, y } = this.props; const font = this.derived; if (font === undefined) { throw new Error("TextNode: font hasn't been derived"); } if (font != null) { canvas.drawText(text, x, y, paint, font); } } } export class TextPathNode extends JsiDrawingNode< TextPathProps, SkTextBlob | null > { constructor(ctx: NodeContext, props: TextPathProps) { super(ctx, NodeType.TextPath, props); } deriveProps() { const path = processPath(this.Skia, this.props.path); const { font, initialOffset } = this.props; if (!font) { return null; } let { text } = this.props; const ids = font.getGlyphIDs(text); const widths = font.getGlyphWidths(ids); const rsx: SkRSXform[] = []; const meas = this.Skia.ContourMeasureIter(path, false, 1); let cont = meas.next(); let dist = initialOffset; for (let i = 0; i < text.length && cont; i++) { const width = widths[i]; dist += width / 2; if (dist > cont.length()) { // jump to next contour cont = meas.next(); if (!cont) { // We have come to the end of the path - terminate the string // right here. text = text.substring(0, i); break; } dist = width / 2; } // Gives us the (x, y) coordinates as well as the cos/sin of the tangent // line at that position. const [p, t] = cont.getPosTan(dist); const adjustedX = p.x - (width / 2) * t.x; const adjustedY = p.y - (width / 2) * t.y; rsx.push(this.Skia.RSXform(t.x, t.y, adjustedX, adjustedY)); dist += width / 2; } return this.Skia.TextBlob.MakeFromRSXform(text, rsx, font); } draw({ canvas, paint }: DrawingContext) { if (!this.derived) { throw new Error("TextPathNode: blob is null"); } canvas.drawTextBlob(this.derived, 0, 0, paint); } } export class TextBlobNode extends JsiDrawingNode<TextBlobProps, null> { constructor(ctx: NodeContext, props: TextBlobProps) { super(ctx, NodeType.TextBlob, props); } protected deriveProps() { return null; } draw({ canvas, paint }: DrawingContext) { const { blob, x, y } = this.props; canvas.drawTextBlob(blob, x, y, paint); } } interface ProcessedGlyphs { glyphs: number[]; positions: SkPoint[]; } export class GlyphsNode extends JsiDrawingNode<GlyphsProps, ProcessedGlyphs> { constructor(ctx: NodeContext, props: GlyphsProps) { super(ctx, NodeType.Glyphs, props); } deriveProps() { return this.props.glyphs.reduce<ProcessedGlyphs>( (acc, glyph) => { const { id, pos } = glyph; acc.glyphs.push(id); acc.positions.push(pos); return acc; }, { glyphs: [], positions: [] } ); } draw({ canvas, paint }: DrawingContext) { if (!this.derived) { throw new Error("GlyphsNode: processedGlyphs is null"); } const { glyphs, positions } = this.derived; const { x, y, font } = this.props; if (font) { canvas.drawGlyphs(glyphs, positions, x, y, font, paint); } } }