modern-text
Version:
Measure and render text in a way that describes the DOM.
244 lines (226 loc) • 11 kB
text/typescript
import { P as Plugin, T as TextMeasurer, a as Paragraph, M as MeasurerResult, C as Character, O as Options, b as MeasureResult, R as RenderOptions, c as Text } from './shared/modern-text.DzKfafj6.mjs';
export { d as Canvas2DRenderer, D as DrawShapePathsOptions, F as Fragment, e as TextEvents, t as textDefaultStyle } from './shared/modern-text.DzKfafj6.mjs';
import { Fonts } from 'modern-font';
import { FullStyle, NormalizedStyle, NormalizedHighlight, NormalizedEffect } from 'modern-idoc';
import { BoundingBox, Vector2, Path2DSet, Transform2D } from 'modern-path2d';
import { D as DeformationPreset } from './shared/modern-text.C2l8Y0PX.mjs';
export { B as BendContext, a as BendPreset, C as CurvePreset, b as DeformationCharInfo, c as DeformationCurve, F as FfdContext, d as FfdPreset, O as OffsetPreset, V as VerbatimContext } from './shared/modern-text.C2l8Y0PX.mjs';
declare function definePlugin(options: Plugin): Plugin;
/**
* The pure-JS, DOM-free text measurer (the only built-in backend).
*
* It computes the four-level boxes (`character.inlineBox`/`lineBox`, `fragment.inlineBox`,
* `paragraph.lineBox`) directly from `modern-font` glyph advances + kerning — no DOM, so it
* runs in Node/SSR/Worker and measures the *exact* font that is rendered (advances come from
* the same SFNT the glyph paths do, so layout and rendering are pixel-consistent by construction).
*
* ## Scope
* - `horizontal-tb` (LTR) and `vertical-rl` (columns right-to-left, glyphs top↓)
* - line breaking: `word-break: break-all` (greedy, break anywhere) + explicit `\n`
* - per-line `text-align` (start/left/center/end/right), `text-indent` (first line)
* - box model: root + paragraph `padding`/`margin` (horizontal), `letter-spacing`,
* `line-height`; block `vertical-align` (top/middle/bottom) at fixed height
* - pair kerning (GPOS `kern` feature / legacy `kern` table) folded into break + positioning
*
* ## Not yet implemented (TODO)
* - UAX#14 `word-break: normal` + 避头尾/kinsoku (`line-break`)
* - BiDi, per-fragment inline `vertical-align`, borders
* - vertical: per-paragraph margin/padding and block alignment
* - ligatures (GSUB) — fine for CJK and most UI Latin
*
* Coordinates are relative to the root border-box top-left.
*/
declare class Measurer implements TextMeasurer {
measure(paragraphs: Paragraph[], rootStyle: FullStyle, _dom?: HTMLElement, fonts?: Fonts): MeasurerResult;
protected _rootPadding(rootStyle: FullStyle): {
top: number;
right: number;
bottom: number;
left: number;
};
protected _measureHorizontal(paragraphs: Paragraph[], rootStyle: FullStyle): MeasurerResult;
/**
* Vertical writing-mode (`vertical-rl`): columns stack right-to-left, glyphs
* flow top→bottom. It is the horizontal layout with the inline and block axes
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox stays
* accurate even though inlineBox carries the content-box rounding residual.
*
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
*/
protected _measureVertical(paragraphs: Paragraph[], rootStyle: FullStyle): MeasurerResult;
/** Advance step including CSS letter-spacing (px). */
protected _advance(character: Character): number;
/**
* Break a paragraph's characters into visual lines.
* v1: `word-break: break-all` (break before any character that would overflow)
* plus explicit `\n`/`\r` hard breaks. The newline itself occupies no line box.
*/
protected _breakLines(paragraph: Paragraph, avail: number): Character[][];
protected _applyKerning(paragraphs: Paragraph[]): void;
protected _unionInto(target: BoundingBox, boxes: BoundingBox[]): void;
protected _shiftAll(paragraphs: Paragraph[], dy: number): void;
dispose(): void;
}
declare function measureText(options: Options, load: true): Promise<MeasureResult>;
declare function measureText(options: Options): MeasureResult;
type RenderTextOptions = Options & RenderOptions;
declare function renderText(options: RenderTextOptions, load: true): Promise<void>;
declare function renderText(options: RenderTextOptions): void;
declare function backgroundPlugin(): Plugin;
/**
* 这些曲线类用于「逐字(-by-word)」沿形状排布文字。
*
* 旧版变形代码针对 `modern-path2d@0.2.5` 编写,该版本内置了
* `CircleCurve` / `EllipseCurve` / `HeartCurve` / `PolygonCurve` / `RectangularCurve`,
* 当前版本(1.6.x)已不再导出。这里从 0.2.5 原样移植其实现,
* 仅做 Vector2 API 适配(`copy`→`copyFrom`、`new Vector2().lerpVectors`→`Vector2.lerp`),
* 以保证逐字排布效果与原版一致。
*
* 仅保留 deformer 实际用到的方法:getPoint / getPointAt / getTangent / getNormal。
*/
declare abstract class ShapeCurve {
arcLengthDivisions: number;
protected _cacheArcLengths?: number[];
protected _needsUpdate: boolean;
abstract getPoint(t: number, output?: Vector2): Vector2;
getPointAt(u: number, output?: Vector2): Vector2;
getLength(): number;
getLengths(divisions?: number): number[];
getUToTMapping(u: number, distance?: number): number;
getTangent(t: number, output?: Vector2): Vector2;
getNormal(t: number, output?: Vector2): Vector2;
}
/**
* 分段曲线基类:由若干子曲线拼成,`getCurve(t)` 选中子曲线并把 `curveT` 设为其局部参数,
* 其余取点/切线/法线统一委托给选中的子曲线。
*/
declare abstract class SegmentedCurve extends ShapeCurve {
protected curveT: number;
abstract getCurve(t: number): ShapeCurve;
getPoint(t: number, output?: Vector2): Vector2;
getPointAt(u: number, output?: Vector2): Vector2;
getTangent(t: number, output?: Vector2): Vector2;
getNormal(t: number, output?: Vector2): Vector2;
}
declare class LineShapeCurve extends ShapeCurve {
start: Vector2;
end: Vector2;
constructor(start: Vector2, end: Vector2);
getPoint(t: number, output?: Vector2): Vector2;
getPointAt(u: number, output?: Vector2): Vector2;
getTangent(_t: number, output?: Vector2): Vector2;
}
declare class CircleCurve extends ShapeCurve {
center: Vector2;
radius: number;
start: number;
end: number;
constructor(center: Vector2, radius: number, start?: number, end?: number);
getPoint(t: number, output?: Vector2): Vector2;
getTangent(t: number, output?: Vector2): Vector2;
getNormal(t: number, output?: Vector2): Vector2;
}
declare class EllipseCurve extends ShapeCurve {
center: Vector2;
radiusX: number;
radiusY: number;
rotation: number;
startAngle: number;
endAngle: number;
clockwise: boolean;
constructor(center?: Vector2, radiusX?: number, radiusY?: number, rotation?: number, startAngle?: number, endAngle?: number, clockwise?: boolean);
getPoint(t: number, output?: Vector2): Vector2;
}
declare class HeartCurve extends SegmentedCurve {
center: Vector2;
size: number;
start: number;
end: number;
protected curves: ShapeCurve[];
constructor(center: Vector2, size: number, start?: number, end?: number);
update(): this;
getCurve(t: number): ShapeCurve;
}
declare class PolygonCurve extends SegmentedCurve {
center: Vector2;
radius: number;
number: number;
start: number;
end: number;
protected curves: LineShapeCurve[];
protected points: Vector2[];
constructor(center: Vector2, radius?: number, number?: number, start?: number, end?: number);
update(): this;
getCurve(t: number): LineShapeCurve;
}
declare class RectangularCurve extends SegmentedCurve {
center: Vector2;
rx: number;
aspectRatio: number;
start: number;
end: number;
protected curves: LineShapeCurve[];
constructor(center: Vector2, rx: number, aspectRatio?: number, start?: number, end?: number);
update(): this;
getCurve(t: number): LineShapeCurve;
}
/**
* 注册一个变形预设(库外部定义,运行时透传)。
* 官方预设包见子入口 `modern-text/deformations`。
*/
declare function defineDeformation(name: string, preset: DeformationPreset): void;
/** 取消注册某个预设 */
declare function removeDeformation(name: string): void;
/** 当前已注册的预设名列表 */
declare function getDeformationNames(): string[];
/**
* 文字变形插件(纯引擎)。
*
* 通过 `text.deformation` 指定**已注册**的预设名:
* ```ts
* import { registerDeformations } from 'modern-text/deformations'
* registerDeformations()
* new Text({ content: 'abc', deformation: { type: 'bend', intensities: [50] } })
* ```
*/
declare function deformationPlugin(): Plugin;
declare function getHighlightStyle(style: NormalizedStyle): NormalizedHighlight;
declare function highlightPlugin(): Plugin;
declare function listStylePlugin(): Plugin;
declare function outlinePlugin(): Plugin;
declare function renderPlugin(): Plugin;
declare function textDecorationPlugin(): Plugin;
interface SvgLoader {
loaded: Map<string, string>;
needsLoad: (source: string) => boolean;
load: (svg: string) => Promise<void>;
}
declare function createSvgLoader(): SvgLoader;
interface SvgParser {
parsed: Map<string, {
dom: SVGElement;
pathSet: Path2DSet;
}>;
parse: (svg: string) => {
dom: SVGElement;
pathSet: Path2DSet;
};
}
declare function createSvgParser(loader: SvgLoader): SvgParser;
declare function getEffectTransform2D(text: Text, effect: NormalizedEffect): Transform2D;
declare function parseTransformOrigin(origin: string, left: number, top: number, width: number, height: number): {
x: number;
y: number;
};
interface ValueContext {
total: number;
fontSize: number;
}
declare function parseValueNumber(value: string | number, ctx: ValueContext): number;
declare function parseColormap(colormap: 'none' | Record<string, string>): Record<string, string>;
declare function isEqualObject(obj1: Record<string, any>, obj2: Record<string, any>): boolean;
declare function isEqualValue(val1: any, val2: any): boolean;
export { Character, CircleCurve, DeformationPreset, EllipseCurve, HeartCurve, MeasureResult, Measurer, MeasurerResult, Options, Paragraph, Plugin, PolygonCurve, RectangularCurve, RenderOptions, Text, TextMeasurer, backgroundPlugin, createSvgLoader, createSvgParser, defineDeformation, definePlugin, deformationPlugin, getDeformationNames, getEffectTransform2D, getHighlightStyle, highlightPlugin, isEqualObject, isEqualValue, listStylePlugin, measureText, outlinePlugin, parseColormap, parseTransformOrigin, parseValueNumber, removeDeformation, renderPlugin, renderText, textDecorationPlugin };
export type { RenderTextOptions, SvgLoader, SvgParser };