postext-folio 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Postext contributors
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,81 @@
1
- # Temporary Holding Version
1
+ # postext-folio
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
+ **A 3D book viewer for [postext](https://www.npmjs.com/package/postext) documents and page images.**
4
+
5
+ `postext-folio` sets a book out on the screen in spreads and lets the reader turn its pages: with the ‹ › buttons, the arrow keys, a swipe, a click on a page, or by taking a page by its edge and dragging it over. Each leaf curls in [three.js](https://threejs.org/) and casts a real shadow on the pages under it. The WebGL canvas draws the book still and turning alike, so a page never changes look when it lands; the DOM pages under it (images or canvases) are the textures' sources and the pages' text alternatives. A right-bound book (Chinese, Japanese, Arabic) lies mirrored and turns leftward. Without WebGL2, or when the reader asks for reduced motion, the spreads simply change.
6
+
7
+ **Website:** [postext.dev](https://postext.dev/) · **Docs:** [A 3D book](https://postext.dev/en/docs/configuration#a-3d-book-postext-folio)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install postext postext-folio three
13
+ ```
14
+
15
+ `postext` and `three` are peer dependencies. Or import everything straight from a CDN:
16
+
17
+ ```js
18
+ import { buildDocument } from 'https://esm.sh/postext';
19
+ import { createFolioFromDocument } from 'https://esm.sh/postext-folio';
20
+ ```
21
+
22
+ ## A postext document as a book
23
+
24
+ ```ts
25
+ import { buildDocument } from 'postext';
26
+ import { createFolioFromDocument } from 'postext-folio';
27
+
28
+ // Postext measures text with the fonts the browser has loaded.
29
+ await document.fonts.load('16px "EB Garamond"');
30
+
31
+ const doc = buildDocument({ markdown }, {
32
+ page: { sizePreset: '17x24' },
33
+ bodyText: { fontFamily: 'EB Garamond' },
34
+ });
35
+
36
+ const book = createFolioFromDocument(document.getElementById('book')!, doc, {
37
+ onChange: ({ pages }) => console.log('showing pages', pages),
38
+ });
39
+
40
+ // After an edit: the same viewer, on the same page.
41
+ book.setDocument(buildDocument({ markdown: edited }, config));
42
+ ```
43
+
44
+ Pages are painted with `renderPageToCanvas` at exactly the device pixels of a page slot, so WebGL shows them texel for pixel, as sharp as the canvas preview, and only around the open spread (`window`, three spreads either side by default). Pages that fall out of that window are freed, so a book of a thousand pages costs the memory of a few. Turning to a far page paints that spread first. Pages a long turn sweeps past show as blank paper while they are in the air.
45
+
46
+ The container sets the size: the book fills it, with the ‹ › buttons and the page count in the margins. Give it a height. A resize paints the pages again at the new size.
47
+
48
+ ## Page images
49
+
50
+ `createFolio` takes any pages: image URLs, `<canvas>` or `<img>` elements, `""` for a blank page.
51
+
52
+ ```ts
53
+ import { createFolio } from 'postext-folio';
54
+
55
+ const book = createFolio(container, {
56
+ pages: ['p01.webp', 'p02.webp', 'p03.webp', { src: 'p04.webp', alt: 'Plate II' }],
57
+ firstPageRecto: true, // page 1 opens alone, on the right
58
+ binding: 'left',
59
+ });
60
+ book.goToPage(2);
61
+ ```
62
+
63
+ ## API
64
+
65
+ <table>
66
+ <thead><tr><th>Export</th><th>What it does</th></tr></thead>
67
+ <tbody>
68
+ <tr><td><code>createFolio(container, options)</code></td><td>The viewer over any pages. Options: <code>pages</code>, <code>firstPageRecto</code> (default true), <code>binding</code> (<code>'left'</code> | <code>'right'</code>), <code>at</code> (page to open on), <code>mode</code> (<code>'auto'</code> | <code>'single'</code> | <code>'double'</code>; auto shows one page at a time below 560 px), <code>paper</code> (blank-page colour), <code>animate</code>, <code>controls</code>, <code>showCount</code> (false keeps the count for screen readers only), <code>labels</code>, <code>onChange</code>, <code>onTarget</code>, <code>onLayout</code>. One page at a time the book is WebGL too: the spine runs along the page's inner edge and the leaf turns over it; a drag towards the spine turns forward, a swipe away from it goes back, a tap turns.</td></tr>
69
+ <tr><td><code>createFolioFromDocument(container, doc, options)</code></td><td>The same over a postext <code>VDTDocument</code>, painted lazily. Spreads, binding and paper come from the document. Adds <code>scale</code>, <code>window</code>, <code>singleInk</code>, <code>pageNegative</code>, <code>alt</code>, and <code>setDocument(doc)</code> on the viewer.</td></tr>
70
+ <tr><td>viewer</td><td><code>goToPage(i, { instant })</code>, <code>goToSpread(i, { instant })</code> (instant opens there without turning), <code>next()</code>, <code>prev()</code>, <code>setPages(pages, opts)</code>, <code>setLabels(labels)</code>, <code>state</code> (<code>{ spread, pages }</code>), <code>element</code>, <code>dispose()</code>.</td></tr>
71
+ <tr><td><code>PageFlipper</code>, <code>canFlip()</code></td><td>The three.js engine alone, for a host that lays out its own spread DOM (as the postext Cookbook does): a canvas over the spread, <code>go(i)</code>, <code>grab</code>/<code>drag</code>/<code>release</code> for the hand, <code>setBook</code>, <code>clear</code>, <code>dispose</code>; <code>{ persistent: true }</code> draws the book at rest too, <code>turnAt</code> sets how far a leaf let go must be to turn over.</td></tr>
72
+ <tr><td><code>spreadsOf(count, firstPageRecto)</code></td><td>The <code>[verso, recto]</code> pairs of a book.</td></tr>
73
+ <tr><td><code>FOLIO_CSS</code></td><td>The viewer's styles (injected once on first use). Restyle with <code>--postext-folio-accent</code> and <code>--postext-folio-nav-border</code> on the container.</td></tr>
74
+ </tbody>
75
+ </table>
76
+
77
+ The viewer takes the keyboard when focused: ←/→ (mirrored for a right-bound book), Page Up/Down, Home and End. Its labels default to English; pass `labels` (`region`, `prev`, `next`, `count`) to translate them.
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,10 @@
1
+ export { PageFlipper, canFlip } from "./pageFlip";
2
+ export type { PageSource, SpreadSrc } from "./pageFlip";
3
+ export { spreadsOf, spreadOfPage } from "./spreads";
4
+ export type { Spread } from "./spreads";
5
+ export { createFolio } from "./viewer";
6
+ export type { FolioLabels, FolioOptions, FolioPage, FolioState, FolioViewer } from "./viewer";
7
+ export { createFolioFromDocument, firstPageIsRecto } from "./postext";
8
+ export type { FolioDocumentOptions, FolioDocumentViewer } from "./postext";
9
+ export { FOLIO_CSS } from "./styles";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACpD,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC9F,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACtE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { PageFlipper, canFlip } from "./pageFlip";
2
+ export { spreadsOf, spreadOfPage } from "./spreads";
3
+ export { createFolio } from "./viewer";
4
+ export { createFolioFromDocument, firstPageIsRecto } from "./postext";
5
+ export { FOLIO_CSS } from "./styles";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,137 @@
1
+ /** A page: an image URL, or a canvas or a decoded image to draw; "" is a
2
+ * blank page (drawn as paper), null no page at all. */
3
+ export type PageSource = string | HTMLCanvasElement | HTMLImageElement | null;
4
+ /** A spread: [verso, recto]. */
5
+ export type SpreadSrc = [PageSource, PageSource];
6
+ /**
7
+ * Turns the pages of the light table's DOM spread. The book is a stack of
8
+ * leaves: leaf k has spread k's recto on its front and spread k + 1's verso
9
+ * on its back. `go(n)` sends every leaf before spread n to the left and
10
+ * the rest to the right; each turns on its own, following the one before
11
+ * it at a short remove, so several pages can be in the air at once. At
12
+ * rest the canvas is transparent and the <img> pages show; while leaves
13
+ * move it draws the spread, lit, with the lifted leaves' shadows.
14
+ *
15
+ * The book is modelled bound on the left. A right-bound one is the same
16
+ * book seen in a mirror (the DOM spread's `dir="rtl"`): the stage is
17
+ * mirrored, so the verso lies on the right and the leaves turn from left
18
+ * to right, while the page images, the pointer and the light are mirrored
19
+ * back to read as they are on the desk.
20
+ */
21
+ export declare class PageFlipper {
22
+ private canvas;
23
+ private spread;
24
+ private book;
25
+ private onSettle;
26
+ /** The reader's hand turned the book to another spread. */
27
+ private onTarget;
28
+ private renderer;
29
+ private scene;
30
+ private camera;
31
+ private left;
32
+ private right;
33
+ private desk;
34
+ /** Everything on the desk, in the book's own coordinates (mirrored for a
35
+ * right-bound book). */
36
+ private stage;
37
+ /** 1 for a left-bound book, −1 for a right-bound one. */
38
+ private sign;
39
+ /** Towards the light, in the book's coordinates. */
40
+ private light;
41
+ private shadowScene;
42
+ private shadowCamera;
43
+ private shadowTarget;
44
+ private casterMaterial;
45
+ private leaves;
46
+ private loader;
47
+ private textures;
48
+ private ready;
49
+ private W;
50
+ private H;
51
+ /** Leaf k lies on the left. */
52
+ private turned;
53
+ private turns;
54
+ private target;
55
+ private raf;
56
+ private starting;
57
+ private disposed;
58
+ /** The spread last reported settled (the one the DOM shows). */
59
+ private reported;
60
+ private persistent;
61
+ private turnAt;
62
+ /** A redraw of the book at rest is queued. */
63
+ private still;
64
+ constructor(canvas: HTMLCanvasElement, spread: HTMLElement, book: SpreadSrc[], at: number, onSettle: (index: number) => void,
65
+ /** The reader's hand turned the book to another spread. */
66
+ onTarget: (index: number) => void,
67
+ /** The edge the book is bound on. */
68
+ binding?: "left" | "right",
69
+ /** `persistent`: the canvas draws the book at rest too (the DOM pages
70
+ * are hidden by the host and serve only as texture sources and text
71
+ * alternatives), so a page looks the same lying still and turning. */
72
+ /** `turnAt`: how far through its turn (0 … 1) a leaf let go of turns
73
+ * over rather than falling back. Default 0.5. */
74
+ { persistent, turnAt }?: {
75
+ persistent?: boolean;
76
+ turnAt?: number;
77
+ });
78
+ /** Fits the camera so the z = 0 plane maps onto the DOM spread 1:1.
79
+ * Sizes are read unrounded and the drawing buffer covers the canvas's
80
+ * device pixels exactly, so a page lying flat is drawn texel for pixel,
81
+ * as the DOM shows it. */
82
+ private layout;
83
+ private flat;
84
+ /** The colour of the book's paper (0 … 1), for its blank pages. */
85
+ setPaper(r: number, g: number, b: number): void;
86
+ /** Starts loading the pages of these spreads as textures. */
87
+ preload(indexes: number[]): void;
88
+ private texture;
89
+ /**
90
+ * Replaces the book's pages (after a relayout, or once more pages are
91
+ * painted), keeping the open spread when `at` is left out. Leaves at
92
+ * rest take their new pages at once; textures no page uses any more are
93
+ * freed. A book of another length starts afresh at `at`.
94
+ */
95
+ setBook(book: SpreadSrc[], at?: number): void;
96
+ private assign;
97
+ private leaf;
98
+ /** Turns the pages until spread `index` lies open. */
99
+ go(index: number): void;
100
+ /** The spread open when no leaf is in the air. */
101
+ private settledAt;
102
+ /** A new turn by the virtual hand, from the bottom corner (or the
103
+ * point clicked) over to its mirror image. */
104
+ private lift;
105
+ private frame;
106
+ private settle;
107
+ /** The pointer on the z = 0 plane, in the book's coordinates (x
108
+ * mirrored for a right-bound book). */
109
+ private toWorld;
110
+ /** Takes hold of the page under the pointer; false when there is none
111
+ * to turn (or pages are already in the air). */
112
+ grab(event: {
113
+ clientX: number;
114
+ clientY: number;
115
+ }): boolean;
116
+ /** Carries the held point to the pointer. */
117
+ drag(event: {
118
+ clientX: number;
119
+ clientY: number;
120
+ }): void;
121
+ /** Lets go. A click turns the page over from where it was taken; a drag
122
+ * carries on over if the page is past halfway or thrown that way, and
123
+ * falls back otherwise. */
124
+ release(click?: boolean): void;
125
+ private draw;
126
+ /** Back to a transparent canvas (the DOM spread shows), unless leaves
127
+ * are moving again. A persistent book is drawn at rest instead. */
128
+ clear(): void;
129
+ /** Draws the book at rest again (a page painted, the box resized) on the
130
+ * next frame; leaves in the air are drawn by their own frames. Only for
131
+ * a persistent book: the other one shows the DOM at rest. */
132
+ redraw(): void;
133
+ dispose(): void;
134
+ }
135
+ /** WebGL is there and the reader has not asked for less motion. */
136
+ export declare function canFlip(): boolean;
137
+ //# sourceMappingURL=pageFlip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageFlip.d.ts","sourceRoot":"","sources":["../src/pageFlip.ts"],"names":[],"mappings":"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"}