textpour 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/README.md +131 -96
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,96 +1,131 @@
1
- # textpour
2
-
3
- A render-agnostic **text-geometry kernel** on top of
4
- [`@chenglou/pretext`](https://github.com/chenglou/pretext).
5
-
6
- - **Shape-flow**: pour text into arbitrary 2D regions — circles, polygons, holes, boolean
7
- combinations, SVG paths, glyph outlines, and raster alpha masks — by routing Pretext's
8
- line-breaking through per-row spans. (CSS `shape-inside` never shipped; this does it.)
9
- - **Typographic quality** (the moat): justification (`align: 'justify'`), soft-hyphenation,
10
- balanced lines, auto-fit (binary-search font size), and conservative band sampling so glyphs
11
- never poke outside tight curves.
12
- - **Cursor point mapping**: map pixel positions to exact grapheme positions and back, for
13
- caret/hit-testing in custom-rendered text.
14
- - **Pluggable paint**: the kernel computes geometry; `Renderer`s paint it. Canvas2D works today;
15
- an HTML-in-Canvas adapter (for high-fidelity, accessible, 3D-surface paint) is stubbed for later.
16
-
17
- The design bet is the **plan/paint split**: Pretext plans cheaply every frame (no DOM reflow); an
18
- expensive high-fidelity backend paints only when the plan changes.
19
-
20
- ## Demo
21
-
22
- Text poured into a circle and a donut (multi-span), reflowing live as the region changes — the same
23
- prepared pass reused on every frame:
24
-
25
- ![textpour demo: text flowing into a circle and a donut, reflowing live](assets/textpour-demo.gif)
26
-
27
- ## Why not just Pretext?
28
-
29
- Pretext is the line-breaking and measurement enginea very good one. It breaks lines at a width
30
- *you give it*, measures without DOM reflow, and its fuller API even does Knuth–Plass justification,
31
- syllable hyphenation, and "shrinkwrap" (`walkLineRanges`). It can already flow text past a floated
32
- image, because that's still **one rectangular width that varies by `y`**.
33
-
34
- What Pretext deliberately does **not** model is **2D geometry**. Its layout call is "one line, one
35
- width." textpour adds exactly that missing layer:
36
-
37
- - **Arbitrary regions, not a scalar width.** A `Region` turns any 2D shape — circles, ellipses,
38
- polygons, boolean unions/intersections/**holes**, SVG paths, glyph outlines, raster alpha masks
39
- into the per-row spans Pretext consumes. Pretext has no notion of a shape, a hole, or an outline.
40
- - **Multiple disjoint spans per line — the "cursor trick."** Pouring a single row across the left
41
- *and* right of a hole (a donut), or into the prongs of a concave shape, in reading order on one
42
- baseline. Pretext's one-line-one-width API can't natively continue a row across a gap; textpour
43
- threads one cursor through every span on the row.
44
- - **Auto-fit to a region** — binary-search the font size that exactly fills a shape. Pretext keys
45
- layout on `(text, font)`; it doesn't size-to-fit a 2D area.
46
- - **A render-agnostic plan/paint kernel** geometry computed once, painted by pluggable
47
- `Renderer`s (Canvas2D today, HTML-in-Canvas later).
48
- - **Cursor point mapping** for hit-testing/caret in custom-rendered text.
49
-
50
- Honest overlap: textpour targets the **published `@chenglou/pretext@0.0.1`**, whose API is just
51
- `prepareWithSegments` + `layoutNextLine`. So textpour's own justification, soft-hyphenation, and
52
- balanced-lines are pragmatic implementations over that minimal surface when Pretext ships its
53
- richer API (Knuth–Plass justify, real hyphenation, `walkLineRanges`), textpour will defer to those
54
- and keep only the geometry it uniquely contributes.
55
-
56
- In one line: **Pretext breaks the lines; textpour decides the shape those lines fill.**
57
-
58
- ## Quickstart
59
-
60
- ```bash
61
- npm install
62
- npm test # builds, runs the pure-logic test suite (56 specs)
63
- npm run build # emits dist/
64
- # demo (needs a browser + http):
65
- npx http-server . # or any static server
66
- # open /demo/index.html
67
- ```
68
-
69
- ## Status
70
-
71
- **Phase 0** (kernel scaffold) and **Phase 1** (shape-flow quality justification, soft-hyphenation,
72
- balanced lines, auto-fit, region-from-outline, conservative band sampling) are complete and tested
73
- (56 specs). See **ROADMAP.md** for what's next — the HTML-in-Canvas renderer (Phase 2), then the
74
- flagship "shaped CSS text on a 3D surface" demo (Phase 3).
75
-
76
- ## Docs
77
-
78
- - **EXPLAINER.md** — plain-words overview, ELI5, and the origin story (start here for the why).
79
- - **CLAUDE.md** — how to work in this repo (conventions, commands, guardrails).
80
- - **SPEC.md** — the full design, API reference, and the relationship to Pretext + HTML-in-Canvas.
81
- - **ROADMAP.md** phased tasks with acceptance + kill criteria.
82
-
83
- ## Example
84
-
85
- ```ts
86
- import { shapeFlow, circle, subtract, Canvas2DRenderer, PretextLineSource } from 'textpour';
87
-
88
- const source = new PretextLineSource(longText, '17px Georgia'); // one prepare pass
89
- const region = subtract(circle(220, 220, 160), circle(220, 220, 67)); // a donut
90
- const result = shapeFlow(source, region, { lineHeight: 24, ascent: 18 });
91
-
92
- new Canvas2DRenderer('17px Georgia', { color: '#1a1a1a' }).render(result, ctx);
93
- // result.overflow / result.endCursor drive auto-fit and multi-region pagination
94
- ```
95
-
96
- MIT.
1
+ # textpour
2
+
3
+ A render-agnostic **text-geometry kernel** on top of
4
+ [`@chenglou/pretext`](https://github.com/chenglou/pretext).
5
+
6
+ - **Shape-flow the reusable core.** Turn any 2D region — circles, ellipses, polygons, boolean
7
+ unions/intersections/**holes**, SVG paths, glyph outlines, raster alpha masks — into the per-row
8
+ spans Pretext fills, threading one cursor across the *several disjoint spans* a single row can have
9
+ (around a hole, through a glyph's counters). This shape→spans geometry is the part worth a library.
10
+ (CSS `shape-inside` never shipped; this does it.)
11
+ - **Cursor point mapping**: map pixel positions to exact grapheme positions and back, for
12
+ caret/hit-testing in custom-rendered text.
13
+ - **Conveniences over the line breaker** (nice-to-haves, *not* the moat): justification, soft-
14
+ hyphenation, balanced lines, auto-fit, conservative band sampling pragmatic implementations over
15
+ the *published* `@chenglou/pretext@0.0.1`; Pretext's fuller API does several of these natively.
16
+ - **Pluggable paint**: the kernel computes geometry; `Renderer`s paint it. Canvas2D today; an
17
+ HTML-in-Canvas adapter (high-fidelity, accessible, 3D-surface paint) is stubbed for later.
18
+
19
+ The design bet is the **plan/paint split**: Pretext plans cheaply every frame (no DOM reflow); an
20
+ expensive high-fidelity backend paints only when the plan changes.
21
+
22
+ ## Demo
23
+
24
+ Text poured into a circle and a donut (multi-span), reflowing live as the region changes — the same
25
+ prepared pass reused on every frame:
26
+
27
+ ![textpour demo: text flowing into a circle and a donut, reflowing live](assets/textpour-demo.gif)
28
+
29
+ A gallery of practical demos lives in `demo/` serve over http (`npm run build` first) and open
30
+ **`demo/gallery.html`**. Start with **Anatomy**, which runs raw Pretext and textpour *side by side*:
31
+ identical pixels for a circle (just inline the loop), and the spans function ballooning into a
32
+ rasterizer for a glyph (reuse the library). The rest Islands, Letterform, Ghostwriter, Reflow,
33
+ Touchpoint — isolate one capability each, with a "textpour vs raw Pretext" code panel on every page.
34
+
35
+ ## Why not just Pretext?
36
+
37
+ Pretext is the line-breaking and measurement engine a very good one. It breaks lines at a width
38
+ *you give it*, measures without DOM reflow, and its fuller API even does Knuth–Plass justification,
39
+ syllable hyphenation, and "shrinkwrap" (`walkLineRanges`). It can already flow text past a floated
40
+ image, because that's still **one rectangular width that varies by `y`**.
41
+
42
+ What Pretext deliberately does **not** model is **2D geometry**. Its layout call is "one line, one
43
+ width." textpour adds exactly that missing layer:
44
+
45
+ - **Arbitrary regions, not a scalar width.** A `Region` turns any 2D shape — circles, ellipses,
46
+ polygons, boolean unions/intersections/**holes**, SVG paths, glyph outlines, raster alpha masks —
47
+ into the per-row spans Pretext consumes. Pretext has no notion of a shape, a hole, or an outline.
48
+ - **Multiple disjoint spans per line — the "cursor trick."** Pouring a single row across the left
49
+ *and* right of a hole (a donut), or into the prongs of a concave shape, in reading order on one
50
+ baseline. Pretext's one-line-one-width API can't natively continue a row across a gap; textpour
51
+ threads one cursor through every span on the row.
52
+ - **Auto-fit to a region** — binary-search the font size that exactly fills a shape. Pretext keys
53
+ layout on `(text, font)`; it doesn't size-to-fit a 2D area.
54
+ - **A render-agnostic plan/paint kernel** — geometry computed once, painted by pluggable
55
+ `Renderer`s (Canvas2D today, HTML-in-Canvas later).
56
+ - **Cursor point mapping** for hit-testing/caret in custom-rendered text.
57
+
58
+ Honest overlap: textpour targets the **published `@chenglou/pretext@0.0.1`**, whose API is just
59
+ `prepareWithSegments` + `layoutNextLine`. So textpour's own justification, soft-hyphenation, and
60
+ balanced-lines are pragmatic implementations over that minimal surface — when Pretext ships its
61
+ richer API (Knuth–Plass justify, real hyphenation, `walkLineRanges`), textpour will defer to those
62
+ and keep only the geometry it uniquely contributes.
63
+
64
+ In one line: **Pretext breaks the lines; textpour decides the shape those lines fill.** The flow loop
65
+ on top is ~12 lines you could inline — what textpour actually packages is `region.spansAt(y)` for
66
+ shapes Pretext has no concept of. A convenience kernel, not a new capability. (See the **Anatomy**
67
+ demo for the side-by-side proof.)
68
+
69
+ ## What this is (and isn't)
70
+
71
+ A **hobby + learning project** as much an excuse to learn the npm release loop (publish, semver,
72
+ CI-on-tag) as a library. It's a thin geometry layer **on top of** Pretext, **not** a replacement for
73
+ or competitor to it: Pretext does the hard part (shaping, line-breaking, measurement); textpour just
74
+ turns shapes into spans. Treat it as a niche convenience, not infrastructure — fun if you're pouring
75
+ text into odd shapes, but don't build anything load-bearing on it.
76
+
77
+ ## Quickstart
78
+
79
+ ```bash
80
+ npm install
81
+ npm test # builds, runs the pure-logic test suite (56 specs)
82
+ npm run build # emits dist/
83
+ # demo (needs a browser + http):
84
+ npx http-server . # or any static server
85
+ # open /demo/gallery.html
86
+ ```
87
+
88
+ ## Status
89
+
90
+ **Phase 0** (kernel scaffold) and **Phase 1** (shape-flow quality — justification, soft-hyphenation,
91
+ balanced lines, auto-fit, region-from-outline, conservative band sampling) are complete and tested
92
+ (56 specs). See **ROADMAP.md** for what's next — the HTML-in-Canvas renderer (Phase 2), then the
93
+ flagship "shaped CSS text on a 3D surface" demo (Phase 3).
94
+
95
+ ## Docs
96
+
97
+ - **EXPLAINER.md** — plain-words overview, ELI5, and the origin story (start here for the why).
98
+ - **CLAUDE.md** — how to work in this repo (conventions, commands, guardrails).
99
+ - **SPEC.md** — the full design, API reference, and the relationship to Pretext + HTML-in-Canvas.
100
+ - **ROADMAP.md** — phased tasks with acceptance + kill criteria.
101
+
102
+ ## Example — Pretext vs textpour
103
+
104
+ Pour text into a column with a hole, both ways — the contrast every demo shows ([live](demo/gallery.html)):
105
+
106
+ ```js
107
+ // raw Pretext: you write the loop AND the geometry
108
+ let cur = { segmentIndex: 0, graphemeIndex: 0 };
109
+ const spansAt = (yc) => { // column minus the hole's chord
110
+ const h = 90 * 90 - (yc - 200) ** 2;
111
+ return h <= 0 ? [[0, 600]] : [[0, 300 - Math.sqrt(h)], [300 + Math.sqrt(h), 600]];
112
+ };
113
+ for (let y = 0; y + 26 <= 400; y += 26)
114
+ for (const [x0, x1] of spansAt(y + 13)) {
115
+ const line = layoutNextLine(prepared, cur, x1 - x0); // one cursor across the gap
116
+ if (!line) break;
117
+ ctx.fillText(line.text, x0, y + 20); cur = line.end;
118
+ }
119
+ ```
120
+
121
+ ```ts
122
+ // textpour: describe the shape, pour
123
+ const region = subtract(rect(0, 0, 600, 400), circle(300, 200, 90));
124
+ const result = shapeFlow(source, region, { lineHeight: 26, ascent: 20, multiSpan: 'fill' });
125
+ renderer.render(result, ctx);
126
+ ```
127
+
128
+ Same output. The loop is glue; **`region` is the part textpour gives you** — and it scales to glyphs,
129
+ masks, and booleans without touching `spansAt`. [Anatomy](demo/anatomy.html) runs both, pixel-for-pixel.
130
+
131
+ MIT.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "textpour",
3
- "version": "0.1.0",
4
- "description": "Pour text into arbitrary shapes — a render-agnostic text-geometry kernel on top of @chenglou/pretext (shape-flow + cursor<->point mapping, pluggable paint adapters).",
3
+ "version": "0.1.2",
4
+ "description": "Hobby/learning project: a small geometry layer on top of @chenglou/pretext — turn any 2D shape (holes, glyphs, image masks) into the per-row spans it fills, plus cursor<->point mapping. Not a Pretext replacement.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
7
7
  "types": "./dist/src/index.d.ts",