/** * Text Renderer * * Handles rendering of text content including: * - Text with letter spacing * - Text decorations (underline, overline, line-through) * - Text shadows * - Webkit line clamp * - Text overflow ellipsis * - Paint order (fill/stroke) * - Font styles */ import { TextContainer } from '../../dom/text-container'; import { CSSParsedDeclaration } from '../../css'; import { Bounds } from '../../css/layout/bounds'; import { TextBounds } from '../../css/layout/text'; import { WRITING_MODE } from '../../css/property-descriptors/writing-mode'; /** * Dependencies required for TextRenderer */ export interface TextRendererDependencies { ctx: CanvasRenderingContext2D; options: { scale: number; }; } export declare const hasCJKCharacters: (text: string) => boolean; export declare const getTextStrokeLineJoin: () => CanvasLineJoin; /** * Text Renderer * * Specialized renderer for text content. * Extracted from CanvasRenderer to improve code organization and maintainability. */ export declare class TextRenderer { private readonly ctx; private readonly options; private readonly decorationRenderer; /** * Baseline ascent per font string. Consecutive text nodes overwhelmingly * share a font, and each measureBaseline call issues a canvas measureText, * so repeated fonts are memoised (bounded; cleared if the working set * exceeds the cap). */ private readonly baselineCache; /** * Per-render glyph width cache. Keyed by glyph + font string, * avoids redundant `measureText` calls when letter-spacing is non-zero. * Cleared at the start of each renderTextNode call. */ private glyphWidthCache; constructor(deps: TextRendererDependencies); /** * Returns the cached width for a glyph+font pair, measuring via * measureText if not yet cached. Only used when letterSpacing !== 0 * (the `canRenderWholeText` fast path handles the zero-spacing case). */ private cachedMeasureText; /** * Iterate grapheme clusters one-by-one, applying correct letter-spacing. * * Issue #73: When letter-spacing is non-zero, text must be rendered character by * character, adding `letterSpacing` to each character's advance width. * * In horizontal text every glyph keeps the alphabetic baseline, CJK included: * inline layout aligns a whole line on its alphabetic baseline regardless of * script, and `baseline` was measured for that. Drawing CJK graphemes at the * same y with `textBaseline = 'ideographic'` lifts them above the Latin text * next to them, and away from where the `letterSpacing === 0` whole-string * path draws them. * * The `renderFn` callback receives (letter, x, y) and performs the actual draw * call (fillText or strokeText), allowing fill and stroke paths to share one * implementation. */ private iterateLettersWithLetterSpacing; private iterateVerticalGlyphs; /** * Render text with letter-spacing applied (fill pass). * When letterSpacing is 0 the whole string is drawn in one call; otherwise each * grapheme is drawn individually so spacing is applied correctly. */ renderTextWithLetterSpacing(text: TextBounds, letterSpacing: number, baseline: number, writingMode?: WRITING_MODE): void; private canRenderWholeText; private renderFillText; private renderStrokeText; private renderTextStrokeWithStyle; private renderTextFillWithShadows; /** * Helper method to render text with paint order support * Reduces code duplication in line-clamp and normal rendering */ private renderTextBoundWithPaintOrder; private renderTextDecoration; private truncateTextWithEllipsis; /** * Create font style array * Public method used by list rendering */ createFontStyle(styles: CSSParsedDeclaration): string[]; /** * -webkit-text-fill-color overrides the fill colour while leaving * decorations and strokes on `color` (e.g. the classic transparent-fill * gradient text). Falls back to `color` when the computed value could not * be resolved (currentcolor). */ private resolveFillColor; /** * Render text with -webkit-line-clamp truncation. * Groups text bounds by their Y position into visual lines, then renders * only the first N-1 complete lines followed by an ellipsis on the Nth line. */ private renderLineClampedText; /** * Render single-line text with text-overflow: ellipsis. * Returns true if ellipsis was applied (caller should skip normal rendering). */ private renderEllipsisText; private measureBaselineCached; renderTextNode(text: TextContainer, styles: CSSParsedDeclaration, containerBounds?: Bounds): Promise; /** * Draw text-emphasis marks over (or under) each grapheme cluster. * * Vertical writing modes are skipped in the current scope: marks would * need to sit beside vertical glyph runs rather than above them. */ private renderTextEmphasis; }