procedural-paper 0.0.0-stage → 0.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -2
  3. package/THIRD_PARTY_NOTICES.md +51 -0
  4. package/dist/cards.d.ts +42 -0
  5. package/dist/color.d.ts +5 -0
  6. package/dist/composition/assets.d.ts +7 -0
  7. package/dist/composition/cords.d.ts +43 -0
  8. package/dist/composition/filters.d.ts +1 -0
  9. package/dist/composition/fit.d.ts +58 -0
  10. package/dist/composition/index.d.ts +27 -0
  11. package/dist/composition/index.js +1247 -0
  12. package/dist/composition/index.js.map +1 -0
  13. package/dist/composition/model.d.ts +72 -0
  14. package/dist/composition/motion-spec.d.ts +98 -0
  15. package/dist/composition/motion.d.ts +36 -0
  16. package/dist/composition/motion.js +291 -0
  17. package/dist/composition/motion.js.map +1 -0
  18. package/dist/composition/nodes.d.ts +7 -0
  19. package/dist/composition/pen.d.ts +6 -0
  20. package/dist/composition/photos.d.ts +41 -0
  21. package/dist/composition/props.d.ts +32 -0
  22. package/dist/composition/react.d.ts +10 -0
  23. package/dist/composition/react.js +22 -0
  24. package/dist/composition/react.js.map +1 -0
  25. package/dist/composition/renderer.d.ts +77 -0
  26. package/dist/composition/rng.d.ts +11 -0
  27. package/dist/composition/scene.d.ts +77 -0
  28. package/dist/composition/unrotate.d.ts +3 -0
  29. package/dist/composition.css +78 -0
  30. package/dist/cpu/compose.d.ts +7 -0
  31. package/dist/cpu/field.d.ts +10 -0
  32. package/dist/cpu/noise.d.ts +7 -0
  33. package/dist/cpu/overlays.d.ts +12 -0
  34. package/dist/cpu/renderer.d.ts +16 -0
  35. package/dist/cpu/strands.d.ts +33 -0
  36. package/dist/dom.d.ts +110 -0
  37. package/dist/dom.js +65 -0
  38. package/dist/dom.js.map +1 -0
  39. package/dist/export.d.ts +20 -0
  40. package/dist/gl/renderer.d.ts +61 -0
  41. package/dist/gl/shaders.d.ts +10 -0
  42. package/dist/gl/sheet-shader.d.ts +1 -0
  43. package/dist/hash.d.ts +12 -0
  44. package/dist/index.d.ts +73 -0
  45. package/dist/index.js +2415 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/params.d.ts +169 -0
  48. package/dist/presets.d.ts +11 -0
  49. package/dist/react.d.ts +20 -0
  50. package/dist/react.js +50 -0
  51. package/dist/react.js.map +1 -0
  52. package/dist/resolve.d.ts +125 -0
  53. package/dist/sheet.d.ts +167 -0
  54. package/dist/stocks.d.ts +12 -0
  55. package/dist/styles.css +13 -0
  56. package/licenses/Apache-2.0.txt +201 -0
  57. package/package.json +116 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maks Surguy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,206 @@
