shape-text 0.2.1 → 0.3.0

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 CHANGED
@@ -21,7 +21,7 @@ bun add shape-text
21
21
 
22
22
  ## Published consumer example
23
23
 
24
- A small React consumer app lives at [examples/react-published-package-consumer](./examples/react-published-package-consumer/). Inside this repo it resolves `shape-text` to the current package surface so it can validate unreleased API additions before the next publish.
24
+ A small React consumer app lives at [examples/react-published-package-consumer](./examples/react-published-package-consumer/). It now renders a minimal ICT `HH:mm:SS` clock screen plus a reaching-hand SVG-mask fill example below it, all driven by the package exports. Inside this repo it installs `shape-text` through a local file dependency instead of aliasing source files, so the app validates the real packaged surface of the current tree.
25
25
 
26
26
  ## Ship readiness
27
27
 
@@ -93,12 +93,42 @@ const svg = renderLayoutToSvg(layout, {
93
93
 
94
94
  ## Shape sources
95
95
 
96
- `shape-text` currently ships two first-class ways to provide the shape paragraph surface:
97
-
98
- - Geometry input: pass explicit polygon points
99
- - Value-derived input: pass a `text-mask` shape derived from text and font
100
-
101
- The low-level API term stays `text-mask`, but the product framing is `value-derived shape`.
96
+ `shape-text` currently ships three first-class ways to provide the shape paragraph surface:
97
+
98
+ - Geometry input: pass explicit polygon points
99
+ - Value-derived input: pass a `text-mask` shape derived from text and font
100
+ - SVG mask input: pass a silhouette path and viewBox through `svg-mask`
101
+
102
+ The low-level API term stays `text-mask`, but the product framing is `value-derived shape`.
103
+
104
+ ## SVG mask example
105
+
106
+ ```ts
107
+ const layout = layoutTextInShape({
108
+ text: 'Shape paragraph can also fill an authored SVG silhouette.',
109
+ textStyle: {
110
+ family: '"Ubuntu", sans-serif',
111
+ size: 12,
112
+ weight: 400,
113
+ color: '#ffffff',
114
+ },
115
+ lineHeight: 14,
116
+ autoFill: true,
117
+ shape: {
118
+ kind: 'svg-mask',
119
+ path: 'M 0 0 L 160 0 L 160 40 L 0 40 Z',
120
+ viewBox: {
121
+ width: 160,
122
+ height: 40,
123
+ },
124
+ size: {
125
+ mode: 'fit-content',
126
+ padding: 4,
127
+ },
128
+ },
129
+ measurer,
130
+ })
131
+ ```
102
132
 
103
133
  ## Value-derived example
104
134
 
@@ -184,6 +214,7 @@ const customFillText = createRandomFillText({
184
214
  - `createCanvasTextMeasurer()`
185
215
  - `createRandomFillText()`
186
216
  - `compileShapeForLayout()`
217
+ - `clearTextMaskShapeCache()`
187
218
  - `getRandomFillPreset()`
188
219
  - `normalizeTextStyleToFont()`
189
220
  - `prepareTextForLayout()`
@@ -201,6 +232,7 @@ const customFillText = createRandomFillText({
201
232
  - The project takes inspiration from `pretext` for the `prepare -> layout` split and streaming line iteration, but owns its geometry, slot policy, and public API.
202
233
  - Geometry and value-derived shapes both compile into reusable line bands before layout.
203
234
  - `text-mask` shapes are raster-compiled into reusable line bands. This is the default value-derived path for browser fonts such as `Arial`, and it is designed so callers can precompile `0-9` and `:` for clock-like UIs.
235
+ - `svg-mask` shapes are raster-compiled from a local SVG path silhouette plus viewBox. V1 intentionally accepts path geometry, not arbitrary raw SVG markup or remote assets.
204
236
  - `autoFill: true` now means one thing: max-fill stream layout that sweeps every usable interval in reading order.
205
237
  - Max fill keeps spaces as normal graphemes instead of stripping them, and it does not fall back to smaller text for leftover pockets.
206
238
  - Random fill helpers are content utilities only; they generate source text but do not change layout rules.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { CompiledShapeBand, CompiledShapeBands, CompiledShapeRegion, CompileShapeForLayoutOptions, Interval, LayoutCursor, LayoutLineRange, LayoutTextInCompiledShapeOptions, LayoutTextInShapeOptions, PolygonShape, PreparedLayoutText, PreparedLayoutToken, RenderLayoutToSvgOptions, ResolvedShapeShadow, ResolvedShapeStyle, ResolvedTextStyle, ShapeInput, ShapeBounds, ShapeShadowInput, ShapeStyleInput, ShapeTextLayout, ShapeTextLine, ShapeTextPoint, TextMaskShape, TextMaskShapeFixedSize, TextMaskShapeFitContentSize, TextMaskShapeSize, TextMaskShapeSizeMode, TextMaskShapeTextMode, TextStyleInput, TextMeasurer, } from './types.js';
1
+ export type { CompiledShapeBand, CompiledShapeBands, CompiledShapeRegion, CompileShapeForLayoutOptions, Interval, LayoutCursor, LayoutLineRange, LayoutTextInCompiledShapeOptions, LayoutTextInShapeOptions, PolygonShape, PreparedLayoutText, PreparedLayoutToken, RenderLayoutToSvgOptions, ResolvedShapeShadow, ResolvedShapeStyle, ResolvedTextStyle, ShapeInput, ShapeBounds, ShapeShadowInput, ShapeStyleInput, ShapeTextLayout, ShapeTextLine, ShapeTextPoint, SvgMaskShape, SvgMaskShapeFixedSize, SvgMaskShapeFitContentSize, SvgMaskShapeSize, SvgMaskShapeViewBox, TextMaskShape, TextMaskShapeFixedSize, TextMaskShapeFitContentSize, TextMaskShapeSize, TextMaskShapeSizeMode, TextMaskShapeTextMode, TextStyleInput, TextMeasurer, } from './types.js';
2
2
  export type { CreateRandomFillTextOptions, RandomIntSelector, } from './random-fill/create-random-fill-text.js';
3
3
  export type { RandomFillPreset, RandomFillPresetId, } from './random-fill/random-fill-presets.js';
4
4
  export { createCanvasTextMeasurer } from './text/create-canvas-text-measurer.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAA;AAChF,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAA;AACzG,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AACxE,OAAO,EAAE,8BAA8B,EAAE,MAAM,+CAA+C,CAAA;AAC9F,OAAO,EAAE,8BAA8B,EAAE,MAAM,+CAA+C,CAAA;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAC7F,OAAO,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAA;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAA;AACvF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAA;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+CA,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAA;AAChF,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAA;AACzG,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AACxE,OAAO,EAAE,8BAA8B,EAAE,MAAM,+CAA+C,CAAA;AAC9F,OAAO,EAAE,8BAA8B,EAAE,MAAM,+CAA+C,CAAA;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAC7F,OAAO,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAA;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAA;AACvF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAA;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA"}
@@ -25,6 +25,9 @@ function renderShapeAttributes(debugView, options) {
25
25
  if (debugView.kind === 'polygon') {
26
26
  return `<polygon points="${renderPoints(debugView.points)}" fill="${fill}" stroke="${stroke}" stroke-width="${options.strokeWidth}"${filter} />`;
27
27
  }
28
+ if (debugView.kind === 'path') {
29
+ return `<path d="${escapeXmlText(debugView.path)}" transform="translate(${debugView.x} ${debugView.y}) scale(${debugView.scaleX} ${debugView.scaleY})" fill="${fill}" stroke="${stroke}" stroke-width="${options.strokeWidth}"${filter} />`;
30
+ }
28
31
  return `<text x="${debugView.x}" y="${debugView.baseline}" fill="${fill}" stroke="${stroke}" stroke-width="${options.strokeWidth}"${filter} style="font:${escapeXmlText(debugView.font)};">${escapeXmlText(debugView.text)}</text>`;
29
32
  }
30
33
  export function renderLayoutToSvg(layout, options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"render-layout-to-svg.js","sourceRoot":"","sources":["../../src/render/render-layout-to-svg.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render-layout-to-svg.js","sourceRoot":"","sources":["../../src/render/render-layout-to-svg.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { compilePolygonShapeForLayout } from './compile-polygon-shape-for-layout.js';
2
+ import { compileSvgMaskShapeForLayout } from './compile-svg-mask-shape-for-layout.js';
2
3
  import { compileTextMaskShapeForLayout } from './compile-text-mask-shape-for-layout.js';
3
4
  export function compileShapeForLayout(options) {
4
5
  if (!Number.isFinite(options.lineHeight) || options.lineHeight <= 0) {
@@ -11,6 +12,8 @@ export function compileShapeForLayout(options) {
11
12
  switch (options.shape.kind) {
12
13
  case 'polygon':
13
14
  return compilePolygonShapeForLayout(options.shape, options.lineHeight, minSlotWidth);
15
+ case 'svg-mask':
16
+ return compileSvgMaskShapeForLayout(options.shape, options.lineHeight, minSlotWidth);
14
17
  case 'text-mask':
15
18
  return compileTextMaskShapeForLayout(options.shape, options.lineHeight, minSlotWidth);
16
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compile-shape-for-layout.js","sourceRoot":"","sources":["../../src/shape/compile-shape-for-layout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAA;AACpF,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAA;AAEvF,MAAM,UAAU,qBAAqB,CACnC,OAAqC;IAErC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,UAAU,GAAG,GAAG,CAAC,CAAA;IACnF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACtE,CAAC;IAED,QAAQ,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,KAAK,SAAS;YACZ,OAAO,4BAA4B,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;QAEtF,KAAK,WAAW;YACd,OAAO,6BAA6B,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IACzF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"compile-shape-for-layout.js","sourceRoot":"","sources":["../../src/shape/compile-shape-for-layout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAA;AACpF,OAAO,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAA;AACrF,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAA;AAEvF,MAAM,UAAU,qBAAqB,CACnC,OAAqC;IAErC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,UAAU,GAAG,GAAG,CAAC,CAAA;IACnF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACtE,CAAC;IAED,QAAQ,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,KAAK,SAAS;YACZ,OAAO,4BAA4B,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;QAEtF,KAAK,UAAU;YACb,OAAO,4BAA4B,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;QAEtF,KAAK,WAAW;YACd,OAAO,6BAA6B,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IACzF,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { CompiledShapeBands, SvgMaskShape } from '../types.js';
2
+ export declare function compileSvgMaskShapeForLayout(shape: SvgMaskShape, lineHeight: number, minSlotWidth: number): CompiledShapeBands;
@@ -0,0 +1,109 @@
1
+ import { buildTextMaskBandsFromAlpha } from './build-text-mask-bands-from-alpha.js';
2
+ import { getSvgMaskPlacement, renderSvgMaskRaster, } from './render-svg-mask-raster.js';
3
+ import { resolveSvgMaskShapeSize, resolveSvgMaskViewBox, } from './resolve-svg-mask-shape-size.js';
4
+ const MAX_MASK_PIXELS = 4_000_000;
5
+ const MAX_CACHE_SIZE = 64;
6
+ const compiledShapeCache = new Map();
7
+ function cacheSet(key, value) {
8
+ if (compiledShapeCache.has(key)) {
9
+ compiledShapeCache.delete(key);
10
+ }
11
+ else if (compiledShapeCache.size >= MAX_CACHE_SIZE) {
12
+ const oldestKey = compiledShapeCache.keys().next().value;
13
+ if (oldestKey !== undefined) {
14
+ compiledShapeCache.delete(oldestKey);
15
+ }
16
+ }
17
+ compiledShapeCache.set(key, value);
18
+ }
19
+ function validateSvgMaskShape(shape, size) {
20
+ if (shape.path.trim().length === 0) {
21
+ throw new Error('svg-mask shape needs a non-empty path value');
22
+ }
23
+ resolveSvgMaskViewBox(shape.viewBox);
24
+ const maskScale = shape.maskScale ?? 2;
25
+ if (!Number.isFinite(maskScale) || maskScale <= 0) {
26
+ throw new Error('svg-mask maskScale must be a finite positive number');
27
+ }
28
+ const pixelWidth = Math.max(1, Math.ceil(size.width * maskScale));
29
+ const pixelHeight = Math.max(1, Math.ceil(size.height * maskScale));
30
+ if (pixelWidth * pixelHeight > MAX_MASK_PIXELS) {
31
+ throw new Error('svg-mask raster request is too large');
32
+ }
33
+ const alphaThreshold = shape.alphaThreshold ?? 16;
34
+ if (!Number.isFinite(alphaThreshold) || alphaThreshold < 0 || alphaThreshold > 255) {
35
+ throw new Error('svg-mask alphaThreshold must be between 0 and 255');
36
+ }
37
+ }
38
+ function buildCacheKey(shape, size, lineHeight, minSlotWidth) {
39
+ return JSON.stringify({
40
+ kind: shape.kind,
41
+ path: shape.path,
42
+ viewBox: resolveSvgMaskViewBox(shape.viewBox),
43
+ sizeMode: size.mode,
44
+ width: size.width,
45
+ height: size.height,
46
+ padding: size.padding,
47
+ maskScale: shape.maskScale ?? 2,
48
+ alphaThreshold: shape.alphaThreshold ?? 16,
49
+ lineHeight,
50
+ minSlotWidth,
51
+ });
52
+ }
53
+ function freezeCompiledShape(compiledShape) {
54
+ for (let bandIndex = 0; bandIndex < compiledShape.bands.length; bandIndex++) {
55
+ const band = compiledShape.bands[bandIndex];
56
+ for (let intervalIndex = 0; intervalIndex < band.intervals.length; intervalIndex++) {
57
+ Object.freeze(band.intervals[intervalIndex]);
58
+ }
59
+ Object.freeze(band.intervals);
60
+ Object.freeze(band);
61
+ }
62
+ Object.freeze(compiledShape.bounds);
63
+ Object.freeze(compiledShape.source);
64
+ Object.freeze(compiledShape.debugView);
65
+ Object.freeze(compiledShape.bands);
66
+ return Object.freeze(compiledShape);
67
+ }
68
+ export function compileSvgMaskShapeForLayout(shape, lineHeight, minSlotWidth) {
69
+ const size = resolveSvgMaskShapeSize(shape);
70
+ validateSvgMaskShape(shape, size);
71
+ const cacheKey = buildCacheKey(shape, size, lineHeight, minSlotWidth);
72
+ const cachedShape = compiledShapeCache.get(cacheKey);
73
+ if (cachedShape !== undefined) {
74
+ compiledShapeCache.delete(cacheKey);
75
+ compiledShapeCache.set(cacheKey, cachedShape);
76
+ return cachedShape;
77
+ }
78
+ const viewBox = resolveSvgMaskViewBox(shape.viewBox);
79
+ const placement = getSvgMaskPlacement(shape, size);
80
+ const { imageData } = renderSvgMaskRaster(shape, size, placement);
81
+ const compiledShape = {
82
+ kind: shape.kind,
83
+ source: shape,
84
+ bounds: { left: 0, top: 0, right: size.width, bottom: size.height },
85
+ bandHeight: lineHeight,
86
+ minSlotWidth,
87
+ bands: buildTextMaskBandsFromAlpha({
88
+ width: size.width,
89
+ height: size.height,
90
+ maskScale: shape.maskScale ?? 2,
91
+ alphaThreshold: shape.alphaThreshold ?? 16,
92
+ }, imageData.data, lineHeight, minSlotWidth),
93
+ debugView: {
94
+ kind: 'path',
95
+ path: shape.path,
96
+ x: placement.x - viewBox.x * placement.scaleX,
97
+ y: placement.y - viewBox.y * placement.scaleY,
98
+ scaleX: placement.scaleX,
99
+ scaleY: placement.scaleY,
100
+ },
101
+ };
102
+ if (compiledShape.bands.every(band => band.intervals.length === 0)) {
103
+ throw new Error('svg-mask shape produced no visible fill area');
104
+ }
105
+ const frozenShape = freezeCompiledShape(compiledShape);
106
+ cacheSet(cacheKey, frozenShape);
107
+ return frozenShape;
108
+ }
109
+ //# sourceMappingURL=compile-svg-mask-shape-for-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-svg-mask-shape-for-layout.js","sourceRoot":"","sources":["../../src/shape/compile-svg-mask-shape-for-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { SvgMaskShape } from '../types.js';
2
+ import { createBrowserCanvas2DContext } from '../text/create-browser-canvas-2d-context.js';
3
+ import { type ResolvedSvgMaskShapeSize } from './resolve-svg-mask-shape-size.js';
4
+ export type SvgMaskPlacement = {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ scaleX: number;
10
+ scaleY: number;
11
+ };
12
+ export type SvgMaskCanvas = {
13
+ context: ReturnType<typeof createBrowserCanvas2DContext>;
14
+ imageData: ImageData;
15
+ };
16
+ export declare function getSvgMaskPlacement(shape: SvgMaskShape, size: ResolvedSvgMaskShapeSize): SvgMaskPlacement;
17
+ export declare function renderSvgMaskRaster(shape: SvgMaskShape, size: ResolvedSvgMaskShapeSize, placement?: SvgMaskPlacement): SvgMaskCanvas;
@@ -0,0 +1,35 @@
1
+ import { createBrowserCanvas2DContext } from '../text/create-browser-canvas-2d-context.js';
2
+ import { resolveSvgMaskViewBox, } from './resolve-svg-mask-shape-size.js';
3
+ export function getSvgMaskPlacement(shape, size) {
4
+ const viewBox = resolveSvgMaskViewBox(shape.viewBox);
5
+ const innerWidth = Math.max(0, size.width - size.padding * 2);
6
+ const innerHeight = Math.max(0, size.height - size.padding * 2);
7
+ const scale = Math.min(innerWidth / viewBox.width, innerHeight / viewBox.height);
8
+ const width = viewBox.width * scale;
9
+ const height = viewBox.height * scale;
10
+ return {
11
+ x: size.padding + Math.max(0, (innerWidth - width) / 2),
12
+ y: size.padding + Math.max(0, (innerHeight - height) / 2),
13
+ width,
14
+ height,
15
+ scaleX: scale,
16
+ scaleY: scale,
17
+ };
18
+ }
19
+ export function renderSvgMaskRaster(shape, size, placement = getSvgMaskPlacement(shape, size)) {
20
+ const maskScale = shape.maskScale ?? 2;
21
+ const pixelWidth = Math.max(1, Math.ceil(size.width * maskScale));
22
+ const pixelHeight = Math.max(1, Math.ceil(size.height * maskScale));
23
+ const context = createBrowserCanvas2DContext(pixelWidth, pixelHeight);
24
+ const viewBox = resolveSvgMaskViewBox(shape.viewBox);
25
+ context.setTransform(1, 0, 0, 1, 0, 0);
26
+ context.clearRect(0, 0, pixelWidth, pixelHeight);
27
+ context.setTransform(placement.scaleX * maskScale, 0, 0, placement.scaleY * maskScale, (placement.x - viewBox.x * placement.scaleX) * maskScale, (placement.y - viewBox.y * placement.scaleY) * maskScale);
28
+ context.fillStyle = '#000000';
29
+ context.fill(new Path2D(shape.path));
30
+ return {
31
+ context,
32
+ imageData: context.getImageData(0, 0, pixelWidth, pixelHeight),
33
+ };
34
+ }
35
+ //# sourceMappingURL=render-svg-mask-raster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-svg-mask-raster.js","sourceRoot":"","sources":["../../src/shape/render-svg-mask-raster.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { SvgMaskShape, SvgMaskShapeViewBox } from '../types.js';
2
+ export type ResolvedSvgMaskShapeSize = {
3
+ mode: 'fit-content' | 'fixed';
4
+ width: number;
5
+ height: number;
6
+ padding: number;
7
+ };
8
+ export declare function resolveSvgMaskViewBox(viewBox: SvgMaskShapeViewBox): Required<SvgMaskShapeViewBox>;
9
+ export declare function resolveSvgMaskShapeSize(shape: SvgMaskShape): ResolvedSvgMaskShapeSize;
@@ -0,0 +1,51 @@
1
+ function assertFiniteNumber(value, label) {
2
+ if (!Number.isFinite(value)) {
3
+ throw new Error(`${label} must be a finite number`);
4
+ }
5
+ return value;
6
+ }
7
+ function assertPositiveFiniteNumber(value, label) {
8
+ if (!Number.isFinite(value) || value <= 0) {
9
+ throw new Error(`${label} must be a finite positive number`);
10
+ }
11
+ return value;
12
+ }
13
+ export function resolveSvgMaskViewBox(viewBox) {
14
+ return {
15
+ x: viewBox.x === undefined ? 0 : assertFiniteNumber(viewBox.x, 'svg-mask viewBox.x'),
16
+ y: viewBox.y === undefined ? 0 : assertFiniteNumber(viewBox.y, 'svg-mask viewBox.y'),
17
+ width: assertPositiveFiniteNumber(viewBox.width, 'svg-mask viewBox.width'),
18
+ height: assertPositiveFiniteNumber(viewBox.height, 'svg-mask viewBox.height'),
19
+ };
20
+ }
21
+ export function resolveSvgMaskShapeSize(shape) {
22
+ if (shape.size !== undefined && (shape.size === null || typeof shape.size !== 'object')) {
23
+ throw new Error('svg-mask size must be an object');
24
+ }
25
+ const viewBox = resolveSvgMaskViewBox(shape.viewBox);
26
+ const size = shape.size ?? {};
27
+ const mode = size.mode ?? 'fit-content';
28
+ if (mode !== 'fit-content' && mode !== 'fixed') {
29
+ throw new Error('svg-mask size.mode must be fit-content or fixed');
30
+ }
31
+ const padding = size.padding === undefined ? 0 : assertFiniteNumber(size.padding, 'svg-mask padding');
32
+ if (padding < 0) {
33
+ throw new Error('svg-mask padding must be a finite non-negative number');
34
+ }
35
+ if (mode === 'fixed') {
36
+ const fixedSize = size;
37
+ return {
38
+ mode,
39
+ width: assertPositiveFiniteNumber(fixedSize.width, 'svg-mask fixed width'),
40
+ height: assertPositiveFiniteNumber(fixedSize.height, 'svg-mask fixed height'),
41
+ padding,
42
+ };
43
+ }
44
+ return {
45
+ mode,
46
+ width: Math.max(1, viewBox.width + padding * 2),
47
+ height: Math.max(1, viewBox.height + padding * 2),
48
+ padding,
49
+ };
50
+ }
51
+ //# sourceMappingURL=resolve-svg-mask-shape-size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-svg-mask-shape-size.js","sourceRoot":"","sources":["../../src/shape/resolve-svg-mask-shape-size.ts"],"names":[],"mappings":"AASA,SAAS,kBAAkB,CAAC,KAAa,EAAE,KAAa;IACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,0BAA0B,CAAC,CAAA;IACrD,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,0BAA0B,CAAC,KAAa,EAAE,KAAa;IAC9D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,mCAAmC,CAAC,CAAA;IAC9D,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAA4B;IAChE,OAAO;QACL,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,oBAAoB,CAAC;QACpF,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,oBAAoB,CAAC;QACpF,KAAK,EAAE,0BAA0B,CAAC,OAAO,CAAC,KAAK,EAAE,wBAAwB,CAAC;QAC1E,MAAM,EAAE,0BAA0B,CAAC,OAAO,CAAC,MAAM,EAAE,yBAAyB,CAAC;KAC9E,CAAA;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAmB;IACzD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;QACxF,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACpD,CAAC;IAED,MAAM,OAAO,GAAG,qBAAqB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAA;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,aAAa,CAAA;IACvC,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;IACpE,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAA;IACrG,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;IAC1E,CAAC;IAED,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,SAAS,GAAG,IAAyC,CAAA;QAE3D,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,0BAA0B,CAAC,SAAS,CAAC,KAAK,EAAE,sBAAsB,CAAC;YAC1E,MAAM,EAAE,0BAA0B,CAAC,SAAS,CAAC,MAAM,EAAE,uBAAuB,CAAC;YAC7E,OAAO;SACR,CAAA;IACH,CAAC;IAED,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,GAAG,OAAO,GAAG,CAAC,CAAC;QAC/C,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC;QACjD,OAAO;KACR,CAAA;AACH,CAAC"}
package/dist/types.d.ts CHANGED
@@ -23,6 +23,31 @@ export type PolygonShape = {
23
23
  kind: 'polygon';
24
24
  points: ShapeTextPoint[];
25
25
  };
26
+ export type SvgMaskShapeFitContentSize = {
27
+ mode?: 'fit-content';
28
+ padding?: number;
29
+ };
30
+ export type SvgMaskShapeFixedSize = {
31
+ mode: 'fixed';
32
+ width: number;
33
+ height: number;
34
+ padding?: number;
35
+ };
36
+ export type SvgMaskShapeSize = SvgMaskShapeFitContentSize | SvgMaskShapeFixedSize;
37
+ export type SvgMaskShapeViewBox = {
38
+ x?: number;
39
+ y?: number;
40
+ width: number;
41
+ height: number;
42
+ };
43
+ export type SvgMaskShape = {
44
+ kind: 'svg-mask';
45
+ path: string;
46
+ viewBox: SvgMaskShapeViewBox;
47
+ size?: SvgMaskShapeSize;
48
+ maskScale?: number;
49
+ alphaThreshold?: number;
50
+ };
26
51
  export type TextMaskShape = {
27
52
  kind: 'text-mask';
28
53
  text: string;
@@ -32,7 +57,7 @@ export type TextMaskShape = {
32
57
  maskScale?: number;
33
58
  alphaThreshold?: number;
34
59
  };
35
- export type ShapeInput = PolygonShape | TextMaskShape;
60
+ export type ShapeInput = PolygonShape | SvgMaskShape | TextMaskShape;
36
61
  export type TextMeasurer = {
37
62
  measureText(text: string, font: string): number;
38
63
  };
@@ -119,6 +144,13 @@ export type CompiledShapeBand = {
119
144
  export type CompiledShapeDebugView = {
120
145
  kind: 'polygon';
121
146
  points: ShapeTextPoint[];
147
+ } | {
148
+ kind: 'path';
149
+ path: string;
150
+ x: number;
151
+ y: number;
152
+ scaleX: number;
153
+ scaleY: number;
122
154
  } | {
123
155
  kind: 'text';
124
156
  text: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shape-text",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Browser-first TypeScript library for shape-paragraph layout inside geometry and value-derived shapes, rendered to SVG.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",