@weasel-js/text 1.3.0 → 1.4.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as ResolvedTextStyle, T as TextStyle, S as StyledRun } from './test-seams-
|
|
2
|
-
export { B as BidiAnalysis, a as BidiReordering, b as BidiResolver, D as DEFAULT_TEXT_STYLE, L as LAYOUT_CACHE_STRUCTURAL_LIMIT, c as LAYOUT_CACHE_VARIANT_LIMIT, d as LaidOutCell, e as LaidOutDecoration, f as LaidOutGroup, g as LaidOutLineBox, h as LaidOutOutlineGlyph, i as LaidOutQuad, j as LaidOutRuns, k as LayoutRunsOpts, M as MARKDOWN_RUN_GRAMMAR, l as ResolvedRun, m as RunFlag, n as RunGrammar, o as RunMarker, p as SCRIPT_METRICS, q as TextAlign, r as TextDirection, s as TextPaint, t as cachedLayoutRuns, u as fontString, v as layoutRuns, w as markdownToRuns, x as resolveAlign, y as resolveRuns, z as resolveTextStyle, A as runsToMarkdown, C as runsToPlainText, E as toRuns } from './test-seams-
|
|
1
|
+
import { R as ResolvedTextStyle, T as TextStyle, S as StyledRun } from './test-seams-CxfS416_.js';
|
|
2
|
+
export { B as BidiAnalysis, a as BidiReordering, b as BidiResolver, D as DEFAULT_TEXT_STYLE, L as LAYOUT_CACHE_STRUCTURAL_LIMIT, c as LAYOUT_CACHE_VARIANT_LIMIT, d as LaidOutCell, e as LaidOutDecoration, f as LaidOutGroup, g as LaidOutLineBox, h as LaidOutOutlineGlyph, i as LaidOutQuad, j as LaidOutRuns, k as LayoutRunsOpts, M as MARKDOWN_RUN_GRAMMAR, l as ResolvedRun, m as RunFlag, n as RunGrammar, o as RunMarker, p as SCRIPT_METRICS, q as TextAlign, r as TextDirection, s as TextPaint, t as cachedLayoutRuns, u as fontString, v as layoutRuns, w as markdownToRuns, x as resolveAlign, y as resolveRuns, z as resolveTextStyle, A as runsToMarkdown, C as runsToPlainText, E as toRuns } from './test-seams-CxfS416_.js';
|
|
3
3
|
import { Rect } from '@weasel-js/geom';
|
|
4
4
|
import { FillStyle, Stroke } from '@weasel-js/paint';
|
|
5
5
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runs.ts","../src/runs/resolveRuns.ts","../src/measure/measureText.ts","../src/measure/measureTextBounds.ts","../src/measure/verticalAlign.ts","../src/measure/lineBoxes.ts","../src/markdownText.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Canonical inline-styling primitive for text nodes. A node's text is\n * either a plain `string` (treated as a single-run, default-styled fragment)\n * or `StyledRun[]` for rich content. `toRuns` is the funnel that normalizes\n * either form into the array shape used by the renderer.\n *\n * Every field except `text` is optional; missing fields fall back to the\n * node-level `TextStyle`. `bold`/`italic` are toggles; richer weight axes\n * (300/500/900) are out of scope for slice 1.\n */\n\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\nimport type { TextStyle } from './textStyle';\n\n/** A span of text with its own styling, as authored. Fields left absent\n * inherit from the node's text style — this is the difference between a run\n * and a fully resolved one. */\nexport interface StyledRun {\n text: string;\n bold?: boolean;\n italic?: boolean;\n fontFamily?: string;\n fontSize?: number;\n fill?: FillStyle;\n /** Outline over this run's glyphs. Overrides the node style's stroke;\n * absent inherits it. Only painted on the outline tier — see\n * {@link TextStyle.stroke}. */\n stroke?: Stroke;\n letterSpacing?: number;\n underline?: boolean;\n strikethrough?: boolean;\n /** Draw a rule above this run's ascent. Additive over the node style. */\n overline?: boolean;\n /**\n * Set this run as a superscript or subscript — a raised or lowered baseline\n * and a smaller size together, which is the pair `<sup>` and `<sub>` imply.\n *\n * A preset over the two primitives below, not a third mechanism: it supplies\n * a `baselineShift` and a `fontScale`, and naming either of those directly\n * overrides that half while leaving the other alone. The numbers are in\n * {@link SCRIPT_METRICS}.\n *\n * There is no node-level counterpart. A whole text node set as a superscript\n * is a smaller node moved up, which the pose already says better.\n */\n script?: 'super' | 'sub';\n /**\n * Raise (positive) or lower (negative) this run off the line's shared\n * baseline, in ems of the *inherited* font size — so a run's rise does not\n * shrink along with the run when `fontScale` also applies.\n */\n baselineShift?: number;\n /**\n * Multiplier on the inherited font size. The relative counterpart to\n * `fontSize`, which pins an absolute size and wins over this when both are\n * present.\n */\n fontScale?: number;\n}\n\n/** Normalize the two accepted spellings of text content — a plain string or\n * an array of runs — to runs. Throws on a run with no string `text`. */\nexport function toRuns(input: string | StyledRun[]): StyledRun[] {\n if (typeof input === 'string') {\n return input.length === 0 ? [] : [{ text: input }];\n }\n for (let i = 0; i < input.length; i++) {\n const r = input[i];\n if (typeof r?.text !== 'string') {\n throw new Error(`toRuns: run at index ${i} is missing string \\`text\\``);\n }\n }\n return input;\n}\n\n/** Concatenate runs, dropping all styling. */\nexport function runsToPlainText(runs: readonly StyledRun[]): string {\n let out = '';\n for (const r of runs) out += r.text;\n return out;\n}\n\n/** A run flag an inline marker can toggle. The additive style toggles on\n * `StyledRun`; the valued fields (family, size, paint, `script`) have no\n * inline spelling and are not addressable this way. */\nexport type RunFlag = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'overline';\n\n/** One inline marker: a delimiter repeated `repeat` times, and the flags it\n * turns on between its opening and closing occurrence. */\nexport interface RunMarker {\n delimiter: string;\n repeat: number;\n flags: readonly RunFlag[];\n}\n\n/**\n * The inline grammar `runsToMarkdown` writes and `markdownToRuns` reads.\n *\n * Markers sharing a delimiter are matched longest-first, which is what lets\n * `***` mean something other than `**` followed by `*`. A backslash escapes\n * any delimiter character and itself, in both directions.\n */\nexport interface RunGrammar {\n markers: readonly RunMarker[];\n}\n\n/**\n * The markdown subset the kit reads and writes: `**bold**`, `*italic*`,\n * `***both***`.\n *\n * Deliberately not the whole of markdown, and deliberately silent on\n * `underline` / `strikethrough` — the two run flags with no entry here are\n * dropped by `runsToMarkdown`, as they always have been. A grammar that wants\n * `~~struck~~` adds one marker; nothing else has to change.\n */\nexport const MARKDOWN_RUN_GRAMMAR: RunGrammar = {\n markers: [\n { delimiter: '*', repeat: 3, flags: ['bold', 'italic'] },\n { delimiter: '*', repeat: 2, flags: ['bold'] },\n { delimiter: '*', repeat: 1, flags: ['italic'] },\n ],\n};\n\n/** Delimiter characters of a grammar, plus the backslash that escapes them. */\nfunction escapees(grammar: RunGrammar): Set<string> {\n const out = new Set<string>(['\\\\']);\n for (const m of grammar.markers) out.add(m.delimiter);\n return out;\n}\n\nfunction escapeFor(text: string, chars: ReadonlySet<string>): string {\n let out = '';\n for (const ch of text) out += chars.has(ch) ? `\\\\${ch}` : ch;\n return out;\n}\n\n/** The flags a run turns on, in the grammar's own marker order so two runs\n * with the same flags always serialize identically. */\nfunction flagsOf(run: StyledRun, grammar: RunGrammar): Set<RunFlag> {\n const on = new Set<RunFlag>();\n for (const m of grammar.markers) {\n for (const f of m.flags) if (run[f]) on.add(f);\n }\n return on;\n}\n\nfunction sameFlags(a: ReadonlySet<RunFlag>, b: readonly RunFlag[]): boolean {\n return a.size === b.length && b.every((f) => a.has(f));\n}\n\n/**\n * Render runs in `grammar`, defaulting to the markdown subset. The flavor\n * written to the clipboard alongside the plain-text one.\n *\n * A run whose flags exactly match one marker takes it; otherwise the markers\n * that cover them nest. Flags no marker spells are dropped — the text still\n * round-trips, without that styling.\n */\nexport function runsToMarkdown(\n runs: readonly StyledRun[],\n grammar: RunGrammar = MARKDOWN_RUN_GRAMMAR,\n): string {\n const chars = escapees(grammar);\n let out = '';\n for (const r of runs) {\n const escaped = escapeFor(r.text, chars);\n const on = flagsOf(r, grammar);\n if (on.size === 0) { out += escaped; continue; }\n\n const exact = grammar.markers.find((m) => sameFlags(on, m.flags));\n if (exact) {\n const d = exact.delimiter.repeat(exact.repeat);\n out += `${d}${escaped}${d}`;\n continue;\n }\n // No single marker says all of it: nest the ones that each say part.\n const covering = grammar.markers.filter(\n (m) => m.flags.length === 1 && on.has(m.flags[0]),\n );\n const open = covering.map((m) => m.delimiter.repeat(m.repeat)).join('');\n const close = [...covering].reverse().map((m) => m.delimiter.repeat(m.repeat)).join('');\n out += `${open}${escaped}${close}`;\n }\n return out;\n}\n\n/**\n * Parse `input` in `grammar`, defaulting to the markdown subset, into styled\n * runs. Newlines are preserved as literal characters inside a run — they are\n * not run-boundary markers in this format.\n */\nexport function markdownToRuns(\n input: string,\n grammar: RunGrammar = MARKDOWN_RUN_GRAMMAR,\n): StyledRun[] {\n const chars = escapees(grammar);\n // Longest-first per delimiter, so `***` is tried before `**` before `*`.\n const byDelimiter = new Map<string, RunMarker[]>();\n for (const m of grammar.markers) {\n const list = byDelimiter.get(m.delimiter) ?? [];\n list.push(m);\n byDelimiter.set(m.delimiter, list);\n }\n for (const list of byDelimiter.values()) list.sort((a, b) => b.repeat - a.repeat);\n\n const runs: StyledRun[] = [];\n const active = new Set<RunFlag>();\n let buf = '';\n let i = 0;\n\n function flush(): void {\n if (buf.length === 0) return;\n const run: StyledRun = { text: buf };\n for (const f of active) run[f] = true;\n runs.push(run);\n buf = '';\n }\n\n while (i < input.length) {\n const ch = input[i];\n\n if (ch === '\\\\' && i + 1 < input.length && chars.has(input[i + 1])) {\n buf += input[i + 1];\n i += 2;\n continue;\n }\n\n const markers = byDelimiter.get(ch);\n if (markers) {\n let count = 0;\n while (i + count < input.length && input[i + count] === ch) count++;\n // The longest marker the delimiter run can pay for. A run shorter than\n // every marker still matches the shortest, which is how a lone `*`\n // toggles italic where `***` toggles both.\n const marker = markers.find((m) => m.repeat <= count) ?? markers[markers.length - 1];\n flush();\n for (const f of marker.flags) {\n if (active.has(f)) active.delete(f);\n else active.add(f);\n }\n i += marker.repeat;\n continue;\n }\n\n buf += ch;\n i++;\n }\n\n flush();\n return runs;\n}\n","/**\n * Apply node-level `ResolvedTextStyle` defaults to each `StyledRun`,\n * producing a fully-resolved run with every styling field set. Downstream\n * layout and draw never re-resolve defaults — `ResolvedRun` is the\n * canonical shape the renderer consumes.\n *\n * `bold`/`italic` toggles on a run are folded into `fontWeight`/`fontStyle`:\n * `bold: true` → fontWeight 700, `italic: true` → fontStyle 'italic'.\n * Explicit `fontFamily` / `fontSize` / `fill` / `letterSpacing` on the run\n * override the node-level value (`letterSpacing: 0` on a run is an override,\n * not an absence — it zeroes inherited tracking).\n *\n * `underline` / `strikethrough` / `overline` are *additive*, like\n * `bold`/`italic`: a run can turn a decoration on but never off, so they\n * resolve as `run.x || style.x` and not `run.x ?? style.x`. See the header of\n * `runs/rangeStyle.ts` for why the model collapses the tri-state.\n *\n * `script` is folded the same way the toggles are, into the two primitives it\n * is a preset over: `baselineShift` and `fontScale`. Both come out as one\n * world-unit `baselineShift` and a final `fontSize`, so layout never learns\n * that superscripts exist — it places a run against a baseline and an offset.\n */\n\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\nimport type { StyledRun } from '../runs';\nimport type { ResolvedTextStyle } from '../textStyle';\n\n/** A run with every style resolved against the node's text style — no\n * optional inheritance left. This is what layout and painting consume. */\nexport interface ResolvedRun {\n text: string;\n fontFamily: string;\n fontSize: number;\n fontWeight: number;\n fontStyle: 'normal' | 'italic';\n fill: FillStyle;\n /** Outline over this run's glyphs, or absent for none. Painted only on the\n * outline tier — a distance field has no geometry to stroke. */\n stroke?: Stroke;\n /** Extra advance added after each glyph of this run, in world units. */\n letterSpacing: number;\n /** Draw a rule below this run's baseline. Additive over the node style. */\n underline: boolean;\n /** Draw a rule through this run's x-height. Additive over the node style. */\n strikethrough: boolean;\n /** Draw a rule above this run's ascent. Additive over the node style. */\n overline: boolean;\n /**\n * How far this run sits off the line's shared baseline, in world units;\n * positive raises. 0 for ordinary text.\n *\n * Already multiplied out against the inherited font size, and already\n * carrying whatever `script` asked for — layout adds it to a baseline and\n * asks nothing about where it came from.\n */\n baselineShift: number;\n}\n\n/**\n * What `script: 'super'` and `script: 'sub'` expand to, as fractions of the\n * inherited font size: `size` scales it, `shift` raises (positive) or lowers\n * (negative) the baseline.\n *\n * These are Adobe's defaults — InDesign and Illustrator ship 58.3% size and\n * 33.3% position for both — chosen because a drawing tool's users have those\n * numbers in their fingers already. They are not read from the font: the\n * `OS/2` table carries real `ySuperscript*` / `ySubscript*` metrics, but the\n * baked atlas tier has no slot for them (see `layoutRuns`'s note on the\n * decoration constants, which are derived for the same reason), and metrics\n * that applied on one glyph tier and not the other would reflow text as it\n * crossed the size threshold.\n *\n * Exported so a consumer building a character panel can show the percentages\n * it is about to apply. Override either half per run with `baselineShift` /\n * `fontScale`.\n */\nexport const SCRIPT_METRICS: Readonly<\n Record<'super' | 'sub', Readonly<{ size: number; shift: number }>>\n> = Object.freeze({\n super: Object.freeze({ size: 0.583, shift: 0.333 }),\n sub: Object.freeze({ size: 0.583, shift: -0.333 }),\n});\n\nfunction numericWeight(w: number | string): number {\n if (typeof w === 'number') return w;\n if (w === 'bold') return 700;\n if (w === 'normal') return 400;\n const parsed = Number(w);\n return Number.isFinite(parsed) ? parsed : 400;\n}\n\n/** Resolve each run's styling against the node's text style, filling in\n * everything the run left inherited. */\nexport function resolveRuns(\n runs: readonly StyledRun[],\n style: ResolvedTextStyle,\n): ResolvedRun[] {\n const out: ResolvedRun[] = [];\n const baseWeight = numericWeight(style.fontWeight);\n for (const run of runs) {\n const script = run.script ? SCRIPT_METRICS[run.script] : undefined;\n // Against the inherited size, not the run's own: a superscript that also\n // shrank its rise would climb less the smaller it got.\n const shiftEm = run.baselineShift ?? script?.shift ?? 0;\n const scale = run.fontScale ?? script?.size ?? 1;\n out.push({\n text: run.text,\n fontFamily: run.fontFamily ?? style.fontFamily,\n // An absolute size wins over a relative one; naming both is a consumer\n // saying \"this size exactly\", which a multiplier cannot improve on.\n fontSize: run.fontSize ?? style.fontSize * scale,\n fontWeight: run.bold ? 700 : baseWeight,\n fontStyle: run.italic ? 'italic' : style.fontStyle,\n fill: run.fill ?? style.fill,\n // Unlike the decorations below, a run's stroke *replaces* the node's\n // rather than adding to it — there is only one outline to paint.\n ...((run.stroke ?? style.stroke) !== undefined\n ? { stroke: run.stroke ?? style.stroke }\n : {}),\n letterSpacing: run.letterSpacing ?? style.letterSpacing,\n // `||`, not `??`: run-level decorations are additive over the node style.\n underline: run.underline || style.underline,\n strikethrough: run.strikethrough || style.strikethrough,\n overline: run.overline || style.overline,\n baselineShift: shiftEm * style.fontSize,\n });\n }\n return out;\n}\n","/**\n * Wrap-aware text measurement. Greedy word-wrap against `maxWidth` using\n * `ctx.measureText`. Lines explicitly broken by `\\n` are preserved; long\n * single words that exceed `maxWidth` are emitted on their own line without\n * mid-word breaking (caller can decide to clip).\n *\n * Returns the laid-out lines, the total block height in world units\n * (`lines.length * fontSize * lineHeight`), and per-line `lineStarts` —\n * the offset of each line's first character in the original `text`. The\n * starts are used by `caretIndexAt` to map a clicked (x, y) back to a\n * character offset in the source string. Trailing whitespace consumed by\n * the wrap is not included in `lines[i]` but is implicit in the gap\n * between `lineStarts[i] + lines[i].length` and `lineStarts[i + 1]`.\n *\n * The caller owns the `ctx.font` setup — pass a context whose `font`\n * already matches `style` (use `fontString(style)`).\n */\n\nimport type { ResolvedTextStyle } from '../textStyle';\n\n/**\n * Advance width of `text` in world units, tracking included.\n *\n * `letter-spacing` is not part of the CSS `font` shorthand, so a context\n * whose `font` was set from `fontString(style)` measures glyphs only. The\n * GL path (`layoutRuns`) adds `letterSpacing` after **every** code point\n * including the last, matching CSS, so this does too — and every 2D-side\n * width has to go through here or the two paths disagree about where a line\n * breaks.\n */\nexport function measuredWidth(\n ctx: CanvasRenderingContext2D,\n text: string,\n style: ResolvedTextStyle,\n): number {\n return ctx.measureText(text).width + text.length * style.letterSpacing;\n}\n\n/** Result of `measureText`: wrapped lines, per-line source offsets, and total block height. */\nexport interface MeasuredText {\n lines: string[];\n lineStarts: number[];\n height: number;\n}\n\n/** Greedy word-wrap text measurement against `maxWidth`; preserves explicit `\\n` breaks. */\nexport function measureText(\n ctx: CanvasRenderingContext2D,\n text: string,\n maxWidth: number,\n style: ResolvedTextStyle,\n): MeasuredText {\n const lines: string[] = [];\n const lineStarts: number[] = [];\n const paragraphs = text.split('\\n');\n\n let pos = 0;\n for (let pi = 0; pi < paragraphs.length; pi++) {\n if (pi > 0) pos += 1; // step past the '\\n' between paragraphs\n const paragraph = paragraphs[pi];\n if (paragraph === '') {\n lines.push('');\n lineStarts.push(pos);\n continue;\n }\n const words = paragraph.split(/(\\s+)/).filter((w) => w.length > 0);\n let current = '';\n let currentStart = pos;\n let consumed = 0; // chars of `paragraph` we've already accounted for\n for (const word of words) {\n if (current === '') currentStart = pos + consumed;\n const candidate = current + word;\n if (current === '' || measuredWidth(ctx, candidate, style) <= maxWidth) {\n current = candidate;\n } else {\n lines.push(current.trimEnd());\n lineStarts.push(currentStart);\n if (/^\\s+$/.test(word)) {\n current = '';\n } else {\n current = word;\n currentStart = pos + consumed;\n }\n }\n consumed += word.length;\n }\n lines.push(current.trimEnd());\n lineStarts.push(currentStart);\n pos += paragraph.length;\n }\n\n const height = lines.length * style.fontSize * style.lineHeight;\n return { lines, lineStarts, height };\n}\n","import type { TextStyle } from '../textStyle';\nimport { resolveTextStyle } from '../textStyle';\nimport { resolveRuns } from '../runs/resolveRuns';\nimport { cachedLayoutRuns } from '../layout/layoutCache';\n\n/** Options for `measureTextBounds`. */\nexport interface MeasureTextBoundsOpts {\n /** Wrap width; words exceeding it start a new line. Default `Infinity` (no wrap). */\n maxWidth?: number;\n /** Overrides `style`'s `lineHeight` multiplier for this measurement. */\n lineHeight?: number;\n}\n\n/**\n * Measure how the GL/MSDF renderer will lay out a single plain-text string,\n * using the registered font atlas metrics. Mirrors {@link textCommand} exactly\n * (`resolveTextStyle` → `resolveRuns` → `cachedLayoutRuns`), so the returned\n * bounds match what actually gets drawn — use it to size backgrounds/pills, place\n * labels, or test overlap without guessing widths.\n *\n * Pass `opts.maxWidth` to measure word-wrapped height (e.g. for a fixed-width\n * text box); omitting it measures the unwrapped single-line width. `opts.lineHeight`\n * overrides the style's multiplier without changing `style` itself.\n *\n * Returns `{ width, height }` in the same units as `style.fontSize`. The font\n * must already be registered via `registerFont`; an unregistered family falls\n * back to the atlas fallback glyph (and warns), so register fonts at app boot\n * before measuring.\n */\nexport function measureTextBounds(\n text: string,\n style?: TextStyle,\n opts?: MeasureTextBoundsOpts,\n): { width: number; height: number } {\n const resolved = resolveTextStyle(style);\n const runs = resolveRuns([{ text }], resolved);\n const { bounds } = cachedLayoutRuns(runs, {\n maxWidth: opts?.maxWidth ?? Infinity,\n lineHeight: opts?.lineHeight ?? resolved.lineHeight,\n align: resolved.align,\n });\n // Copied: the layout behind it is shared, and handing a caller a mutable\n // reference into a cache entry is how one measurement corrupts the next.\n return { width: bounds.width, height: bounds.height };\n}\n","/**\n * Box vertical alignment for text draw commands. Given the command's box\n * `height` and the laid-out text block's height, returns the Y offset to\n * apply to every quad. `'top'` (or an undefined `align`, or a missing box\n * `height`) is the legacy behavior: offset 0.\n */\nexport type TextVerticalAlign = 'top' | 'center' | 'bottom';\n\n/** How far to shift laid-out text down inside a box of `boxHeight` to satisfy\n * a vertical alignment. Zero when the box height is unknown or alignment is\n * top. */\nexport function verticalAlignOffset(\n align: TextVerticalAlign | undefined,\n boxHeight: number | undefined,\n textHeight: number,\n): number {\n if (align === undefined || align === 'top' || boxHeight === undefined) return 0;\n const slack = boxHeight - textHeight;\n return align === 'center' ? slack / 2 : slack;\n}\n","/**\n * Where a text node's lines actually sit inside its pose box.\n *\n * A text pose is a *wrap box*, not a bounding box: `\"Away\"` in a 309-unit-wide\n * box leaves most of the box empty, and anything that treats the pose as the\n * node's extent — picking, lasso, clipping, SVG export — claims that empty\n * space. `textLineBoxes` returns the per-line rectangles instead.\n *\n * The numbers come from `cachedLayoutRuns`, the same memoized walk that\n * positions the glyphs, through the same `resolveTextStyle` → `resolveRuns`\n * chain as `textCommand` — so the boxes cannot drift from what is painted. In\n * particular they honor `align` (a centered line reports its own span, not the\n * wrap width) and `verticalAlign` (the block shifts inside `[y, y + height]`\n * exactly as `drawText` shifts the quads).\n *\n * These are line boxes, not ink boxes: each is `fontSize * lineHeight` tall\n * from the pen's line top. Ink can escape vertically at a `lineHeight` low\n * enough — see the `bounds` note at the end of `layoutRuns` — which is the\n * right trade for hit-testing, where a box that hugged the ink would make\n * an `x` harder to click than an `X`.\n */\n\nimport type { Rect } from '@weasel-js/geom';\nimport { resolveTextStyle } from '../textStyle';\nimport { resolveRuns } from '../runs/resolveRuns';\nimport { toRuns } from '../runs';\nimport { cachedLayoutRuns } from '../layout/layoutCache';\nimport { verticalAlignOffset } from './verticalAlign';\nimport type { TextPose } from '../pose';\n\n/** Options for {@link textLineBoxes}. */\nexport interface TextLineBoxesOpts {\n /** Grow every box by this much on all four sides (world units). Default 0.\n * Picking wants a little slack so a single hairline row of text is still\n * grabbable; clipping and export want none. */\n padding?: number;\n /** Keep boxes for blank lines (zero width). Default `false` — a blank line\n * covers no area, so for hit-testing and silhouettes it is noise. Pass\n * `true` when the indices have to line up with the wrapped lines. */\n includeEmpty?: boolean;\n /**\n * Wrap width. Default `pose.width`, which is what `createTextLayer` passes\n * and what `TextPose` means by its box.\n *\n * Pass `Infinity` for a node painted by the built-in `kit:text` painter:\n * that painter deliberately does **not** forward `maxWidth` (see\n * `NodeShape.ts`), so its text does not wrap, and boxes computed with a\n * finite width would wrap where the paint did not.\n */\n maxWidth?: number;\n}\n\n/** Per-line rectangles for a text pose, in world units, in layout order. */\nexport function textLineBoxes(pose: TextPose, opts: TextLineBoxesOpts = {}): Rect[] {\n const padding = opts.padding ?? 0;\n const style = resolveTextStyle(pose.style);\n // `runs` wins when non-empty, matching every painter: empty runs are not a\n // styling, so they fall back to the plain string rather than measure nothing.\n const source = pose.runs && pose.runs.length > 0 ? pose.runs : pose.text;\n const runs = resolveRuns(toRuns(source), style);\n const laid = cachedLayoutRuns(runs, {\n maxWidth: opts.maxWidth ?? pose.width,\n lineHeight: style.lineHeight,\n align: style.align,\n });\n // The same translate `drawText` applies to the quads — `layoutRuns` is\n // origin-relative, so the pose's position lands here — plus the\n // `verticalAlign` shift, so the boxes stay on the text under every setting\n // rather than only the default 'top'.\n const dx = pose.x;\n const dy = pose.y + verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);\n\n const out: Rect[] = [];\n for (const line of laid.lines) {\n if (!opts.includeEmpty && line.x1 <= line.x0) continue;\n out.push({\n x: line.x0 + dx - padding,\n y: line.y0 + dy - padding,\n width: line.x1 - line.x0 + padding * 2,\n height: line.y1 - line.y0 + padding * 2,\n });\n }\n return out;\n}\n","import { markdownToRuns, type StyledRun } from './runs';\n\nexport type { StyledRun };\n\n/** Width-measurement strategy for `layoutMarkdown`; canvas-backed default supplied by `createMarkdownRenderer`. */\nexport type MeasureFn = (text: string, fontSize: number, bold: boolean, italic: boolean) => number;\n\n/** A `StyledRun` with its computed x-offset relative to the start of its line. */\nexport interface PositionedRun extends StyledRun {\n x: number;\n}\n\n/** A single laid-out line of text: its positioned runs, total width, and computed line height. */\nexport interface LayoutLine {\n runs: PositionedRun[];\n width: number;\n height: number;\n}\n\n/** Output of `layoutMarkdown`: per-line breakdown plus overall block dimensions. */\nexport interface LayoutResult {\n lines: LayoutLine[];\n width: number;\n height: number;\n}\n\n/** Word-wrap parsed runs into lines bounded by `maxWidth`; pass `Infinity` for single-line layout. */\nexport function layoutMarkdown(\n runs: StyledRun[],\n maxWidth: number,\n fontSize: number,\n measure: MeasureFn,\n lineHeightFactor: number = 1.3,\n): LayoutResult {\n if (runs.length === 0) return { lines: [], width: 0, height: 0 };\n\n const lines: LayoutLine[] = [];\n let currentRuns: PositionedRun[] = [];\n let lineX = 0;\n let lineMaxSize = 0;\n\n function commitLine() {\n const effectiveFontSize = lineMaxSize > 0 ? lineMaxSize : fontSize;\n const lineHeight = effectiveFontSize * lineHeightFactor;\n lines.push({ runs: currentRuns, width: lineX, height: lineHeight });\n currentRuns = [];\n lineX = 0;\n lineMaxSize = 0;\n }\n\n function processSegment(segRun: StyledRun) {\n const effectiveSize = segRun.fontSize ?? fontSize;\n lineMaxSize = Math.max(lineMaxSize, effectiveSize);\n\n if (maxWidth === Infinity) {\n const w = measure(segRun.text, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n currentRuns.push({ ...segRun, x: lineX });\n lineX += w;\n return;\n }\n\n // Word-wrap: split run text by spaces\n const words = segRun.text.split(/ /);\n let wordBuf = '';\n\n for (let wi = 0; wi < words.length; wi++) {\n const word = words[wi];\n const candidate = wordBuf.length > 0 ? wordBuf + ' ' + word : word;\n const candidateW = measure(candidate, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n\n if (lineX + candidateW > maxWidth && (lineX > 0 || wordBuf.length > 0)) {\n // Flush current wordBuf as a run on the current line\n if (wordBuf.length > 0) {\n const w = measure(wordBuf, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n currentRuns.push({ ...segRun, text: wordBuf, x: lineX });\n lineX += w;\n }\n commitLine();\n lineMaxSize = Math.max(lineMaxSize, effectiveSize);\n wordBuf = word;\n } else {\n // Either fits, or is an oversized single word starting a fresh line — accept it\n wordBuf = candidate;\n }\n }\n\n // Flush remaining wordBuf\n if (wordBuf.length > 0) {\n const w = measure(wordBuf, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n currentRuns.push({ ...segRun, text: wordBuf, x: lineX });\n lineX += w;\n }\n }\n\n for (const run of runs) {\n // markdownToRuns embeds newlines inside runs; split on '\\n' so each\n // segment becomes its own line via commitLine().\n const segments = run.text.split('\\n');\n for (let si = 0; si < segments.length; si++) {\n if (si > 0) commitLine();\n const seg = segments[si];\n if (seg.length > 0) {\n processSegment({ ...run, text: seg });\n }\n }\n }\n\n // Commit final line\n if (currentRuns.length > 0) {\n commitLine();\n }\n\n const width = Math.max(...lines.map((l) => l.width));\n const height = lines.reduce((sum, l) => sum + l.height, 0);\n return { lines, width, height };\n}\n\n/** Pluggable text-painting strategy. The default fills white at `(x, y)`; markdown renderers replace this. */\nexport type TextRenderer = (\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n) => void;\n\n/** Font styling options threaded through `createMarkdownRenderer`. */\nexport interface MarkdownFontOptions {\n /** Font-family spec (e.g. `'\"Iowan Old Style\", Georgia, serif'`). Defaults to `sans-serif`. */\n family?: string;\n /** Numeric weight applied to non-bold runs. Bold runs always use `bold`. Default `normal`. */\n weight?: string | number;\n /** Override fill color. When set, used for all runs (italic and bold). */\n color?: string;\n /** Multiplier applied to font size for line height. Default 1.3. */\n lineHeight?: number;\n}\n\nfunction buildFont(\n fontSize: number,\n bold: boolean,\n italic: boolean,\n opts: MarkdownFontOptions = {},\n): string {\n const family = opts.family ?? 'sans-serif';\n const weight = bold ? 'bold' : (opts.weight !== undefined ? String(opts.weight) : 'normal');\n const parts: string[] = [];\n if (italic) parts.push('italic');\n parts.push(weight);\n parts.push(`${fontSize}px ${family}`);\n return parts.join(' ');\n}\n\nfunction canvasMeasure(ctx: CanvasRenderingContext2D, opts: MarkdownFontOptions = {}): MeasureFn {\n return (text, fontSize, bold, italic) => {\n ctx.font = buildFont(fontSize, bold, italic, opts);\n return ctx.measureText(text).width;\n };\n}\n\n/** Build a fill+stroke `TextRenderer` pair for a markdown string at the given size; pre-computes layout once. */\nexport function createMarkdownRenderer(\n ctx: CanvasRenderingContext2D,\n text: string,\n fontSize: number,\n maxWidth: number = Infinity,\n fontOpts: MarkdownFontOptions = {},\n): { renderer: TextRenderer; strokeRenderer: TextRenderer; width: number; height: number } {\n const measure = canvasMeasure(ctx, fontOpts);\n const parsed = markdownToRuns(text);\n const layout = layoutMarkdown(parsed, maxWidth, fontSize, measure, fontOpts.lineHeight);\n\n const renderer: TextRenderer = (_ctx, _text, x, y) => {\n let lineY = y;\n for (const line of layout.lines) {\n for (const run of line.runs) {\n const effSize = run.fontSize ?? fontSize;\n _ctx.font = buildFont(effSize, run.bold ?? false, run.italic ?? false, fontOpts);\n _ctx.fillStyle = fontOpts.color\n ?? (run.italic && !run.bold ? 'rgba(255, 255, 255, 0.7)' : '#FFFFFF');\n const lineOffset = (layout.width - line.width) / 2;\n _ctx.fillText(run.text, x + lineOffset + run.x, lineY);\n }\n lineY += line.height;\n }\n };\n\n const strokeRenderer: TextRenderer = (_ctx, _text, x, y) => {\n let lineY = y;\n for (const line of layout.lines) {\n for (const run of line.runs) {\n const effSize = run.fontSize ?? fontSize;\n _ctx.font = buildFont(effSize, run.bold ?? false, run.italic ?? false, fontOpts);\n const lineOffset = (layout.width - line.width) / 2;\n _ctx.strokeText(run.text, x + lineOffset + run.x, lineY);\n }\n lineY += line.height;\n }\n };\n\n return { renderer, strokeRenderer, width: layout.width, height: layout.height };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/runs.ts","../src/runs/resolveRuns.ts","../src/measure/measureText.ts","../src/measure/measureTextBounds.ts","../src/measure/verticalAlign.ts","../src/measure/lineBoxes.ts","../src/markdownText.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Canonical inline-styling primitive for text nodes. A node's text is\n * either a plain `string` (treated as a single-run, default-styled fragment)\n * or `StyledRun[]` for rich content. `toRuns` is the funnel that normalizes\n * either form into the array shape used by the renderer.\n *\n * Every field except `text` is optional; missing fields fall back to the\n * node-level `TextStyle`. `bold`/`italic` are toggles; richer weight axes\n * (300/500/900) are out of scope for slice 1.\n */\n\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\n\n/** A span of text with its own styling, as authored. Fields left absent\n * inherit from the node's text style — this is the difference between a run\n * and a fully resolved one. */\nexport interface StyledRun {\n text: string;\n bold?: boolean;\n italic?: boolean;\n fontFamily?: string;\n fontSize?: number;\n fill?: FillStyle;\n /** Outline over this run's glyphs. Overrides the node's own `data.stroke`;\n * absent inherits it. Only painted on the outline tier. */\n stroke?: Stroke;\n letterSpacing?: number;\n underline?: boolean;\n strikethrough?: boolean;\n /** Draw a rule above this run's ascent. Additive over the node style. */\n overline?: boolean;\n /**\n * Set this run as a superscript or subscript — a raised or lowered baseline\n * and a smaller size together, which is the pair `<sup>` and `<sub>` imply.\n *\n * A preset over the two primitives below, not a third mechanism: it supplies\n * a `baselineShift` and a `fontScale`, and naming either of those directly\n * overrides that half while leaving the other alone. The numbers are in\n * {@link SCRIPT_METRICS}.\n */\n script?: 'super' | 'sub';\n /**\n * Raise (positive) or lower (negative) this run off the line's shared\n * baseline, in ems of the *inherited* font size — so a run's rise does not\n * shrink along with the run when `fontScale` also applies.\n */\n baselineShift?: number;\n /**\n * Multiplier on the inherited font size. The relative counterpart to\n * `fontSize`, which pins an absolute size and wins over this when both are\n * present.\n */\n fontScale?: number;\n}\n\n/** Normalize the two accepted spellings of text content — a plain string or\n * an array of runs — to runs. Throws on a run with no string `text`. */\nexport function toRuns(input: string | StyledRun[]): StyledRun[] {\n if (typeof input === 'string') {\n return input.length === 0 ? [] : [{ text: input }];\n }\n for (let i = 0; i < input.length; i++) {\n const r = input[i];\n if (typeof r?.text !== 'string') {\n throw new Error(`toRuns: run at index ${i} is missing string \\`text\\``);\n }\n }\n return input;\n}\n\n/** Concatenate runs, dropping all styling. */\nexport function runsToPlainText(runs: readonly StyledRun[]): string {\n let out = '';\n for (const r of runs) out += r.text;\n return out;\n}\n\n/** A run flag an inline marker can toggle. The additive style toggles on\n * `StyledRun`; the valued fields (family, size, paint, `script`) have no\n * inline spelling and are not addressable this way. */\nexport type RunFlag = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'overline';\n\n/** One inline marker: a delimiter repeated `repeat` times, and the flags it\n * turns on between its opening and closing occurrence. */\nexport interface RunMarker {\n delimiter: string;\n repeat: number;\n flags: readonly RunFlag[];\n}\n\n/**\n * The inline grammar `runsToMarkdown` writes and `markdownToRuns` reads.\n *\n * Markers sharing a delimiter are matched longest-first, which is what lets\n * `***` mean something other than `**` followed by `*`. A backslash escapes\n * any delimiter character and itself, in both directions.\n */\nexport interface RunGrammar {\n markers: readonly RunMarker[];\n}\n\n/**\n * The markdown subset the kit reads and writes: `**bold**`, `*italic*`,\n * `***both***`.\n *\n * Deliberately not the whole of markdown, and deliberately silent on\n * `underline` / `strikethrough` — the two run flags with no entry here are\n * dropped by `runsToMarkdown`, as they always have been. A grammar that wants\n * `~~struck~~` adds one marker; nothing else has to change.\n */\nexport const MARKDOWN_RUN_GRAMMAR: RunGrammar = {\n markers: [\n { delimiter: '*', repeat: 3, flags: ['bold', 'italic'] },\n { delimiter: '*', repeat: 2, flags: ['bold'] },\n { delimiter: '*', repeat: 1, flags: ['italic'] },\n ],\n};\n\n/** Delimiter characters of a grammar, plus the backslash that escapes them. */\nfunction escapees(grammar: RunGrammar): Set<string> {\n const out = new Set<string>(['\\\\']);\n for (const m of grammar.markers) out.add(m.delimiter);\n return out;\n}\n\nfunction escapeFor(text: string, chars: ReadonlySet<string>): string {\n let out = '';\n for (const ch of text) out += chars.has(ch) ? `\\\\${ch}` : ch;\n return out;\n}\n\n/** The flags a run turns on, in the grammar's own marker order so two runs\n * with the same flags always serialize identically. */\nfunction flagsOf(run: StyledRun, grammar: RunGrammar): Set<RunFlag> {\n const on = new Set<RunFlag>();\n for (const m of grammar.markers) {\n for (const f of m.flags) if (run[f]) on.add(f);\n }\n return on;\n}\n\nfunction sameFlags(a: ReadonlySet<RunFlag>, b: readonly RunFlag[]): boolean {\n return a.size === b.length && b.every((f) => a.has(f));\n}\n\n/**\n * Render runs in `grammar`, defaulting to the markdown subset. The flavor\n * written to the clipboard alongside the plain-text one.\n *\n * A run whose flags exactly match one marker takes it; otherwise the markers\n * that cover them nest. Flags no marker spells are dropped — the text still\n * round-trips, without that styling.\n */\nexport function runsToMarkdown(\n runs: readonly StyledRun[],\n grammar: RunGrammar = MARKDOWN_RUN_GRAMMAR,\n): string {\n const chars = escapees(grammar);\n let out = '';\n for (const r of runs) {\n const escaped = escapeFor(r.text, chars);\n const on = flagsOf(r, grammar);\n if (on.size === 0) { out += escaped; continue; }\n\n const exact = grammar.markers.find((m) => sameFlags(on, m.flags));\n if (exact) {\n const d = exact.delimiter.repeat(exact.repeat);\n out += `${d}${escaped}${d}`;\n continue;\n }\n // No single marker says all of it: nest the ones that each say part.\n const covering = grammar.markers.filter(\n (m) => m.flags.length === 1 && on.has(m.flags[0]),\n );\n const open = covering.map((m) => m.delimiter.repeat(m.repeat)).join('');\n const close = [...covering].reverse().map((m) => m.delimiter.repeat(m.repeat)).join('');\n out += `${open}${escaped}${close}`;\n }\n return out;\n}\n\n/**\n * Parse `input` in `grammar`, defaulting to the markdown subset, into styled\n * runs. Newlines are preserved as literal characters inside a run — they are\n * not run-boundary markers in this format.\n */\nexport function markdownToRuns(\n input: string,\n grammar: RunGrammar = MARKDOWN_RUN_GRAMMAR,\n): StyledRun[] {\n const chars = escapees(grammar);\n // Longest-first per delimiter, so `***` is tried before `**` before `*`.\n const byDelimiter = new Map<string, RunMarker[]>();\n for (const m of grammar.markers) {\n const list = byDelimiter.get(m.delimiter) ?? [];\n list.push(m);\n byDelimiter.set(m.delimiter, list);\n }\n for (const list of byDelimiter.values()) list.sort((a, b) => b.repeat - a.repeat);\n\n const runs: StyledRun[] = [];\n const active = new Set<RunFlag>();\n let buf = '';\n let i = 0;\n\n function flush(): void {\n if (buf.length === 0) return;\n const run: StyledRun = { text: buf };\n for (const f of active) run[f] = true;\n runs.push(run);\n buf = '';\n }\n\n while (i < input.length) {\n const ch = input[i];\n\n if (ch === '\\\\' && i + 1 < input.length && chars.has(input[i + 1])) {\n buf += input[i + 1];\n i += 2;\n continue;\n }\n\n const markers = byDelimiter.get(ch);\n if (markers) {\n let count = 0;\n while (i + count < input.length && input[i + count] === ch) count++;\n // The longest marker the delimiter run can pay for. A run shorter than\n // every marker still matches the shortest, which is how a lone `*`\n // toggles italic where `***` toggles both.\n const marker = markers.find((m) => m.repeat <= count) ?? markers[markers.length - 1];\n flush();\n for (const f of marker.flags) {\n if (active.has(f)) active.delete(f);\n else active.add(f);\n }\n i += marker.repeat;\n continue;\n }\n\n buf += ch;\n i++;\n }\n\n flush();\n return runs;\n}\n","/**\n * Apply node-level `ResolvedTextStyle` defaults to each `StyledRun`,\n * producing a fully-resolved run with every styling field set. Downstream\n * layout and draw never re-resolve defaults — `ResolvedRun` is the\n * canonical shape the renderer consumes.\n *\n * `bold`/`italic` toggles on a run are folded into `fontWeight`/`fontStyle`:\n * `bold: true` → fontWeight 700, `italic: true` → fontStyle 'italic'.\n * Explicit `fontFamily` / `fontSize` / `fill` / `letterSpacing` on the run\n * override the node-level value (`letterSpacing: 0` on a run is an override,\n * not an absence — it zeroes inherited tracking).\n *\n * `underline` / `strikethrough` / `overline` are *additive*, like\n * `bold`/`italic`: a run can turn a decoration on but never off, so they\n * resolve as `run.x || style.x` and not `run.x ?? style.x`. See the header of\n * `runs/rangeStyle.ts` for why the model collapses the tri-state.\n *\n * `script` is folded the same way the toggles are, into the two primitives it\n * is a preset over: `baselineShift` and `fontScale`. Both come out as one\n * world-unit `baselineShift` and a final `fontSize`, so layout never learns\n * that superscripts exist — it places a run against a baseline and an offset.\n */\n\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\nimport type { StyledRun } from '../runs';\nimport type { ResolvedTextStyle } from '../textStyle';\n\n/** A run with every style resolved against the node's text style — no\n * optional inheritance left. This is what layout and painting consume. */\nexport interface ResolvedRun {\n text: string;\n fontFamily: string;\n fontSize: number;\n fontWeight: number;\n fontStyle: 'normal' | 'italic';\n fill: FillStyle;\n /** Outline over this run's glyphs, or absent for none. Painted only on the\n * outline tier — a distance field has no geometry to stroke. */\n stroke?: Stroke;\n /** Extra advance added after each glyph of this run, in world units. */\n letterSpacing: number;\n /** Draw a rule below this run's baseline. Additive over the node style. */\n underline: boolean;\n /** Draw a rule through this run's x-height. Additive over the node style. */\n strikethrough: boolean;\n /** Draw a rule above this run's ascent. Additive over the node style. */\n overline: boolean;\n /**\n * How far this run sits off the line's shared baseline, in world units;\n * positive raises. 0 for ordinary text.\n *\n * Already multiplied out against the inherited font size, and already\n * carrying whatever `script` asked for — layout adds it to a baseline and\n * asks nothing about where it came from.\n */\n baselineShift: number;\n}\n\n/**\n * What `script: 'super'` and `script: 'sub'` expand to, as fractions of the\n * inherited font size: `size` scales it, `shift` raises (positive) or lowers\n * (negative) the baseline.\n *\n * These are Adobe's defaults — InDesign and Illustrator ship 58.3% size and\n * 33.3% position for both — chosen because a drawing tool's users have those\n * numbers in their fingers already. They are not read from the font: the\n * `OS/2` table carries real `ySuperscript*` / `ySubscript*` metrics, but the\n * baked atlas tier has no slot for them (see `layoutRuns`'s note on the\n * decoration constants, which are derived for the same reason), and metrics\n * that applied on one glyph tier and not the other would reflow text as it\n * crossed the size threshold.\n *\n * Exported so a consumer building a character panel can show the percentages\n * it is about to apply. Override either half per run with `baselineShift` /\n * `fontScale`.\n */\nexport const SCRIPT_METRICS: Readonly<\n Record<'super' | 'sub', Readonly<{ size: number; shift: number }>>\n> = Object.freeze({\n super: Object.freeze({ size: 0.583, shift: 0.333 }),\n sub: Object.freeze({ size: 0.583, shift: -0.333 }),\n});\n\nfunction numericWeight(w: number | string): number {\n if (typeof w === 'number') return w;\n if (w === 'bold') return 700;\n if (w === 'normal') return 400;\n const parsed = Number(w);\n return Number.isFinite(parsed) ? parsed : 400;\n}\n\n/** Resolve each run's styling against the node's text style, filling in\n * everything the run left inherited. */\nexport function resolveRuns(\n runs: readonly StyledRun[],\n style: ResolvedTextStyle,\n): ResolvedRun[] {\n const out: ResolvedRun[] = [];\n const baseWeight = numericWeight(style.fontWeight);\n for (const run of runs) {\n const script = run.script ? SCRIPT_METRICS[run.script] : undefined;\n // Against the inherited size, not the run's own: a superscript that also\n // shrank its rise would climb less the smaller it got.\n const shiftEm = run.baselineShift ?? script?.shift ?? 0;\n const scale = run.fontScale ?? script?.size ?? 1;\n out.push({\n text: run.text,\n fontFamily: run.fontFamily ?? style.fontFamily,\n // An absolute size wins over a relative one; naming both is a consumer\n // saying \"this size exactly\", which a multiplier cannot improve on.\n fontSize: run.fontSize ?? style.fontSize * scale,\n fontWeight: run.bold ? 700 : baseWeight,\n fontStyle: run.italic ? 'italic' : style.fontStyle,\n fill: run.fill ?? style.fill,\n // Unlike the decorations below, a run's stroke *replaces* the node's\n // rather than adding to it — there is only one outline to paint.\n ...((run.stroke ?? style.stroke) !== undefined\n ? { stroke: run.stroke ?? style.stroke }\n : {}),\n letterSpacing: run.letterSpacing ?? style.letterSpacing,\n // `||`, not `??`: run-level decorations are additive over the node style.\n underline: run.underline || style.underline,\n strikethrough: run.strikethrough || style.strikethrough,\n overline: run.overline || style.overline,\n baselineShift: shiftEm * style.fontSize,\n });\n }\n return out;\n}\n","/**\n * Wrap-aware text measurement. Greedy word-wrap against `maxWidth` using\n * `ctx.measureText`. Lines explicitly broken by `\\n` are preserved; long\n * single words that exceed `maxWidth` are emitted on their own line without\n * mid-word breaking (caller can decide to clip).\n *\n * Returns the laid-out lines, the total block height in world units\n * (`lines.length * fontSize * lineHeight`), and per-line `lineStarts` —\n * the offset of each line's first character in the original `text`. The\n * starts are used by `caretIndexAt` to map a clicked (x, y) back to a\n * character offset in the source string. Trailing whitespace consumed by\n * the wrap is not included in `lines[i]` but is implicit in the gap\n * between `lineStarts[i] + lines[i].length` and `lineStarts[i + 1]`.\n *\n * The caller owns the `ctx.font` setup — pass a context whose `font`\n * already matches `style` (use `fontString(style)`).\n */\n\nimport type { ResolvedTextStyle } from '../textStyle';\n\n/**\n * Advance width of `text` in world units, tracking included.\n *\n * `letter-spacing` is not part of the CSS `font` shorthand, so a context\n * whose `font` was set from `fontString(style)` measures glyphs only. The\n * GL path (`layoutRuns`) adds `letterSpacing` after **every** code point\n * including the last, matching CSS, so this does too — and every 2D-side\n * width has to go through here or the two paths disagree about where a line\n * breaks.\n */\nexport function measuredWidth(\n ctx: CanvasRenderingContext2D,\n text: string,\n style: ResolvedTextStyle,\n): number {\n return ctx.measureText(text).width + text.length * style.letterSpacing;\n}\n\n/** Result of `measureText`: wrapped lines, per-line source offsets, and total block height. */\nexport interface MeasuredText {\n lines: string[];\n lineStarts: number[];\n height: number;\n}\n\n/** Greedy word-wrap text measurement against `maxWidth`; preserves explicit `\\n` breaks. */\nexport function measureText(\n ctx: CanvasRenderingContext2D,\n text: string,\n maxWidth: number,\n style: ResolvedTextStyle,\n): MeasuredText {\n const lines: string[] = [];\n const lineStarts: number[] = [];\n const paragraphs = text.split('\\n');\n\n let pos = 0;\n for (let pi = 0; pi < paragraphs.length; pi++) {\n if (pi > 0) pos += 1; // step past the '\\n' between paragraphs\n const paragraph = paragraphs[pi];\n if (paragraph === '') {\n lines.push('');\n lineStarts.push(pos);\n continue;\n }\n const words = paragraph.split(/(\\s+)/).filter((w) => w.length > 0);\n let current = '';\n let currentStart = pos;\n let consumed = 0; // chars of `paragraph` we've already accounted for\n for (const word of words) {\n if (current === '') currentStart = pos + consumed;\n const candidate = current + word;\n if (current === '' || measuredWidth(ctx, candidate, style) <= maxWidth) {\n current = candidate;\n } else {\n lines.push(current.trimEnd());\n lineStarts.push(currentStart);\n if (/^\\s+$/.test(word)) {\n current = '';\n } else {\n current = word;\n currentStart = pos + consumed;\n }\n }\n consumed += word.length;\n }\n lines.push(current.trimEnd());\n lineStarts.push(currentStart);\n pos += paragraph.length;\n }\n\n const height = lines.length * style.fontSize * style.lineHeight;\n return { lines, lineStarts, height };\n}\n","import type { TextStyle } from '../textStyle';\nimport { resolveTextStyle } from '../textStyle';\nimport { resolveRuns } from '../runs/resolveRuns';\nimport { cachedLayoutRuns } from '../layout/layoutCache';\n\n/** Options for `measureTextBounds`. */\nexport interface MeasureTextBoundsOpts {\n /** Wrap width; words exceeding it start a new line. Default `Infinity` (no wrap). */\n maxWidth?: number;\n /** Overrides `style`'s `lineHeight` multiplier for this measurement. */\n lineHeight?: number;\n}\n\n/**\n * Measure how the GL/MSDF renderer will lay out a single plain-text string,\n * using the registered font atlas metrics. Mirrors {@link textCommand} exactly\n * (`resolveTextStyle` → `resolveRuns` → `cachedLayoutRuns`), so the returned\n * bounds match what actually gets drawn — use it to size backgrounds/pills, place\n * labels, or test overlap without guessing widths.\n *\n * Pass `opts.maxWidth` to measure word-wrapped height (e.g. for a fixed-width\n * text box); omitting it measures the unwrapped single-line width. `opts.lineHeight`\n * overrides the style's multiplier without changing `style` itself.\n *\n * Returns `{ width, height }` in the same units as `style.fontSize`. The font\n * must already be registered via `registerFont`; an unregistered family falls\n * back to the atlas fallback glyph (and warns), so register fonts at app boot\n * before measuring.\n */\nexport function measureTextBounds(\n text: string,\n style?: TextStyle,\n opts?: MeasureTextBoundsOpts,\n): { width: number; height: number } {\n const resolved = resolveTextStyle(style);\n const runs = resolveRuns([{ text }], resolved);\n const { bounds } = cachedLayoutRuns(runs, {\n maxWidth: opts?.maxWidth ?? Infinity,\n lineHeight: opts?.lineHeight ?? resolved.lineHeight,\n align: resolved.align,\n });\n // Copied: the layout behind it is shared, and handing a caller a mutable\n // reference into a cache entry is how one measurement corrupts the next.\n return { width: bounds.width, height: bounds.height };\n}\n","/**\n * Box vertical alignment for text draw commands. Given the command's box\n * `height` and the laid-out text block's height, returns the Y offset to\n * apply to every quad. `'top'` (or an undefined `align`, or a missing box\n * `height`) is the legacy behavior: offset 0.\n */\nexport type TextVerticalAlign = 'top' | 'center' | 'bottom';\n\n/** How far to shift laid-out text down inside a box of `boxHeight` to satisfy\n * a vertical alignment. Zero when the box height is unknown or alignment is\n * top. */\nexport function verticalAlignOffset(\n align: TextVerticalAlign | undefined,\n boxHeight: number | undefined,\n textHeight: number,\n): number {\n if (align === undefined || align === 'top' || boxHeight === undefined) return 0;\n const slack = boxHeight - textHeight;\n return align === 'center' ? slack / 2 : slack;\n}\n","/**\n * Where a text node's lines actually sit inside its pose box.\n *\n * A text pose is a *wrap box*, not a bounding box: `\"Away\"` in a 309-unit-wide\n * box leaves most of the box empty, and anything that treats the pose as the\n * node's extent — picking, lasso, clipping, SVG export — claims that empty\n * space. `textLineBoxes` returns the per-line rectangles instead.\n *\n * The numbers come from `cachedLayoutRuns`, the same memoized walk that\n * positions the glyphs, through the same `resolveTextStyle` → `resolveRuns`\n * chain as `textCommand` — so the boxes cannot drift from what is painted. In\n * particular they honor `align` (a centered line reports its own span, not the\n * wrap width) and `verticalAlign` (the block shifts inside `[y, y + height]`\n * exactly as `drawText` shifts the quads).\n *\n * These are line boxes, not ink boxes: each is `fontSize * lineHeight` tall\n * from the pen's line top. Ink can escape vertically at a `lineHeight` low\n * enough — see the `bounds` note at the end of `layoutRuns` — which is the\n * right trade for hit-testing, where a box that hugged the ink would make\n * an `x` harder to click than an `X`.\n */\n\nimport type { Rect } from '@weasel-js/geom';\nimport { resolveTextStyle } from '../textStyle';\nimport { resolveRuns } from '../runs/resolveRuns';\nimport { toRuns } from '../runs';\nimport { cachedLayoutRuns } from '../layout/layoutCache';\nimport { verticalAlignOffset } from './verticalAlign';\nimport type { TextPose } from '../pose';\n\n/** Options for {@link textLineBoxes}. */\nexport interface TextLineBoxesOpts {\n /** Grow every box by this much on all four sides (world units). Default 0.\n * Picking wants a little slack so a single hairline row of text is still\n * grabbable; clipping and export want none. */\n padding?: number;\n /** Keep boxes for blank lines (zero width). Default `false` — a blank line\n * covers no area, so for hit-testing and silhouettes it is noise. Pass\n * `true` when the indices have to line up with the wrapped lines. */\n includeEmpty?: boolean;\n /**\n * Wrap width. Default `pose.width`, which is what `createTextLayer` passes\n * and what `TextPose` means by its box.\n *\n * Pass `Infinity` for a node painted by the built-in `kit:text` painter:\n * that painter deliberately does **not** forward `maxWidth` (see\n * `NodeShape.ts`), so its text does not wrap, and boxes computed with a\n * finite width would wrap where the paint did not.\n */\n maxWidth?: number;\n}\n\n/** Per-line rectangles for a text pose, in world units, in layout order. */\nexport function textLineBoxes(pose: TextPose, opts: TextLineBoxesOpts = {}): Rect[] {\n const padding = opts.padding ?? 0;\n const style = resolveTextStyle(pose.style);\n // `runs` wins when non-empty, matching every painter: empty runs are not a\n // styling, so they fall back to the plain string rather than measure nothing.\n const source = pose.runs && pose.runs.length > 0 ? pose.runs : pose.text;\n const runs = resolveRuns(toRuns(source), style);\n const laid = cachedLayoutRuns(runs, {\n maxWidth: opts.maxWidth ?? pose.width,\n lineHeight: style.lineHeight,\n align: style.align,\n });\n // The same translate `drawText` applies to the quads — `layoutRuns` is\n // origin-relative, so the pose's position lands here — plus the\n // `verticalAlign` shift, so the boxes stay on the text under every setting\n // rather than only the default 'top'.\n const dx = pose.x;\n const dy = pose.y + verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);\n\n const out: Rect[] = [];\n for (const line of laid.lines) {\n if (!opts.includeEmpty && line.x1 <= line.x0) continue;\n out.push({\n x: line.x0 + dx - padding,\n y: line.y0 + dy - padding,\n width: line.x1 - line.x0 + padding * 2,\n height: line.y1 - line.y0 + padding * 2,\n });\n }\n return out;\n}\n","import { markdownToRuns, type StyledRun } from './runs';\n\nexport type { StyledRun };\n\n/** Width-measurement strategy for `layoutMarkdown`; canvas-backed default supplied by `createMarkdownRenderer`. */\nexport type MeasureFn = (text: string, fontSize: number, bold: boolean, italic: boolean) => number;\n\n/** A `StyledRun` with its computed x-offset relative to the start of its line. */\nexport interface PositionedRun extends StyledRun {\n x: number;\n}\n\n/** A single laid-out line of text: its positioned runs, total width, and computed line height. */\nexport interface LayoutLine {\n runs: PositionedRun[];\n width: number;\n height: number;\n}\n\n/** Output of `layoutMarkdown`: per-line breakdown plus overall block dimensions. */\nexport interface LayoutResult {\n lines: LayoutLine[];\n width: number;\n height: number;\n}\n\n/** Word-wrap parsed runs into lines bounded by `maxWidth`; pass `Infinity` for single-line layout. */\nexport function layoutMarkdown(\n runs: StyledRun[],\n maxWidth: number,\n fontSize: number,\n measure: MeasureFn,\n lineHeightFactor: number = 1.3,\n): LayoutResult {\n if (runs.length === 0) return { lines: [], width: 0, height: 0 };\n\n const lines: LayoutLine[] = [];\n let currentRuns: PositionedRun[] = [];\n let lineX = 0;\n let lineMaxSize = 0;\n\n function commitLine() {\n const effectiveFontSize = lineMaxSize > 0 ? lineMaxSize : fontSize;\n const lineHeight = effectiveFontSize * lineHeightFactor;\n lines.push({ runs: currentRuns, width: lineX, height: lineHeight });\n currentRuns = [];\n lineX = 0;\n lineMaxSize = 0;\n }\n\n function processSegment(segRun: StyledRun) {\n const effectiveSize = segRun.fontSize ?? fontSize;\n lineMaxSize = Math.max(lineMaxSize, effectiveSize);\n\n if (maxWidth === Infinity) {\n const w = measure(segRun.text, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n currentRuns.push({ ...segRun, x: lineX });\n lineX += w;\n return;\n }\n\n // Word-wrap: split run text by spaces\n const words = segRun.text.split(/ /);\n let wordBuf = '';\n\n for (let wi = 0; wi < words.length; wi++) {\n const word = words[wi];\n const candidate = wordBuf.length > 0 ? wordBuf + ' ' + word : word;\n const candidateW = measure(candidate, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n\n if (lineX + candidateW > maxWidth && (lineX > 0 || wordBuf.length > 0)) {\n // Flush current wordBuf as a run on the current line\n if (wordBuf.length > 0) {\n const w = measure(wordBuf, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n currentRuns.push({ ...segRun, text: wordBuf, x: lineX });\n lineX += w;\n }\n commitLine();\n lineMaxSize = Math.max(lineMaxSize, effectiveSize);\n wordBuf = word;\n } else {\n // Either fits, or is an oversized single word starting a fresh line — accept it\n wordBuf = candidate;\n }\n }\n\n // Flush remaining wordBuf\n if (wordBuf.length > 0) {\n const w = measure(wordBuf, effectiveSize, segRun.bold ?? false, segRun.italic ?? false);\n currentRuns.push({ ...segRun, text: wordBuf, x: lineX });\n lineX += w;\n }\n }\n\n for (const run of runs) {\n // markdownToRuns embeds newlines inside runs; split on '\\n' so each\n // segment becomes its own line via commitLine().\n const segments = run.text.split('\\n');\n for (let si = 0; si < segments.length; si++) {\n if (si > 0) commitLine();\n const seg = segments[si];\n if (seg.length > 0) {\n processSegment({ ...run, text: seg });\n }\n }\n }\n\n // Commit final line\n if (currentRuns.length > 0) {\n commitLine();\n }\n\n const width = Math.max(...lines.map((l) => l.width));\n const height = lines.reduce((sum, l) => sum + l.height, 0);\n return { lines, width, height };\n}\n\n/** Pluggable text-painting strategy. The default fills white at `(x, y)`; markdown renderers replace this. */\nexport type TextRenderer = (\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n) => void;\n\n/** Font styling options threaded through `createMarkdownRenderer`. */\nexport interface MarkdownFontOptions {\n /** Font-family spec (e.g. `'\"Iowan Old Style\", Georgia, serif'`). Defaults to `sans-serif`. */\n family?: string;\n /** Numeric weight applied to non-bold runs. Bold runs always use `bold`. Default `normal`. */\n weight?: string | number;\n /** Override fill color. When set, used for all runs (italic and bold). */\n color?: string;\n /** Multiplier applied to font size for line height. Default 1.3. */\n lineHeight?: number;\n}\n\nfunction buildFont(\n fontSize: number,\n bold: boolean,\n italic: boolean,\n opts: MarkdownFontOptions = {},\n): string {\n const family = opts.family ?? 'sans-serif';\n const weight = bold ? 'bold' : (opts.weight !== undefined ? String(opts.weight) : 'normal');\n const parts: string[] = [];\n if (italic) parts.push('italic');\n parts.push(weight);\n parts.push(`${fontSize}px ${family}`);\n return parts.join(' ');\n}\n\nfunction canvasMeasure(ctx: CanvasRenderingContext2D, opts: MarkdownFontOptions = {}): MeasureFn {\n return (text, fontSize, bold, italic) => {\n ctx.font = buildFont(fontSize, bold, italic, opts);\n return ctx.measureText(text).width;\n };\n}\n\n/** Build a fill+stroke `TextRenderer` pair for a markdown string at the given size; pre-computes layout once. */\nexport function createMarkdownRenderer(\n ctx: CanvasRenderingContext2D,\n text: string,\n fontSize: number,\n maxWidth: number = Infinity,\n fontOpts: MarkdownFontOptions = {},\n): { renderer: TextRenderer; strokeRenderer: TextRenderer; width: number; height: number } {\n const measure = canvasMeasure(ctx, fontOpts);\n const parsed = markdownToRuns(text);\n const layout = layoutMarkdown(parsed, maxWidth, fontSize, measure, fontOpts.lineHeight);\n\n const renderer: TextRenderer = (_ctx, _text, x, y) => {\n let lineY = y;\n for (const line of layout.lines) {\n for (const run of line.runs) {\n const effSize = run.fontSize ?? fontSize;\n _ctx.font = buildFont(effSize, run.bold ?? false, run.italic ?? false, fontOpts);\n _ctx.fillStyle = fontOpts.color\n ?? (run.italic && !run.bold ? 'rgba(255, 255, 255, 0.7)' : '#FFFFFF');\n const lineOffset = (layout.width - line.width) / 2;\n _ctx.fillText(run.text, x + lineOffset + run.x, lineY);\n }\n lineY += line.height;\n }\n };\n\n const strokeRenderer: TextRenderer = (_ctx, _text, x, y) => {\n let lineY = y;\n for (const line of layout.lines) {\n for (const run of line.runs) {\n const effSize = run.fontSize ?? fontSize;\n _ctx.font = buildFont(effSize, run.bold ?? false, run.italic ?? false, fontOpts);\n const lineOffset = (layout.width - line.width) / 2;\n _ctx.strokeText(run.text, x + lineOffset + run.x, lineY);\n }\n lineY += line.height;\n }\n };\n\n return { renderer, strokeRenderer, width: layout.width, height: layout.height };\n}\n"]}
|
|
@@ -21,9 +21,8 @@ interface StyledRun {
|
|
|
21
21
|
fontFamily?: string;
|
|
22
22
|
fontSize?: number;
|
|
23
23
|
fill?: FillStyle;
|
|
24
|
-
/** Outline over this run's glyphs. Overrides the node
|
|
25
|
-
* absent inherits it. Only painted on the outline tier
|
|
26
|
-
* {@link TextStyle.stroke}. */
|
|
24
|
+
/** Outline over this run's glyphs. Overrides the node's own `data.stroke`;
|
|
25
|
+
* absent inherits it. Only painted on the outline tier. */
|
|
27
26
|
stroke?: Stroke;
|
|
28
27
|
letterSpacing?: number;
|
|
29
28
|
underline?: boolean;
|
|
@@ -38,9 +37,6 @@ interface StyledRun {
|
|
|
38
37
|
* a `baselineShift` and a `fontScale`, and naming either of those directly
|
|
39
38
|
* overrides that half while leaving the other alone. The numbers are in
|
|
40
39
|
* {@link SCRIPT_METRICS}.
|
|
41
|
-
*
|
|
42
|
-
* There is no node-level counterpart. A whole text node set as a superscript
|
|
43
|
-
* is a smaller node moved up, which the pose already says better.
|
|
44
40
|
*/
|
|
45
41
|
script?: 'super' | 'sub';
|
|
46
42
|
/**
|
package/dist/test-seams.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { _ as _resetLayoutCacheForTests } from './test-seams-
|
|
1
|
+
export { _ as _resetLayoutCacheForTests } from './test-seams-CxfS416_.js';
|
|
2
2
|
import '@weasel-js/paint';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/text",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-pre.1",
|
|
4
4
|
"description": "Typography for weasel: styled runs, style resolution, kerned glyph layout, wrap and measurement. No scene graph, no React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"./package.json": "./package.json"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@weasel-js/geom": "1.
|
|
24
|
-
"@weasel-js/paint": "1.
|
|
23
|
+
"@weasel-js/geom": "1.4.0-pre.1",
|
|
24
|
+
"@weasel-js/paint": "1.4.0-pre.1"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"test": "vitest run",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"provenance": true
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@weasel-js/bidi": "1.
|
|
53
|
+
"@weasel-js/bidi": "1.4.0-pre.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@weasel-js/font": "1.
|
|
56
|
+
"@weasel-js/font": "1.4.0-pre.1"
|
|
57
57
|
}
|
|
58
58
|
}
|