import { NormalizedStyle, NormalizedFill, NormalizedOutline, FullStyle, LinearGradientWithType, RadialGradientWithType, NormalizedEffect, NormalizedShadow, TextObject, ReactivableEvents, Reactivable, NormalizedText } from 'modern-idoc';
import { SFNT, Fonts } from 'modern-font';
import { BoundingBox, Path2D, Vector2, Vector2Like, Path2DStyle, Path2DSet } from 'modern-path2d';

declare class Fragment {
    readonly content: string;
    readonly style: NormalizedStyle;
    readonly fill: NormalizedFill | undefined;
    readonly outline: NormalizedOutline | undefined;
    readonly index: number;
    readonly parent: Paragraph;
    inlineBox: BoundingBox;
    characters: Character[];
    computedStyle: FullStyle;
    computedFill: NormalizedFill | undefined;
    computedOutline: NormalizedOutline | undefined;
    get computedContent(): string;
    constructor(content: string, style: NormalizedStyle | undefined, fill: NormalizedFill | undefined, outline: NormalizedOutline | undefined, index: number, parent: Paragraph);
    update(): this;
    initCharacters(): this;
}

interface GlyphTemplate {
    path: Path2D;
    glyphBox?: BoundingBox;
}
interface FontMetrics {
    sfnt: SFNT;
    unitsPerEm: number;
    advanceHeight: number;
    baseline: number;
    ascender: number;
    descender: number;
    underlinePosition: number;
    underlineThickness: number;
    strikeoutPosition: number;
    strikeoutSize: number;
    typoAscender: number;
    typoDescender: number;
    typoLineGap: number;
    winAscent: number;
    winDescent: number;
    xHeight: number;
    capHeight: number;
    centerDiviation: number;
    fontStyle?: 'bold' | 'italic';
}
declare class Character {
    content: string;
    index: number;
    parent: Fragment;
    protected _path?: Path2D<Character>;
    protected _lazyPath?: {
        tmpl: GlyphTemplate;
        x: number;
        y: number;
        style: any;
    };
    get path(): Path2D<Character>;
    set path(value: Path2D<Character>);
    inlineBox: BoundingBox;
    get lineBox(): BoundingBox;
    glyphBox?: BoundingBox;
    advanceWidth: number;
    kerningBefore: number;
    protected _glyphIndex?: number;
    protected _metrics?: FontMetrics;
    get advanceHeight(): number;
    get baseline(): number;
    get ascender(): number;
    get descender(): number;
    get underlinePosition(): number;
    get underlineThickness(): number;
    get strikeoutPosition(): number;
    get strikeoutSize(): number;
    get typoAscender(): number;
    get typoDescender(): number;
    get typoLineGap(): number;
    get winAscent(): number;
    get winDescent(): number;
    get xHeight(): number;
    get capHeight(): number;
    get centerDiviation(): number;
    get fontStyle(): 'bold' | 'italic' | undefined;
    translateY(dy: number): void;
    get compatibleGlyphBox(): BoundingBox;
    get center(): Vector2;
    get computedFill(): NormalizedFill | undefined;
    get computedOutline(): NormalizedOutline | undefined;
    get computedStyle(): FullStyle;
    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;
    computeKerningBefore(prev?: Character): number;
    /**
     * Populate glyph metrics only (advance width/height, ascender/descender,
     * baseline, …) without building the glyph `path` or touching boxes.
     *
     * The pure-JS `Measurer` must know advances *before* it can place characters,
     * so it calls this ahead of layout. `update()` later recomputes the same metrics
     * while building the path, so this is idempotent.
     */
    measureGlyph(fonts?: Fonts): this;
    update(fonts?: Fonts): this;
    protected _updateFromCache(sfnt: SFNT, style: FullStyle): void;
    protected _italic(path: Path2D, startPoint?: Vector2Like): void;
    getGlyphMinMax(min?: Vector2, max?: Vector2, withStyle?: boolean): {
        min: Vector2;
        max: Vector2;
    } | undefined;
    getGlyphBoundingBox(withStyle?: boolean): BoundingBox | undefined;
}

