/** * Shared types and defaults for text measurement and rendering. * * The default VALUES live in @polotno/schema (`@polotno/schema/defaults`, * zod-free) — the single source of truth the zod text schema also builds its * `.default()`s from. This module owns the loose runtime shape: the partial, * un-normalized element the render/measure entry points accept, plus the * call-site defaulting normalizeDesign would otherwise do. */ import { TEXT_DEFAULTS } from '@polotno/schema/defaults'; import type { TextElement } from '@polotno/schema'; export { TEXT_DEFAULTS }; /** * Interface representing a text element (or plain object with text properties) * Used for both rendering and measurement. The fields are the exact subset the * render path reads, with their types bound to the schema's `TextElement` so * shape drift fails to compile. */ export interface TextElementLike extends Partial> { text: string; width: number; a?: { width?: number; height?: number; fontSize?: number; opacity?: number; rotation?: number; x?: number; y?: number; }; } /** * Applies default values to a text element-like object */ export declare function applyTextDefaults>(element: T): T & typeof TEXT_DEFAULTS; /** * `textTransform` applied to one run of text, the way the canvas applies it. * * A renderer with a DOM declares `text-transform` and the browser does the * work. A renderer that lays out its own glyphs has to transform the characters * itself, and then it must match render-tag, which transforms each style RUN as * it measures it (its own `applyTextTransform`, `lib/layout.js:167`) — so call * this per segment, not per element. * The granularity is observable, and the right answer is the odd-looking one: * `capitalize` on `hello` gives "HeLlo" per run and "Hello" per element, * and render-tag draws "HeLlo". * * `capitalize` starts a word after punctuation as well as after whitespace, * which is what CSS does — minus the apostrophe, so `don't` stays `Don't` * instead of becoming `Don'T`. */ export declare function applyTextTransform(text: string, transform: string | undefined): string; /** * THE UNMEASURED-HEIGHT CONTRACT — the one statement of this rule; call sites * point here rather than restating it. * * A text element's height is DERIVED: whatever the text, font, width and * lineHeight lay out to. The editor caches the last measured value so * verticalAlign and backgrounds have a box to work with, and `height: 0` (or a * missing height) is how the format says that cache is empty — it is the text * model's prop default and what `normalizeDesign()` emits. * * The sentinel is in-band, so every renderer reading raw JSON has to know it. * Read as a literal zero-pixel box it is silently catastrophic: a shrink-to-fit * loop shrinks the font to its floor and the page comes out blank. Hence these * two helpers instead of ad-hoc `if (!element.height)` tests: * * - `hasMeasuredHeight` — gate what only makes sense against a real box * (fit-to-height, clipping, PowerPoint autofit). * - `resolveTextBoxHeight` — the box to lay out in, falling back to the content * height, so verticalAlign middle/bottom collapse to top instead of dragging * the text above the element's own y. * * The canvas is where the sentinel gets repaired: the editor's text model keeps * 0 until the first render measures the content and writes the real height. * Exporters can receive raw normalized JSON, so they honor the sentinel too. * * (Curved text has its own box rule; it lives with `resolveCurveBoxHeight` in * ./text-html, next to the code that implements it.) */ export declare function hasMeasuredHeight(element: { height?: number | null; }): boolean; /** * What counts as a numeric `lineHeight` string — the multiplier a CSS-ish * value denotes ("1.5", "150%"), or null when it denotes none ("auto", junk). * The syntax is a semantic rule, so it has one home: the editor's model * normalizes stored values with it (`normalizeLineHeight`) and * `lineHeightRatio` resolves raw JSON with it. */ export declare function parseNumericLineHeight(value: string): number | null; /** * The multiplier an element's `lineHeight` denotes, or null when it denotes * none ("auto", junk, absent). ONE rule for both forms: any finite multiplier * passes through, however odd (0 collapses lines, negatives stack them) — that * is what raw `fontSize * lineHeight` did before these helpers existed, and * what the editor model keeps. Real designs carry CSS-ish strings ("1.5", * "150%") and exporters read raw JSON, so those resolve here too. * * Use this where "no spacing specified" is itself meaningful (PowerPoint falls * back to its own font metrics); use `lineHeightRatio` where the caller needs * a number to multiply by. */ export declare function numericLineHeight(element: { lineHeight?: number | string; }): number | null; /** * `lineHeight` as a plain multiplier to multiply by — the DOM-free * approximation, resolving "auto" to the default rather than the font's own * metrics. Where a live DOM is available the canvas uses `resolveLineHeight` * (./text-html), which measures the real font: prefer that, and reach for this * in contexts that cannot measure (pptx, pdf, static CSS generation). */ export declare function lineHeightRatio(element: { lineHeight?: number | string; }): number; /** * The height to lay text out in: the measured box when there is one, else the * content height the caller just computed. See `hasMeasuredHeight`. * `contentHeight` is evaluated eagerly — when computing it costs a measurement * pass, branch on `hasMeasuredHeight` instead so the common path skips it. */ export declare function resolveTextBoxHeight(element: { height?: number | null; }, contentHeight: number): number; /** * How far `verticalAlign` shifts a text block down inside its box — the offset * the canvas applies as `paddingTop` (polotno/canvas/render-tag-element.tsx), * and the one every renderer has to reproduce or its text sits at a different * height than the editor showed. * * **Deliberately NOT clamped to >= 0.** When content overflows a small box under * `middle`/`bottom`, the canvas, html-export and Konva's native valign all keep * the negative delta and shift the content — and its background polygon — up * past the box top. Clamping pins overflowing text top-aligned instead, which is * a visible divergence from all three; pdf-export carried a local `Math.max(0, * …)` and did exactly that. A renderer that cannot draw outside its box should * clip at DRAW time, not by moving the text. * * An unmeasured height makes the content its own box, so both branches * collapse to 0 — see the contract above. */ export declare function verticalTextOffset(element: { height?: number | null; verticalAlign?: string; }, contentHeight: number): number;