@weasel-js/text 1.9.4 → 1.10.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/README.md +12 -10
- package/dist/{chunk-VZNK3C6F.js → chunk-EFPJZYNI.js} +361 -695
- package/dist/chunk-EFPJZYNI.js.map +1 -0
- package/dist/index.d.ts +513 -62
- package/dist/index.js +791 -21
- package/dist/index.js.map +1 -1
- package/dist/{test-seams-Czz9ua7X.d.ts → test-seams-CuLNcDew.d.ts} +191 -351
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +6 -5
- package/dist/chunk-VZNK3C6F.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
|
-
import { S as StyledRun, T as TextStyle, L as LaidOutRuns, R as ResolvedTextStyle, a as ResolvedRun, b as LayoutRunsOpts, c as
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { S as StyledRun, T as TextStyle, L as LaidOutRuns, R as ResolvedTextStyle, a as ResolvedRun, b as LayoutRunsOpts, c as TextPaint } from './test-seams-CuLNcDew.js';
|
|
2
|
+
export { B as BidiAnalysis, d as BidiReordering, e as BidiResolver, D as DEFAULT_TEXT_STYLE, F as FontVariantCaps, f as LAYOUT_CACHE_STRUCTURAL_LIMIT, g as LAYOUT_CACHE_VARIANT_LIMIT, h as LaidOutCell, i as LaidOutLineBox, M as MARKDOWN_RUN_GRAMMAR, P as PlacedGlyph, j as RunFlag, k as RunGrammar, l as RunMarker, m as RunSourceMap, n as SCRIPT_METRICS, o as SMALL_CAPS_SCALE, p as ScriptPreset, q as SmallCapsText, r as TextAlign, s as TextDirection, t as TextTransform, u as TransformedRunText, v as cachedLayoutRuns, w as fontString, x as isBoldWeight, y as isSmallCapsLetter, z as layoutRuns, A as markdownToRuns, C as numericWeight, E as paragraphDirection, G as registerBidi, H as resolveAlign, I as resolveRunFace, J as resolveRuns, K as resolveTextStyle, N as runsToMarkdown, O as runsToPlainText, Q as scriptMetrics, U as scriptMetricsFor, V as smallCapsScale, W as smallCapsScaleFor, X as smallCapsText, Y as toRuns, Z as transformRunTexts } from './test-seams-CuLNcDew.js';
|
|
3
|
+
import { FaceRuleMetrics, FaceMetrics, FontStyle } from '@weasel-js/font';
|
|
4
|
+
import { Fill, Stroke } from '@weasel-js/paint';
|
|
5
|
+
import { Rect, PathInWorldPose, PolygonPath } from '@weasel-js/geom';
|
|
6
|
+
import { Mixed } from '@weasel-js/registry';
|
|
7
|
+
export { MIXED, Mixed } from '@weasel-js/registry';
|
|
8
|
+
|
|
9
|
+
/** The three text decoration rules, in the order every tier paints them. */
|
|
10
|
+
type DecorationKind = 'underline' | 'strikethrough' | 'overline';
|
|
11
|
+
/**
|
|
12
|
+
* Decoration placement and weight for a face that states none, as fractions
|
|
13
|
+
* of the run's `fontSize`. Offsets are the *top* edge of the rule, measured
|
|
14
|
+
* down from the baseline — so the two rules that sit above it are negative.
|
|
15
|
+
*
|
|
16
|
+
* Derived, not measured: they apply to an atlas baked before `gen-font`
|
|
17
|
+
* recorded `faceMetrics`, a custom outline parser that reports none, and the
|
|
18
|
+
* canvas tier, which has no font tables to read.
|
|
19
|
+
*/
|
|
20
|
+
declare const DEFAULT_DECORATION_METRICS: Readonly<Record<DecorationKind, FaceRuleMetrics>>;
|
|
21
|
+
/**
|
|
22
|
+
* Where each rule sits for a face, in ems: the face's own underline and
|
|
23
|
+
* strikeout where it states them, the defaults where it does not. No font
|
|
24
|
+
* table places an overline, so it keeps the default offset and takes the
|
|
25
|
+
* underline's weight, as browsers draw it.
|
|
26
|
+
*
|
|
27
|
+
* Every text tier places its rules through this, so one face puts a rule in
|
|
28
|
+
* the same place whichever tier paints it.
|
|
29
|
+
*/
|
|
30
|
+
declare function decorationMetrics(face?: FaceMetrics): Readonly<Record<DecorationKind, FaceRuleMetrics>>;
|
|
6
31
|
|
|
7
32
|
/**
|
|
8
33
|
* Line break opportunities per the Unicode Line Breaking Algorithm
|
|
@@ -58,7 +83,7 @@ interface TextPose {
|
|
|
58
83
|
style?: TextStyle;
|
|
59
84
|
/** Glyph paint — the same `data.fill` / `data.stroke` a scene text node
|
|
60
85
|
* carries. Not part of `style`: paint is not typography. */
|
|
61
|
-
fill?:
|
|
86
|
+
fill?: Fill | null;
|
|
62
87
|
stroke?: Stroke | null;
|
|
63
88
|
/** Box vertical alignment within `[y, y+height]`. Default 'top'. */
|
|
64
89
|
verticalAlign?: TextVerticalAlign;
|
|
@@ -107,37 +132,145 @@ interface TextPoseLayout {
|
|
|
107
132
|
declare function layoutTextPose(pose: TextPose, scale?: number): TextPoseLayout;
|
|
108
133
|
|
|
109
134
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
135
|
+
* What to draw for laid-out text: which tier paints each glyph, which glyphs
|
|
136
|
+
* can be staged together, and the decoration rules. Layout places the glyphs
|
|
137
|
+
* and knows nothing of this; no decision here moves one.
|
|
138
|
+
*
|
|
139
|
+
* Underline, strikethrough and overline come out as solid rectangles built
|
|
140
|
+
* from pen positions, not from quad extents: quads exist only for glyphs with
|
|
141
|
+
* ink, so a decorated stretch's spaces would punch holes in a rule derived
|
|
142
|
+
* from them.
|
|
113
143
|
*/
|
|
114
144
|
|
|
145
|
+
/** One textured glyph quad, relative to the text's own top-left. */
|
|
146
|
+
interface LaidOutQuad {
|
|
147
|
+
x0: number;
|
|
148
|
+
y0: number;
|
|
149
|
+
x1: number;
|
|
150
|
+
y1: number;
|
|
151
|
+
u0: number;
|
|
152
|
+
v0: number;
|
|
153
|
+
u1: number;
|
|
154
|
+
v1: number;
|
|
155
|
+
/** The glyph's baseline. The synthetic-italic skew leans vertices about it. */
|
|
156
|
+
baselineY: number;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One glyph the renderer should tessellate rather than sample.
|
|
160
|
+
*
|
|
161
|
+
* `d` is em-space SVG path data — 1 unit is 1 em, y grows down, origin on the
|
|
162
|
+
* baseline at the pen. Placing it is therefore a uniform scale by `scale`
|
|
163
|
+
* followed by a translate to `(x, baselineY)`. `key` identifies the cached
|
|
164
|
+
* tessellation — face identity plus codepoint.
|
|
165
|
+
*/
|
|
166
|
+
interface LaidOutOutlineGlyph {
|
|
167
|
+
d: string;
|
|
168
|
+
key: string;
|
|
169
|
+
/** Pen position: where em-space x = 0 lands, in world units. */
|
|
170
|
+
x: number;
|
|
171
|
+
/** The glyph's baseline: where em-space y = 0 lands, in world units. */
|
|
172
|
+
baselineY: number;
|
|
173
|
+
/** World units per em — the glyph's `fontSize`, since em space is unit-scale. */
|
|
174
|
+
scale: number;
|
|
175
|
+
}
|
|
115
176
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
177
|
+
* Glyphs that share a resolved face, glyph source and paint, so a renderer
|
|
178
|
+
* can bind once for all of them. Groups come in the order their first glyph
|
|
179
|
+
* appears, line by line in visual order, and paint in that order.
|
|
119
180
|
*/
|
|
120
|
-
|
|
181
|
+
interface LaidOutGroup {
|
|
182
|
+
/** Resolved atlas family — may differ from the requested family when the
|
|
183
|
+
* cross-family fallback policy substituted a default. A renderer looks the
|
|
184
|
+
* atlas up by this, exactly. */
|
|
185
|
+
family: string;
|
|
186
|
+
/** Resolved variant — matches the registered atlas and the texture-cache key. */
|
|
187
|
+
weight: number;
|
|
188
|
+
style: FontStyle;
|
|
189
|
+
/** Gap between the request and the resolved match, for the renderer to fake. */
|
|
190
|
+
synthetic: {
|
|
191
|
+
bold: boolean;
|
|
192
|
+
italic: boolean;
|
|
193
|
+
};
|
|
194
|
+
/** Which glyph source this group binds: a baked MSDF atlas, the runtime
|
|
195
|
+
* canvas-SDF dynamic atlas, tessellated font outlines, or the dynamic
|
|
196
|
+
* atlas's color pages, whose quads are emoji painted in their own colors. */
|
|
197
|
+
source: 'atlas' | 'canvas' | 'outline' | 'color';
|
|
198
|
+
/** Dynamic-atlas page index for 'canvas' and 'color' groups, each in its
|
|
199
|
+
* own set of pages; 0 for the others. */
|
|
200
|
+
page: number;
|
|
201
|
+
/** The run's fill. It holds no paint — `null`, or a fill without one — only
|
|
202
|
+
* on an `'outline'` group carrying a stroke, since nothing else can put ink
|
|
203
|
+
* down without a fill. A `'color'` group takes its opacity and nothing else
|
|
204
|
+
* from it. */
|
|
205
|
+
fill: Fill | null;
|
|
206
|
+
/** Outline painted over the glyphs. Only ever set on an `'outline'` group —
|
|
207
|
+
* the SDF tiers have no geometry to stroke, which is why a stroke pulls its
|
|
208
|
+
* run onto the outline tier at any size. A run the tier cannot serve at all
|
|
209
|
+
* (no registered outlines, synthetic bold) stays on the atlas, unstroked. */
|
|
210
|
+
stroke?: Stroke;
|
|
211
|
+
/** Textured glyph quads. Always empty for an `'outline'` group. */
|
|
212
|
+
quads: LaidOutQuad[];
|
|
213
|
+
/** Outline geometry. Always empty for an `'atlas'` / `'canvas'` group. */
|
|
214
|
+
glyphs: LaidOutOutlineGlyph[];
|
|
215
|
+
}
|
|
121
216
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* so
|
|
217
|
+
* One decoration rule: an axis-aligned solid rectangle in the same space as
|
|
218
|
+
* `LaidOutQuad`. Never spans a line break, and carries the fill of the run(s)
|
|
219
|
+
* it decorates so the rule follows the text color.
|
|
125
220
|
*/
|
|
126
|
-
|
|
221
|
+
interface LaidOutDecoration {
|
|
222
|
+
kind: DecorationKind;
|
|
223
|
+
x0: number;
|
|
224
|
+
y0: number;
|
|
225
|
+
x1: number;
|
|
226
|
+
y1: number;
|
|
227
|
+
fill: Fill;
|
|
228
|
+
}
|
|
229
|
+
/** Options for {@link paintRuns}. */
|
|
230
|
+
interface PaintRunsOpts {
|
|
231
|
+
/**
|
|
232
|
+
* World-space `fontSize` at or above which glyphs are emitted as outline
|
|
233
|
+
* geometry, when the resolved face has outlines registered. Omit (the
|
|
234
|
+
* default) to keep every glyph on its SDF tier.
|
|
235
|
+
*
|
|
236
|
+
* Ignored for a family that resolved to the outline tier because it has no
|
|
237
|
+
* atlas: there the geometry is the only rendering.
|
|
238
|
+
*/
|
|
239
|
+
outlineMinSize?: number;
|
|
240
|
+
}
|
|
241
|
+
/** What {@link paintRuns} returns. Relative to the text's own top-left, as the layout is. */
|
|
242
|
+
interface PaintedRuns {
|
|
243
|
+
groups: LaidOutGroup[];
|
|
244
|
+
/** Decoration rules, in line order; within a stretch, underline then
|
|
245
|
+
* strikethrough then overline. Empty when nothing is decorated. */
|
|
246
|
+
decorations: LaidOutDecoration[];
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Derive what to draw for `laid`, the layout of `runs` — or of runs that
|
|
250
|
+
* differ from them only in paint and decoration, which a layout does not read.
|
|
251
|
+
*/
|
|
252
|
+
declare function paintRuns(laid: LaidOutRuns, runs: readonly ResolvedRun[], opts?: PaintRunsOpts): PaintedRuns;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* {@link paintRuns}, reusing the previous result while the paints, the outline
|
|
256
|
+
* threshold's effect, and the glyphs available are what they were. `laid` must
|
|
257
|
+
* be the layout of `runs`, as for `paintRuns`.
|
|
258
|
+
*
|
|
259
|
+
* **The result is shared and must be treated as immutable.**
|
|
260
|
+
*/
|
|
261
|
+
declare function cachedPaintRuns(laid: LaidOutRuns, runs: readonly ResolvedRun[], opts?: PaintRunsOpts): PaintedRuns;
|
|
127
262
|
|
|
128
263
|
/**
|
|
129
264
|
* Wrap-aware text measurement for a 2D context. Lines end where `layoutRuns`
|
|
130
|
-
* ends them
|
|
131
|
-
* `maxWidth
|
|
132
|
-
*
|
|
133
|
-
* is
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* Trailing spaces and tabs hang and are not included in `lines[i]`; neither is the
|
|
140
|
-
* hard break that closed it.
|
|
265
|
+
* ends them, with widths from `ctx.measureText` plus tracking. A word wider
|
|
266
|
+
* than `maxWidth` is emitted on its own line unbroken.
|
|
267
|
+
*
|
|
268
|
+
* `lineStarts` is the UTF-16 offset of each line's first character in `text`.
|
|
269
|
+
* Trailing spaces and tabs hang and are not included in `lines[i]`; neither is
|
|
270
|
+
* the hard break that closed it.
|
|
271
|
+
*
|
|
272
|
+
* A tab runs to its next stop and `fillText` draws one as a single space, so
|
|
273
|
+
* paint a line from `spans[i]` and not from `lines[i]`.
|
|
141
274
|
*
|
|
142
275
|
* The caller owns the `ctx.font` setup — pass a context whose `font`
|
|
143
276
|
* already matches `style` (use `fontString(style)`).
|
|
@@ -146,24 +279,26 @@ declare function paragraphDirection(text: string, direction?: TextDirection | 'a
|
|
|
146
279
|
/**
|
|
147
280
|
* Advance width of `text` in world units, tracking included.
|
|
148
281
|
*
|
|
149
|
-
* `letter-spacing` is not part of the CSS `font` shorthand, so
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* width has to go through here or the two paths disagree about where a line
|
|
154
|
-
* breaks.
|
|
282
|
+
* `letter-spacing` is not part of the CSS `font` shorthand, so `ctx` measures
|
|
283
|
+
* glyphs only. `letterSpacing` is added after every grapheme cluster
|
|
284
|
+
* including the last, as `layoutRuns` does — every 2D-side width has to go
|
|
285
|
+
* through here or the two paths disagree about where a line breaks.
|
|
155
286
|
*/
|
|
156
287
|
declare function measuredWidth(ctx: CanvasRenderingContext2D, text: string, style: ResolvedTextStyle): number;
|
|
288
|
+
/** A stretch of a line between tabs, and how far into the line it starts. */
|
|
289
|
+
interface MeasuredSpan {
|
|
290
|
+
text: string;
|
|
291
|
+
x: number;
|
|
292
|
+
}
|
|
157
293
|
/** Result of `measureText`: wrapped lines, per-line source offsets, and total block height. */
|
|
158
294
|
interface MeasuredText {
|
|
159
295
|
lines: string[];
|
|
296
|
+
/** Per line, what to paint and where: its text split at its tabs. */
|
|
297
|
+
spans: MeasuredSpan[][];
|
|
160
298
|
lineStarts: number[];
|
|
161
299
|
height: number;
|
|
162
300
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Wrap `text` against `maxWidth` at the break opportunities `layoutRuns`
|
|
165
|
-
* wraps at, measuring with `ctx`.
|
|
166
|
-
*/
|
|
301
|
+
/** Wrap `text` against `maxWidth` at the break opportunities `layoutRuns` wraps at, measuring with `ctx`. */
|
|
167
302
|
declare function measureText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, style: ResolvedTextStyle): MeasuredText;
|
|
168
303
|
|
|
169
304
|
/** Options for `measureTextBounds`. */
|
|
@@ -178,19 +313,12 @@ interface MeasureTextBoundsOpts {
|
|
|
178
313
|
}
|
|
179
314
|
/**
|
|
180
315
|
* Measure how the GL/MSDF renderer will lay out a single plain-text string,
|
|
181
|
-
* using the registered font atlas metrics. Mirrors `textCommand` exactly
|
|
182
|
-
*
|
|
183
|
-
* bounds match what actually gets drawn — use it to size backgrounds/pills, place
|
|
184
|
-
* labels, or test overlap without guessing widths.
|
|
185
|
-
*
|
|
186
|
-
* Pass `opts.maxWidth` to measure word-wrapped height (e.g. for a fixed-width
|
|
187
|
-
* text box); omitting it measures the unwrapped single-line width. `opts.lineHeight`
|
|
188
|
-
* overrides the style's multiplier without changing `style` itself.
|
|
316
|
+
* using the registered font atlas metrics. Mirrors `textCommand` exactly, so
|
|
317
|
+
* the returned bounds match what actually gets drawn.
|
|
189
318
|
*
|
|
190
319
|
* Returns `{ width, height }` in the same units as `style.fontSize`. The font
|
|
191
320
|
* must already be registered via `registerFont`; an unregistered family falls
|
|
192
|
-
* back to the atlas fallback glyph (and warns)
|
|
193
|
-
* before measuring.
|
|
321
|
+
* back to the atlas fallback glyph (and warns).
|
|
194
322
|
*/
|
|
195
323
|
declare function measureTextBounds(text: string, style?: TextStyle, opts?: MeasureTextBoundsOpts): {
|
|
196
324
|
width: number;
|
|
@@ -200,20 +328,16 @@ declare function measureTextBounds(text: string, style?: TextStyle, opts?: Measu
|
|
|
200
328
|
/**
|
|
201
329
|
* Where a text node's lines actually sit inside its pose box.
|
|
202
330
|
*
|
|
203
|
-
* A text pose is a layout box, not a bounding box
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* The numbers come from `layoutTextPose`, the layout every painter of a text
|
|
209
|
-
* pose draws, so the boxes cannot drift from what is painted — they wrap only
|
|
210
|
-
* where the style declares `wrap`, and honor `align` and `verticalAlign`.
|
|
331
|
+
* A text pose is a layout box, not a bounding box, and anything that treats
|
|
332
|
+
* the pose as the node's extent — picking, lasso, clipping, SVG export —
|
|
333
|
+
* claims its empty space. The numbers come from `layoutTextPose`, the layout
|
|
334
|
+
* every painter of a text pose draws, so the boxes cannot drift from what is
|
|
335
|
+
* painted.
|
|
211
336
|
*
|
|
212
337
|
* These are line boxes, not ink boxes: each is `fontSize * lineHeight` tall
|
|
213
338
|
* from the pen's line top. Ink can escape vertically at a `lineHeight` low
|
|
214
|
-
* enough
|
|
215
|
-
*
|
|
216
|
-
* an `x` harder to click than an `X`.
|
|
339
|
+
* enough, which is the right trade for hit-testing, where a box that hugged
|
|
340
|
+
* the ink would make an `x` harder to click than an `X`.
|
|
217
341
|
*/
|
|
218
342
|
|
|
219
343
|
/** Options for {@link textLineBoxes}. */
|
|
@@ -230,6 +354,58 @@ interface TextLineBoxesOpts {
|
|
|
230
354
|
/** Per-line rectangles for a text pose, in world units, in layout order. */
|
|
231
355
|
declare function textLineBoxes(pose: TextPose, opts?: TextLineBoxesOpts): Rect[];
|
|
232
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Hit-testing for text nodes. `caretIndexAt` reads the caret stops off
|
|
359
|
+
* `layoutTextPose`, the layout the painters draw and `textLineBoxes` picks
|
|
360
|
+
* against, so the caret cannot land on a different line — or between different
|
|
361
|
+
* glyphs — than the one under the pointer.
|
|
362
|
+
*/
|
|
363
|
+
|
|
364
|
+
/** Options for `pointInTextPose`. */
|
|
365
|
+
interface PointInTextPoseOpts {
|
|
366
|
+
/** Extra padding (world units) added to the rect on all sides. Default 0. */
|
|
367
|
+
padding?: number;
|
|
368
|
+
}
|
|
369
|
+
/** Coarse pose-rect hit-test for a text node — suitable for click-to-edit dispatch. */
|
|
370
|
+
declare function pointInTextPose(x: number, y: number, pose: TextPose, opts?: PointInTextPoseOpts): boolean;
|
|
371
|
+
/**
|
|
372
|
+
* Map a world-space point inside `pose` to a character offset into the pose's
|
|
373
|
+
* text (0..length). Clicks above the first line clamp to 0; clicks below the
|
|
374
|
+
* last line clamp to the end. Within a line, the caret lands between two
|
|
375
|
+
* glyphs at whichever side of the advance cell's midpoint `x` falls on.
|
|
376
|
+
*
|
|
377
|
+
* Honors `pose.runs` (a mixed-size line snaps on the cells each run actually
|
|
378
|
+
* produced) and `pose.verticalAlign`. The offset is into the runs'
|
|
379
|
+
* concatenated text, which `TextPose` requires to equal `pose.text`.
|
|
380
|
+
*/
|
|
381
|
+
declare function caretIndexAt(x: number, y: number, pose: TextPose): number;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Resize a text node's pose to fit its content.
|
|
385
|
+
*
|
|
386
|
+
* - `axis: 'height'` (default): keep `pose.width`, recompute `height` as
|
|
387
|
+
* the block height plus optional vertical padding — wrapped at the width
|
|
388
|
+
* inside the horizontal padding when the style declares `wrap`.
|
|
389
|
+
* - `axis: 'both'`: ignore wrapping; recompute both `width` (longest
|
|
390
|
+
* `\n`-split line) and `height` (line count × line height) plus padding.
|
|
391
|
+
*
|
|
392
|
+
* `pose.x` / `pose.y` are preserved; for a different growth direction, adjust
|
|
393
|
+
* `x` / `y` after calling.
|
|
394
|
+
*/
|
|
395
|
+
|
|
396
|
+
/** Options for `fitTextPose`. */
|
|
397
|
+
interface FitTextPoseOptions {
|
|
398
|
+
/** Which axis (or axes) to recompute. Default: `'height'`. */
|
|
399
|
+
axis?: 'height' | 'both';
|
|
400
|
+
/** Padding (world units) added to all four sides. Default 0. */
|
|
401
|
+
padding?: number | {
|
|
402
|
+
x?: number;
|
|
403
|
+
y?: number;
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/** Recompute a `TextPose`'s `width`/`height` to fit its content; pure helper, doesn't mutate scene state. */
|
|
407
|
+
declare function fitTextPose(pose: TextPose, opts?: FitTextPoseOptions): TextPose;
|
|
408
|
+
|
|
233
409
|
/** Width-measurement strategy for `layoutMarkdown`; canvas-backed default supplied by `createMarkdownRenderer`. */
|
|
234
410
|
type MeasureFn = (text: string, fontSize: number, bold: boolean, italic: boolean) => number;
|
|
235
411
|
/** The face a run's rules and script presets come from, for `layoutMarkdown`.
|
|
@@ -266,9 +442,10 @@ interface LayoutResult {
|
|
|
266
442
|
/**
|
|
267
443
|
* Word-wrap parsed runs into lines bounded by `maxWidth`, breaking at the
|
|
268
444
|
* UAX #14 opportunities `layoutRuns` breaks at; pass `Infinity` for
|
|
269
|
-
* single-line layout.
|
|
445
|
+
* single-line layout. A tab is a run of its own, as wide as it takes to reach
|
|
446
|
+
* its next stop, `tabSize` spaces apart.
|
|
270
447
|
*/
|
|
271
|
-
declare function layoutMarkdown(runs: StyledRun[], maxWidth: number, fontSize: number, measure: MeasureFn, lineHeightFactor?: number, faceOf?: FaceMetricsFn): LayoutResult;
|
|
448
|
+
declare function layoutMarkdown(runs: StyledRun[], maxWidth: number, fontSize: number, measure: MeasureFn, lineHeightFactor?: number, faceOf?: FaceMetricsFn, tabSize?: number): LayoutResult;
|
|
272
449
|
/** Pluggable text-painting strategy. The default fills white at `(x, y)`; markdown renderers replace this. */
|
|
273
450
|
type TextRenderer = (ctx: CanvasRenderingContext2D, text: string, x: number, y: number) => void;
|
|
274
451
|
/** Font styling options threaded through `createMarkdownRenderer`. */
|
|
@@ -281,6 +458,8 @@ interface MarkdownFontOptions {
|
|
|
281
458
|
color?: string;
|
|
282
459
|
/** Multiplier applied to font size for line height. Default 1.3. */
|
|
283
460
|
lineHeight?: number;
|
|
461
|
+
/** Distance between tab stops, in spaces, as CSS `tab-size`. Default 8. */
|
|
462
|
+
tabSize?: number;
|
|
284
463
|
}
|
|
285
464
|
/**
|
|
286
465
|
* Build a fill+stroke `TextRenderer` pair, laying the text out once.
|
|
@@ -298,4 +477,276 @@ declare function createMarkdownRenderer(ctx: CanvasRenderingContext2D, text: str
|
|
|
298
477
|
height: number;
|
|
299
478
|
};
|
|
300
479
|
|
|
301
|
-
|
|
480
|
+
/** Visual options for `renderLabel`. */
|
|
481
|
+
interface LabelOptions {
|
|
482
|
+
align?: 'center' | 'left';
|
|
483
|
+
fontSize?: number;
|
|
484
|
+
renderText?: TextRenderer;
|
|
485
|
+
/** Override the pill width (content width, excluding padding). */
|
|
486
|
+
width?: number;
|
|
487
|
+
/** Override the pill height (content height, excluding padding). */
|
|
488
|
+
height?: number;
|
|
489
|
+
/** Horizontal padding inside the pill. Default 4 (screen-pixel default). */
|
|
490
|
+
padX?: number;
|
|
491
|
+
/** Vertical padding inside the pill. Default 1 (screen-pixel default). */
|
|
492
|
+
padY?: number;
|
|
493
|
+
/** Corner radius of the pill background. Default 3 (screen-pixel default). */
|
|
494
|
+
cornerRadius?: number;
|
|
495
|
+
}
|
|
496
|
+
/** Render a text label with a 75%-opaque black pill background and white text. */
|
|
497
|
+
declare function renderLabel(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, options?: LabelOptions): void;
|
|
498
|
+
/** Default `TextRenderer` used by `renderLabel`: white fill, no styling. */
|
|
499
|
+
declare const defaultLabelTextRenderer: TextRenderer;
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* The kit's one answer to "is this node text?": its data carries a string
|
|
503
|
+
* `text`. Routing, the `textEdit` dep, and `useSceneTextEdit` all ask it.
|
|
504
|
+
*/
|
|
505
|
+
declare function isTextData(data: unknown): data is {
|
|
506
|
+
text: string;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Range-addressed styling over `StyledRun[]`.
|
|
511
|
+
*
|
|
512
|
+
* Character offsets index the concatenated run text (`runsToPlainText`), and
|
|
513
|
+
* a range is half-open: `[start, end)`. Ranges are clamped, never wrapped,
|
|
514
|
+
* and a collapsed or inverted range (`start >= end`) is empty. Offsets are
|
|
515
|
+
* UTF-16 code units, so a boundary can bisect a surrogate pair.
|
|
516
|
+
*
|
|
517
|
+
* **Run-level flags are additive over the node's `TextStyle`: a run can
|
|
518
|
+
* turn `bold` / `italic` / `underline` / `strikethrough` on, never off.**
|
|
519
|
+
* So a flag is stored only when true, and absent reads as `false`. A run
|
|
520
|
+
* that must be lighter than a bold node says so with its own numeric
|
|
521
|
+
* `fontWeight`, which `bold` is a preset over (see {@link supersededKey}).
|
|
522
|
+
* The decorations have no such primitive, and the tri-state is collapsed by
|
|
523
|
+
* choice, to keep one canonical form per styling. Resolution must therefore
|
|
524
|
+
* read decorations as `run.underline || style.underline`, not `??`.
|
|
525
|
+
*
|
|
526
|
+
* Copies here are shallow: a `fill` object is shared by reference between
|
|
527
|
+
* the input runs, the patch, every run the patch touched, and the value
|
|
528
|
+
* `styleAtRange` reports. Treat `fill` values as immutable.
|
|
529
|
+
*/
|
|
530
|
+
|
|
531
|
+
/** Every styleable key of a run, each either a concrete value or MIXED (the
|
|
532
|
+
* runs in the range disagree at that key). */
|
|
533
|
+
type RangeStyle = {
|
|
534
|
+
[K in Exclude<keyof StyledRun, 'text'>]?: StyledRun[K] | Mixed;
|
|
535
|
+
};
|
|
536
|
+
/** What `applyStyleToRange` writes. */
|
|
537
|
+
type RunStylePatch = Partial<Omit<StyledRun, 'text'>>;
|
|
538
|
+
/** Every key of a run that carries styling — i.e. everything but `text`. */
|
|
539
|
+
type StyleKey = Exclude<keyof StyledRun, 'text'>;
|
|
540
|
+
/**
|
|
541
|
+
* The styling shared by every run overlapping `[start, end)`: a concrete
|
|
542
|
+
* value where the range agrees, `MIXED` where it doesn't. Keys no run
|
|
543
|
+
* in range sets are absent (they inherit the node's `TextStyle`) — except
|
|
544
|
+
* the additive flags, which read as `false`. An empty range reads as `{}`.
|
|
545
|
+
* A reported `fill` is the run's own object, not a copy.
|
|
546
|
+
*/
|
|
547
|
+
declare function styleAtRange(runs: readonly StyledRun[], start: number, end: number): RangeStyle;
|
|
548
|
+
/**
|
|
549
|
+
* Does any run carry styling of its own? A flag stored as `false` counts as
|
|
550
|
+
* none, so runs whose styling was toggled back off read as `false`.
|
|
551
|
+
*/
|
|
552
|
+
declare function runsCarryStyling(runs: readonly StyledRun[]): boolean;
|
|
553
|
+
/**
|
|
554
|
+
* The key a patch clears without naming it. `bold` is a preset over
|
|
555
|
+
* `fontWeight`, and a run keeps at most one of the two: writing either drops
|
|
556
|
+
* the other, so a Bold toggle is not hidden under a weight it cannot beat and
|
|
557
|
+
* a picked weight is not shadowed by a stale flag. Naming both writes both.
|
|
558
|
+
*/
|
|
559
|
+
declare function supersededKey(patch: RunStylePatch): 'bold' | 'fontWeight' | undefined;
|
|
560
|
+
/**
|
|
561
|
+
* `range` with `patch` laid over it, keyed as a write of `patch` would leave
|
|
562
|
+
* the runs — for showing styling armed for the next keystroke over the
|
|
563
|
+
* styling at the caret. A key the patch leaves `undefined` keeps the range's
|
|
564
|
+
* value; the key it supersedes is dropped.
|
|
565
|
+
*/
|
|
566
|
+
declare function patchRangeStyle(range: RangeStyle, patch: RunStylePatch): RangeStyle;
|
|
567
|
+
/**
|
|
568
|
+
* Write `patch` over `[start, end)`, splitting runs at the boundaries and
|
|
569
|
+
* coalescing neighbors that end up identical. A patch value of `undefined`
|
|
570
|
+
* or `false` deletes the key instead of storing it.
|
|
571
|
+
*
|
|
572
|
+
* Returns a new array of new runs (though a nested `fill` is shared by
|
|
573
|
+
* reference). Normalization — merging identical neighbors, dropping empty
|
|
574
|
+
* runs — applies to the whole array, not just the patched span, and to every
|
|
575
|
+
* call, including one whose range is empty.
|
|
576
|
+
*/
|
|
577
|
+
declare function applyStyleToRange(runs: readonly StyledRun[], start: number, end: number, patch: RunStylePatch): StyledRun[];
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The weight a range renders at, and whether that reads as bold, resolved as
|
|
581
|
+
* `resolveRuns` resolves a run: its own `fontWeight`, else 700 for `bold`,
|
|
582
|
+
* else the node's weight.
|
|
583
|
+
*/
|
|
584
|
+
declare function rangeWeight(range: RangeStyle | null, style: TextStyle | undefined): {
|
|
585
|
+
fontWeight: number | Mixed;
|
|
586
|
+
bold: boolean | Mixed;
|
|
587
|
+
};
|
|
588
|
+
/**
|
|
589
|
+
* What is actually rendering across a range: `range` (from `styleAtRange`,
|
|
590
|
+
* or `useTextEdit`'s `rangeStyle`) resolved against the node's own `style`
|
|
591
|
+
* and `paint`, as the canvas resolves runs. `null` reads the node alone.
|
|
592
|
+
*
|
|
593
|
+
* Flags are additive — a run can turn one on, never off — so a node flag
|
|
594
|
+
* makes the flag `true` across the range, `MIXED` runs included. Every other
|
|
595
|
+
* key is the run's value where the range sets one, otherwise the node's.
|
|
596
|
+
* The node level is resolved first, so the result has a value for every
|
|
597
|
+
* key but the run-only ones, and for `fill` unless the node is unfilled.
|
|
598
|
+
* Bold is read off the weight that renders (see {@link rangeWeight}), since
|
|
599
|
+
* a run's own `fontWeight` can take a bold node's text back to regular.
|
|
600
|
+
*/
|
|
601
|
+
declare function effectiveRangeStyle(range: RangeStyle | null, style: TextStyle | undefined, paint?: TextPaint): RangeStyle;
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Turning a run-level flag **off** inside a node that sets it.
|
|
605
|
+
*
|
|
606
|
+
* Run flags are additive over the node's `TextStyle` — a run turns `bold` /
|
|
607
|
+
* `italic` / `underline` / `strikethrough` on, never off (see the contract at
|
|
608
|
+
* the top of `rangeStyle.ts`). This resolves it by rewriting rather than by
|
|
609
|
+
* widening the model: clear the flag on the node, and set it on every run
|
|
610
|
+
* *outside* the range. The rendered result is identical, with `StyledRun`
|
|
611
|
+
* unchanged.
|
|
612
|
+
*
|
|
613
|
+
* The alternative was a tri-state run flag (`true` / `false` / inherit). It
|
|
614
|
+
* cannot cover `italic`: that is a boolean on a run but `fontStyle` on the
|
|
615
|
+
* node, so a run's `false` has no node-level boolean to override.
|
|
616
|
+
*
|
|
617
|
+
* `bold` needs neither: a run carries a numeric `fontWeight`, which overrides
|
|
618
|
+
* the node's outright, so lowering bold writes a regular weight over the
|
|
619
|
+
* range and leaves the node and the rest of the text alone.
|
|
620
|
+
*/
|
|
621
|
+
|
|
622
|
+
/** The additive run flags. */
|
|
623
|
+
type FlagKey = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'overline';
|
|
624
|
+
/** The result of toggling a style flag over a text range: the rewritten runs
|
|
625
|
+
* and node style. */
|
|
626
|
+
interface SetFlagResult {
|
|
627
|
+
runs: StyledRun[];
|
|
628
|
+
style: TextStyle;
|
|
629
|
+
}
|
|
630
|
+
/** Does the node style carry this flag? */
|
|
631
|
+
declare function nodeHasFlag(style: TextStyle, key: FlagKey): boolean;
|
|
632
|
+
/** The patch that takes bold off a range: the flag, and — in a node that is
|
|
633
|
+
* itself bold — a regular weight to override the node's. */
|
|
634
|
+
declare function unboldPatch(style: TextStyle): RunStylePatch;
|
|
635
|
+
/**
|
|
636
|
+
* Set `key` to `value` over `[start, end)`.
|
|
637
|
+
*
|
|
638
|
+
* Turning a flag **on**, or off in a node that doesn't set it, is the ordinary
|
|
639
|
+
* additive write and leaves `style` alone. Turning it off in a node that *does*
|
|
640
|
+
* set it takes the rewrite: the node flag is cleared and the flag is written
|
|
641
|
+
* onto the complement of the range. Bold is the exception, written as a
|
|
642
|
+
* weight over the range alone (see {@link unboldPatch}).
|
|
643
|
+
*
|
|
644
|
+
* `runs` is normalized on every path, so the complement collapses back to one
|
|
645
|
+
* run when the range is empty and the whole array coalesces as usual.
|
|
646
|
+
*/
|
|
647
|
+
declare function setFlagOverRange(runs: readonly StyledRun[], style: TextStyle, start: number, end: number, key: FlagKey, value: boolean): SetFlagResult;
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Synthetic-oblique angle, in radians — 12°, the conventional CSS
|
|
651
|
+
* `font-style: oblique`. Shared by everything that fakes an italic: the SDF
|
|
652
|
+
* tier skews quads by it, the outline tier shears glyph geometry by it, and
|
|
653
|
+
* `textToPath` bakes the same shear into extracted outlines. One constant so a
|
|
654
|
+
* face that falls back to the upright atlas leans the same amount however it
|
|
655
|
+
* ends up being drawn.
|
|
656
|
+
*/
|
|
657
|
+
declare const SYNTHETIC_ITALIC_RADIANS = 0.2094;
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Text as path geometry: the glyph outlines a text node draws, placed in
|
|
661
|
+
* world space and composed into one `Path`. The glyphs are the ones
|
|
662
|
+
* `layoutRuns` placed, so they land exactly where the renderer's outline tier
|
|
663
|
+
* puts them, with the tier forced on for every glyph whatever its size.
|
|
664
|
+
*/
|
|
665
|
+
|
|
666
|
+
/** The fields of a text node's data that decide its glyph geometry, and the
|
|
667
|
+
* paint `textToPathsByPaint` splits it by. Paint never changes the geometry. */
|
|
668
|
+
interface TextOutlineSource {
|
|
669
|
+
text: string;
|
|
670
|
+
runs?: readonly StyledRun[];
|
|
671
|
+
style?: TextStyle;
|
|
672
|
+
verticalAlign?: TextVerticalAlign;
|
|
673
|
+
/** The node's `data.fill`: `null` is no fill, absent the default black. */
|
|
674
|
+
fill?: Fill | null;
|
|
675
|
+
/** The node's `data.stroke`: absent or `null` is no stroke. */
|
|
676
|
+
stroke?: Stroke | null;
|
|
677
|
+
}
|
|
678
|
+
/** One paint's share of a text node's outlines — see {@link textToPathsByPaint}. */
|
|
679
|
+
interface TextPaintPath {
|
|
680
|
+
/** The resolved fill these glyphs wear: the run's own, else the node's.
|
|
681
|
+
* `null` for unfilled text, painted by its stroke alone. */
|
|
682
|
+
fill: Fill | null;
|
|
683
|
+
/** The resolved stroke — the run's own, else the node's — or absent. */
|
|
684
|
+
stroke?: Stroke;
|
|
685
|
+
/** World-space outlines, as {@link textToPath} builds them. */
|
|
686
|
+
path: PolygonPath;
|
|
687
|
+
}
|
|
688
|
+
/** A text node as {@link textToPath} reads it: its data and its pose. */
|
|
689
|
+
interface TextNodeSource {
|
|
690
|
+
data: TextOutlineSource;
|
|
691
|
+
pose: PathInWorldPose;
|
|
692
|
+
}
|
|
693
|
+
/** Options for {@link textToPath}. */
|
|
694
|
+
interface TextToPathOptions {
|
|
695
|
+
/** The view scale a `{ px }` `fontSize` or `letterSpacing` resolves
|
|
696
|
+
* against, as for `textCommandFromPose`. Default 1. */
|
|
697
|
+
scale?: number;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Why a text node has no outline geometry to give:
|
|
701
|
+
*
|
|
702
|
+
* - `'no-outlines'` — the face that serves it has no outlines registered
|
|
703
|
+
* (`registerFontOutlines`), so there is no geometry to read.
|
|
704
|
+
* - `'outlines-loading'` — registered but not parsed yet. `loadTextOutlines`
|
|
705
|
+
* waits for it; the call has already started the load.
|
|
706
|
+
* - `'outlines-failed'` — registered, and the bytes did not load or parse.
|
|
707
|
+
* - `'synthetic-bold'` — the requested weight is faked from a lighter face.
|
|
708
|
+
* The renderer thickens the distance field for that, and a path has no
|
|
709
|
+
* field to thicken. Converting would silently lighten the text.
|
|
710
|
+
* - `'color-glyph'` — the text holds an emoji, which is a picture in its own
|
|
711
|
+
* colors with no outline. Nothing a caller registers changes that.
|
|
712
|
+
*/
|
|
713
|
+
type TextOutlinesFailure = 'no-outlines' | 'color-glyph' | 'outlines-loading' | 'outlines-failed' | 'synthetic-bold';
|
|
714
|
+
/** Thrown by {@link textToPath} when some run of the text has no outline
|
|
715
|
+
* geometry. Names the run's requested face. */
|
|
716
|
+
declare class TextOutlinesError extends Error {
|
|
717
|
+
readonly reason: TextOutlinesFailure;
|
|
718
|
+
readonly family: string;
|
|
719
|
+
readonly weight: number;
|
|
720
|
+
readonly style: FontStyle;
|
|
721
|
+
constructor(reason: TextOutlinesFailure, family: string, weight: number, style: FontStyle, detail: string);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* The outline geometry of a text node, in world space: every glyph and every
|
|
725
|
+
* underline, strikethrough and overline rule it draws, as one `'nonzero'`
|
|
726
|
+
* compound path whose filled region is their union. Glyphs keep the font's
|
|
727
|
+
* curves; overlapping contours are not merged — run the result through
|
|
728
|
+
* `pathUnion` for that, at the cost of flattening the curves.
|
|
729
|
+
*
|
|
730
|
+
* `pose` is the node's box, rotation, and `transform`. Throws
|
|
731
|
+
* {@link TextOutlinesError} when any run's face cannot supply outlines — never
|
|
732
|
+
* returns a partial or empty path for that. Text with no ink (empty, or only
|
|
733
|
+
* spaces) returns an empty path.
|
|
734
|
+
*/
|
|
735
|
+
declare function textToPath(data: TextOutlineSource, pose: PathInWorldPose, opts?: TextToPathOptions): PolygonPath;
|
|
736
|
+
/**
|
|
737
|
+
* {@link textToPath}, split by paint: one path per distinct resolved fill and
|
|
738
|
+
* stroke, each gathering the glyphs and decoration rules that wear it — a
|
|
739
|
+
* rule takes the fill of the run it underlines. Groups come in the order
|
|
740
|
+
* their paint first appears in the text; a paint whose runs put down no ink
|
|
741
|
+
* has none. Paint resolves as the renderer resolves it. Throws as
|
|
742
|
+
* `textToPath` does.
|
|
743
|
+
*/
|
|
744
|
+
declare function textToPathsByPaint(data: TextOutlineSource, pose: PathInWorldPose, opts?: TextToPathOptions): TextPaintPath[];
|
|
745
|
+
/**
|
|
746
|
+
* Load the outlines of every face `data` is set in — the requested faces and
|
|
747
|
+
* the ones they resolve to — so a following {@link textToPath} does not fail
|
|
748
|
+
* with `'outlines-loading'`. Resolves once each has loaded or failed.
|
|
749
|
+
*/
|
|
750
|
+
declare function loadTextOutlines(data: TextOutlineSource, opts?: TextToPathOptions): Promise<void>;
|
|
751
|
+
|
|
752
|
+
export { BREAK_ALLOWED, BREAK_MANDATORY, DEFAULT_DECORATION_METRICS, type DecorationKind, type FaceMetricsFn, type FitTextPoseOptions, type FlagKey, type LabelOptions, type LaidOutDecoration, type LaidOutGroup, type LaidOutOutlineGlyph, type LaidOutQuad, LaidOutRuns, type LayoutLine, type LayoutResult, LayoutRunsOpts, type MarkdownFontOptions, type MeasureFn, type MeasureTextBoundsOpts, type MeasuredSpan, type MeasuredText, NO_BREAK, type PaintRunsOpts, type PaintedRuns, type PointInTextPoseOpts, type PositionedRun, type RangeStyle, ResolvedRun, ResolvedTextStyle, type RunStylePatch, SYNTHETIC_ITALIC_RADIANS, type SetFlagResult, type StyleKey, StyledRun, type TextLineBoxesOpts, type TextNodeSource, type TextOutlineSource, TextOutlinesError, type TextOutlinesFailure, TextPaint, type TextPaintPath, type TextPose, type TextPoseLayout, type TextPoseLayoutInput, type TextRenderer, TextStyle, type TextToPathOptions, type TextVerticalAlign, applyStyleToRange, cachedPaintRuns, caretIndexAt, createMarkdownRenderer, decorationMetrics, defaultLabelTextRenderer, effectiveRangeStyle, fitTextPose, isHardLineBreak, isTextData, layoutMarkdown, layoutTextPose, lineBreakOpportunities, loadTextOutlines, measureText, measureTextBounds, measuredWidth, nodeHasFlag, paintRuns, patchRangeStyle, pointInTextPose, rangeWeight, renderLabel, runsCarryStyling, setFlagOverRange, styleAtRange, supersededKey, textLineBoxes, textPoseLayoutInput, textToPath, textToPathsByPaint, unboldPatch, verticalAlignOffset };
|