interface DrawShapePathsOptions extends Partial<Path2DStyle> {
    clipRect?: BoundingBox;
}
declare class Canvas2DRenderer {
    text: Text;
    context: CanvasRenderingContext2D;
    pixelRatio: number;
    region?: {
        x: number;
        y: number;
        width: number;
        height: number;
    };
    constructor(text: Text, context: CanvasRenderingContext2D);
    protected _setupView: () => void;
    protected _setupColors: () => void;
    setup: () => this;
    protected _parseColor: (source: string | CanvasGradient | CanvasPattern | LinearGradientWithType | RadialGradientWithType, box: BoundingBox) => string | CanvasGradient | CanvasPattern;
    protected _uploadedStyles: string[];
    uploadColor: (box: BoundingBox, ctx: {
        style?: NormalizedStyle;
        fill?: NormalizedFill;
        outline?: NormalizedOutline;
        effects?: NormalizedEffect[];
    }) => void;
    protected _mergePathStyle(path: Path2D, style: Partial<Path2DStyle>): Partial<Path2DStyle>;
    drawPath: (path: Path2D, options?: DrawShapePathsOptions) => void;
    effectToPathStyle(effect: NormalizedEffect): Partial<Path2DStyle>;
    transformEffect(effect: NormalizedEffect): void;
    protected _shadowCanvas?: HTMLCanvasElement;
    protected _shadowCtx?: CanvasRenderingContext2D | null;
    drawWithShadow: (shadow: NormalizedShadow, drawFn: () => void) => void;
    drawCharacter: (character: Character, effect?: NormalizedEffect) => void;
}

/**
 * A measurer's output: the paragraphs with their four-level boxes filled in place,
 * plus the overall bounding box. (Formerly `MeasureDomResult`; layout is now
 * DOM-free via {@link import('./Measurer').Measurer}.)
 */
interface MeasurerResult {
    paragraphs: Paragraph[];
    boundingBox: BoundingBox;
}
interface Plugin {
    name: string;
    pathSet?: Path2DSet;
    getBoundingBox?: (text: Text) => BoundingBox | undefined;
    update?: (text: Text) => void;
    updateOrder?: number;
    render?: (renderer: Canvas2DRenderer) => void;
    renderOrder?: number;
    load?: (text: Text) => Promise<void>;
    context?: Record<string, any>;
}
/**
 * Pluggable layout backend. Implementations fill the four-level boxes
 * (`character.inlineBox`/`lineBox`, `fragment.inlineBox`, `paragraph.lineBox`)
 * in place and return the overall bounding box.
 *
 * The built-in backend is the pure-JS, DOM-free {@link import('./Measurer').Measurer},
 * which resolves glyph advances/kerning from `modern-font` — it runs in Node/SSR/Worker
 * and measures the exact font that is rendered. `fonts` is passed positionally by
 * `Text.measure()`.
 */
interface TextMeasurer {
    measure: (paragraphs: Paragraph[], rootStyle: FullStyle, dom?: HTMLElement, fonts?: Fonts) => MeasurerResult;
    dispose?: () => void;
}
interface Options extends TextObject {
    debug?: boolean;
    measureDom?: HTMLElement;
    fonts?: Fonts;
    plugins?: Plugin[];
}

