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.
- package/README.md +75 -151
- package/lib/css-resolver.d.ts.map +1 -1
- package/lib/css-resolver.js +16 -0
- package/lib/css-resolver.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -32
- package/lib/index.js.map +1 -1
- package/lib/layout.d.ts +6 -1
- package/lib/layout.d.ts.map +1 -1
- package/lib/layout.js +189 -29
- package/lib/layout.js.map +1 -1
- package/lib/path/glyph-layout.d.ts +120 -0
- package/lib/path/glyph-layout.d.ts.map +1 -0
- package/lib/path/glyph-layout.js +337 -0
- package/lib/path/glyph-layout.js.map +1 -0
- package/lib/path/grapheme.d.ts +2 -0
- package/lib/path/grapheme.d.ts.map +1 -0
- package/lib/path/grapheme.js +39 -0
- package/lib/path/grapheme.js.map +1 -0
- package/lib/path/index.d.ts +131 -0
- package/lib/path/index.d.ts.map +1 -0
- package/lib/path/index.js +421 -0
- package/lib/path/index.js.map +1 -0
- package/lib/path/svg-path.d.ts +68 -0
- package/lib/path/svg-path.d.ts.map +1 -0
- package/lib/path/svg-path.js +393 -0
- package/lib/path/svg-path.js.map +1 -0
- package/lib/render-tag.umd.js +4 -4
- package/lib/render-tag.umd.js.map +1 -1
- package/lib/render.d.ts +18 -0
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +3 -3
- package/lib/render.js.map +1 -1
- package/lib/types.d.ts +18 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,421 @@
|
|
|
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 { parseHTML } from '../parse.js';
|
|
33
|
+
import { resolveStylesFromCSS, paintOrderHasStrokeFirst } from '../css-resolver.js';
|
|
34
|
+
import { applyFont, isTransparent } from '../layout.js';
|
|
35
|
+
import { parseTextShadows, parseLinearGradient, drawDecorationLine, } from '../render.js';
|
|
36
|
+
import { pathFromString } from './svg-path.js';
|
|
37
|
+
import { flattenSegments, layoutGlyphsOnPath, baselineLocalY, } from './glyph-layout.js';
|
|
38
|
+
/**
|
|
39
|
+
* Compute glyph placements for rich text along a path, without drawing.
|
|
40
|
+
* Useful for inspection, hit-testing, or rendering the same layout multiple times.
|
|
41
|
+
* The HTML's CSS resolves against an infinite-width container, so wrapping
|
|
42
|
+
* does not happen — all text flows along the path as one logical line.
|
|
43
|
+
*
|
|
44
|
+
* The caller's ctx (when passed) has its `font`, `fontKerning`, and
|
|
45
|
+
* `letterSpacing` state saved+restored around the measurement work; nothing
|
|
46
|
+
* leaks to the caller.
|
|
47
|
+
*/
|
|
48
|
+
export function layoutTextOnPath(config) {
|
|
49
|
+
const { html, align = 'left', textBaseline = 'alphabetic' } = config;
|
|
50
|
+
const path = typeof config.path === 'string' ? pathFromString(config.path) : config.path;
|
|
51
|
+
const measureCtx = config.ctx ?? createMeasureCtx();
|
|
52
|
+
const ownsCtx = config.ctx === undefined;
|
|
53
|
+
const { fragment, css } = parseHTML(html);
|
|
54
|
+
const { tree, cleanup } = resolveStylesFromCSS(fragment, css, Number.MAX_SAFE_INTEGER);
|
|
55
|
+
if (!ownsCtx)
|
|
56
|
+
measureCtx.save();
|
|
57
|
+
try {
|
|
58
|
+
const segments = flattenSegments(tree);
|
|
59
|
+
return layoutGlyphsOnPath({ segments, path, ctx: measureCtx, align, textBaseline });
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
if (!ownsCtx)
|
|
63
|
+
measureCtx.restore();
|
|
64
|
+
cleanup();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Draw a pre-computed layout onto a canvas context.
|
|
69
|
+
*
|
|
70
|
+
* Rendering passes (matching CSS painting order):
|
|
71
|
+
* 1. Span backgrounds (background-color, curve-following polygons)
|
|
72
|
+
* 2. Text shadows (per glyph, multi-shadow supported, rotates with glyph)
|
|
73
|
+
* 3. Glyph fill + stroke (paint-order aware; gradient text via slicing)
|
|
74
|
+
* 4. Text decoration (underline / line-through / overline)
|
|
75
|
+
*
|
|
76
|
+
* ctx state is saved+restored around the entire batch — nothing leaks.
|
|
77
|
+
*/
|
|
78
|
+
export function drawTextOnPathLayout(config) {
|
|
79
|
+
const { layout, ctx } = config;
|
|
80
|
+
if (layout.glyphs.length === 0)
|
|
81
|
+
return;
|
|
82
|
+
const tb = layout.textBaseline;
|
|
83
|
+
ctx.save();
|
|
84
|
+
try {
|
|
85
|
+
drawBackgrounds(ctx, layout.glyphs, tb);
|
|
86
|
+
drawShadowsAndGlyphs(ctx, layout.glyphs, layout.textWidth, tb);
|
|
87
|
+
drawDecorations(ctx, layout.glyphs, tb);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
ctx.restore();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Convenience: compute layout and draw in a single call.
|
|
95
|
+
*/
|
|
96
|
+
export function drawTextOnPath(config) {
|
|
97
|
+
const layout = layoutTextOnPath({
|
|
98
|
+
html: config.html,
|
|
99
|
+
path: config.path,
|
|
100
|
+
align: config.align,
|
|
101
|
+
textBaseline: config.textBaseline,
|
|
102
|
+
ctx: config.ctx,
|
|
103
|
+
});
|
|
104
|
+
drawTextOnPathLayout({ layout, ctx: config.ctx });
|
|
105
|
+
return layout;
|
|
106
|
+
}
|
|
107
|
+
function createMeasureCtx() {
|
|
108
|
+
const canvas = typeof OffscreenCanvas !== 'undefined'
|
|
109
|
+
? new OffscreenCanvas(1, 1)
|
|
110
|
+
: document.createElement('canvas');
|
|
111
|
+
return canvas.getContext('2d');
|
|
112
|
+
}
|
|
113
|
+
// ─── Pass 1: Backgrounds ──────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Round-trip a color through `ctx.fillStyle` to get the browser's
|
|
116
|
+
* canonical form. This makes `red` and `rgb(255, 0, 0)` and `#f00`
|
|
117
|
+
* compare equal so adjacent spans with semantically-identical colors
|
|
118
|
+
* group into one polygon / one stroke (no visible seam, dash phase
|
|
119
|
+
* continuous). Results cached so we don't pay the round-trip per glyph.
|
|
120
|
+
*/
|
|
121
|
+
const _colorCanonicalCache = new WeakMap();
|
|
122
|
+
function canonicalColor(ctx, color) {
|
|
123
|
+
if (!color)
|
|
124
|
+
return '';
|
|
125
|
+
let perCtx = _colorCanonicalCache.get(ctx);
|
|
126
|
+
if (!perCtx) {
|
|
127
|
+
perCtx = new Map();
|
|
128
|
+
_colorCanonicalCache.set(ctx, perCtx);
|
|
129
|
+
}
|
|
130
|
+
const cached = perCtx.get(color);
|
|
131
|
+
if (cached !== undefined)
|
|
132
|
+
return cached;
|
|
133
|
+
// ctx.fillStyle returns the canonical form when read back.
|
|
134
|
+
const prev = ctx.fillStyle;
|
|
135
|
+
try {
|
|
136
|
+
ctx.fillStyle = color;
|
|
137
|
+
const canon = typeof ctx.fillStyle === 'string' ? ctx.fillStyle : color;
|
|
138
|
+
perCtx.set(color, canon);
|
|
139
|
+
return canon;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
perCtx.set(color, color);
|
|
143
|
+
return color;
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
ctx.fillStyle = prev;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Group consecutive glyphs sharing a visible background color and draw each
|
|
151
|
+
* group as a curve-following polygon (top edge + bottom edge).
|
|
152
|
+
*/
|
|
153
|
+
function drawBackgrounds(ctx, glyphs, tb) {
|
|
154
|
+
let i = 0;
|
|
155
|
+
while (i < glyphs.length) {
|
|
156
|
+
const bg = glyphs[i].style.backgroundColor;
|
|
157
|
+
if (!bg || isTransparent(bg)) {
|
|
158
|
+
i++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const canon = canonicalColor(ctx, bg);
|
|
162
|
+
let j = i + 1;
|
|
163
|
+
while (j < glyphs.length &&
|
|
164
|
+
canonicalColor(ctx, glyphs[j].style.backgroundColor) === canon)
|
|
165
|
+
j++;
|
|
166
|
+
fillGlyphPolygon(ctx, glyphs.slice(i, j), bg, tb);
|
|
167
|
+
i = j;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build the four-corner box of one glyph in world coordinates. Local y=0 is
|
|
172
|
+
* the path point — actual baseline sits at `baselineLocalY(tb, ascent, descent)`.
|
|
173
|
+
* The polygon spans `[baseY - ascent, baseY + descent]` so it hugs the painted
|
|
174
|
+
* glyph extent regardless of which textBaseline anchor was chosen.
|
|
175
|
+
*/
|
|
176
|
+
function glyphCorners(g, tb) {
|
|
177
|
+
const c = Math.cos(g.rotation);
|
|
178
|
+
const s = Math.sin(g.rotation);
|
|
179
|
+
const baseY = baselineLocalY(tb, g.ascent, g.descent);
|
|
180
|
+
const top = baseY - g.ascent;
|
|
181
|
+
const bottom = baseY + g.descent;
|
|
182
|
+
const w = g.width;
|
|
183
|
+
return {
|
|
184
|
+
tl: { x: g.x + (-s) * top, y: g.y + c * top },
|
|
185
|
+
tr: { x: g.x + c * w + (-s) * top, y: g.y + s * w + c * top },
|
|
186
|
+
br: { x: g.x + c * w + (-s) * bottom, y: g.y + s * w + c * bottom },
|
|
187
|
+
bl: { x: g.x + (-s) * bottom, y: g.y + c * bottom },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Fill a polygon hugging the path through a sequence of consecutive glyphs. */
|
|
191
|
+
function fillGlyphPolygon(ctx, group, fill, tb) {
|
|
192
|
+
if (group.length === 0)
|
|
193
|
+
return;
|
|
194
|
+
ctx.save();
|
|
195
|
+
ctx.fillStyle = fill;
|
|
196
|
+
ctx.beginPath();
|
|
197
|
+
const first = glyphCorners(group[0], tb);
|
|
198
|
+
ctx.moveTo(first.tl.x, first.tl.y);
|
|
199
|
+
for (let i = 0; i < group.length; i++) {
|
|
200
|
+
const c = glyphCorners(group[i], tb);
|
|
201
|
+
ctx.lineTo(c.tl.x, c.tl.y);
|
|
202
|
+
ctx.lineTo(c.tr.x, c.tr.y);
|
|
203
|
+
}
|
|
204
|
+
for (let i = group.length - 1; i >= 0; i--) {
|
|
205
|
+
const c = glyphCorners(group[i], tb);
|
|
206
|
+
ctx.lineTo(c.br.x, c.br.y);
|
|
207
|
+
ctx.lineTo(c.bl.x, c.bl.y);
|
|
208
|
+
}
|
|
209
|
+
ctx.closePath();
|
|
210
|
+
ctx.fill();
|
|
211
|
+
ctx.restore();
|
|
212
|
+
}
|
|
213
|
+
// ─── Pass 2: Shadows + glyph fill/stroke ─────────────────────────────
|
|
214
|
+
/**
|
|
215
|
+
* Iterate glyphs once. For each: draw the configured text-shadow stack (if
|
|
216
|
+
* any), then fill (possibly through a sliced gradient) and stroke per
|
|
217
|
+
* paint-order.
|
|
218
|
+
*/
|
|
219
|
+
function drawShadowsAndGlyphs(ctx, glyphs, textWidth, tb) {
|
|
220
|
+
for (const g of glyphs) {
|
|
221
|
+
ctx.save();
|
|
222
|
+
ctx.translate(g.x, g.y);
|
|
223
|
+
ctx.rotate(g.rotation);
|
|
224
|
+
applyFont(ctx, g.style);
|
|
225
|
+
// Keep ctx.textBaseline at 'alphabetic' and offset fillText's y instead.
|
|
226
|
+
// This keeps our bounds / decoration / background math in agreement with
|
|
227
|
+
// what fillText actually paints, regardless of which textBaseline the
|
|
228
|
+
// caller picked.
|
|
229
|
+
ctx.textBaseline = 'alphabetic';
|
|
230
|
+
// Mirror the letter-spacing used at measurement time. Without this,
|
|
231
|
+
// shaped runs (Arabic / Indic / Thai) whose `g.width` was measured WITH
|
|
232
|
+
// letter-spacing render visibly tighter than the reserved width.
|
|
233
|
+
// Always assign so a stale value from earlier glyphs doesn't leak.
|
|
234
|
+
ctx.letterSpacing = `${g.style.letterSpacing || 0}px`;
|
|
235
|
+
const baseY = baselineLocalY(tb, g.ascent, g.descent);
|
|
236
|
+
// Shadow pass — drawn underneath the glyph fill. Multi-shadow stacks
|
|
237
|
+
// paint last-listed-first so the first declared shadow is on top.
|
|
238
|
+
const shadows = parseTextShadows(g.style.textShadow);
|
|
239
|
+
if (shadows.length > 0) {
|
|
240
|
+
for (let i = shadows.length - 1; i >= 0; i--) {
|
|
241
|
+
const sh = shadows[i];
|
|
242
|
+
ctx.save();
|
|
243
|
+
ctx.shadowOffsetX = sh.offsetX;
|
|
244
|
+
ctx.shadowOffsetY = sh.offsetY;
|
|
245
|
+
ctx.shadowBlur = sh.blur;
|
|
246
|
+
ctx.shadowColor = sh.color;
|
|
247
|
+
ctx.fillStyle = effectiveFillColor(g.style);
|
|
248
|
+
ctx.fillText(g.char, 0, baseY);
|
|
249
|
+
ctx.restore();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
drawGlyphFillAndStroke(ctx, g, textWidth, baseY);
|
|
253
|
+
ctx.restore();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function drawGlyphFillAndStroke(ctx, g, textWidth, baseY) {
|
|
257
|
+
const { style } = g;
|
|
258
|
+
// Mirror the main renderer's transparency check: EITHER -webkit-text-fill-color
|
|
259
|
+
// or color being 'transparent' suppresses the fill. effectiveFillColor alone
|
|
260
|
+
// would only catch one of the two by falling back through the precedence.
|
|
261
|
+
const fillTransparent = style.webkitTextFillColor === 'transparent' ||
|
|
262
|
+
style.color === 'transparent' ||
|
|
263
|
+
isTransparent(effectiveFillColor(style));
|
|
264
|
+
const isStroked = style.webkitTextStrokeWidth > 0;
|
|
265
|
+
const usesGradient = style.webkitBackgroundClip === 'text' &&
|
|
266
|
+
style.backgroundImage && style.backgroundImage !== 'none';
|
|
267
|
+
// Use the canonical helper instead of an ad-hoc regex — paint-order tokens
|
|
268
|
+
// are positional ("fill stroke" = fill first), not a flag bag.
|
|
269
|
+
const paintStrokeFirst = paintOrderHasStrokeFirst(style.paintOrder || '');
|
|
270
|
+
const drawFill = () => {
|
|
271
|
+
if (usesGradient) {
|
|
272
|
+
drawGradientGlyph(ctx, g, textWidth, baseY);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (fillTransparent) {
|
|
276
|
+
if (isStroked)
|
|
277
|
+
return;
|
|
278
|
+
ctx.fillStyle = 'transparent';
|
|
279
|
+
ctx.fillText(g.char, 0, baseY);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
ctx.fillStyle = effectiveFillColor(style);
|
|
283
|
+
ctx.fillText(g.char, 0, baseY);
|
|
284
|
+
};
|
|
285
|
+
const drawStroke = () => {
|
|
286
|
+
if (!isStroked)
|
|
287
|
+
return;
|
|
288
|
+
ctx.save();
|
|
289
|
+
ctx.lineJoin = 'round';
|
|
290
|
+
ctx.lineWidth = style.webkitTextStrokeWidth;
|
|
291
|
+
ctx.strokeStyle = style.webkitTextStrokeColor || style.color;
|
|
292
|
+
ctx.strokeText(g.char, 0, baseY);
|
|
293
|
+
ctx.restore();
|
|
294
|
+
};
|
|
295
|
+
if (paintStrokeFirst) {
|
|
296
|
+
drawStroke();
|
|
297
|
+
drawFill();
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
drawFill();
|
|
301
|
+
drawStroke();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Render one glyph with a gradient fill that flows along the path. The
|
|
306
|
+
* gradient is created once per glyph spanning the FULL text width in the
|
|
307
|
+
* glyph's local rotated frame, offset so the glyph's slice lines up with
|
|
308
|
+
* the global gradient. Neighbouring glyphs stitch into one continuous color
|
|
309
|
+
* curve when viewed along the path.
|
|
310
|
+
*/
|
|
311
|
+
function drawGradientGlyph(ctx, g, textWidth, baseY) {
|
|
312
|
+
// The gradient is laid out from (-pathOffset, *) to (textWidth-pathOffset, *)
|
|
313
|
+
// in this glyph's local frame, so global gradient at fraction t/textWidth
|
|
314
|
+
// matches what neighbouring glyphs render. The vertical band tracks the
|
|
315
|
+
// glyph's full extent around the baseline anchor.
|
|
316
|
+
const gradient = parseLinearGradient(ctx, g.style.backgroundImage, -g.pathOffset, textWidth, baseY - g.ascent, g.ascent + g.descent);
|
|
317
|
+
if (!gradient) {
|
|
318
|
+
ctx.fillStyle = effectiveFillColor(g.style);
|
|
319
|
+
ctx.fillText(g.char, 0, baseY);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
ctx.fillStyle = gradient;
|
|
323
|
+
ctx.fillText(g.char, 0, baseY);
|
|
324
|
+
}
|
|
325
|
+
// ─── Pass 3: Decorations ─────────────────────────────────────────────
|
|
326
|
+
/**
|
|
327
|
+
* Underline / line-through / overline: stroke a curve that follows the path
|
|
328
|
+
* at the appropriate vertical offset. Groups consecutive glyphs sharing
|
|
329
|
+
* decoration-line + style + color.
|
|
330
|
+
*/
|
|
331
|
+
function drawDecorations(ctx, glyphs, tb) {
|
|
332
|
+
const lines = ['underline', 'line-through', 'overline'];
|
|
333
|
+
for (const lineKind of lines) {
|
|
334
|
+
let i = 0;
|
|
335
|
+
while (i < glyphs.length) {
|
|
336
|
+
const style = glyphs[i].style;
|
|
337
|
+
if (!style.textDecorationLine || !style.textDecorationLine.includes(lineKind)) {
|
|
338
|
+
i++;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const color = style.textDecorationColor || style.color;
|
|
342
|
+
const decoStyle = style.textDecorationStyle || 'solid';
|
|
343
|
+
const colorCanon = canonicalColor(ctx, color);
|
|
344
|
+
let j = i + 1;
|
|
345
|
+
while (j < glyphs.length &&
|
|
346
|
+
glyphs[j].style.textDecorationLine &&
|
|
347
|
+
glyphs[j].style.textDecorationLine.includes(lineKind) &&
|
|
348
|
+
canonicalColor(ctx, glyphs[j].style.textDecorationColor || glyphs[j].style.color) === colorCanon &&
|
|
349
|
+
(glyphs[j].style.textDecorationStyle || 'solid') === decoStyle)
|
|
350
|
+
j++;
|
|
351
|
+
strokeDecorationAlongGlyphs(ctx, glyphs.slice(i, j), lineKind, decoStyle, color, tb);
|
|
352
|
+
i = j;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function strokeDecorationAlongGlyphs(ctx, group, lineKind, decoStyle, color, tb) {
|
|
357
|
+
if (group.length === 0)
|
|
358
|
+
return;
|
|
359
|
+
const lineWidth = Math.max(1, group[0].style.fontSize / 15);
|
|
360
|
+
// Per-glyph local y for this decoration kind. The decoration position is
|
|
361
|
+
// baseline-relative, so we shift by the baseline's local-y under the
|
|
362
|
+
// current textBaseline to land in the right spot on the canvas.
|
|
363
|
+
const localY = (g) => {
|
|
364
|
+
const baseY = baselineLocalY(tb, g.ascent, g.descent);
|
|
365
|
+
if (lineKind === 'underline')
|
|
366
|
+
return baseY + g.descent * 0.5;
|
|
367
|
+
if (lineKind === 'line-through')
|
|
368
|
+
return baseY - g.ascent * 0.3;
|
|
369
|
+
return baseY - g.ascent * 0.9; // overline
|
|
370
|
+
};
|
|
371
|
+
if (decoStyle === 'double' || decoStyle === 'wavy') {
|
|
372
|
+
// For double/wavy we draw each glyph segment independently using the
|
|
373
|
+
// shared helper so the visual matches the main renderer.
|
|
374
|
+
for (const g of group) {
|
|
375
|
+
ctx.save();
|
|
376
|
+
ctx.translate(g.x, g.y);
|
|
377
|
+
ctx.rotate(g.rotation);
|
|
378
|
+
drawDecorationLine(ctx, 0, localY(g), g.width, lineWidth, decoStyle, color);
|
|
379
|
+
ctx.restore();
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
// Solid / dotted / dashed: a single continuous polyline through (left,
|
|
384
|
+
// right) endpoints of each glyph projected into world space. Stroking
|
|
385
|
+
// once keeps the dash phase continuous across the whole decoration run.
|
|
386
|
+
ctx.save();
|
|
387
|
+
ctx.strokeStyle = color;
|
|
388
|
+
ctx.lineWidth = lineWidth;
|
|
389
|
+
if (decoStyle === 'dotted')
|
|
390
|
+
ctx.setLineDash([lineWidth, lineWidth * 2]);
|
|
391
|
+
else if (decoStyle === 'dashed')
|
|
392
|
+
ctx.setLineDash([lineWidth * 3, lineWidth * 2]);
|
|
393
|
+
ctx.beginPath();
|
|
394
|
+
for (let i = 0; i < group.length; i++) {
|
|
395
|
+
const g = group[i];
|
|
396
|
+
const yOff = localY(g);
|
|
397
|
+
const c = Math.cos(g.rotation);
|
|
398
|
+
const s = Math.sin(g.rotation);
|
|
399
|
+
const left = { x: g.x + (-s) * yOff, y: g.y + c * yOff };
|
|
400
|
+
const right = {
|
|
401
|
+
x: g.x + c * g.width + (-s) * yOff,
|
|
402
|
+
y: g.y + s * g.width + c * yOff,
|
|
403
|
+
};
|
|
404
|
+
if (i === 0)
|
|
405
|
+
ctx.moveTo(left.x, left.y);
|
|
406
|
+
else
|
|
407
|
+
ctx.lineTo(left.x, left.y);
|
|
408
|
+
ctx.lineTo(right.x, right.y);
|
|
409
|
+
}
|
|
410
|
+
ctx.stroke();
|
|
411
|
+
ctx.setLineDash([]);
|
|
412
|
+
ctx.restore();
|
|
413
|
+
}
|
|
414
|
+
// ─── Style helpers ───────────────────────────────────────────────────
|
|
415
|
+
function effectiveFillColor(style) {
|
|
416
|
+
if (style.webkitTextFillColor && style.webkitTextFillColor !== 'transparent') {
|
|
417
|
+
return style.webkitTextFillColor;
|
|
418
|
+
}
|
|
419
|
+
return style.color;
|
|
420
|
+
}
|
|
421
|
+
//# 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"}
|