shape-text 0.1.0 → 0.2.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
@@ -12,29 +12,20 @@ Browser-first TypeScript library for shape-paragraph layout and SVG rendering.
12
12
  - Shape-first API, not a thin wrapper over `pretext`
13
13
  - Compile-shape boundary for cache-friendly repeated rendering
14
14
 
15
- ## Install
16
-
17
- ```bash
18
- npm install shape-text
19
- bun add shape-text
20
- ```
21
-
22
- ## Local demo and browser E2E
23
-
24
- ```bash
25
- npx playwright install chromium
26
- npm run demo
27
- npm run e2e:ui
28
- ```
29
-
30
- - `npm run demo` starts the React workbench on `http://127.0.0.1:4173/`
31
- - `npm run demo:preview` serves the built demo
32
- - `npm run e2e` and `npm run e2e:ui` run Playwright against the built preview app on port `4174`, which is closer to production than hitting the dev server
33
- - The npm scripts resolve their own package root so they still work even if Windows launches `cmd.exe` with a broken fallback cwd
34
-
35
- ## Ship readiness
36
-
37
- Library packaging is validated for both `npm` and `bun`.
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install shape-text
19
+ bun add shape-text
20
+ ```
21
+
22
+ ## Published consumer example
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.
25
+
26
+ ## Ship readiness
27
+
28
+ Library packaging is validated for both `npm` and `bun`.
38
29
 
39
30
  ```bash
40
31
  npm run ship:check
