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 +140 -163
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/random-fill/create-random-fill-text.d.ts +12 -0
- package/dist/random-fill/create-random-fill-text.js +70 -0
- package/dist/random-fill/create-random-fill-text.js.map +1 -0
- package/dist/random-fill/random-fill-presets.d.ts +59 -0
- package/dist/random-fill/random-fill-presets.js +16 -0
- package/dist/random-fill/random-fill-presets.js.map +1 -0
- package/package.json +7 -7
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
|
-
##
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
-
|
|
184
|
-
- `shape.
|
|
185
|
-
- `
|
|
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
|
-
##
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```
|
|
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
|
-
-
|
|
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":"
|
|
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.
|
|
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",
|