1
- # Temporary Holding Version
1
+ # procedural-paper API
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ MIT © 2026 Maks Surguy. [Source](https://github.com/msurguy/procedural-paper) · [Demo](https://msurguy.github.io/procedural-paper/)
4
+
5
+ Seamless procedural paper textures and 25 paper UI components, generated from
6
+ code without source images.
7
+
8
+ ```sh
9
+ npm install procedural-paper
10
+ ```
11
+
12
+ ## Entry points
13
+
14
+ | Import | Purpose |
15
+ | --- | --- |
16
+ | `procedural-paper` | Dependency-free texture/sheet rendering, stock recipes, card presets |
17
+ | `procedural-paper/dom` | `createPaperSheet`, `createPaperCard`, shared `renderPaperLayers` compositor |
18
+ | `procedural-paper/react` | `PaperSheet`, `PaperCard`; requires React 18.2+ or 19 |
19
+ | `procedural-paper/styles.css` | Required, namespaced component styling |
20
+
21
+ Build output is ESM with TypeScript declarations. CPU texture generation works in
22
+ Node without a DOM. DOM helpers must be called in the browser. React components
23
+ are SSR-safe and hydrate a plain paper container before rendering textures.
24
+
25
+ ## Textures
26
+
27
+ ```ts
28
+ import { generatePaper, encodePNG } from 'procedural-paper';
29
+ const paper = await generatePaper({
30
+ preset: 'watercolor-cold-press', width: 1024, height: 768, seed: 7,
31
+ outputs: ['color', 'albedo', 'overlay', 'height', 'normal'],
32
+ params: { color: { base: '#f4efe4' } },
33
+ backend: 'auto', // or 'webgl2' / 'cpu'
34
+ });
35
+ const png = await encodePNG(paper.maps.color!);
36
+ ```
37
+
38
+ `presets`, `paramSchema`, `buildParams`, `mergeParams`, `getParam` and `setParam`
39
+ provide discovery and editing. `toBlob` and `toCanvas` provide browser outputs.
40
+ Reuse `PaperRenderer` for repeated work and call `dispose()` when finished.
41
+ `preview()` and `renderSheet()` require WebGL2; CPU rendering covers repeating maps.
42
+
43
+ ## Vanilla components
44
+
45
+ ```ts
46
+ import { createPaperCard, createPaperSheet } from 'procedural-paper/dom';
47
+ import 'procedural-paper/styles.css';
48
+ const card = createPaperCard({ preset: 'polaroid', seed: 4 });
49
+ const photo = document.createElement('img');
50
+ photo.src = '/my-photo.jpg'; photo.alt = 'A mountain lake';
51
+ card.slots.photo.append(photo);
52
+ card.slots.content.textContent = 'Summer';
53
+ document.body.append(card.element);
54
+ card.update({ seed: 5 }); // preserves content nodes and listeners
55
+ // card.dispose(); // removes element and releases its owned renderer
56
+
57
+ const sheet = createPaperSheet({ width: 240, height: 320, preset: 'kraft',
58
+ sheet: { shape: { edges: { left: 'torn' } } } });
59
+ ```
60
+
61
+ Card options: `preset`, optional `width`, `height`, `seed`, paper `params`, sheet
62
+ `sheet` overrides, and optional borrowed `renderer`. Sheet options require
63
+ `width` and `height`; their `preset` is a texture preset instead of a card ID.
64
+ Dimensions are explicit CSS pixels, not automatically measured from children.
65
+ Pixel ratio defaults to device pixel ratio capped at 2; override with `sheet.pixelRatio`.
66
+ Card geometry and content regions scale with width/height. Hole radii and edge
67
+ texture detail remain in CSS pixels. `params` merges into the stock recipe;
68
+ `sheet` replaces top-level preset sheet groups. `update` shallow-merges options;
69
+ pass `undefined` to clear an optional override. `sheet.seed` overrides the outline seed.
70
+
71
+ `slots` contains live DOM containers for all regions. Unused regions are empty;
72
+ when switching preset, former slots are hidden and retained for reuse. Caller
73
+ content is never interpreted as HTML strings. Text remains selectable and controls
74
+ remain interactive; canvases and shading do not intercept pointer events.
75
+ Consumers own their content's event handlers and any object URLs they create.
76
+ A borrowed renderer is never disposed by a component.
77
+
78
+ If the GPU is unavailable, components retain readable content on a solid paper
79
+ background. Rendering failures emit `papererror` on the vanilla element. Invalid
80
+ preset IDs or non-positive/non-finite dimensions throw before rendering.
81
+
82
+ ## React
83
+
84
+ ```tsx
85
+ import { PaperCard, PaperSheet } from 'procedural-paper/react';
86
+ import 'procedural-paper/styles.css';
87
+ export function Example() {
88
+ return <PaperCard preset="polaroid" seed={4}
89
+ slots={{ photo: <img src="/my-photo.jpg" alt="A mountain lake" /> }}>
90
+ Summer
91
+ </PaperCard>;
92
+ }
93
+ ```
94
+
95
+ Props mirror vanilla options plus `children`, `slots`, `className`, `style`, and
96
+ `aria-label`. `children` fills `content`; `slots` supplies named React regions.
97
+ React owns the children throughout updates. Memoize parameter/sheet objects and
98
+ borrow a renderer when rendering many components. Effects clean up on unmount
99
+ and support Strict Mode. The wrapper is marked as a client entry point for React
100
+ server-component frameworks; CSS should be imported at the application's entry.
101
+
102
+ ## Card presets
103
+
104
+ `cardPresetIds` and `cardPresets` enumerate all 25 styles. `getCardPreset(id)`
105
+ returns an independent copy. `resolveCard(options)` returns paper parameters,
106
+ sheet settings and named content regions without accessing the DOM.
107
+
108
+ | IDs | Regions in addition to `content` |
109
+ | --- | --- |
110
+ | sticky, notebook, napkin, scrap, graph, biz, quote, tag, receipt | none |
111
+ | index | title |
112
+ | ticket | stub |
113
+ | memo | signature |
114
+ | watercolor, clipping, stamp, tracing, tapelabel, print | none |
115
+ | polaroid, photonote, vintage, tear | photo |
116
+ | album | photo1, photo2, caption2 |
117
+ | booth | photo1, photo2, photo3, photo4 |
118
+ | film | photo1, photo2, photo3 |
119
+
120
+ `paperStocks` and `stockParams(key)` expose the desk's paper recipes. Presets
121
+ include outlines, relief, photo regions and ruled/grid decorations. Supply your
122
+ own content and images. The library doesn't bundle photos, fonts, sample prose,
123
+ photo grading, text fitting, attachments, physics, or handwriting animations.
124
+
125
+ ## Low-level sheets
126
+
127
+ ```ts
128
+ import { PaperRenderer, buildParams } from 'procedural-paper';
129
+ const renderer = new PaperRenderer();
130
+ const result = renderer.renderSheet(buildParams('kraft'), {
131
+ width: 240, height: 320, pixelRatio: 2,
132
+ shape: { edges: { left: 'torn' } },
133
+ }, ['base', 'shade', 'shadow']);
134
+ renderer.dispose();
135
+ ```
136
+
137
+ Place live content between base and shade; shade uses `mix-blend-mode: hard-light`.
138
+ `renderPaperLayers(renderer, params, sheet)` performs the browser canvas conversion
139
+ and returns canvases plus mask CSS. Its output includes the transparent margin;
140
+ the helper creates no object URLs. This is also how the desk integrates its own
141
+ interaction layers with the library.
142
+
143
+ ## Export an exact desk component
144
+
145
+ Select a rendered card in the **Desk** tab, then choose **Export React** or
146
+ **Export Vanilla**. The dialog previews that card independently, including its
147
+ paper, editable text, photographs, tape, pins, cords, shadows and content motion.
148
+ Choose a file to copy, or download the ZIP to get the complete example.
149
+
150
+ The ZIP contains both framework examples, `composition.json`, a static asset
151
+ resolver, selected local photographs and fonts, optional pen data, credits and
152
+ licenses, and a small Vite application. Run `npm install` then `npm run dev` in
153
+ that folder. Assets work without Google
154
+ Fonts or other external services; installing dependencies still requires an
155
+ available package source.
156
+
157
+ ```ts
158
+ import { createPaperComposition } from 'procedural-paper/composition';
159
+ import 'procedural-paper/composition.css';
160
+ import { resolveAsset } from './assets';
161
+ import data from './composition.json';
162
+ import type { PaperComposition } from 'procedural-paper/composition';
163
+
164
+ const paper = createPaperComposition({
165
+ composition: data as unknown as PaperComposition,
166
+ resolveAsset,
167
+ fit: 'contain',
168
+ });
169
+ document.querySelector('#app')!.append(paper.element);
170
+ await paper.ready;
171
+ // paper.play(), paper.pause(), paper.replay(), paper.seek(0.5)
172
+ // paper.dispose() when removing the component.
173
+ ```
174
+
175
+ React uses `PaperComposition` from `procedural-paper/composition/react`, with the
176
+ same options and an optional ref exposing playback controls. It cleans up on
177
+ unmount and reports asynchronous failures through `onError`. `onReady` signals
178
+ completion. The vanilla handle supports `update(options)` and `ready`.
179
+
180
+ Composition entry points are optional and do not add their renderers or motion
181
+ libraries to the base texture API. Install `@chenglou/pretext@0.0.9` for composition
182
+ text fitting, `brushmark@0.1.2`, and `tegaki@1.1.0` when bundling the composition
183
+ entry. Motion and pen engines load lazily at runtime; bundlers still need these
184
+ peers available to resolve their chunks. React itself remains an optional peer dependency.
185
+
186
+ The version 1 composition model records local dimensions, material parameters,
187
+ lighting, texture scale, rotation, stable content node IDs, fitting inputs,
188
+ attachment geometry, photo grading, asset metadata and resolved motion steps.
189
+ The desk and exported components share the same rendering algorithms. Texture
190
+ images are generated; desk background scans are never part of a component export.
191
+ The runtime renders at fixed dimensions and can scale the entire result to its
192
+ container with `fit: 'contain'`.
193
+
194
+ Use `setCompositionText(composition, nodeId, newText)` to create an edited copy.
195
+ Fitted text is measured again and animation references within the changed block
196
+ are moved to that block. For richer changes, edit the node tree directly and keep
197
+ its animation references valid. To change photos, update their asset records,
198
+ resolver URLs and photo specifications. Exported JSON is configuration for your
199
+ own application; it is not a general HTML import format.
200
+
201
+ The asset resolver must return a URL for every declared asset. Missing assets
202
+ reject `ready` instead of silently substituting photos. WebGL2 is required for
203
+ full visual fidelity; the runtime marks a readable material/content fallback with
204
+ `data-rendered="fallback"` and `rendered === false`. Reduced-motion preferences
205
+ finish content immediately. Dragging, peeling, selection highlights and the desk
206
+ background belong to the demo and are excluded from component exports.
@@ -0,0 +1,51 @@
1
+ # Third-party notices
2
+
3
+ Original project code is MIT © 2026 Maks Surguy. Third-party materials retain
4
+ their own licenses; the MIT license does not replace those terms.
5
+
6
+ ## Paper Shaders
7
+
8
+ The paper overlay algorithms acknowledge the paper-texture shader in
9
+ [Paper Shaders](https://github.com/paper-design/shaders), by Paper Design,
10
+ licensed under Apache-2.0. This project's implementation reworks the overlays
11
+ for periodic tiling and matching CPU/GPU rendering. Attribution appears in
12
+ `src/params.ts` and `src/cpu/overlays.ts` in the library source. The upstream
13
+ license is included in `licenses/Apache-2.0.txt` in both repository and package.
14
+
15
+ ## Demo-only materials (not included in the npm library)
16
+
17
+ - Desk scan derivatives: Poly Haven CC0. Full author, source and modification
18
+ records: `apps/demo/public/textures/README.md`.
19
+ - Sample photo derivatives: StockSnap CC0, found through Openverse. Full author
20
+ and source records: `apps/demo/public/photos/README.md`.
21
+ - Hershey stroke font data: acknowledgements and redistribution conditions in
22
+ each `apps/demo/src/desk/pen-fonts/hershey-*/LICENSE.txt`.
23
+ - EMS fonts: Sheldon B. Michaels; SVG conversion by Windell H. Oskay. Derived
24
+ from Allura (Rob Leuschke), Covered By Your Grace (Kimberly Geswein), and
25
+ Source Sans (Paul D. Hunt / Adobe). Attribution and full OFL text accompany
26
+ each font under `apps/demo/src/desk/pen-fonts/ems-*`. Tegaki generates pen
27
+ outlines and the generation script merges overlapping strokes.
28
+ - Self-hosted Google Fonts retain their upstream licenses. The npm library
29
+ does not bundle font binaries; composition consumers supply their own asset URLs.
30
+ - Brushmark, Tegaki and Pretext are demo dependencies and optional library
31
+ composition peers. The texture core has no runtime dependencies.
32
+ Their upstream licenses remain with their distributions. A demo build copies
33
+ available package license files and bundled font notices into `licenses/`.
34
+
35
+ Only the documented CC0 derivatives belong in the demo's image directories.
36
+ Private scans, reference photographs, and original source collections must not
37
+ be committed. The release check enforces the current image allowlist.
38
+
39
+ ## Desk example exports
40
+
41
+ The demo self-hosts its Google Fonts faces in `apps/demo/public/fonts`, with
42
+ per-family license files. `apps/demo/src/desk/font-assets.json` records their
43
+ upstream URLs and SHA-256 hashes. `apps/demo/scripts/vendor-fonts.mjs` refreshes
44
+ this manifest deliberately; it is not part of normal builds. Component ZIPs copy
45
+ only the selected assets and their license/credit files. The npm library does
46
+ not bundle these font binaries or demo photos.
47
+
48
+ Optional composition peers: @chenglou/pretext (text layout), brushmark (content
49
+ motion), and tegaki (pen strokes). Existing pen font license files accompany
50
+ exports containing those fonts. Generated example code is MIT; the included
51
+ media and font licenses retain their own terms.
@@ -0,0 +1,42 @@
1
+ import { type DeepPartial, type PaperParams } from './params';
2
+ import type { SheetSpec } from './sheet';
3
+ export declare const cardPresetIds: readonly ["sticky", "index", "notebook", "napkin", "scrap", "graph", "biz", "quote", "tag", "receipt", "ticket", "memo", "watercolor", "clipping", "stamp", "tracing", "tapelabel", "polaroid", "photonote", "print", "vintage", "tear", "album", "booth", "film"];
4
+ export type CardPresetId = typeof cardPresetIds[number];
5
+ /** Region coordinates are CSS pixels, relative to the sheet's top-left corner. */
6
+ export interface CardRegion {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ }
12
+ export interface CardPreset {
13
+ id: CardPresetId;
14
+ label: string;
15
+ stock: string;
16
+ width: number;
17
+ height: number;
18
+ sheet: Omit<SheetSpec, 'width' | 'height' | 'pixelRatio'>;
19
+ regions: Record<string, CardRegion>;
20
+ ruling?: 'index' | 'notebook' | 'grid';
21
+ }
22
+ /** Canonical paper styles from Paper Desk, without demo content or attachments. */
23
+ export declare const cardPresets: readonly CardPreset[];
24
+ export interface CardOptions {
25
+ preset: CardPresetId;
26
+ width?: number;
27
+ height?: number;
28
+ seed?: number;
29
+ params?: DeepPartial<PaperParams>;
30
+ /** Top-level sheet groups replace their preset counterparts. */
31
+ sheet?: Omit<SheetSpec, 'width' | 'height'>;
32
+ }
33
+ export declare function getCardPreset(id: CardPresetId): CardPreset;
34
+ /** Scale the preset's geometry and regions to explicit CSS dimensions. */
35
+ export declare function resolveCard(options: CardOptions): {
36
+ preset: CardPreset;
37
+ regions: {
38
+ [k: string]: CardRegion;
39
+ };
40
+ params: PaperParams;
41
+ sheet: SheetSpec;
42
+ };
@@ -0,0 +1,5 @@
1
+ export type RGB = [number, number, number];
2
+ /** Parse `#rgb`, `#rrggbb` (or `rgb(r,g,b)`) into 0..1 sRGB components. */
3
+ export declare function parseColor(input: string): RGB;
4
+ export declare function toHex([r, g, b]: RGB): string;
5
+ export declare function luminance([r, g, b]: RGB): number;
@@ -0,0 +1,7 @@
1
+ import type { AssetResolver, CompositionAsset } from './model';
2
+ import type { PenContext } from './pen';
3
+ export declare function loadCompositionAssets(assets: CompositionAsset[], resolve: AssetResolver, pen: boolean): Promise<{
4
+ urls: Map<string, string>;
5
+ penContext: PenContext | undefined;
6
+ dispose: () => void;
7
+ }>;
@@ -0,0 +1,43 @@
1
+ import type { Rng } from './rng';
2
+ import type { Sprite } from './props';
3
+ export type CordKind = 'jute' | 'hemp' | 'baker' | 'cotton' | 'satin' | 'grosgrain' | 'wire';
4
+ type P3 = [number, number, number];
5
+ export interface Polyline {
6
+ pts: P3[];
7
+ /** Per point: true if that point lies under the sheet. */
8
+ under: boolean[];
9
+ /** Free ends fray (twine) or get a cut (ribbon). */
10
+ freeStart: boolean;
11
+ freeEnd: boolean;
12
+ }
13
+ export interface CordSpec {
14
+ kind: CordKind;
15
+ width: number;
16
+ colors: [string, string];
17
+ lines: Polyline[];
18
+ /** Knots: x, y, radius (lying on top). */
19
+ knots: [number, number, number][];
20
+ seed: number;
21
+ }
22
+ export declare function createCordRenderer(): {
23
+ renderCord: (spec: CordSpec, part: "front" | "back", dpr: number, azimuth: number, elevation: number) => Sprite | null;
24
+ dispose(): void;
25
+ };
26
+ export declare const renderCord: ReturnType<typeof createCordRenderer>['renderCord'];
27
+ export declare const CORD_STYLES: {
28
+ kind: CordKind;
29
+ colors: [string, string];
30
+ width: number;
31
+ weight: number;
32
+ label: string;
33
+ }[];
34
+ /**
35
+ * A cord tied through a tag's hole at (cx, cy), radius hr, tag top edge at y = 0.
36
+ * Twine and wire: lark's head (two strands over the edge, bight behind the hole,
37
+ * doubled tails). Ribbon: threaded once, knotted just past the edge, two V-cut tails.
38
+ */
39
+ export declare function tagCord(rng: Rng, cx: number, cy: number, hr: number, tagW: number): {
40
+ spec: CordSpec;
41
+ label: string;
42
+ };
43
+ export {};
@@ -0,0 +1 @@
1
+ export declare const filterMarkup = " <svg width=\"0\" height=\"0\" style=\"position: absolute\" aria-hidden=\"true\">\n <filter id=\"f-ink\" x=\"-5%\" y=\"-5%\" width=\"110%\" height=\"110%\">\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.8\" numOctaves=\"2\" seed=\"4\" result=\"n\" />\n <feDisplacementMap in=\"SourceGraphic\" in2=\"n\" scale=\"1.4\" xChannelSelector=\"R\" yChannelSelector=\"G\" />\n </filter>\n <filter id=\"f-pencil\" x=\"-5%\" y=\"-5%\" width=\"110%\" height=\"110%\">\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"1.3\" numOctaves=\"2\" seed=\"9\" result=\"n\" />\n <feColorMatrix in=\"n\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.4 0 0 0 -0.55\" result=\"m\" />\n <feComposite in=\"SourceGraphic\" in2=\"m\" operator=\"in\" result=\"g\" />\n <feDisplacementMap in=\"g\" in2=\"n\" scale=\"1\" xChannelSelector=\"G\" yChannelSelector=\"B\" />\n </filter>\n <filter id=\"f-stamp\" x=\"-5%\" y=\"-10%\" width=\"110%\" height=\"120%\">\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.55\" numOctaves=\"3\" seed=\"2\" result=\"n\" />\n <feColorMatrix in=\"n\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3.2 0 0 0 -0.75\" result=\"m\" />\n <feComposite in=\"SourceGraphic\" in2=\"m\" operator=\"in\" result=\"g\" />\n <feDisplacementMap in=\"g\" in2=\"n\" scale=\"1.6\" xChannelSelector=\"R\" yChannelSelector=\"G\" />\n </filter>\n <filter id=\"f-wash\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.035\" numOctaves=\"3\" seed=\"7\" result=\"n\" />\n <feDisplacementMap in=\"SourceGraphic\" in2=\"n\" scale=\"16\" xChannelSelector=\"R\" yChannelSelector=\"G\" result=\"d\" />\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.9\" numOctaves=\"2\" seed=\"5\" result=\"g\" />\n <feColorMatrix in=\"g\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.2 0 0 0 0.25\" result=\"ga\" />\n <feComposite in=\"d\" in2=\"ga\" operator=\"in\" />\n </filter>\n <filter id=\"f-stain\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.06\" numOctaves=\"3\" seed=\"11\" result=\"n\" />\n <feDisplacementMap in=\"SourceGraphic\" in2=\"n\" scale=\"7\" xChannelSelector=\"R\" yChannelSelector=\"G\" />\n </filter>\n </svg>";
@@ -0,0 +1,58 @@
1
+ export interface Font {
2
+ family: string;
3
+ size: number;
4
+ kind: 'hand' | 'mono';
5
+ marker?: boolean;
6
+ weight?: string;
7
+ }
8
+ export interface FitBlock {
9
+ /** Plain text used for measuring (and rendering, unless `html` is given). */
10
+ text: string;
11
+ /** Rendered markup when it differs from `text` (e.g. typewriter spans). */
12
+ html?: string;
13
+ /** Size relative to the block's base size (em). */
14
+ scale?: number;
15
+ /** Extra space above the block (em of its own size). */
16
+ gap?: number;
17
+ /** Letter-spacing in em. */
18
+ spacing?: number;
19
+ cls?: string;
20
+ /** Font weight for this block (defaults to the spec's). */
21
+ weight?: string;
22
+ /** Only the first block is kept... unless this is set (titles, signatures stay). */
23
+ keep?: boolean;
24
+ }
25
+ export interface FitSpec {
26
+ font: Font;
27
+ weight?: string;
28
+ /** Box in px. */
29
+ width: number;
30
+ height: number;
31
+ /** Largest / smallest font size in px (final, after the font's own size factor). */
32
+ max: number;
33
+ min: number;
34
+ /** Line height as a multiple of the font size... */
35
+ lineHeight?: number;
36
+ /** ...or a fixed pitch in px (ruled paper). */
37
+ pitch?: number;
38
+ whiteSpace?: 'normal' | 'pre-wrap';
39
+ /** Drop trailing blocks when even `min` doesn't fit (default true). */
40
+ drop?: boolean;
41
+ /** Never wrap past this many lines (labels, one-liners). */
42
+ maxLines?: number;
43
+ /** Vertical writing mode: width/height describe the inline/block axes. */
44
+ vertical?: boolean;
45
+ blocks: FitBlock[];
46
+ }
47
+ export interface FitResult {
48
+ size: number;
49
+ kept: number;
50
+ lines: number;
51
+ }
52
+ export declare function fitText(spec: FitSpec): Promise<FitResult>;
53
+ export declare const fitSpecs: WeakMap<HTMLElement, FitSpec>;
54
+ export declare function blocksHTML(blocks: FitBlock[]): string;
55
+ /** Register `el` to be fitted before its card is shown; renders at max size for now. */
56
+ export declare function fitLater(el: HTMLElement, spec: FitSpec): void;
57
+ /** Fit every registered text block inside `root`. */
58
+ export declare function runFits(root: HTMLElement): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { PaperRenderer } from '../index';
2
+ import { type AssetResolver, type PaperComposition } from './model';
3
+ import type { CardPlan } from './scene';
4
+ export * from './model';
5
+ export type { CompositionNode } from './model';
6
+ export interface CompositionOptions {
7
+ composition: PaperComposition;
8
+ resolveAsset: AssetResolver;
9
+ autoplay?: boolean;
10
+ fit?: 'natural' | 'contain';
11
+ renderer?: PaperRenderer;
12
+ }
13
+ export interface CompositionHandle {
14
+ element: HTMLDivElement;
15
+ readonly ready: Promise<void>;
16
+ readonly rendered: boolean;
17
+ update(options: Partial<CompositionOptions>): Promise<void>;
18
+ play(): void;
19
+ pause(): void;
20
+ replay(): void;
21
+ seek(progress: number): void;
22
+ dispose(): void;
23
+ }
24
+ export declare function hydrateComposition(composition: PaperComposition, scope: string): CardPlan;
25
+ /** Use the same SVG filter definitions, with instance-local IDs. */
26
+ export declare function mountCompositionFilters(host: HTMLElement, scope: string): void;
27
+ export declare function createPaperComposition(initial: CompositionOptions): CompositionHandle;