modern-text
Version:
Measure and render text in a way that describes the DOM.
390 lines (364 loc) • 13 kB
text/typescript
import { NormalizedStyle, TextContent, NormalizedTextContent, NormalizedHighlight } from 'modern-idoc';
import { BoundingBox, Path2DDrawStyle, Path2D, Vector2, VectorLike, Path2DSet, Matrix3 } from 'modern-path2d';
import { Fonts, SFNT } from 'modern-font';
declare function parseColor(ctx: CanvasRenderingContext2D, source: string | CanvasGradient | CanvasPattern, box: BoundingBox): string | CanvasGradient | CanvasPattern;
declare function uploadColor(style: Partial<NormalizedStyle>, box: BoundingBox, ctx: CanvasRenderingContext2D): void;
interface LinearGradient {
x0: number;
y0: number;
x1: number;
y1: number;
stops: {
offset: number;
color: string;
}[];
}
interface DrawShapePathsOptions extends Partial<NormalizedStyle>, Omit<Partial<Path2DDrawStyle>, 'fill' | 'outline'> {
ctx: CanvasRenderingContext2D;
path: Path2D;
fontSize: number;
clipRect?: BoundingBox;
}
declare function drawPath(options: DrawShapePathsOptions): void;
declare function setupView(ctx: CanvasRenderingContext2D, pixelRatio: number, boundingBox: BoundingBox): void;
declare class Fragment {
content: string;
style: Partial<NormalizedStyle>;
parent: Paragraph;
inlineBox: BoundingBox;
characters: Character[];
computedStyle: NormalizedStyle;
get computedContent(): string;
constructor(content: string, style: Partial<NormalizedStyle> | undefined, parent: Paragraph);
updateComputedStyle(): this;
initCharacters(): this;
}
declare class Character {
content: string;
index: number;
parent: Fragment;
path: Path2D;
lineBox: BoundingBox;
inlineBox: BoundingBox;
glyphBox?: BoundingBox;
advanceWidth: number;
advanceHeight: number;
underlinePosition: number;
underlineThickness: number;
strikeoutPosition: number;
strikeoutSize: number;
ascender: number;
descender: number;
typoAscender: number;
typoDescender: number;
typoLineGap: number;
winAscent: number;
winDescent: number;
xHeight: number;
capHeight: number;
baseline: number;
centerDiviation: number;
fontStyle?: 'bold' | 'italic';
get compatibleGlyphBox(): BoundingBox;
get center(): Vector2;
get computedStyle(): NormalizedStyle;
get isVertical(): boolean;
get fontSize(): number;
get fontHeight(): number;
constructor(content: string, index: number, parent: Fragment);
protected _getFontSFNT(fonts?: Fonts): SFNT | undefined;
updateGlyph(sfnt?: SFNT | undefined): this;
update(fonts?: Fonts): this;
protected _italic(path: Path2D, startPoint?: VectorLike): void;
getGlyphMinMax(min?: Vector2, max?: Vector2, withStyle?: boolean): {
min: Vector2;
max: Vector2;
} | undefined;
getGlyphBoundingBox(withStyle?: boolean): BoundingBox | undefined;
drawTo(ctx: CanvasRenderingContext2D, config?: Partial<NormalizedStyle>): void;
}
declare class Paragraph {
style: Partial<NormalizedStyle>;
parentStyle: NormalizedStyle;
lineBox: BoundingBox;
fragments: Fragment[];
computedStyle: NormalizedStyle;
constructor(style: Partial<NormalizedStyle>, parentStyle: NormalizedStyle);
updateComputedStyle(): this;
addFragment(content: string, style?: Partial<NormalizedStyle>): Fragment;
}
interface TextPlugin {
name: string;
pathSet?: Path2DSet;
getBoundingBox?: (text: Text$1) => BoundingBox | undefined;
update?: (text: Text$1) => void;
updateOrder?: number;
render?: (ctx: CanvasRenderingContext2D, text: Text$1) => void;
renderOrder?: number;
load?: (text: Text$1) => Promise<void>;
}
interface TextOptions {
debug?: boolean;
style?: Partial<NormalizedStyle>;
content?: TextContent;
effects?: Partial<NormalizedStyle>[];
measureDOM?: HTMLElement;
fonts?: Fonts;
plugins?: TextPlugin[];
}
type EventListenerValue<T = any> = (ev: T) => void;
type EventListenerOptions = boolean | AddEventListenerOptions;
interface EventListener<T = any> {
value: EventListenerValue<T>;
options?: EventListenerOptions;
}
declare class EventEmitter<T extends Record<string, any> = Record<string, any>> {
eventListeners: Map<keyof T, EventListener<any> | EventListener<any>[]>;
addEventListener<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this;
removeEventListener<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this;
removeAllListeners(): this;
hasEventListener(event: string): boolean;
dispatchEvent<K extends keyof T>(event: K, arg: T[K]): boolean;
on<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this;
once<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>): this;
off<K extends keyof T>(event: K, listener: EventListenerValue<T[K]>, options?: EventListenerOptions): this;
emit<K extends keyof T>(event: K, arg: T[K]): void;
}
interface MeasuredParagraph {
paragraphIndex: number;
left: number;
top: number;
width: number;
height: number;
}
interface MeasuredFragment {
paragraphIndex: number;
fragmentIndex: number;
left: number;
top: number;
width: number;
height: number;
}
interface MeasuredCharacter {
paragraphIndex: number;
fragmentIndex: number;
characterIndex: number;
newParagraphIndex: number;
content: string;
left: number;
top: number;
width: number;
height: number;
textHeight: number;
textWidth: number;
}
interface measureDOMResult {
paragraphs: Paragraph[];
boundingBox: BoundingBox;
}
declare class Measurer {
static notZeroStyles: Set<string>;
static pxStyles: Set<string>;
protected _toDOMStyle(style: Record<string, any>): Record<string, any>;
/**
* <section style="...">
* <ul>
* <li style="...">
* <span style="...">...</span>
* <span>...</span>
* </li>
* </ul>
* </section>
*/
createDOM(paragraphs: Paragraph[], rootStyle: NormalizedStyle): HTMLElement;
measureDOMText(text: Text): {
content: string;
top: number;
left: number;
width: number;
height: number;
}[];
measureDOM(dom: HTMLElement): {
paragraphs: MeasuredParagraph[];
fragments: MeasuredFragment[];
characters: MeasuredCharacter[];
};
measureParagraphDOM(paragraphs: Paragraph[], dom: HTMLElement): measureDOMResult;
measure(paragraphs: Paragraph[], rootStyle: NormalizedStyle, dom?: HTMLElement): measureDOMResult;
}
interface TextRenderOptions {
view: HTMLCanvasElement;
pixelRatio?: number;
onContext?: (context: CanvasRenderingContext2D) => void;
}
interface MeasureResult {
paragraphs: Paragraph[];
lineBox: BoundingBox;
rawGlyphBox: BoundingBox;
glyphBox: BoundingBox;
pathBox: BoundingBox;
boundingBox: BoundingBox;
}
declare const textDefaultStyle: NormalizedStyle;
interface TextEventMap {
update: {
text: Text$1;
};
measure: {
text: Text$1;
result: MeasureResult;
};
render: {
text: Text$1;
view: HTMLCanvasElement;
pixelRatio: number;
};
}
declare class Text$1 extends EventEmitter<TextEventMap> {
debug: boolean;
content: NormalizedTextContent;
style: Partial<NormalizedStyle>;
effects?: Partial<NormalizedStyle>[];
measureDOM?: HTMLElement;
needsUpdate: boolean;
computedStyle: NormalizedStyle;
paragraphs: Paragraph[];
lineBox: BoundingBox;
rawGlyphBox: BoundingBox;
glyphBox: BoundingBox;
pathBox: BoundingBox;
boundingBox: BoundingBox;
measurer: Measurer;
plugins: Map<string, TextPlugin>;
fonts?: Fonts;
get fontSize(): number;
get isVertical(): boolean;
get characters(): Character[];
constructor(options?: TextOptions);
set(options?: TextOptions): void;
use(plugin: TextPlugin): this;
forEachCharacter(handle: (character: Character, ctx: {
paragraphIndex: number;
fragmentIndex: number;
characterIndex: number;
}) => void): this;
load(): Promise<void>;
updateParagraphs(): this;
createDOM(): HTMLElement;
measure(dom?: HTMLElement | undefined): MeasureResult;
getGlyphBox(): BoundingBox;
updatePathBox(): this;
updateBoundingBox(): this;
requestUpdate(): this;
update(dom?: HTMLElement | undefined): this;
render(options: TextRenderOptions): void;
toString(): string;
}
declare function uploadColors(ctx: CanvasRenderingContext2D, text: Text$1): void;
declare function definePlugin(options: TextPlugin): TextPlugin;
declare function measureText(options: TextOptions, load: true): Promise<MeasureResult>;
declare function measureText(options: TextOptions): MeasureResult;
type RenderTextOptions = TextOptions & TextRenderOptions;
declare function renderText(options: RenderTextOptions, load: true): Promise<void>;
declare function renderText(options: RenderTextOptions): void;
declare function background(): TextPlugin;
declare function getHighlightStyle(style: NormalizedStyle): NormalizedHighlight;
declare function highlight(): TextPlugin;
declare function listStyle(): TextPlugin;
declare function outline(): TextPlugin;
declare function render(): TextPlugin;
declare function getTransform2D(text: Text$1, style: Partial<NormalizedStyle>): Matrix3;
declare function textDecoration(): TextPlugin;
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;
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;
declare function hexToRgb(hex: string): string | null;
declare function filterEmpty(val: Record<string, any> | undefined): Record<string, any> | undefined;
interface SelectableCharacter {
color: string;
left: number;
top: number;
width: number;
height: number;
content: string;
isFirst?: boolean;
isLast?: boolean;
isLastSelected?: boolean;
isCrlf?: boolean;
}
declare class TextEditor extends HTMLElement {
static observedAttributes: string[];
static register(): void;
left: number;
top: number;
width: number;
height: number;
pixelRatio: number;
text: Text$1;
composition: boolean;
selection: number[];
prevSelection: number[];
protected _oldText: string;
$preview: HTMLCanvasElement;
$textInput: HTMLTextAreaElement;
$cursor: HTMLElement;
$cursorInput: HTMLElement;
get selectionMinMax(): {
min: number;
max: number;
};
get selectedCharacters(): SelectableCharacter[];
get selectableCharacters(): SelectableCharacter[];
get cursorPosition(): {
left: number;
top: number;
width: number;
height: number;
color: string;
};
constructor();
update(options: TextOptions): void;
getTextValue(): string;
getContentValue(content: NormalizedTextContent, newString?: string, oldString?: string): NormalizedTextContent;
setTextInput(newText: string): void;
onInput(): void;
protected _timer?: any;
onKeydown(e: KeyboardEvent): void;
onFocus(e: Event): void;
onBlur(): void;
findNearest(options: {
x: number;
y: number;
xWeight?: number;
yWeight?: number;
}): number;
updateSelection(): void;
updateDOMSelection(): void;
onMousedown(e: MouseEvent): void;
render(): void;
attributeChangedCallback(name: string, oldValue: any, newValue: any): void;
}
export { Character, Fragment, Measurer, Paragraph, Text$1 as Text, TextEditor, background, createSVGLoader, createSVGParser, definePlugin, drawPath, filterEmpty, getHighlightStyle, getTransform2D, hexToRgb, highlight, isEqualObject, isEqualValue, listStyle, measureText, outline, parseColor, parseColormap, parseValueNumber, render, renderText, setupView, textDecoration, textDefaultStyle, uploadColor, uploadColors };
export type { DrawShapePathsOptions, LinearGradient, MeasureResult, MeasuredCharacter, MeasuredFragment, MeasuredParagraph, RenderTextOptions, SVGLoader, SVGParser, SelectableCharacter, TextEventMap, TextOptions, TextPlugin, TextRenderOptions, measureDOMResult };