render-tag 0.1.6 → 0.1.8

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,155 @@
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
+ import { applyFont } from '../layout.js';
7
+ import { stringToArray } from './grapheme.js';
8
+ /**
9
+ * Flatten a styled tree into a flat sequence of styled text segments.
10
+ * Walks in document order; concatenates text under nested inline elements.
11
+ * Each `#text` node contributes one segment with its resolved style.
12
+ */
13
+ export function flattenSegments(root) {
14
+ const out = [];
15
+ function walk(node, inheritedRtl) {
16
+ const rtl = node.style.direction === 'rtl' || inheritedRtl;
17
+ if (node.tagName === '#text' && node.textContent) {
18
+ out.push({ text: node.textContent, style: node.style, rtl });
19
+ return;
20
+ }
21
+ for (const child of node.children)
22
+ walk(child, rtl);
23
+ }
24
+ walk(root, false);
25
+ return out;
26
+ }
27
+ /**
28
+ * Lay out graphemes along a path. Pure: does not call ctx.fillText.
29
+ *
30
+ * Algorithm:
31
+ * 1. Per segment, split into graphemes and measure each via ctx.measureText.
32
+ * RTL segments have their grapheme order reversed before walking the path
33
+ * (so a left-to-right path walk produces correct visual order).
34
+ * 2. Compute total natural width.
35
+ * 3. Pick a starting offset along the path based on `align`.
36
+ * 4. For each grapheme:
37
+ * p0 = path.getPointAtLength(offset)
38
+ * p1 = path.getPointAtLength(offset + width)
39
+ * rotation = atan2(p1.y - p0.y, p1.x - p0.x)
40
+ * A trailing kerning slack (sum-of-glyph-widths > whole-string width)
41
+ * can push the last glyph 1–2px past pathLength; clamp the end point
42
+ * to pathLength in that narrow case to avoid dropping the last glyph.
43
+ * 5. If a glyph would extend past the path entirely, stop emitting.
44
+ */
45
+ export function layoutGlyphsOnPath(input) {
46
+ const { segments, path, ctx, align } = input;
47
+ // 1. Pre-measure all graphemes, segment-by-segment.
48
+ // Caller's ctx state is mutated here (font, fontKerning, letterSpacing).
49
+ // The outer drawTextOnPath/drawTextOnPathLayout calls ctx.save before this
50
+ // and ctx.restore after, so the leak doesn't reach the caller.
51
+ const preGlyphs = [];
52
+ let measuredWholeWidth = 0;
53
+ let maxLineHeight = 0;
54
+ for (const seg of segments) {
55
+ if (!seg.text)
56
+ continue;
57
+ const graphemes = stringToArray(seg.text);
58
+ if (graphemes.length === 0)
59
+ continue;
60
+ if (seg.rtl)
61
+ graphemes.reverse();
62
+ applyFont(ctx, seg.style);
63
+ // Always assign — when the current segment's letterSpacing is 0/unset,
64
+ // we still need to reset the previous segment's value.
65
+ ctx.letterSpacing = `${seg.style.letterSpacing || 0}px`;
66
+ // Track ribbon thickness: CSS line-height in px when set, else font size.
67
+ const lh = seg.style.lineHeight > 0 ? seg.style.lineHeight : seg.style.fontSize;
68
+ if (lh > maxLineHeight)
69
+ maxLineHeight = lh;
70
+ // Per-glyph width via measureText (browsers honor letterSpacing here).
71
+ for (const g of graphemes) {
72
+ const m = ctx.measureText(g);
73
+ preGlyphs.push({
74
+ char: g,
75
+ width: m.width,
76
+ style: seg.style,
77
+ // Only ASCII space (U+0020) is justify-eligible. \n/\t/  and
78
+ // other Unicode whitespace must NOT expand — that would break
79
+ // no-break-space contract and treat <br>-synthesized newlines as
80
+ // expansible spaces.
81
+ isSpace: g === ' ',
82
+ });
83
+ }
84
+ // Whole-segment width — kerning makes this < sum of per-glyph widths.
85
+ measuredWholeWidth += ctx.measureText(seg.text).width;
86
+ }
87
+ // 2. Sum natural width (sum of glyph widths + letterSpacing per glyph).
88
+ let textWidth = 0;
89
+ for (const g of preGlyphs) {
90
+ textWidth += g.width + (g.style.letterSpacing || 0);
91
+ }
92
+ // Trim the last letterSpacing — it shouldn't trail.
93
+ if (preGlyphs.length > 0) {
94
+ textWidth -= preGlyphs[preGlyphs.length - 1].style.letterSpacing || 0;
95
+ }
96
+ // Kerning slack: how much the per-glyph sum can exceed the measured
97
+ // whole-string width. When the path is sized to match the visual text,
98
+ // we use this to allow up to N px of overshoot at the path's tail end.
99
+ const kerningSlack = Math.max(0, textWidth - measuredWholeWidth);
100
+ // 3. Starting offset + per-space extra (justify).
101
+ const pathLength = path.length;
102
+ let startOffset = 0;
103
+ let extraPerSpace = 0;
104
+ if (align === 'center') {
105
+ startOffset = Math.max(0, (pathLength - textWidth) / 2);
106
+ }
107
+ else if (align === 'right') {
108
+ startOffset = Math.max(0, pathLength - textWidth);
109
+ }
110
+ else if (align === 'justify') {
111
+ const spaceCount = preGlyphs.filter(g => g.isSpace).length;
112
+ if (spaceCount > 0 && pathLength > textWidth) {
113
+ extraPerSpace = (pathLength - textWidth) / spaceCount;
114
+ }
115
+ }
116
+ // 4. Walk the path.
117
+ const glyphs = [];
118
+ let offset = startOffset;
119
+ for (let i = 0; i < preGlyphs.length; i++) {
120
+ const g = preGlyphs[i];
121
+ const effectiveWidth = g.width + (g.isSpace ? extraPerSpace : 0);
122
+ const p0 = path.getPointAtLength(offset);
123
+ if (!p0)
124
+ break;
125
+ let endLen = offset + effectiveWidth;
126
+ let p1;
127
+ if (endLen > pathLength) {
128
+ // Clamp to pathLength only if the overshoot is within the kerning slack —
129
+ // matches konva's behavior to keep the last glyph from getting dropped.
130
+ if (endLen - pathLength <= kerningSlack + 0.5) {
131
+ p1 = path.getPointAtLength(pathLength);
132
+ }
133
+ else {
134
+ break;
135
+ }
136
+ }
137
+ else {
138
+ p1 = path.getPointAtLength(endLen);
139
+ }
140
+ if (!p1)
141
+ break;
142
+ const rotation = Math.atan2(p1.y - p0.y, p1.x - p0.x);
143
+ glyphs.push({
144
+ char: g.char,
145
+ x: p0.x,
146
+ y: p0.y,
147
+ rotation,
148
+ width: g.width,
149
+ style: g.style,
150
+ });
151
+ offset = endLen + (g.style.letterSpacing || 0);
152
+ }
153
+ return { glyphs, textWidth, pathLength, lineHeight: maxLineHeight };
154
+ }
155
+ //# 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,96 @@
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, mirror the main API's split:
17
+ * const result = layoutTextOnPath({ html, path, align });
18
+ * drawTextOnPathLayout({ layout: result, ctx });
19
+ * Use the split when you want to inspect / hit-test glyphs before drawing,
20
+ * or render the same layout onto multiple targets.
21
+ *
22
+ * Out of scope for the first cut: text-shadow, gradient/background-clip text,
23
+ * text-decoration (underline/line-through), per-glyph kerning callbacks.
24
+ * Mixed-script BiDi shaping is not supported (matches the konva limit) — pure
25
+ * RTL strings render correctly via CSS `direction: rtl` or the `dir` attribute.
26
+ */
27
+ import { type PathLike } from './svg-path.js';
28
+ import { type AlignMode, type GlyphPlacement } from './glyph-layout.js';
29
+ export type { PathLike, GlyphPlacement, AlignMode };
30
+ export interface LayoutTextOnPathConfig {
31
+ /** Rich-text HTML (same dialect as render-tag's main API). */
32
+ html: string;
33
+ /** SVG path 'd' attribute string, or a PathLike implementation. */
34
+ path: string | PathLike;
35
+ /** Alignment of text along the path (default 'left'). */
36
+ align?: AlignMode;
37
+ /**
38
+ * Optional measurement context. If omitted, an offscreen canvas is created.
39
+ * Pass one to share font measurement caches with other render-tag calls.
40
+ */
41
+ ctx?: CanvasRenderingContext2D;
42
+ }
43
+ export interface TextOnPathLayout {
44
+ /** Per-glyph placement records (origin point, rotation, style). */
45
+ glyphs: GlyphPlacement[];
46
+ /** Sum of per-glyph advances (the natural width of the rendered text). */
47
+ textWidth: number;
48
+ /** Total arc length of the path. */
49
+ pathLength: number;
50
+ /**
51
+ * Max line height across all segments (CSS line-height in px when set,
52
+ * else the segment's font size). Use as the thickness for a background
53
+ * ribbon polygon around curved text.
54
+ */
55
+ lineHeight: number;
56
+ }
57
+ export interface DrawTextOnPathLayoutConfig {
58
+ /** Result from layoutTextOnPath(). */
59
+ layout: TextOnPathLayout;
60
+ /** Destination 2D context. The transform is saved/restored per glyph. */
61
+ ctx: CanvasRenderingContext2D;
62
+ }
63
+ export interface DrawTextOnPathConfig {
64
+ /** Rich-text HTML (same dialect as render-tag's main API). */
65
+ html: string;
66
+ /** SVG path 'd' attribute string, or a PathLike implementation. */
67
+ path: string | PathLike;
68
+ /** Destination 2D context. */
69
+ ctx: CanvasRenderingContext2D;
70
+ /** Alignment of text along the path (default 'left'). */
71
+ align?: AlignMode;
72
+ }
73
+ export type DrawTextOnPathResult = TextOnPathLayout;
74
+ /**
75
+ * Compute glyph placements for rich text along a path, without drawing.
76
+ * Useful for inspection, hit-testing, or rendering the same layout multiple times.
77
+ * The HTML's CSS resolves against an infinite-width container, so wrapping
78
+ * does not happen — all text flows along the path as one logical line.
79
+ *
80
+ * The caller's ctx (when passed) has its `font`, `fontKerning`, and
81
+ * `letterSpacing` state saved+restored around the measurement work; nothing
82
+ * leaks to the caller.
83
+ */
84
+ export declare function layoutTextOnPath(config: LayoutTextOnPathConfig): TextOnPathLayout;
85
+ /**
86
+ * Draw a pre-computed layout onto a canvas context.
87
+ * Each glyph is rendered with its own style; ctx state is saved/restored
88
+ * around the whole batch so the caller's state is preserved.
89
+ */
90
+ export declare function drawTextOnPathLayout(config: DrawTextOnPathLayoutConfig): void;
91
+ /**
92
+ * Convenience: compute layout and draw in a single call.
93
+ * Equivalent to `drawTextOnPathLayout({ layout: layoutTextOnPath(...), ctx })`.
94
+ */
95
+ export declare function drawTextOnPath(config: DrawTextOnPathConfig): DrawTextOnPathResult;
96
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/path/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;AAEpD,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;;;OAGG;IACH,GAAG,CAAC,EAAE,wBAAwB,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,sCAAsC;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,yEAAyE;IACzE,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;CACnB;AAED,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAEpD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CAmBjF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,IAAI,CAQ7E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAWjF"}
@@ -0,0 +1,145 @@
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, mirror the main API's split:
17
+ * const result = layoutTextOnPath({ html, path, align });
18
+ * drawTextOnPathLayout({ layout: result, ctx });
19
+ * Use the split when you want to inspect / hit-test glyphs before drawing,
20
+ * or render the same layout onto multiple targets.
21
+ *
22
+ * Out of scope for the first cut: text-shadow, gradient/background-clip text,
23
+ * text-decoration (underline/line-through), per-glyph kerning callbacks.
24
+ * Mixed-script BiDi shaping is not supported (matches the konva limit) — pure
25
+ * RTL strings render correctly via CSS `direction: rtl` or the `dir` attribute.
26
+ */
27
+ import { parseHTML } from '../parse.js';
28
+ import { resolveStylesFromCSS } from '../css-resolver.js';
29
+ import { applyFont } from '../layout.js';
30
+ import { pathFromString } from './svg-path.js';
31
+ import { flattenSegments, layoutGlyphsOnPath, } from './glyph-layout.js';
32
+ /**
33
+ * Compute glyph placements for rich text along a path, without drawing.
34
+ * Useful for inspection, hit-testing, or rendering the same layout multiple times.
35
+ * The HTML's CSS resolves against an infinite-width container, so wrapping
36
+ * does not happen — all text flows along the path as one logical line.
37
+ *
38
+ * The caller's ctx (when passed) has its `font`, `fontKerning`, and
39
+ * `letterSpacing` state saved+restored around the measurement work; nothing
40
+ * leaks to the caller.
41
+ */
42
+ export function layoutTextOnPath(config) {
43
+ const { html, align = 'left' } = config;
44
+ const path = typeof config.path === 'string' ? pathFromString(config.path) : config.path;
45
+ // Use the caller's ctx for measurement if given; otherwise spin up our own.
46
+ const measureCtx = config.ctx ?? createMeasureCtx();
47
+ const ownsCtx = config.ctx === undefined;
48
+ const { fragment, css } = parseHTML(html);
49
+ const { tree, cleanup } = resolveStylesFromCSS(fragment, css, Number.MAX_SAFE_INTEGER);
50
+ if (!ownsCtx)
51
+ measureCtx.save();
52
+ try {
53
+ const segments = flattenSegments(tree);
54
+ return layoutGlyphsOnPath({ segments, path, ctx: measureCtx, align });
55
+ }
56
+ finally {
57
+ if (!ownsCtx)
58
+ measureCtx.restore();
59
+ cleanup();
60
+ }
61
+ }
62
+ /**
63
+ * Draw a pre-computed layout onto a canvas context.
64
+ * Each glyph is rendered with its own style; ctx state is saved/restored
65
+ * around the whole batch so the caller's state is preserved.
66
+ */
67
+ export function drawTextOnPathLayout(config) {
68
+ const { layout, ctx } = config;
69
+ ctx.save();
70
+ try {
71
+ for (const g of layout.glyphs)
72
+ drawGlyph(ctx, g);
73
+ }
74
+ finally {
75
+ ctx.restore();
76
+ }
77
+ }
78
+ /**
79
+ * Convenience: compute layout and draw in a single call.
80
+ * Equivalent to `drawTextOnPathLayout({ layout: layoutTextOnPath(...), ctx })`.
81
+ */
82
+ export function drawTextOnPath(config) {
83
+ // Save/restore is handled inside layoutTextOnPath + drawTextOnPathLayout,
84
+ // so the caller's ctx state survives both stages even when sharing one ctx.
85
+ const layout = layoutTextOnPath({
86
+ html: config.html,
87
+ path: config.path,
88
+ align: config.align,
89
+ ctx: config.ctx,
90
+ });
91
+ drawTextOnPathLayout({ layout, ctx: config.ctx });
92
+ return layout;
93
+ }
94
+ function createMeasureCtx() {
95
+ const canvas = typeof OffscreenCanvas !== 'undefined'
96
+ ? new OffscreenCanvas(1, 1)
97
+ : document.createElement('canvas');
98
+ return canvas.getContext('2d');
99
+ }
100
+ /** Draw a single grapheme at its origin with the given rotation. */
101
+ function drawGlyph(ctx, g) {
102
+ const { style } = g;
103
+ ctx.save();
104
+ ctx.translate(g.x, g.y);
105
+ ctx.rotate(g.rotation);
106
+ applyFont(ctx, style);
107
+ ctx.textBaseline = 'alphabetic';
108
+ // letterSpacing isn't applied per single glyph — it's already baked into
109
+ // the glyph offset advance in glyph-layout.
110
+ const fill = effectiveFill(style);
111
+ const isStroked = style.webkitTextStrokeWidth > 0;
112
+ const isFillTransparent = fill === 'transparent';
113
+ const paintStrokeFirst = /paint-order:\s*stroke/i.test(style.paintOrder || '') ||
114
+ /^stroke/i.test((style.paintOrder || '').trim());
115
+ const drawFill = () => {
116
+ if (isFillTransparent && isStroked)
117
+ return;
118
+ ctx.fillStyle = fill;
119
+ ctx.fillText(g.char, 0, 0);
120
+ };
121
+ const drawStroke = () => {
122
+ if (!isStroked)
123
+ return;
124
+ ctx.lineJoin = 'round';
125
+ ctx.lineWidth = style.webkitTextStrokeWidth;
126
+ ctx.strokeStyle = style.webkitTextStrokeColor || style.color;
127
+ ctx.strokeText(g.char, 0, 0);
128
+ };
129
+ if (paintStrokeFirst) {
130
+ drawStroke();
131
+ drawFill();
132
+ }
133
+ else {
134
+ drawFill();
135
+ drawStroke();
136
+ }
137
+ ctx.restore();
138
+ }
139
+ function effectiveFill(style) {
140
+ if (style.webkitTextFillColor && style.webkitTextFillColor !== 'transparent') {
141
+ return style.webkitTextFillColor;
142
+ }
143
+ return style.color;
144
+ }
145
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/path/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Minimal SVG path 'd' attribute parser + arc-length tabulation.
3
+ * Supports: M m L l H h V v C c S s Q q T t A a Z z.
4
+ *
5
+ * Curves are sampled uniformly in parameter space and the cumulative
6
+ * arc-length is tabulated. `getPointAtLength` binary-searches that table
7
+ * and lerps between adjacent samples. Accuracy is "good enough for visual
8
+ * rendering" — not for hit-testing or precise length math.
9
+ */
10
+ export interface Point {
11
+ x: number;
12
+ y: number;
13
+ }
14
+ export interface PathLike {
15
+ length: number;
16
+ /** Returns the (x, y) on the path at arc length `t`, or null if `t` is out of range. */
17
+ getPointAtLength(t: number): Point | null;
18
+ }
19
+ type Seg = {
20
+ type: 'L';
21
+ x0: number;
22
+ y0: number;
23
+ x1: number;
24
+ y1: number;
25
+ } | {
26
+ type: 'C';
27
+ x0: number;
28
+ y0: number;
29
+ c1x: number;
30
+ c1y: number;
31
+ c2x: number;
32
+ c2y: number;
33
+ x1: number;
34
+ y1: number;
35
+ } | {
36
+ type: 'Q';
37
+ x0: number;
38
+ y0: number;
39
+ cx: number;
40
+ cy: number;
41
+ x1: number;
42
+ y1: number;
43
+ } | {
44
+ type: 'A';
45
+ x0: number;
46
+ y0: number;
47
+ rx: number;
48
+ ry: number;
49
+ xAxisRotation: number;
50
+ largeArc: boolean;
51
+ sweep: boolean;
52
+ x1: number;
53
+ y1: number;
54
+ };
55
+ /**
56
+ * Parse path data into an array of normalized absolute-coordinate segments.
57
+ * Drops moveTo commands (they don't draw); inserts a closing line for Z.
58
+ */
59
+ export declare function parsePathData(d: string): Seg[];
60
+ /**
61
+ * Build a PathLike interface from normalized segments.
62
+ * Tabulates cumulative arc-length per segment for O(log N) length-to-point lookup.
63
+ */
64
+ export declare function buildPathLike(segs: Seg[]): PathLike;
65
+ /** Convenience: parse a path string and build a PathLike in one call. */
66
+ export declare function pathFromString(d: string): PathLike;
67
+ export {};
68
+ //# sourceMappingURL=svg-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg-path.d.ts","sourceRoot":"","sources":["../../src/path/svg-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;CAC3C;AAED,KAAK,GAAG,GACJ;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7D;IACE,IAAI,EAAE,GAAG,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;CACxB,GACD;IACE,IAAI,EAAE,GAAG,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;CACxB,GACD;IACE,IAAI,EAAE,GAAG,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;CACxB,CAAC;AAiCN;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAuK9C;AAqGD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,QAAQ,CAgFnD;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAElD"}