render-tag 0.1.7 → 0.1.9

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.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Pure layout: walks styled segments and lays each grapheme along a path.
3
+ * No canvas rendering happens here — that's the caller's job. Reusable
4
+ * for hit-testing, debugging, alternate renderers (SVG, GPU).
5
+ *
6
+ * Joining-script support: graphemes from Arabic, Hebrew, Indic, Thai, Khmer,
7
+ * Myanmar and related scripts within a single segment are grouped into one
8
+ * "shaped run" placement. The browser's fillText/measureText then shapes the
9
+ * run as a unit (cursive joining, reordering, conjuncts) — something
10
+ * per-grapheme drawing cannot do.
11
+ */
12
+ import type { ResolvedStyle, StyledNode } from '../types.js';
13
+ import type { PathLike } from './svg-path.js';
14
+ export interface Segment {
15
+ text: string;
16
+ style: ResolvedStyle;
17
+ /** True when this segment should be laid out right-to-left. */
18
+ rtl: boolean;
19
+ }
20
+ export interface GlyphPlacement {
21
+ /**
22
+ * Renderable text unit — usually a single grapheme cluster, but joining
23
+ * scripts (Arabic, Indic, Thai, Khmer, Myanmar, …) emit multi-grapheme runs
24
+ * here so the browser can shape them correctly during fillText.
25
+ */
26
+ char: string;
27
+ /** Origin of the glyph on the path (translate target before fillText). */
28
+ x: number;
29
+ y: number;
30
+ /** Tangent angle at the glyph origin, in radians. */
31
+ rotation: number;
32
+ /** Advance width of the glyph or shaped run. */
33
+ width: number;
34
+ /** Resolved style to apply when drawing this glyph. */
35
+ style: ResolvedStyle;
36
+ /** Font ascent above the baseline (px) — used for visual extent. */
37
+ ascent: number;
38
+ /** Font descent below the baseline (px). */
39
+ descent: number;
40
+ /** Distance from the start of the text along the path (px). */
41
+ pathOffset: number;
42
+ /** True when this placement is a shaped run, not a single grapheme. */
43
+ shaped: boolean;
44
+ }
45
+ export type AlignMode = 'left' | 'center' | 'right' | 'justify';
46
+ /**
47
+ * Where the path runs relative to the rendered text.
48
+ * - `alphabetic` (default) — path = text baseline; descenders drop below.
49
+ * - `middle` — path runs through the vertical center of the text.
50
+ * - `top` — path runs along the top of the text.
51
+ * - `bottom` — path runs along the bottom (including descenders).
52
+ * - `hanging` / `ideographic` — approximations of the matching CSS values.
53
+ */
54
+ export type TextBaseline = 'alphabetic' | 'middle' | 'top' | 'bottom' | 'hanging' | 'ideographic';
55
+ export interface LayoutInput {
56
+ segments: Segment[];
57
+ path: PathLike;
58
+ ctx: CanvasRenderingContext2D;
59
+ align: AlignMode;
60
+ textBaseline: TextBaseline;
61
+ }
62
+ export interface LayoutOutput {
63
+ glyphs: GlyphPlacement[];
64
+ /** Sum of glyph widths + letterSpacing (the natural width of the rendered text). */
65
+ textWidth: number;
66
+ pathLength: number;
67
+ /**
68
+ * Max line height across all segments. Resolved CSS line-height in px when
69
+ * set; otherwise the segment's font size. Useful as the ribbon thickness
70
+ * when drawing a background polygon around curved text.
71
+ */
72
+ lineHeight: number;
73
+ /**
74
+ * Visible bounding box of the rendered text in the layout's coordinate
75
+ * system. Computed as the union of each glyph's cell, where the cell is
76
+ * width × per-glyph line-height (CSS line-height in px when set, else
77
+ * font size) and rotated by the glyph's tangent. lineHeight is
78
+ * distributed above/below the baseline by the font's ascent/descent
79
+ * ratio. Per-glyph (not max-across) so mixed-size curve text doesn't
80
+ * inflate.
81
+ *
82
+ * Consumers (e.g. Polotno) use this to keep an element's width/height in
83
+ * sync with the rendered model. The library does not consume `bounds`
84
+ * itself — it's purely exposed for callers.
85
+ */
86
+ bounds: {
87
+ x: number;
88
+ y: number;
89
+ width: number;
90
+ height: number;
91
+ };
92
+ /** The textBaseline mode used for this layout (echoes the input). */
93
+ textBaseline: TextBaseline;
94
+ }
95
+ /**
96
+ * Returns the local-y of the alphabetic baseline given a textBaseline
97
+ * choice and a glyph's ascent/descent. All baseline-relative computations
98
+ * (decoration positions, background polygons, bounds cells) ADD this offset
99
+ * to their local-y so the path line through (0,0) corresponds to the
100
+ * requested baseline anchor.
101
+ */
102
+ export declare function baselineLocalY(tb: TextBaseline, ascent: number, descent: number): number;
103
+ /**
104
+ * Flatten a styled tree into a flat sequence of styled text segments.
105
+ * Walks in document order; concatenates text under nested inline elements.
106
+ * Each `#text` node contributes one segment with its resolved style.
107
+ */
108
+ export declare function flattenSegments(root: StyledNode): Segment[];
109
+ /**
110
+ * Lay out graphemes along a path. Pure: does not call ctx.fillText.
111
+ *
112
+ * Algorithm:
113
+ * 1. Per segment, split into graphemes and shaped runs, measure each.
114
+ * 2. Compute total natural width.
115
+ * 3. Pick a starting offset along the path based on `align`.
116
+ * 4. For each placement: get p0 / p1 from the path, rotation = atan2(p1-p0).
117
+ * If a placement would overshoot, only allow it within kerning slack.
118
+ */
119
+ export declare function layoutGlyphsOnPath(input: LayoutInput): LayoutOutput;
120
+ //# sourceMappingURL=glyph-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyph-layout.d.ts","sourceRoot":"","sources":["../../src/path/glyph-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,eAAe,CAAC;AAErD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,+DAA+D;IAC/D,GAAG,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,EAAE,aAAa,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB,YAAY,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAE3E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,wBAAwB,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;OAYG;IACH,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,qEAAqE;IACrE,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAChD,MAAM,CASR;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,EAAE,CAY3D;AAwID;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CA8GnE"}
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Pure layout: walks styled segments and lays each grapheme along a path.
3
+ * No canvas rendering happens here — that's the caller's job. Reusable
4
+ * for hit-testing, debugging, alternate renderers (SVG, GPU).
5
+ *
6
+ * Joining-script support: graphemes from Arabic, Hebrew, Indic, Thai, Khmer,
7
+ * Myanmar and related scripts within a single segment are grouped into one
8
+ * "shaped run" placement. The browser's fillText/measureText then shapes the
9
+ * run as a unit (cursive joining, reordering, conjuncts) — something
10
+ * per-grapheme drawing cannot do.
11
+ */
12
+ import { applyFont, getFontMetrics } from '../layout.js';
13
+ import { stringToArray } from './grapheme.js';
14
+ /**
15
+ * Returns the local-y of the alphabetic baseline given a textBaseline
16
+ * choice and a glyph's ascent/descent. All baseline-relative computations
17
+ * (decoration positions, background polygons, bounds cells) ADD this offset
18
+ * to their local-y so the path line through (0,0) corresponds to the
19
+ * requested baseline anchor.
20
+ */
21
+ export function baselineLocalY(tb, ascent, descent) {
22
+ switch (tb) {
23
+ case 'alphabetic': return 0;
24
+ case 'middle': return (ascent - descent) / 2;
25
+ case 'top': return ascent;
26
+ case 'bottom': return -descent;
27
+ case 'hanging': return ascent * 0.8;
28
+ case 'ideographic': return -descent * 0.5;
29
+ }
30
+ }
31
+ /**
32
+ * Flatten a styled tree into a flat sequence of styled text segments.
33
+ * Walks in document order; concatenates text under nested inline elements.
34
+ * Each `#text` node contributes one segment with its resolved style.
35
+ */
36
+ export function flattenSegments(root) {
37
+ const out = [];
38
+ function walk(node, inheritedRtl) {
39
+ const rtl = node.style.direction === 'rtl' || inheritedRtl;
40
+ if (node.tagName === '#text' && node.textContent) {
41
+ out.push({ text: node.textContent, style: node.style, rtl });
42
+ return;
43
+ }
44
+ for (const child of node.children)
45
+ walk(child, rtl);
46
+ }
47
+ walk(root, false);
48
+ return out;
49
+ }
50
+ // Unicode ranges where graphemes need shape-aware rendering. The browser's
51
+ // fillText handles these correctly only when given the whole run at once,
52
+ // not one grapheme at a time:
53
+ // - Hebrew (no joining, but RTL+BiDi)
54
+ // - Arabic + presentation forms (cursive joining)
55
+ // - N'Ko, Mandaic, Syriac, Thaana (joining/RTL)
56
+ // - Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada,
57
+ // Malayalam, Sinhala (Indic reordering + conjuncts)
58
+ // - Thai, Lao (combining marks + word break)
59
+ // - Tibetan (stacking)
60
+ // - Myanmar (reordering + stacking)
61
+ // - Khmer (reordering + subscript consonants)
62
+ // IMPORTANT: written with `\u` escapes only. Mixing literal RTL characters
63
+ // confuses the regex parser at parse time — e.g. the precomposed `יִ` is
64
+ // actually two code points (U+05D9 + U+05B4) and a literal range starting
65
+ // at the second code point engulfs CJK / Hangul / Hiragana / Katakana,
66
+ // causing `needsShaping('中')` to return true.
67
+ const SHAPING_RE = new RegExp('[' +
68
+ '\\u0590-\\u05FF' + // Hebrew
69
+ '\\u0600-\\u06FF' + // Arabic
70
+ '\\u0700-\\u074F' + // Syriac
71
+ '\\u0750-\\u077F' + // Arabic Supplement
72
+ '\\u0780-\\u07BF' + // Thaana
73
+ '\\u07C0-\\u07FF' + // NKo
74
+ '\\u0800-\\u083F' + // Samaritan
75
+ '\\u0840-\\u085F' + // Mandaic
76
+ '\\u0860-\\u086F' + // Syriac Supplement
77
+ '\\u08A0-\\u08FF' + // Arabic Extended-A
78
+ '\\u0900-\\u097F' + // Devanagari
79
+ '\\u0980-\\u09FF' + // Bengali
80
+ '\\u0A00-\\u0A7F' + // Gurmukhi
81
+ '\\u0A80-\\u0AFF' + // Gujarati
82
+ '\\u0B00-\\u0B7F' + // Oriya
83
+ '\\u0B80-\\u0BFF' + // Tamil
84
+ '\\u0C00-\\u0C7F' + // Telugu
85
+ '\\u0C80-\\u0CFF' + // Kannada
86
+ '\\u0D00-\\u0D7F' + // Malayalam
87
+ '\\u0D80-\\u0DFF' + // Sinhala
88
+ '\\u0E00-\\u0E7F' + // Thai
89
+ '\\u0E80-\\u0EFF' + // Lao
90
+ '\\u0F00-\\u0FFF' + // Tibetan
91
+ '\\u1000-\\u109F' + // Myanmar
92
+ '\\u1780-\\u17FF' + // Khmer
93
+ '\\u1800-\\u18AF' + // Mongolian
94
+ '\\uFB1D-\\uFB4F' + // Hebrew Presentation Forms
95
+ '\\uFB50-\\uFDFF' + // Arabic Presentation Forms-A
96
+ '\\uFE70-\\uFEFF' + // Arabic Presentation Forms-B
97
+ ']');
98
+ function needsShaping(s) {
99
+ return SHAPING_RE.test(s);
100
+ }
101
+ /**
102
+ * Split a single styled segment into PreGlyphs.
103
+ *
104
+ * Non-joining graphemes (Latin, CJK, …) emit one PreGlyph per grapheme so the
105
+ * curve can drive per-glyph rotation. Joining-script graphemes are grouped
106
+ * into runs (split at whitespace + style boundaries) so the browser can shape
107
+ * them correctly when we later call fillText on the run as a whole.
108
+ *
109
+ * For RTL segments, the RUN ORDER is reversed (not the graphemes inside a
110
+ * shaped run) — that way Arabic words still shape correctly while flowing in
111
+ * visual right-to-left order along an LTR path walk.
112
+ */
113
+ function preGlyphsForSegment(ctx, seg) {
114
+ const graphemes = stringToArray(seg.text);
115
+ if (graphemes.length === 0)
116
+ return [];
117
+ applyFont(ctx, seg.style);
118
+ // Always assign — when the current segment's letterSpacing is 0/unset,
119
+ // we still need to reset the previous segment's value.
120
+ ctx.letterSpacing = `${seg.style.letterSpacing || 0}px`;
121
+ const { ascent, descent } = getFontMetrics(ctx, seg.style);
122
+ // Group graphemes into (shaped run | single non-shaped grapheme).
123
+ // Boundaries: shape-status change, ASCII whitespace.
124
+ const runs = [];
125
+ let currentShapedRun = '';
126
+ for (const g of graphemes) {
127
+ const isSpace = g === ' ';
128
+ if (needsShaping(g) && !isSpace) {
129
+ currentShapedRun += g;
130
+ }
131
+ else {
132
+ if (currentShapedRun) {
133
+ runs.push({ text: currentShapedRun, shaped: true, isSpace: false });
134
+ currentShapedRun = '';
135
+ }
136
+ runs.push({ text: g, shaped: false, isSpace });
137
+ }
138
+ }
139
+ if (currentShapedRun) {
140
+ runs.push({ text: currentShapedRun, shaped: true, isSpace: false });
141
+ }
142
+ // RTL: reverse run order. Don't reverse graphemes inside a shaped run —
143
+ // the browser will lay them out right-to-left during fillText.
144
+ if (seg.rtl)
145
+ runs.reverse();
146
+ // Measure each run with the current ctx font/letterSpacing.
147
+ const out = [];
148
+ for (const r of runs) {
149
+ const width = ctx.measureText(r.text).width;
150
+ out.push({
151
+ text: r.text,
152
+ width,
153
+ style: seg.style,
154
+ isSpace: r.isSpace,
155
+ ascent,
156
+ descent,
157
+ shaped: r.shaped,
158
+ });
159
+ }
160
+ return out;
161
+ }
162
+ /**
163
+ * Lay out graphemes along a path. Pure: does not call ctx.fillText.
164
+ *
165
+ * Algorithm:
166
+ * 1. Per segment, split into graphemes and shaped runs, measure each.
167
+ * 2. Compute total natural width.
168
+ * 3. Pick a starting offset along the path based on `align`.
169
+ * 4. For each placement: get p0 / p1 from the path, rotation = atan2(p1-p0).
170
+ * If a placement would overshoot, only allow it within kerning slack.
171
+ */
172
+ export function layoutGlyphsOnPath(input) {
173
+ const { segments, path, ctx, align, textBaseline } = input;
174
+ // 1. Pre-measure all placements (one per grapheme or shaped run).
175
+ // Caller's ctx state is mutated here (font, fontKerning, letterSpacing).
176
+ // The outer drawTextOnPath/drawTextOnPathLayout calls ctx.save before this
177
+ // and ctx.restore after, so the leak doesn't reach the caller.
178
+ const preGlyphs = [];
179
+ let measuredWholeWidth = 0;
180
+ let maxLineHeight = 0;
181
+ for (const seg of segments) {
182
+ if (!seg.text)
183
+ continue;
184
+ const lh = seg.style.lineHeight > 0 ? seg.style.lineHeight : seg.style.fontSize;
185
+ if (lh > maxLineHeight)
186
+ maxLineHeight = lh;
187
+ const segGlyphs = preGlyphsForSegment(ctx, seg);
188
+ if (segGlyphs.length === 0)
189
+ continue;
190
+ preGlyphs.push(...segGlyphs);
191
+ // Whole-segment width — kerning makes this < sum of per-glyph widths.
192
+ measuredWholeWidth += ctx.measureText(seg.text).width;
193
+ }
194
+ // 2. Sum natural width (sum of placement widths + letterSpacing per item).
195
+ let textWidth = 0;
196
+ for (const g of preGlyphs) {
197
+ textWidth += g.width + (g.style.letterSpacing || 0);
198
+ }
199
+ // Trim the last letterSpacing — it shouldn't trail.
200
+ if (preGlyphs.length > 0) {
201
+ textWidth -= preGlyphs[preGlyphs.length - 1].style.letterSpacing || 0;
202
+ }
203
+ // Kerning slack: how much the per-glyph sum can exceed the measured
204
+ // whole-string width. When the path is sized to match the visual text,
205
+ // we use this to allow up to N px of overshoot at the path's tail end.
206
+ const kerningSlack = Math.max(0, textWidth - measuredWholeWidth);
207
+ // 3. Starting offset + per-space extra (justify).
208
+ const pathLength = path.length;
209
+ let startOffset = 0;
210
+ let extraPerSpace = 0;
211
+ if (align === 'center') {
212
+ startOffset = Math.max(0, (pathLength - textWidth) / 2);
213
+ }
214
+ else if (align === 'right') {
215
+ startOffset = Math.max(0, pathLength - textWidth);
216
+ }
217
+ else if (align === 'justify') {
218
+ const spaceCount = preGlyphs.filter(g => g.isSpace).length;
219
+ if (spaceCount > 0 && pathLength > textWidth) {
220
+ extraPerSpace = (pathLength - textWidth) / spaceCount;
221
+ }
222
+ }
223
+ // 4. Walk the path.
224
+ // We track two cumulative offsets:
225
+ // - `offset` is the path arc-length (used for path.getPointAtLength), and
226
+ // advances by `effectiveWidth` (incl. justify extraPerSpace).
227
+ // - `naturalOffset` is the glyph's position in NATURAL text-space
228
+ // [0, textWidth]. Used as `pathOffset` for gradient slicing — must NOT
229
+ // include extraPerSpace, otherwise late justified glyphs map past the
230
+ // gradient's last stop.
231
+ const glyphs = [];
232
+ let offset = startOffset;
233
+ let naturalOffset = 0;
234
+ for (let i = 0; i < preGlyphs.length; i++) {
235
+ const g = preGlyphs[i];
236
+ const effectiveWidth = g.width + (g.isSpace ? extraPerSpace : 0);
237
+ const p0 = path.getPointAtLength(offset);
238
+ if (!p0)
239
+ break;
240
+ let endLen = offset + effectiveWidth;
241
+ let p1;
242
+ if (endLen > pathLength) {
243
+ // Clamp to pathLength only if the overshoot is within the kerning slack —
244
+ // keeps the last glyph from getting dropped to a sub-px rounding miss.
245
+ if (endLen - pathLength <= kerningSlack + 0.5) {
246
+ p1 = path.getPointAtLength(pathLength);
247
+ }
248
+ else {
249
+ break;
250
+ }
251
+ }
252
+ else {
253
+ p1 = path.getPointAtLength(endLen);
254
+ }
255
+ if (!p1)
256
+ break;
257
+ const rotation = Math.atan2(p1.y - p0.y, p1.x - p0.x);
258
+ glyphs.push({
259
+ char: g.text,
260
+ x: p0.x,
261
+ y: p0.y,
262
+ rotation,
263
+ width: g.width,
264
+ style: g.style,
265
+ ascent: g.ascent,
266
+ descent: g.descent,
267
+ pathOffset: naturalOffset,
268
+ shaped: g.shaped,
269
+ });
270
+ offset = endLen + (g.style.letterSpacing || 0);
271
+ naturalOffset += g.width + (g.style.letterSpacing || 0);
272
+ }
273
+ const bounds = computeBounds(glyphs, textBaseline);
274
+ return {
275
+ glyphs,
276
+ textWidth,
277
+ pathLength,
278
+ lineHeight: maxLineHeight,
279
+ bounds,
280
+ textBaseline,
281
+ };
282
+ }
283
+ /**
284
+ * Bounding box of the rendered text: union of each glyph's
285
+ * `width × per-glyph line-height` cell, rotated by the glyph's tangent.
286
+ *
287
+ * The cell height is the per-glyph line-height (CSS line-height when set,
288
+ * else font-size — Polotno's chosen metric). It is distributed
289
+ * ASYMMETRICALLY above/below the baseline using the font's natural
290
+ * ascent/descent ratio, so cap-height + ascender area is covered and the
291
+ * cell doesn't waste pixels below an empty descender. For a typical font
292
+ * with ascent ~25 / descent ~7 / lineHeight 32, the cell extends ~25px
293
+ * above and ~7px below the baseline, matching the painted glyph extent.
294
+ *
295
+ * Empty layouts return `{ x: 0, y: 0, width: 0, height: 0 }`.
296
+ */
297
+ function computeBounds(glyphs, textBaseline) {
298
+ if (glyphs.length === 0)
299
+ return { x: 0, y: 0, width: 0, height: 0 };
300
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
301
+ for (const g of glyphs) {
302
+ const lh = g.style.lineHeight > 0 ? g.style.lineHeight : g.style.fontSize;
303
+ // Distribute the lineHeight above/below the baseline by the font's
304
+ // natural ascent/descent ratio (CSS line-box half-leading semantics).
305
+ const fontHeight = g.ascent + g.descent;
306
+ const ascentShare = fontHeight > 0 ? g.ascent / fontHeight : 0.75;
307
+ const topFromBaseline = lh * ascentShare; // above baseline
308
+ const bottomFromBaseline = lh * (1 - ascentShare); // below baseline
309
+ // Shift by the local-y of the baseline so the cell stays correct under
310
+ // any textBaseline (e.g. 'middle' places the cell vertically centred
311
+ // on the path; 'top' moves the entire cell down).
312
+ const baseY = baselineLocalY(textBaseline, g.ascent, g.descent);
313
+ const top = baseY - topFromBaseline;
314
+ const bottom = baseY + bottomFromBaseline;
315
+ const c = Math.cos(g.rotation);
316
+ const s = Math.sin(g.rotation);
317
+ // Local cell: x in [0, width], y in [top, bottom]. Rotate + translate.
318
+ const corners = [
319
+ { x: g.x + (-s) * top, y: g.y + c * top },
320
+ { x: g.x + c * g.width + (-s) * top, y: g.y + s * g.width + c * top },
321
+ { x: g.x + c * g.width + (-s) * bottom, y: g.y + s * g.width + c * bottom },
322
+ { x: g.x + (-s) * bottom, y: g.y + c * bottom },
323
+ ];
324
+ for (const p of corners) {
325
+ if (p.x < minX)
326
+ minX = p.x;
327
+ if (p.y < minY)
328
+ minY = p.y;
329
+ if (p.x > maxX)
330
+ maxX = p.x;
331
+ if (p.y > maxY)
332
+ maxY = p.y;
333
+ }
334
+ }
335
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
336
+ }
337
+ //# sourceMappingURL=glyph-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyph-layout.js","sourceRoot":"","sources":["../../src/path/glyph-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function stringToArray(s: string): string[];
2
+ //# sourceMappingURL=grapheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grapheme.d.ts","sourceRoot":"","sources":["../../src/path/grapheme.ts"],"names":[],"mappings":"AAwBA,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAUjD"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Split a string into user-perceived characters (grapheme clusters).
3
+ * Uses Intl.Segmenter when available (handles ZWJ emoji sequences, regional
4
+ * indicators, combining marks). Falls back to a surrogate-pair-aware split
5
+ * for older environments.
6
+ *
7
+ * Equivalent in spirit to konva's stringToArray helper.
8
+ */
9
+ let _segmenter;
10
+ function getSegmenter() {
11
+ if (_segmenter !== undefined)
12
+ return _segmenter;
13
+ if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
14
+ try {
15
+ _segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
16
+ }
17
+ catch {
18
+ _segmenter = null;
19
+ }
20
+ }
21
+ else {
22
+ _segmenter = null;
23
+ }
24
+ return _segmenter;
25
+ }
26
+ export function stringToArray(s) {
27
+ if (!s)
28
+ return [];
29
+ const seg = getSegmenter();
30
+ if (seg) {
31
+ const out = [];
32
+ for (const piece of seg.segment(s))
33
+ out.push(piece.segment);
34
+ return out;
35
+ }
36
+ // Fallback: handle UTF-16 surrogate pairs but not full grapheme clusters.
37
+ return Array.from(s);
38
+ }
39
+ //# sourceMappingURL=grapheme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grapheme.js","sourceRoot":"","sources":["../../src/path/grapheme.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,IAAI,UAA6C,CAAC;AAElD,SAAS,YAAY;IACnB,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAChD,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QACxE,IAAI,CAAC;YACH,UAAU,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACP,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClB,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5D,OAAO,GAAG,CAAC;IACb,CAAC;IACD,0EAA0E;IAC1E,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * render-tag/path — draw rich text along an SVG path on a 2D canvas.
3
+ *
4
+ * Reuses render-tag's HTML+CSS pipeline (parseHTML, resolveStylesFromCSS) and
5
+ * font primitives (applyFont) so styled spans, fonts, and colors come straight
6
+ * from the input HTML — no separate option surface.
7
+ *
8
+ * import { drawTextOnPath } from 'render-tag/path';
9
+ * drawTextOnPath({
10
+ * html: '<b>Hello</b> <span style="color:red">world</span>',
11
+ * path: 'M0,50 Q200,0 400,50',
12
+ * ctx,
13
+ * align: 'center',
14
+ * });
15
+ *
16
+ * For finer control:
17
+ * const result = layoutTextOnPath({ html, path, align });
18
+ * drawTextOnPathLayout({ layout: result, ctx });
19
+ *
20
+ * Supported styles (matches the main renderer):
21
+ * font-family / size / weight / style / kerning
22
+ * color, -webkit-text-fill-color, -webkit-text-stroke (width + color), paint-order
23
+ * background-color (span backgrounds drawn as curve-following polygons)
24
+ * text-shadow (rotates with glyphs; multi-shadow supported)
25
+ * text-decoration: underline / line-through / overline (solid/dotted/dashed/double/wavy)
26
+ * text-decoration-color, text-decoration-style
27
+ * background-clip:text + background-image:linear-gradient (gradient flows along the path)
28
+ * letter-spacing, direction: rtl, dir="rtl"
29
+ * Arabic / Hebrew / Indic / Thai / Khmer / Myanmar — shaped runs are
30
+ * rendered as a unit so cursive joining and reordering work correctly.
31
+ */
32
+ import { type PathLike } from './svg-path.js';
33
+ import { type AlignMode, type GlyphPlacement, type TextBaseline } from './glyph-layout.js';
34
+ export type { PathLike, GlyphPlacement, AlignMode, TextBaseline };
35
+ export interface LayoutTextOnPathConfig {
36
+ /** Rich-text HTML (same dialect as render-tag's main API). */
37
+ html: string;
38
+ /** SVG path 'd' attribute string, or a PathLike implementation. */
39
+ path: string | PathLike;
40
+ /** Alignment of text along the path (default 'left'). */
41
+ align?: AlignMode;
42
+ /**
43
+ * Where the path runs relative to the rendered text. Default
44
+ * `'alphabetic'` (path = baseline, descenders drop below). Use `'middle'`
45
+ * for design-tool style "text centered on path" rendering. The choice
46
+ * also affects `bounds`.
47
+ */
48
+ textBaseline?: TextBaseline;
49
+ /**
50
+ * Optional measurement context. If omitted, an offscreen canvas is created.
51
+ * Pass one to share font measurement caches with other render-tag calls.
52
+ */
53
+ ctx?: CanvasRenderingContext2D;
54
+ }
55
+ export interface TextOnPathLayout {
56
+ /** Per-glyph (or per-shaped-run) placement records. */
57
+ glyphs: GlyphPlacement[];
58
+ /** Sum of per-glyph advances (the natural width of the rendered text). */
59
+ textWidth: number;
60
+ /** Total arc length of the path. */
61
+ pathLength: number;
62
+ /**
63
+ * Max line height across all segments. Useful as the thickness for a
64
+ * background ribbon polygon around curved text.
65
+ */
66
+ lineHeight: number;
67
+ /**
68
+ * Visible bounding box of the rendered text in the layout's coordinate
69
+ * system (same shape as `DOMRect`). Computed as the union of each glyph's
70
+ * `width × per-glyph line-height` cell, rotated by the glyph's tangent.
71
+ *
72
+ * Consumer-facing: render-tag does not consume `bounds` itself. Use it to
73
+ * keep a parent element's width/height in sync with the rendered curved
74
+ * text without re-walking the glyphs.
75
+ */
76
+ bounds: {
77
+ x: number;
78
+ y: number;
79
+ width: number;
80
+ height: number;
81
+ };
82
+ /** The textBaseline mode the layout was computed with. */
83
+ textBaseline: TextBaseline;
84
+ }
85
+ export interface DrawTextOnPathLayoutConfig {
86
+ /** Result from layoutTextOnPath(). */
87
+ layout: TextOnPathLayout;
88
+ /** Destination 2D context. Saved+restored around the whole batch. */
89
+ ctx: CanvasRenderingContext2D;
90
+ }
91
+ export interface DrawTextOnPathConfig {
92
+ /** Rich-text HTML (same dialect as render-tag's main API). */
93
+ html: string;
94
+ /** SVG path 'd' attribute string, or a PathLike implementation. */
95
+ path: string | PathLike;
96
+ /** Destination 2D context. */
97
+ ctx: CanvasRenderingContext2D;
98
+ /** Alignment of text along the path (default 'left'). */
99
+ align?: AlignMode;
100
+ /** Where the path runs relative to the text (default 'alphabetic'). */
101
+ textBaseline?: TextBaseline;
102
+ }
103
+ export type DrawTextOnPathResult = TextOnPathLayout;
104
+ /**
105
+ * Compute glyph placements for rich text along a path, without drawing.
106
+ * Useful for inspection, hit-testing, or rendering the same layout multiple times.
107
+ * The HTML's CSS resolves against an infinite-width container, so wrapping
108
+ * does not happen — all text flows along the path as one logical line.
109
+ *
110
+ * The caller's ctx (when passed) has its `font`, `fontKerning`, and
111
+ * `letterSpacing` state saved+restored around the measurement work; nothing
112
+ * leaks to the caller.
113
+ */
114
+ export declare function layoutTextOnPath(config: LayoutTextOnPathConfig): TextOnPathLayout;
115
+ /**
116
+ * Draw a pre-computed layout onto a canvas context.
117
+ *
118
+ * Rendering passes (matching CSS painting order):
119
+ * 1. Span backgrounds (background-color, curve-following polygons)
120
+ * 2. Text shadows (per glyph, multi-shadow supported, rotates with glyph)
121
+ * 3. Glyph fill + stroke (paint-order aware; gradient text via slicing)
122
+ * 4. Text decoration (underline / line-through / overline)
123
+ *
124
+ * ctx state is saved+restored around the entire batch — nothing leaks.
125
+ */
126
+ export declare function drawTextOnPathLayout(config: DrawTextOnPathLayoutConfig): void;
127
+ /**
128
+ * Convenience: compute layout and draw in a single call.
129
+ */
130
+ export declare function drawTextOnPath(config: DrawTextOnPathConfig): DrawTextOnPathResult;
131
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/path/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAWH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,yDAAyD;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;OAGG;IACH,GAAG,CAAC,EAAE,wBAAwB,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,0DAA0D;IAC1D,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,sCAAsC;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,qEAAqE;IACrE,GAAG,EAAE,wBAAwB,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,8BAA8B;IAC9B,GAAG,EAAE,wBAAwB,CAAC;IAC9B,yDAAyD;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAEpD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CAkBjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,IAAI,CAa7E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAUjF"}