@@ -58,20 +49,25 @@ import {
58
49
 
59
50
  const measurer = createCanvasTextMeasurer()
60
51
 
61
- const layout = layoutTextInShape({
62
- text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
63
- textStyle: {
64
- family: '"Helvetica Neue", Arial, sans-serif',
65
- size: 16,
66
- weight: 700,
67
- style: 'italic',
68
- color: '#111827',
69
- },
70
- lineHeight: 22,
71
- shape: {
72
- kind: 'polygon',
73
- points: [
74
- { x: 0, y: 0 },
52
+ const layout = layoutTextInShape({
53
+ text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
54
+ textStyle: {
55
+ // Swap `family` to any browser-available font stack.
56
+ family: '"Helvetica Neue", Arial, sans-serif',
57
+ // Increase or decrease `size` with `lineHeight` together.
58
+ size: 16,
59
+ // Typical weights: 400, 500, 700.
60
+ weight: 700,
61
+ // `style` can be omitted, `italic`, or `oblique`.
62
+ style: 'italic',
63
+ color: '#111827',
64
+ },
65
+ lineHeight: 22,
66
+ shape: {
67
+ // Replace `polygon` with `text-mask` for value-derived shapes.
68
+ kind: 'polygon',
69
+ points: [
70
+ { x: 0, y: 0 },
75
71
  { x: 240, y: 0 },
76
72
  { x: 240, y: 280 },
77
73
  { x: 0, y: 280 },
@@ -79,13 +75,14 @@ const layout = layoutTextInShape({
79
75
  },
80
76
  measurer,
81
77
  })
82
-
83
- const svg = renderLayoutToSvg(layout, {
84
- background: '#fffdf7',
85
- shapeStyle: {
86
- backgroundColor: '#dbeafe',
87
- borderColor: '#94a3b8',
88
- borderWidth: 2,
78
+
79
+ const svg = renderLayoutToSvg(layout, {
80
+ background: '#fffdf7',
81
+ shapeStyle: {
82
+ // Replace the palette or omit `shapeStyle` if you only want the text layer.
83
+ backgroundColor: '#dbeafe',
84
+ borderColor: '#94a3b8',
85
+ borderWidth: 2,
89
86
  shadow: {
90
87
  blur: 6,
91
88
  offsetY: 6,
@@ -106,33 +103,36 @@ The low-level API term stays `text-mask`, but the product framing is `value-deri
106
103
  ## Value-derived example
107
104
 
108
105
  ```ts
109
- const layout = layoutTextInShape({
110
- text: 'Shape paragraph can fill a value-derived silhouette too.',
111
- textStyle: {
112
- family: 'Arial, sans-serif',
113
- size: 16,
106
+ const layout = layoutTextInShape({
107
+ text: 'Shape paragraph can fill a value-derived silhouette too.',
108
+ textStyle: {
109
+ family: 'Arial, sans-serif',
110
+ size: 16,
114
111
  weight: 700,
115
112
  color: '#0f172a',
116
- },
117
- lineHeight: 20,
118
- autoFill: true,
119
- shape: {
120
- kind: 'text-mask',
121
- text: '23',
122
- font: '700 420px Arial',
123
- size: {
124
- mode: 'fit-content',
125
- padding: 10,
126
- },
113
+ },
114
+ lineHeight: 20,
115
+ // Set `autoFill: false` for normal paragraph flow instead of max-fill repeat coverage.
116
+ autoFill: true,
117
+ shape: {
118
+ kind: 'text-mask',
119
+ // Replace with any glyph string, for example `SALE`, `09`, or `AB`.
120
+ text: '23',
121
+ font: '700 420px Arial',
122
+ size: {
123
+ // Use `fixed` with explicit `width` / `height` when you need a forced raster box.
124
+ mode: 'fit-content',
125
+ padding: 10,
126
+ },
127
127
  },
128
128
  measurer,
129
129
  })
130
130
  ```
131
131
 
132
- ## Sequential value-derived regions
133
-
134
- ```ts
135
- const layout = layoutTextInShape({
132
+ ## Sequential value-derived regions
133
+
134
+ ```ts
135
+ const layout = layoutTextInShape({
136
136
  text: 'ABCDEFGHIJ',
137
137
  textStyle: {
138
138
  family: 'Arial, sans-serif',
@@ -140,34 +140,57 @@ const layout = layoutTextInShape({
140
140
  weight: 700,
141
141
  },
142
142
  lineHeight: 18,
143
- shape: {
144
- kind: 'text-mask',
145
- text: 'AB',
146
- font: '700 160px Arial',
147
- size: {
143
+ shape: {
144
+ kind: 'text-mask',
145
+ text: 'AB',
146
+ font: '700 160px Arial',
147
+ size: {
148
148
  mode: 'fixed',
149
- width: 260,
150
- height: 180,
151
- },
152
- shapeTextMode: 'per-character',
153
- },
154
- measurer,
155
- })
156
- ```
157
-
158
- `shape.size` defaults to `{ mode: 'fit-content', padding: 0 }`. Use `mode: 'fixed'` only when you need to force the glyph mask into an explicit raster box.
159
-
160
- `shape.shapeTextMode` defaults to `'whole-text'`. Set it to `'per-character'` to compile one ordered region per non-space grapheme and flow layout through those regions in shape-text order.
161
-
162
- ## Public API
163
-
164
- - `createCanvasTextMeasurer()`
165
- - `compileShapeForLayout()`
166
- - `normalizeTextStyleToFont()`
167
- - `prepareTextForLayout()`
168
- - `layoutNextLineFromPreparedText()`
169
- - `layoutNextLineFromRepeatedText()`
170
- - `getBandIntervalsFromPolygon()`
149
+ width: 260,
150
+ height: 180,
151
+ },
152
+ // Omit this or set `whole-text` to treat the full string as one shape region.
153
+ shapeTextMode: 'per-character',
154
+ },
155
+ measurer,
156
+ })
157
+ ```
158
+
159
+ `shape.size` defaults to `{ mode: 'fit-content', padding: 0 }`. Use `mode: 'fixed'` only when you need to force the glyph mask into an explicit raster box.
160
+
161
+ `shape.shapeTextMode` defaults to `'whole-text'`. Set it to `'per-character'` to compile one ordered region per non-space grapheme and flow layout through those regions in shape-text order.
162
+
163
+ ## Random fill helpers
164
+
165
+ ```ts
166
+ import { createRandomFillText } from 'shape-text'
167
+
168
+ const fillText = createRandomFillText({
169
+ // Presets: 'ascii', 'binary', 'hex', 'octal', 'symbol'.
170
+ preset: 'hex',
171
+ // Omit `length` to use the preset default length.
172
+ length: 48,
173
+ })
174
+
175
+ const customFillText = createRandomFillText({
176
+ // Custom alphabets work too.
177
+ alphabet: 'ABCD1234',
178
+ length: 32,
179
+ })
180
+ ```
181
+
182
+ ## Public API
183
+
184
+ - `createCanvasTextMeasurer()`
185
+ - `createRandomFillText()`
186
+ - `compileShapeForLayout()`
187
+ - `getRandomFillPreset()`
188
+ - `normalizeTextStyleToFont()`
189
+ - `prepareTextForLayout()`
190
+ - `randomFillPresets`
191
+ - `layoutNextLineFromPreparedText()`
192
+ - `layoutNextLineFromRepeatedText()`
193
+ - `getBandIntervalsFromPolygon()`
171
194
  - `layoutTextInCompiledShape()`
172
195
  - `layoutTextInShape()`
173
196
  - `renderLayoutToSvg()`
@@ -177,44 +200,33 @@ const layout = layoutTextInShape({
177
200
  - V1 keeps the text engine simple on purpose. It uses `Intl.Segmenter` for grapheme-safe word breaking, but it does not promise full browser-parity for every writing system.
178
201
  - The project takes inspiration from `pretext` for the `prepare -> layout` split and streaming line iteration, but owns its geometry, slot policy, and public API.
179
202
  - Geometry and value-derived shapes both compile into reusable line bands before layout.
180
- - `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.
181
- - `autoFill: true` now means one thing: max-fill stream layout that sweeps every usable interval in reading order.
182
- - Max fill keeps spaces as normal graphemes instead of stripping them, and it does not fall back to smaller text for leftover pockets.
183
- - `text-mask` sizing now lives under `shape.size`. The default `fit-content` mode measures the text mask first and grows the raster box to avoid clipping multi-character shapes such as `23`.
184
- - `shape.shapeTextMode: 'per-character'` keeps the full text-mask debug view, but also compiles ordered per-character regions for sequential fill across multi-character shape text.
185
- - `textStyle` is the new data-driven API for size, weight, italic/oblique, family, and default text color. Legacy `font` string input still works.
203
+ - `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.
204
+ - `autoFill: true` now means one thing: max-fill stream layout that sweeps every usable interval in reading order.
205
+ - Max fill keeps spaces as normal graphemes instead of stripping them, and it does not fall back to smaller text for leftover pockets.
206
+ - Random fill helpers are content utilities only; they generate source text but do not change layout rules.
207
+ - `text-mask` sizing now lives under `shape.size`. The default `fit-content` mode measures the text mask first and grows the raster box to avoid clipping multi-character shapes such as `23`.
208
+ - `shape.shapeTextMode: 'per-character'` keeps the full text-mask debug view, but also compiles ordered per-character regions for sequential fill across multi-character shape text.
209
+ - `textStyle` is the new data-driven API for size, weight, italic/oblique, family, and default text color. Legacy `font` string input still works.
186
210
  - `shapeStyle` lives in `renderLayoutToSvg()` because fill, border, and shadow do not affect line breaking or shape compilation.
187
211
  - For late-loading web fonts, compile after the font is ready if you want immediate cache reuse. The compiler skips cache writes until `document.fonts.check()` reports the font as ready.
188
212
 
189
- ## Local E2E
190
-
191
- Install the local browser once:
192
-
193
- ```bash
194
- npx playwright install chromium
195
- ```
196
-
197
- Run the local browser suite:
198
-
199
- ```bash
200
- npm run e2e
201
- ```
202
-
203
- Run unit coverage for `src/`:
204
-
205
- ```bash
206
- npm run test:coverage
207
- ```
208
-
209
- Useful dev modes:
210
-
211
- ```bash
212
- npm run e2e:ui
213
- npm run e2e:headed
214
- npm run e2e:debug
215
- ```
216
-
217
- Playwright now targets the React workbench in `demo/`, so browser coverage runs against the same app used for manual exploration.
213
+ ## Maintainer checks
214
+
215
+ Run the local validation stack:
216
+
217
+ ```bash
218
+ npm run check
219
+ npm run ship:check
220
+ npm run e2e
221
+ ```
222
+
223
+ Useful extras:
224
+
225
+ ```bash
226
+ npm run test:coverage
227
+ npm run e2e:ui
228
+ npm run e2e:headed
229
+ ```
218
230
 
219
231
  ## Publish notes
220
232
 
@@ -224,41 +236,6 @@ Playwright now targets the React workbench in `demo/`, so browser coverage runs
224
236
  - On Windows terminals that start inside a `\\?\C:\...` cwd, prefer `npm run publish:npm` instead of raw `npm publish`
225
237
  - PR validation now runs through `.github/workflows/ci.yml`
226
238
  - Tag releases now run through `.github/workflows/release.yml`
227
- - Preferred publish path is npm Trusted Publisher via GitHub Actions OIDC, with `NPM_TOKEN` as fallback only
239
+ - Release tags now skip npm publish automatically if that exact version already exists on npm
240
+ - Preferred publish path after the first release is npm Trusted Publisher via GitHub Actions OIDC, with `NPM_TOKEN` as fallback only
228
241
  - Maintainer release steps and repository settings live in [docs/deployment-guide.md](./docs/deployment-guide.md)
229
-
230
- ## Local Demo UI
231
-
232
- Open the React workbench:
233
-
234
- ```bash
235
- npm run demo
236
- ```
237
-
238
- Then open:
239
-
240
- ```text
241
- http://127.0.0.1:4173/
242
- ```
243
-
244
- Build the workbench explicitly:
245
-
246
- ```bash
247
- npm run demo:build
248
- ```
249
-
250
- Preview the built app:
251
-
252
- ```bash
253
- npm run demo:preview
254
- ```
255
-
256
- The workbench includes:
257
-
258
- - geometry vs value-derived shape source switching
259
- - direct `shape.text` editing for value-derived shapes
260
- - `shape.size.mode` switching between `fit-content` and `fixed`
261
- - `shapeTextMode` switching between `whole-text` and sequential `per-character` value-derived regions
262
- - random character-pattern fill presets
263
- - a payload JSON editor for the live `layout` + `render` request
264
- - a scrollable full-output SVG viewport with `Zoom out`, `Zoom in`, `100%`, and `Fit` controls
package/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
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';
2
+ export type { CreateRandomFillTextOptions, RandomIntSelector, } from './random-fill/create-random-fill-text.js';
3
+ export type { RandomFillPreset, RandomFillPresetId, } from './random-fill/random-fill-presets.js';
2
4
  export { createCanvasTextMeasurer } from './text/create-canvas-text-measurer.js';
3
5
  export { normalizeTextStyleToFont, resolveLayoutTextStyle } from './text/normalize-text-style-to-font.js';
4
6
  export { prepareTextForLayout } from './text/prepare-text-for-layout.js';
5
7
  export { layoutNextLineFromPreparedText } from './text/layout-next-line-from-prepared-text.js';
6
8
  export { layoutNextLineFromRepeatedText } from './text/layout-next-line-from-repeated-text.js';
9
+ export { createRandomFillText } from './random-fill/create-random-fill-text.js';
10
+ export { getRandomFillPreset, randomFillPresets } from './random-fill/random-fill-presets.js';
7
11
  export { getBandIntervalsFromPolygon } from './geometry/get-band-intervals-from-polygon.js';
8
12
  export { compileShapeForLayout } from './shape/compile-shape-for-layout.js';
9
13
  export { layoutTextInCompiledShape } from './layout/layout-text-in-compiled-shape.js';
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ export { normalizeTextStyleToFont, resolveLayoutTextStyle } from './text/normali
3
3
  export { prepareTextForLayout } from './text/prepare-text-for-layout.js';
4
4
  export { layoutNextLineFromPreparedText } from './text/layout-next-line-from-prepared-text.js';
5
5
  export { layoutNextLineFromRepeatedText } from './text/layout-next-line-from-repeated-text.js';
6
+ export { createRandomFillText } from './random-fill/create-random-fill-text.js';
7
+ export { getRandomFillPreset, randomFillPresets } from './random-fill/random-fill-presets.js';
6
8
  export { getBandIntervalsFromPolygon } from './geometry/get-band-intervals-from-polygon.js';
7
9
  export { compileShapeForLayout } from './shape/compile-shape-for-layout.js';
8
10
  export { layoutTextInCompiledShape } from './layout/layout-text-in-compiled-shape.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkCA,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,2BAA2B,EAAE,MAAM,+CAA+C,CAAA;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAA;AAC3E,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":"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,yBAAyB,EAAE,MAAM,2CAA2C,CAAA;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { type RandomFillPresetId } from './random-fill-presets.js';
2
+ export type RandomIntSelector = (max: number) => number;
3
+ export type CreateRandomFillTextOptions = {
4
+ preset: RandomFillPresetId;
5
+ length?: number;
6
+ randomInt?: RandomIntSelector;
7
+ } | {
8
+ alphabet: string;
9
+ length: number;
10
+ randomInt?: RandomIntSelector;
11
+ };
12
+ export declare function createRandomFillText(options: CreateRandomFillTextOptions): string;
@@ -0,0 +1,70 @@
1
+ import { getRandomFillPreset } from './random-fill-presets.js';
2
+ const graphemeSegmenter = typeof Intl?.Segmenter === 'function'
3
+ ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
4
+ : null;
5
+ function createDefaultRandomIntSelector() {
6
+ if (typeof globalThis.crypto?.getRandomValues === 'function') {
7
+ const buffer = new Uint32Array(1);
8
+ return max => {
9
+ globalThis.crypto.getRandomValues(buffer);
10
+ return buffer[0] % max;
11
+ };
12
+ }
13
+ return max => Math.floor(Math.random() * max);
14
+ }
15
+ function segmentAlphabet(alphabet) {
16
+ if (!alphabet) {
17
+ throw new Error('Random fill alphabet must not be empty');
18
+ }
19
+ if (graphemeSegmenter) {
20
+ return Array.from(graphemeSegmenter.segment(alphabet), segment => segment.segment);
21
+ }
22
+ return Array.from(alphabet);
23
+ }
24
+ function getAlphabetSymbols(options) {
25
+ if ('alphabet' in options) {
26
+ return segmentAlphabet(options.alphabet);
27
+ }
28
+ const preset = getRandomFillPreset(options.preset);
29
+ if (!preset) {
30
+ throw new Error(`Unknown random fill preset: ${String(options.preset)}`);
31
+ }
32
+ return segmentAlphabet(preset.alphabet);
33
+ }
34
+ function getLength(options) {
35
+ if ('alphabet' in options) {
36
+ return options.length;
37
+ }
38
+ if (options.length !== undefined) {
39
+ return options.length;
40
+ }
41
+ const preset = getRandomFillPreset(options.preset);
42
+ if (!preset) {
43
+ throw new Error(`Unknown random fill preset: ${String(options.preset)}`);
44
+ }
45
+ return preset.defaultLength;
46
+ }
47
+ function assertValidLength(length) {
48
+ if (!Number.isInteger(length) || length <= 0) {
49
+ throw new Error('Random fill length must be a positive integer');
50
+ }
51
+ return length;
52
+ }
53
+ function getRandomIndex(randomInt, alphabetLength) {
54
+ const value = randomInt(alphabetLength);
55
+ if (!Number.isInteger(value) || value < 0 || value >= alphabetLength) {
56
+ throw new Error('Random fill selector must return an integer between 0 and max - 1');
57
+ }
58
+ return value;
59
+ }
60
+ export function createRandomFillText(options) {
61
+ const alphabet = getAlphabetSymbols(options);
62
+ const length = assertValidLength(getLength(options));
63
+ const randomInt = options.randomInt ?? createDefaultRandomIntSelector();
64
+ let text = '';
65
+ for (let index = 0; index < length; index++) {
66
+ text += alphabet[getRandomIndex(randomInt, alphabet.length)];
67
+ }
68
+ return text;
69
+ }
70
+ //# sourceMappingURL=create-random-fill-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-random-fill-text.js","sourceRoot":"","sources":["../../src/random-fill/create-random-fill-text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ export type RandomFillPresetId = 'ascii' | 'binary' | 'hex' | 'octal' | 'symbol';
2
+ export type RandomFillPreset = {
3
+ id: RandomFillPresetId;
4
+ label: string;
5
+ alphabet: string;
6
+ defaultLength: number;
7
+ };
8
+ export declare const randomFillPresets: readonly [{
9
+ readonly id: "ascii";
10
+ readonly label: "ASCII";
11
+ readonly alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()[]{}<>?/|+-=";
12
+ readonly defaultLength: 96;
13
+ }, {
14
+ readonly id: "binary";
15
+ readonly label: "BINARY";
16
+ readonly alphabet: "01";
17
+ readonly defaultLength: 128;
18
+ }, {
19
+ readonly id: "hex";
20
+ readonly label: "HEX";
21
+ readonly alphabet: "0123456789ABCDEF";
22
+ readonly defaultLength: 112;
23
+ }, {
24
+ readonly id: "octal";
25
+ readonly label: "OCTAL";
26
+ readonly alphabet: "01234567";
27
+ readonly defaultLength: 120;
28
+ }, {
29
+ readonly id: "symbol";
30
+ readonly label: "SYMBOL";
31
+ readonly alphabet: "<>[]{}()/\\|+-=_*#@~";
32
+ readonly defaultLength: 96;
33
+ }];
34
+ export declare function getRandomFillPreset(id: RandomFillPresetId): {
35
+ readonly id: "ascii";
36
+ readonly label: "ASCII";
37
+ readonly alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()[]{}<>?/|+-=";
38
+ readonly defaultLength: 96;
39
+ } | {
40
+ readonly id: "binary";
41
+ readonly label: "BINARY";
42
+ readonly alphabet: "01";
43
+ readonly defaultLength: 128;
44
+ } | {
45
+ readonly id: "hex";
46
+ readonly label: "HEX";
47
+ readonly alphabet: "0123456789ABCDEF";
48
+ readonly defaultLength: 112;
49
+ } | {
50
+ readonly id: "octal";
51
+ readonly label: "OCTAL";
52
+ readonly alphabet: "01234567";
53
+ readonly defaultLength: 120;
54
+ } | {
55
+ readonly id: "symbol";
56
+ readonly label: "SYMBOL";
57
+ readonly alphabet: "<>[]{}()/\\|+-=_*#@~";
58
+ readonly defaultLength: 96;
59
+ } | undefined;
@@ -0,0 +1,16 @@
1
+ export const randomFillPresets = [
2
+ {
3
+ id: 'ascii',
4
+ label: 'ASCII',
5
+ alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()[]{}<>?/|+-=',
6
+ defaultLength: 96,
7
+ },
8
+ { id: 'binary', label: 'BINARY', alphabet: '01', defaultLength: 128 },
9
+ { id: 'hex', label: 'HEX', alphabet: '0123456789ABCDEF', defaultLength: 112 },
10
+ { id: 'octal', label: 'OCTAL', alphabet: '01234567', defaultLength: 120 },
11
+ { id: 'symbol', label: 'SYMBOL', alphabet: '<>[]{}()/\\|+-=_*#@~', defaultLength: 96 },
12
+ ];
13
+ export function getRandomFillPreset(id) {
14
+ return randomFillPresets.find(preset => preset.id === id);
15
+ }
16
+ //# sourceMappingURL=random-fill-presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random-fill-presets.js","sourceRoot":"","sources":["../../src/random-fill/random-fill-presets.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B;QACE,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,OAAO;QACd,QAAQ,EACN,sFAAsF;QACxF,aAAa,EAAE,EAAE;KAClB;IACD,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE;IACrE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,aAAa,EAAE,GAAG,EAAE;IAC7E,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,EAAE;IACzE,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,sBAAsB,EAAE,aAAa,EAAE,EAAE,EAAE;CACxC,CAAA;AAEhD,MAAM,UAAU,mBAAmB,CAAC,EAAsB;IACxD,OAAO,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;AAC3D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shape-text",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -47,12 +47,12 @@
47
47
  "demo": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs')\"",
48
48
  "demo:dev": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs')\"",
49
49
  "demo:build": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs',['--build'])\"",
50
- "demo:preview": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs',['--preview'])\"",
51
- "prepack": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-build.mjs')\"",
52
- "publish:npm": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-manual-npm-publish.mjs', process.argv.slice(1))\" --",
53
- "ship:check": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-ship-check.mjs')\"",
54
- "release:check-tag": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/validate-release-tag.mjs', process.argv.slice(1))\""
55
- },
50
+ "demo:preview": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs',['--preview'])\"",
51
+ "prepack": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-build.mjs')\"",
52
+ "publish:npm": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-manual-npm-publish.mjs', process.argv.slice(1))\" --",
53
+ "ship:check": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-ship-check.mjs')\"",
54
+ "release:check-tag": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/validate-release-tag.mjs', process.argv.slice(1))\""
55
+ },
56
56
  "keywords": [
57
57
  "svg",
58
58
  "text-layout",