@zinejs/core 0.1.0 → 0.3.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 +7 -2
- package/dist/cssRenderer-OzRRoEa4.js +127 -0
- package/dist/cssRenderer-OzRRoEa4.js.map +1 -0
- package/dist/engine/spread.d.ts +10 -5
- package/dist/geometry/curls/cone.d.ts +17 -0
- package/dist/geometry/curls/flick.d.ts +20 -0
- package/dist/geometry/curls/index.d.ts +5 -0
- package/dist/geometry/curls/leaf.d.ts +19 -0
- package/dist/geometry/curls/mesh.d.ts +20 -0
- package/dist/geometry/curls/roll.d.ts +8 -0
- package/dist/geometry/curls/simple.d.ts +7 -0
- package/dist/geometry/curls/types.d.ts +25 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +476 -315
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +47 -60
- package/dist/index.umd.cjs.map +1 -1
- package/dist/renderer/types.d.ts +22 -0
- package/dist/renderer/webglRenderer.d.ts +6 -5
- package/dist/source/compose.d.ts +16 -0
- package/dist/source/types.d.ts +6 -0
- package/dist/types-D-rEO00p.js +12 -0
- package/dist/types-D-rEO00p.js.map +1 -0
- package/dist/webglRenderer-Bg6VF4CW.js +558 -0
- package/dist/webglRenderer-Bg6VF4CW.js.map +1 -0
- package/dist/zine.d.ts +12 -3
- package/options.schema.json +27 -7
- package/package.json +1 -1
- package/dist/cssRenderer-BACNXO8M.js +0 -68
- package/dist/cssRenderer-BACNXO8M.js.map +0 -1
- package/dist/flipProgressToPose-DZWSuKoE.js +0 -13
- package/dist/flipProgressToPose-DZWSuKoE.js.map +0 -1
- package/dist/webglRenderer-D-1UyC-B.js +0 -306
- package/dist/webglRenderer-D-1UyC-B.js.map +0 -1
package/README.md
CHANGED
|
@@ -46,9 +46,13 @@ new Zine(document.getElementById('book'), {
|
|
|
46
46
|
new Zine(container, {
|
|
47
47
|
source, // ImageSource | PdfSource (required)
|
|
48
48
|
direction: 'ltr', // 'ltr' | 'rtl'
|
|
49
|
-
|
|
49
|
+
spreadMode: 'cover', // 'double' | 'single' | 'cover' (lone first page) | 'book' (lone first & last)
|
|
50
|
+
curl: 'cone', // WebGL2 only: 'cone' | 'roll' | 'leaf' | 'flick' | 'simple'
|
|
51
|
+
frontCover: 'front.jpg', // optional image prepended as a lone front cover
|
|
52
|
+
backCover: 'back.jpg', // optional image appended as a lone back cover
|
|
53
|
+
pages: { 4: 'ad.jpg' }, // optional: replace source pages with images (0-based; -1 = last)
|
|
50
54
|
startPage: 0,
|
|
51
|
-
flipDuration: 800, // ms
|
|
55
|
+
flipDuration: 800, // ms (spread timing; a lone page stretches it and eases out)
|
|
52
56
|
singlePageThreshold: 640, // px: below this, show one page at a time
|
|
53
57
|
clickToFlip: 'edge', // 'edge' | 'half' | 'off'
|
|
54
58
|
zoom: { enabled: true, max: 4, wheel: true, doubleClick: [1, 2, 4] },
|
|
@@ -56,6 +60,7 @@ new Zine(container, {
|
|
|
56
60
|
});
|
|
57
61
|
```
|
|
58
62
|
|
|
63
|
+
See the [root README](../../README.md#curl-models) for what each curl model does.
|
|
59
64
|
## Methods & events
|
|
60
65
|
|
|
61
66
|
```js
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//#region src/geometry/flipProgressToPose.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
let t = e < 0 ? 0 : e > 1 ? 1 : e, n = Math.sin(t * Math.PI);
|
|
4
|
+
return {
|
|
5
|
+
angle: t * Math.PI,
|
|
6
|
+
curl: n,
|
|
7
|
+
shadowAlpha: n
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/renderer/cssRenderer.ts
|
|
12
|
+
var t = 180 / Math.PI;
|
|
13
|
+
function n(e) {
|
|
14
|
+
return e.left === null == (e.right === null) ? 0 : e.right === null ? 1 : -1;
|
|
15
|
+
}
|
|
16
|
+
function r(e) {
|
|
17
|
+
let t = e === "forward" ? "to right" : "to left";
|
|
18
|
+
return [
|
|
19
|
+
`linear-gradient(${t}, rgba(0,0,0,0.6), rgba(0,0,0,0.15) 40%, rgba(0,0,0,0) 60%)`,
|
|
20
|
+
`linear-gradient(${t}, transparent 40%, rgba(255,255,255,0.32) 66%, transparent 90%)`,
|
|
21
|
+
`linear-gradient(${t}, transparent 66%, rgba(0,0,0,0.35))`
|
|
22
|
+
].join(",");
|
|
23
|
+
}
|
|
24
|
+
var i = class {
|
|
25
|
+
#e = null;
|
|
26
|
+
#t;
|
|
27
|
+
#n;
|
|
28
|
+
#r;
|
|
29
|
+
#i;
|
|
30
|
+
#a;
|
|
31
|
+
#o;
|
|
32
|
+
#s;
|
|
33
|
+
#c;
|
|
34
|
+
#l;
|
|
35
|
+
#u = 0;
|
|
36
|
+
#d = 0;
|
|
37
|
+
#f = 0;
|
|
38
|
+
#p = 0;
|
|
39
|
+
#m = !1;
|
|
40
|
+
#h = 0;
|
|
41
|
+
mount(e) {
|
|
42
|
+
let t = e.ownerDocument;
|
|
43
|
+
return this.#e = e, this.#t = t.createElement("div"), this.#t.className = "zine-clip", this.#t.style.cssText = "position:relative;width:100%;height:100%;overflow:hidden;", this.#n = t.createElement("div"), this.#n.className = "zine-viewport", this.#n.style.cssText = "position:absolute;inset:0;transform-origin:0 0;", this.#r = t.createElement("div"), this.#r.className = "zine-book", this.#r.style.cssText = "position:absolute;inset:0;perspective:2000px;transform-style:preserve-3d;", this.#i = this.#x(t, "zine-page-left", "left:0;"), this.#a = this.#x(t, "zine-page-right", "right:0;"), this.#o = t.createElement("div"), this.#o.className = "zine-leaf", this.#o.style.cssText = "position:absolute;top:0;width:50%;height:100%;transform-style:preserve-3d;display:none;", this.#s = t.createElement("canvas"), this.#s.className = "zine-leaf-front", this.#s.style.cssText = "position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;", this.#c = t.createElement("canvas"), this.#c.className = "zine-leaf-back", this.#c.style.cssText = "position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;transform:rotateY(180deg);", this.#l = t.createElement("div"), this.#l.className = "zine-leaf-shadow", this.#l.style.cssText = "position:absolute;inset:0;opacity:0;pointer-events:none;", this.#l.style.background = r("forward"), this.#o.append(this.#s, this.#c, this.#l), this.#r.append(this.#i, this.#a, this.#o), this.#n.append(this.#r), this.#t.append(this.#n), e.append(this.#t), Promise.resolve();
|
|
44
|
+
}
|
|
45
|
+
destroy() {
|
|
46
|
+
this.#t?.remove(), this.#e = null;
|
|
47
|
+
}
|
|
48
|
+
renderSpread(e, t, r) {
|
|
49
|
+
r?.fill ? (this.#i.style.width = "100%", this.#a.style.display = "none", this.#S(this.#i, t.right ?? t.left)) : (this.#i.style.width = "50%", this.#a.style.display = "", this.#S(this.#i, t.left), this.#S(this.#a, t.right)), this.#m = r?.fill ?? !1, this.#h = this.#m ? 0 : n(t), this.#_(t), this.#v(), this.#g(this.#h), this.#o.style.display = "none", this.#o.style.transform = "", this.#l.style.opacity = "0";
|
|
50
|
+
}
|
|
51
|
+
beginFlip(e, t, i, a) {
|
|
52
|
+
a?.fill ? (this.#i.style.width = "100%", this.#a.style.display = "none", this.#o.style.width = "100%", this.#o.style.left = "0", this.#o.style.transformOrigin = i === "forward" ? "left center" : "right center", this.#S(this.#i, t.right ?? t.left), this.#S(this.#s, e.right ?? e.left), this.#S(this.#c, t.right ?? t.left)) : (this.#i.style.width = "50%", this.#a.style.display = "", this.#o.style.width = "50%", i === "forward" ? (this.#S(this.#i, e.left), this.#S(this.#a, t.right), this.#S(this.#s, e.right), this.#S(this.#c, t.left), this.#o.style.left = "50%", this.#o.style.transformOrigin = "left center") : (this.#S(this.#a, e.right), this.#S(this.#i, t.left), this.#S(this.#s, e.left), this.#S(this.#c, t.right), this.#o.style.left = "0", this.#o.style.transformOrigin = "right center")), this.#u = a?.fill ? 0 : n(e), this.#d = a?.fill ? 0 : n(t), this.#m = a?.fill ?? !1, this.#_(t), this.#v(), this.#g(this.#u), this.#o.style.display = "block", this.#o.style.transform = "rotateY(0deg)", this.#l.style.background = r(i), this.#l.style.opacity = "0";
|
|
53
|
+
}
|
|
54
|
+
setFlipProgress(n, r) {
|
|
55
|
+
let i = e(n), a = i.angle * t;
|
|
56
|
+
this.#g(this.#u + (this.#d - this.#u) * n), this.#o.style.display = "block", this.#o.style.transform = `rotateY(${r === "forward" ? -a : a}deg)`, this.#l.style.opacity = String(i.shadowAlpha);
|
|
57
|
+
}
|
|
58
|
+
#g(e) {
|
|
59
|
+
let t = e * (this.#p / 4);
|
|
60
|
+
this.#r.style.transform = t ? `translateX(${t}px)` : "";
|
|
61
|
+
}
|
|
62
|
+
#_(e) {
|
|
63
|
+
let t = e.left ?? e.right;
|
|
64
|
+
t && t.height && (this.#f = t.width / t.height);
|
|
65
|
+
}
|
|
66
|
+
#v() {
|
|
67
|
+
let e = this.#y();
|
|
68
|
+
this.#p = e.width;
|
|
69
|
+
let t = this.#r.style;
|
|
70
|
+
t.inset = "auto", t.left = `${e.x}px`, t.top = `${e.y}px`, t.width = `${e.width}px`, t.height = `${e.height}px`;
|
|
71
|
+
}
|
|
72
|
+
#y() {
|
|
73
|
+
let e = this.#e?.clientWidth ?? 0, t = this.#e?.clientHeight ?? 0;
|
|
74
|
+
if (this.#f <= 0 || e <= 0 || t <= 0) return {
|
|
75
|
+
x: 0,
|
|
76
|
+
y: 0,
|
|
77
|
+
width: e,
|
|
78
|
+
height: t
|
|
79
|
+
};
|
|
80
|
+
let n = (this.#m ? 1 : 2) * this.#f, r = e, i = t;
|
|
81
|
+
return n > e / t ? i = e / n : r = t * n, {
|
|
82
|
+
x: (e - r) / 2,
|
|
83
|
+
y: (t - i) / 2,
|
|
84
|
+
width: r,
|
|
85
|
+
height: i
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
#b() {
|
|
89
|
+
let e = this.#y();
|
|
90
|
+
return this.#h === 0 ? e : {
|
|
91
|
+
x: e.x + e.width / 4,
|
|
92
|
+
y: e.y,
|
|
93
|
+
width: e.width / 2,
|
|
94
|
+
height: e.height
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
setViewTransform(e, t, n) {
|
|
98
|
+
this.#n.style.transform = `translate(${t}px, ${n}px) scale(${e})`;
|
|
99
|
+
}
|
|
100
|
+
measure() {
|
|
101
|
+
let e = this.#e, t = e?.clientWidth ?? 0, n = e?.clientHeight ?? 0, r = this.#f > 0, i = r ? this.#y() : void 0, a = r ? this.#b() : void 0;
|
|
102
|
+
return {
|
|
103
|
+
containerWidth: t,
|
|
104
|
+
containerHeight: n,
|
|
105
|
+
pageWidth: t / 2,
|
|
106
|
+
pageHeight: n,
|
|
107
|
+
book: i,
|
|
108
|
+
content: a
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
#x(e, t, n) {
|
|
112
|
+
let r = e.createElement("canvas");
|
|
113
|
+
return r.className = t, r.style.cssText = `position:absolute;top:0;width:50%;height:100%;${n}`, r;
|
|
114
|
+
}
|
|
115
|
+
#S(e, t) {
|
|
116
|
+
let n = e.getContext("2d");
|
|
117
|
+
if (t === null) {
|
|
118
|
+
n?.clearRect(0, 0, e.width, e.height);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
e.width = t.width, e.height = t.height, n?.drawImage(t, 0, 0);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
125
|
+
export { i as CssRenderer };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=cssRenderer-OzRRoEa4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cssRenderer-OzRRoEa4.js","names":["#container","#clip","#viewport","#book","#pageLeft","#makeCanvas","#pageRight","#leaf","#leafFront","#leafBack","#leafShadow","#paint","#fill","#contentShift","#trackAspect","#layoutBook","#applyShift","#fromShiftUnit","#toShiftUnit","#bookW","#pageAspect","#bookBox","#contentBox"],"sources":["../src/geometry/flipProgressToPose.ts","../src/renderer/cssRenderer.ts"],"sourcesContent":["export interface FlipPose {\n /** Turning-page rotation about the spine, radians: 0 (flat/open) → π (fully turned). */\n angle: number;\n /** Mesh cylinder-wrap curl, 0..1 — flat at the ends, peaks edge-on at t=0.5. CSS renderer ignores it. */\n curl: number;\n /** Fold-shadow intensity, 0..1 — peaks edge-on at t=0.5. */\n shadowAlpha: number;\n}\n\n/**\n * Map a 0→1 flip progress to a renderer-neutral fold pose.\n *\n * Linear in `t` on purpose — easing belongs to the animator that produces `t`,\n * so seeking to a fixed progress (e.g. the 0/.25/.5/.75/1 visual-regression stops)\n * is deterministic. The CSS renderer consumes `angle` as a spine `rotateY`; the\n * WebGL2 renderer additionally bends its mesh by `curl`.\n */\nexport function flipProgressToPose(t: number): FlipPose {\n const clamped = t < 0 ? 0 : t > 1 ? 1 : t;\n const arc = Math.sin(clamped * Math.PI);\n return {\n angle: clamped * Math.PI,\n curl: arc,\n shadowAlpha: arc,\n };\n}\n","import { flipProgressToPose } from '../geometry/flipProgressToPose';\nimport type { Spread } from '../engine/spread';\nimport type {\n FlipDirection,\n LayoutMetrics,\n PageContent,\n Renderer,\n RenderOptions,\n SpreadContent,\n} from './types';\n\nconst RAD_TO_DEG = 180 / Math.PI;\n\n/** How far to shift a spread to centre a lone page, in units of a quarter container width:\n * -1 = only a right page (cover), +1 = only a left page (back), 0 = a full spread. */\nfunction shiftUnit(content: SpreadContent): number {\n const lone = (content.left === null) !== (content.right === null);\n return lone ? (content.right !== null ? -1 : 1) : 0;\n}\n\n/**\n * Layered gradients painted over the turning leaf to fake paper depth on the flat\n * fold: a crease shadow at the spine, a soft curve sheen, and a free-edge shade.\n * The spine is the leaf's pivot edge — left for a forward flip, right for backward.\n */\nfunction foldOverlay(direction: FlipDirection): string {\n const dir = direction === 'forward' ? 'to right' : 'to left';\n return [\n `linear-gradient(${dir}, rgba(0,0,0,0.6), rgba(0,0,0,0.15) 40%, rgba(0,0,0,0) 60%)`,\n `linear-gradient(${dir}, transparent 40%, rgba(255,255,255,0.32) 66%, transparent 90%)`,\n `linear-gradient(${dir}, transparent 66%, rgba(0,0,0,0.35))`,\n ].join(',');\n}\n\n/**\n * Baseline renderer: a 2-panel CSS-3D fold. Runs on every browser, no GPU. Pages\n * are <canvas> elements; the turning leaf (front + back faces, backface-hidden)\n * rotates about the spine driven by flipProgressToPose(), with a gradient shadow\n * overlay for depth. `beginFlip` stages the surfaces; `setFlipProgress` animates.\n */\nexport class CssRenderer implements Renderer {\n #container: HTMLElement | null = null;\n #clip!: HTMLDivElement;\n #viewport!: HTMLDivElement;\n #book!: HTMLDivElement;\n #pageLeft!: HTMLCanvasElement;\n #pageRight!: HTMLCanvasElement;\n #leaf!: HTMLDivElement;\n #leafFront!: HTMLCanvasElement;\n #leafBack!: HTMLCanvasElement;\n #leafShadow!: HTMLDivElement;\n // Lone-page centering, interpolated across a flip (see the WebGL renderer).\n #fromShiftUnit = 0;\n #toShiftUnit = 0;\n // Book aspect fit: the spread is letterboxed to the page aspect (see the WebGL renderer).\n #pageAspect = 0;\n #bookW = 0;\n #fill = false;\n #contentShift = 0; // shiftUnit of the current static spread (nonzero = lone page, centered)\n\n mount(container: HTMLElement): Promise<void> {\n const doc = container.ownerDocument;\n this.#container = container;\n\n // Fixed-size clip box; NOT transformed, so zoom can't scale the crop region.\n this.#clip = doc.createElement('div');\n this.#clip.className = 'zine-clip';\n this.#clip.style.cssText = 'position:relative;width:100%;height:100%;overflow:hidden;';\n\n // Transform target for zoom/pan; lives inside the clip.\n this.#viewport = doc.createElement('div');\n this.#viewport.className = 'zine-viewport';\n this.#viewport.style.cssText = 'position:absolute;inset:0;transform-origin:0 0;';\n\n this.#book = doc.createElement('div');\n this.#book.className = 'zine-book';\n this.#book.style.cssText =\n 'position:absolute;inset:0;perspective:2000px;transform-style:preserve-3d;';\n\n this.#pageLeft = this.#makeCanvas(doc, 'zine-page-left', 'left:0;');\n this.#pageRight = this.#makeCanvas(doc, 'zine-page-right', 'right:0;');\n\n this.#leaf = doc.createElement('div');\n this.#leaf.className = 'zine-leaf';\n this.#leaf.style.cssText =\n 'position:absolute;top:0;width:50%;height:100%;transform-style:preserve-3d;display:none;';\n\n this.#leafFront = doc.createElement('canvas');\n this.#leafFront.className = 'zine-leaf-front';\n this.#leafFront.style.cssText =\n 'position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;';\n\n this.#leafBack = doc.createElement('canvas');\n this.#leafBack.className = 'zine-leaf-back';\n this.#leafBack.style.cssText =\n 'position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;transform:rotateY(180deg);';\n\n this.#leafShadow = doc.createElement('div');\n this.#leafShadow.className = 'zine-leaf-shadow';\n this.#leafShadow.style.cssText = 'position:absolute;inset:0;opacity:0;pointer-events:none;';\n this.#leafShadow.style.background = foldOverlay('forward'); // default; beginFlip sets per direction\n\n this.#leaf.append(this.#leafFront, this.#leafBack, this.#leafShadow);\n this.#book.append(this.#pageLeft, this.#pageRight, this.#leaf);\n this.#viewport.append(this.#book);\n this.#clip.append(this.#viewport);\n container.append(this.#clip);\n\n return Promise.resolve();\n }\n\n destroy(): void {\n this.#clip?.remove();\n this.#container = null;\n }\n\n renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void {\n if (options?.fill) {\n // Single-page mode: the lone page fills the container; hide the other panel.\n this.#pageLeft.style.width = '100%';\n this.#pageRight.style.display = 'none';\n this.#paint(this.#pageLeft, content.right ?? content.left);\n } else {\n this.#pageLeft.style.width = '50%';\n this.#pageRight.style.display = '';\n this.#paint(this.#pageLeft, content.left);\n this.#paint(this.#pageRight, content.right);\n }\n this.#fill = options?.fill ?? false;\n this.#contentShift = this.#fill ? 0 : shiftUnit(content);\n this.#trackAspect(content);\n this.#layoutBook();\n this.#applyShift(this.#contentShift);\n // A fresh spread cancels any in-progress flip.\n this.#leaf.style.display = 'none';\n this.#leaf.style.transform = '';\n this.#leafShadow.style.opacity = '0';\n }\n\n beginFlip(\n from: SpreadContent,\n to: SpreadContent,\n direction: FlipDirection,\n options?: RenderOptions,\n ): void {\n if (options?.fill) {\n // Single-page: a full-width leaf turns about one edge, revealing `to` underneath.\n this.#pageLeft.style.width = '100%';\n this.#pageRight.style.display = 'none';\n this.#leaf.style.width = '100%';\n this.#leaf.style.left = '0';\n this.#leaf.style.transformOrigin = direction === 'forward' ? 'left center' : 'right center';\n this.#paint(this.#pageLeft, to.right ?? to.left);\n this.#paint(this.#leafFront, from.right ?? from.left);\n this.#paint(this.#leafBack, to.right ?? to.left);\n } else {\n this.#pageLeft.style.width = '50%';\n this.#pageRight.style.display = '';\n this.#leaf.style.width = '50%';\n if (direction === 'forward') {\n // Right page lifts and swings left about the spine. Left stays; right reveals `to`.\n this.#paint(this.#pageLeft, from.left);\n this.#paint(this.#pageRight, to.right);\n this.#paint(this.#leafFront, from.right);\n this.#paint(this.#leafBack, to.left);\n this.#leaf.style.left = '50%';\n this.#leaf.style.transformOrigin = 'left center';\n } else {\n // Left page swings right about its right edge. Right stays; left reveals `to`.\n this.#paint(this.#pageRight, from.right);\n this.#paint(this.#pageLeft, to.left);\n this.#paint(this.#leafFront, from.left);\n this.#paint(this.#leafBack, to.right);\n this.#leaf.style.left = '0';\n this.#leaf.style.transformOrigin = 'right center';\n }\n }\n this.#fromShiftUnit = options?.fill ? 0 : shiftUnit(from);\n this.#toShiftUnit = options?.fill ? 0 : shiftUnit(to);\n this.#fill = options?.fill ?? false;\n this.#trackAspect(to);\n this.#layoutBook();\n this.#applyShift(this.#fromShiftUnit);\n this.#leaf.style.display = 'block';\n this.#leaf.style.transform = 'rotateY(0deg)';\n this.#leafShadow.style.background = foldOverlay(direction);\n this.#leafShadow.style.opacity = '0';\n }\n\n setFlipProgress(t: number, direction: FlipDirection): void {\n const pose = flipProgressToPose(t);\n const deg = pose.angle * RAD_TO_DEG;\n this.#applyShift(this.#fromShiftUnit + (this.#toShiftUnit - this.#fromShiftUnit) * t);\n this.#leaf.style.display = 'block';\n this.#leaf.style.transform = `rotateY(${direction === 'forward' ? -deg : deg}deg)`;\n this.#leafShadow.style.opacity = String(pose.shadowAlpha);\n }\n\n #applyShift(unit: number): void {\n const px = unit * (this.#bookW / 4);\n this.#book.style.transform = px ? `translateX(${px}px)` : '';\n }\n\n /** Remember the page aspect (width/height) so the book can be letterboxed to it. */\n #trackAspect(content: SpreadContent): void {\n const p = content.left ?? content.right;\n if (p && p.height) this.#pageAspect = p.width / p.height;\n }\n\n /** Letterbox the 2-page book to the page aspect, centered in the container, so pages\n * fill their halves without stretching; the surround is the container background. */\n #layoutBook(): void {\n const b = this.#bookBox();\n this.#bookW = b.width;\n const s = this.#book.style;\n s.inset = 'auto';\n s.left = `${b.x}px`;\n s.top = `${b.y}px`;\n s.width = `${b.width}px`;\n s.height = `${b.height}px`;\n }\n\n /** The fitted book rect in container px (letterbox aware) — a stable 2-page area. */\n #bookBox(): { x: number; y: number; width: number; height: number } {\n const cw = this.#container?.clientWidth ?? 0;\n const ch = this.#container?.clientHeight ?? 0;\n if (this.#pageAspect <= 0 || cw <= 0 || ch <= 0) return { x: 0, y: 0, width: cw, height: ch };\n const ba = (this.#fill ? 1 : 2) * this.#pageAspect;\n let bw = cw;\n let bh = ch;\n if (ba > cw / ch) bh = cw / ba;\n else bw = ch * ba;\n return { x: (cw - bw) / 2, y: (ch - bh) / 2, width: bw, height: bh };\n }\n\n /** Where the current spread is actually painted: the book, or its centered half for a lone\n * page (mirrors #applyShift). This is what the engine hit-tests against. */\n #contentBox(): { x: number; y: number; width: number; height: number } {\n const b = this.#bookBox();\n return this.#contentShift !== 0 ? { x: b.x + b.width / 4, y: b.y, width: b.width / 2, height: b.height } : b;\n }\n\n setViewTransform(scale: number, x: number, y: number): void {\n this.#viewport.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;\n }\n\n measure(): LayoutMetrics {\n const el = this.#container;\n const w = el?.clientWidth ?? 0;\n const h = el?.clientHeight ?? 0;\n const known = this.#pageAspect > 0;\n const book = known ? this.#bookBox() : undefined;\n const content = known ? this.#contentBox() : undefined;\n return { containerWidth: w, containerHeight: h, pageWidth: w / 2, pageHeight: h, book, content };\n }\n\n #makeCanvas(doc: Document, className: string, extra: string): HTMLCanvasElement {\n const c = doc.createElement('canvas');\n c.className = className;\n c.style.cssText = `position:absolute;top:0;width:50%;height:100%;${extra}`;\n return c;\n }\n\n #paint(canvas: HTMLCanvasElement, content: PageContent | null): void {\n const ctx = canvas.getContext('2d');\n if (content === null) {\n ctx?.clearRect(0, 0, canvas.width, canvas.height);\n return;\n }\n canvas.width = content.width;\n canvas.height = content.height;\n ctx?.drawImage(content, 0, 0);\n }\n}\n"],"mappings":"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"}
|
package/dist/engine/spread.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export type Direction = 'ltr' | 'rtl';
|
|
2
|
+
/**
|
|
3
|
+
* How pages group into spreads:
|
|
4
|
+
* - `double`: paired from the start — `[0|1] [2|3] …`
|
|
5
|
+
* - `single`: one page per spread — `[0] [1] …`
|
|
6
|
+
* - `cover`: first page alone, then paired — `[·|0] [1|2] …`
|
|
7
|
+
* - `book`: first AND last alone (front + back cover) — `[·|0] [1|2] … [N-1|·]`
|
|
8
|
+
*/
|
|
9
|
+
export type SpreadMode = 'double' | 'single' | 'cover' | 'book';
|
|
2
10
|
export interface Spread {
|
|
3
11
|
/** Page index shown on the left, or null for a blank side. */
|
|
4
12
|
left: number | null;
|
|
@@ -7,17 +15,14 @@ export interface Spread {
|
|
|
7
15
|
}
|
|
8
16
|
export interface SpreadOptions {
|
|
9
17
|
direction?: Direction;
|
|
10
|
-
|
|
11
|
-
cover?: boolean;
|
|
12
|
-
/** One page per spread (narrow/portrait viewports). Takes precedence over `cover`. */
|
|
13
|
-
singlePage?: boolean;
|
|
18
|
+
mode?: SpreadMode;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Map a flat page list (indices 0..pageCount-1) into display spreads.
|
|
17
22
|
*
|
|
18
23
|
* Reading order is always preserved; `direction` only mirrors each spread's
|
|
19
24
|
* left/right positions, so RTL is just an LTR layout with the sides swapped.
|
|
20
|
-
* Out-of-range slots are `null` (a blank half on odd counts /
|
|
25
|
+
* Out-of-range slots are `null` (a blank half on odd counts / a lone cover).
|
|
21
26
|
*/
|
|
22
27
|
export declare function buildSpreads(pageCount: number, options?: SpreadOptions): Spread[];
|
|
23
28
|
/** Whether a container this wide should fall back to one page per spread. */
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type PageMesh } from './mesh';
|
|
2
|
+
import type { CurlAnchor } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Natural conical page curl — the Xerox PARC / iBooks model.
|
|
5
|
+
*
|
|
6
|
+
* The leaf wraps an imaginary right circular cone whose apex rides the spine and
|
|
7
|
+
* whose opening angle θ breathes over the turn (Hong, Card & Chen, 2004). A corner
|
|
8
|
+
* grab yields a diagonal cone; a mid-edge grab blends toward a cylinder so the
|
|
9
|
+
* free edge lifts as one. The curled sheet then rotates about the spine and settles
|
|
10
|
+
* flat on the far side. Arc length from the apex is preserved, so the paper never
|
|
11
|
+
* stretches — the motion reads as stiff, premium stock rather than cloth.
|
|
12
|
+
*
|
|
13
|
+
* On a full-width lone page (`anchor.fill`) the hinge is the container edge with no
|
|
14
|
+
* facing half to land on: the cone softens, the flop is delayed so the peel stays
|
|
15
|
+
* over the page longer, and the renderer dissolves the sheet as it finishes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function deformCone(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type PageMesh } from './mesh';
|
|
2
|
+
import type { CurlAnchor } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Inertial follow-through page flick.
|
|
5
|
+
*
|
|
6
|
+
* The leaf is a cantilever clamped at the spine and driven through a rotation
|
|
7
|
+
* ρ(t) = πt. Paper inertia (the Euler force, ∝ −ρ̈) bends the sheet opposite the
|
|
8
|
+
* angular acceleration with a first-mode-like profile — zero curvature at the
|
|
9
|
+
* clamped spine, maximal at the free edge. Under an eased turn the acceleration
|
|
10
|
+
* changes sign at mid-flight, so the free edge trails while the turn winds up,
|
|
11
|
+
* swings through vertical, then overtakes and settles as the turn slows: the
|
|
12
|
+
* follow-through/overlapping-action principle, grounded in base-rotated
|
|
13
|
+
* cantilever dynamics. Tangent angles are integrated along the width, so the
|
|
14
|
+
* sheet never stretches, and the curvature direction reversing over the turn is
|
|
15
|
+
* what none of the wrap-style curls do.
|
|
16
|
+
*
|
|
17
|
+
* Rows far from the grabbed corner lag slightly more than the driven row, so a
|
|
18
|
+
* corner grab pulls a soft living diagonal across the sheet.
|
|
19
|
+
*/
|
|
20
|
+
export declare function deformFlick(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CurlModel, CurlType } from './types';
|
|
2
|
+
export { createPageMesh, computeNormals, type PageMesh } from './mesh';
|
|
3
|
+
export { CURL_TYPES, DEFAULT_CURL } from './types';
|
|
4
|
+
export type { CurlAnchor, CurlDeform, CurlModel, CurlType } from './types';
|
|
5
|
+
export declare const CURLS: Record<CurlType, CurlModel>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PageMesh } from './mesh';
|
|
2
|
+
import type { CurlAnchor } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Traveling smooth-curvature leaf curl.
|
|
5
|
+
*
|
|
6
|
+
* Inspired by rolling-cylinder page curls and modern developable-surface book
|
|
7
|
+
* simulations, but with a compact raised-cosine curvature wave instead of a
|
|
8
|
+
* constant-radius cylinder. The sheet has three regions:
|
|
9
|
+
*
|
|
10
|
+
* 1. untouched paper before the wave,
|
|
11
|
+
* 2. an isometric bend whose curvature smoothly rises and falls,
|
|
12
|
+
* 3. a flat, reflected flap after the wave.
|
|
13
|
+
*
|
|
14
|
+
* The wave travels from the free edge to just beyond the spine while its width grows
|
|
15
|
+
* and collapses. Integrating a unit tangent preserves material length exactly; straight
|
|
16
|
+
* ruling lines preserve distance along the curl axis. Curvature reaches zero at both
|
|
17
|
+
* seams, eliminating the hard crease shared by circular/fold models.
|
|
18
|
+
*/
|
|
19
|
+
export declare function deformLeaf(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared grid mesh + normal utilities for the page-curl models.
|
|
3
|
+
*
|
|
4
|
+
* A curl model fills `positions`/`normals` in place each frame; UVs are static grid
|
|
5
|
+
* coordinates (no texture swimming). All models bend the same (cols+1)×(rows+1) grid.
|
|
6
|
+
*/
|
|
7
|
+
export interface PageMesh {
|
|
8
|
+
readonly cols: number;
|
|
9
|
+
readonly rows: number;
|
|
10
|
+
/** ((cols+1)*(rows+1))*2 — static grid UVs (u across width, v down height). */
|
|
11
|
+
readonly uvs: Float32Array;
|
|
12
|
+
/** *3 — deformed positions, updated by the curl model. */
|
|
13
|
+
readonly positions: Float32Array;
|
|
14
|
+
/** *3 — per-vertex normals, updated by the curl model. */
|
|
15
|
+
readonly normals: Float32Array;
|
|
16
|
+
}
|
|
17
|
+
/** Allocate a (cols+1)×(rows+1) grid mesh with static UVs (positions/normals filled by a curl). */
|
|
18
|
+
export declare function createPageMesh(cols: number, rows: number): PageMesh;
|
|
19
|
+
/** Per-vertex normals from central differences of the deformed grid (front = +z when flat). */
|
|
20
|
+
export declare function computeNormals(mesh: PageMesh): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type PageMesh } from './mesh';
|
|
2
|
+
/**
|
|
3
|
+
* Roll-and-flop. The leaf wraps a cylinder whose wrap angle grows 0→π over the first
|
|
4
|
+
* half while its radius (= W/theta) shrinks, so the sheet rolls up off the surface in
|
|
5
|
+
* place; over the second half it unwraps π→0 while rigidly rotating around the spine
|
|
6
|
+
* 0→-π down onto the far side. Arc length across the width is exactly preserved.
|
|
7
|
+
*/
|
|
8
|
+
export declare function deformRoll(mesh: PageMesh, W: number, H: number, t: number): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PageMesh } from './mesh';
|
|
2
|
+
/**
|
|
3
|
+
* Flat page turn — the plainest flip, matching the CSS renderer. The leaf stays a rigid
|
|
4
|
+
* flat sheet and rotates around the spine (angle 0→π), going edge-on at the midpoint. No
|
|
5
|
+
* cylinder bend; GPU-rendered here with the shared perspective so it reads like the CSS fold.
|
|
6
|
+
*/
|
|
7
|
+
export declare function deformSimple(mesh: PageMesh, W: number, H: number, t: number): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curl type identifiers and the deform contract. Kept free of the deform
|
|
3
|
+
* implementations so the engine (and CSS-only builds) can reference the union and the
|
|
4
|
+
* validation list without pulling the WebGL-only curl math into the eager bundle.
|
|
5
|
+
*/
|
|
6
|
+
import type { PageMesh } from './mesh';
|
|
7
|
+
/** Where a corner-anchored curl folds from: y in [0,1], 0 = top edge, 1 = bottom edge. */
|
|
8
|
+
export interface CurlAnchor {
|
|
9
|
+
y: number;
|
|
10
|
+
/**
|
|
11
|
+
* Full-width lone page (single-page mode). The leaf hinges at the container edge and
|
|
12
|
+
* has no facing half to land on — deforms may soften and delay the flop so the curl
|
|
13
|
+
* reads naturally before the renderer dissolves the sheet.
|
|
14
|
+
*/
|
|
15
|
+
fill?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type CurlDeform = (mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor) => void;
|
|
18
|
+
export interface CurlModel {
|
|
19
|
+
readonly deform: CurlDeform;
|
|
20
|
+
/** True when the fold originates at a corner (`anchor.y`) rather than the whole edge. */
|
|
21
|
+
readonly anchored: boolean;
|
|
22
|
+
}
|
|
23
|
+
export type CurlType = 'roll' | 'cone' | 'leaf' | 'flick' | 'simple';
|
|
24
|
+
export declare const CURL_TYPES: CurlType[];
|
|
25
|
+
export declare const DEFAULT_CURL: CurlType;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export type { RendererOption, RendererKind } from './renderer/select';
|
|
|
7
7
|
export type { Renderer, SpreadContent, PageContent, FlipDirection, LayoutMetrics, RenderOptions } from './renderer/types';
|
|
8
8
|
export type { ZineEventMap } from './engine/emitter';
|
|
9
9
|
export type { Direction, Spread } from './engine/spread';
|
|
10
|
+
export { CURL_TYPES } from './geometry/curls/types';
|
|
11
|
+
export type { CurlType, CurlAnchor } from './geometry/curls/types';
|