interface RenderOptions {
    view: HTMLCanvasElement;
    pixelRatio?: number;
    /** 只渲染 boundingBox 内的这一子块（相对偏移 + 尺寸），用于超大文字按 GPU 上限分块栅格。 */
    region?: {
        x: number;
        y: number;
        width: number;
        height: number;
    };
    onContext?: (context: CanvasRenderingContext2D) => void;
}
interface MeasureResult {
    paragraphs: Paragraph[];
    lineBox: BoundingBox;
    rawGlyphBox: BoundingBox;
    glyphBox: BoundingBox;
    pathBox: BoundingBox;
    boundingBox: BoundingBox;
}
declare const textDefaultStyle: FullStyle;
interface TextEvents extends ReactivableEvents {
    update: [ctx: {
        text: Text;
    }];
    measure: [ctx: {
        text: Text;
        result: MeasureResult;
    }];
    render: [ctx: {
        text: Text;
        view: HTMLCanvasElement;
        pixelRatio: number;
    }];
}
interface Text {
    on: <K extends keyof TextEvents & string>(event: K, listener: (...args: TextEvents[K]) => void) => this;
    once: <K extends keyof TextEvents & string>(event: K, listener: (...args: TextEvents[K]) => void) => this;
    off: <K extends keyof TextEvents & string>(event: K, listener: (...args: TextEvents[K]) => void) => this;
    emit: <K extends keyof TextEvents & string>(event: K, ...args: TextEvents[K]) => this;
}
declare class Text extends Reactivable {
    debug: boolean;
    content: NormalizedText['content'];
    style?: NormalizedText['style'];
    effects?: NormalizedText['effects'];
    fill?: NormalizedText['fill'];
    outline?: NormalizedText['outline'];
    deformation?: NormalizedText['deformation'];
    measureDom?: HTMLElement;
    fonts?: Fonts;
    needsUpdate: boolean;
    computedStyle: FullStyle;
    computedFill: NormalizedFill | undefined;
    computedOutline: NormalizedOutline | undefined;
    computedEffects: NormalizedEffect[];
    inlineBox: BoundingBox;
    lineBox: BoundingBox;
    rawGlyphBox: BoundingBox;
    glyphBox: BoundingBox;
    pathBox: BoundingBox;
    boundingBox: BoundingBox;
    measurer: TextMeasurer;
    plugins: Map<string, Plugin>;
    pathSets: Path2DSet[];
    protected _paragraphs: Paragraph[];
    protected _cachedCharacters?: Character[];
    incrementalLayout: boolean;
    protected _prevParagraphs: Paragraph[];
    protected _prevContentKeys: string[];
    protected _prevStyleKey: string;
    protected _prevFonts: unknown;
    protected _prevFontsSet: boolean;
    protected _prevFontSig: string;
    protected _pendingFontSig: string;
    protected _pendingContentKeys: string[];
    protected _pendingStyleKey: string;
    protected _pluginsByUpdateOrder: Plugin[];
    protected _pluginsByRenderOrder: Plugin[];
    protected _renderer?: Canvas2DRenderer;
    protected _rendererCtx?: CanvasRenderingContext2D;
    get paragraphs(): Paragraph[];
    set paragraphs(value: Paragraph[]);
    get fontSize(): number;
    get defaultFamily(): string;
    get isVertical(): boolean;
    get characters(): Character[];
    constructor(options?: Options);
    set(options?: Options): void;
    use(plugin: Plugin): this;
    protected _resortPlugins(): void;
    forEachCharacter(handle: (character: Character, ctx: {
        paragraphIndex: number;
        fragmentIndex: number;
        characterIndex: number;
    }) => void): this;
    load(): Promise<void>;
    /**
     * Eagerly decode the fonts this text uses, off the main thread — WOFF tables
     * are decompressed via modern-font's async `createSFNTAsync` (fflate async).
     * This warms the SFNT cache so the synchronous `measure()` / `render()` pass
     * never stalls the main thread inflating WOFF tables on first glyph access.
     *
     * No-op for already-decoded fonts and for formats without async decoding.
     */
    protected _decodeFonts(): Promise<number>;
    /**
     * Coerce numeric style fields to finite numbers.
     *
     * `normalizeText`/`normalizeStyle` only runs through the constructor, so a
     * `style` provided via direct assignment or `setPropertyAccessor` reaches
     * `computedStyle` un-normalized. An invalid numeric value (`''`, `NaN`,
     * `'10px'`, `'50%'`) would then poison layout arithmetic — e.g.
     * `textIndent ?? 0` keeps `''` (`??` does not catch empty strings), which
     * corrupts glyph positions and the text fails to render. Fall every numeric
     * field back to its default when it is not a finite number (parsing leading
     * numerics like `parseFloat`, matching the normalized path).
     */
    protected _normalizeComputedStyle(style: FullStyle): FullStyle;
    protected _buildParagraph(p: NormalizedText['content'][number], pIndex: number): Paragraph;
    /**
     * 本文本「已用字体族 → 当前解析到的字体源」的签名。
     * 遍历根/段落/片段声明的 fontFamily，各取 `fonts.get(family)?.src`（modern-font 里每个已加载字体/兜底
     * 字体的唯一源标识）拼成签名。某族未加载时 `get` 返回兜底字体（其 src 如 modern-font:embedded-fallback），
     * 加载完成后返回该字体（src=字体 URL）→ src 变化 → 签名变化，据此可靠感知「字体到位」并作废增量复用。
     */
    protected _computeFontSig(): string;
    protected _canReuseLayout(styleKey: string, fontSig: string): boolean;
    protected _update(): this;
    measure(dom?: HTMLElement | undefined): MeasureResult;
    getGlyphBox(): BoundingBox;
    protected _paragraphGlyphBox(paragraph: Paragraph): BoundingBox | undefined;
    protected _computeGlyphBox(): BoundingBox;
    protected _updateInlineBox(): this;
    protected _updatePathBox(): this;
    protected _updateBoundingBox(): this;
    requestUpdate(): this;
    update(dom?: HTMLElement | undefined): this;
    protected _getRenderer(ctx: CanvasRenderingContext2D): Canvas2DRenderer;
    render(options: RenderOptions): void;
    dispose(): void;
    toString(): string;
}

declare class Paragraph {
    readonly style: NormalizedStyle;
    readonly index: number;
    readonly parent: Text;
    lineBox: BoundingBox;
    fragments: Fragment[];
    fill?: NormalizedFill;
    outline?: NormalizedOutline;
    _layoutDirty: boolean;
    _layoutTop: number;
    _layoutHeight: number;
    _layoutRight: number;
    _glyphBox?: BoundingBox;
    _layoutValid: boolean;
    computedStyle: FullStyle;
    computedFill: NormalizedFill | undefined;
    computedOutline: NormalizedOutline | undefined;
    constructor(style: NormalizedStyle, index: number, parent: Text);
    update(): this;
}

export { Character as C, Fragment as F, Paragraph as a, Text as c, Canvas2DRenderer as d, textDefaultStyle as t };
export type { DrawShapePathsOptions as D, MeasurerResult as M, Options as O, Plugin as P, RenderOptions as R, TextMeasurer as T, MeasureResult as b, TextEvents as e };
