@zinejs/core 0.6.0 → 0.7.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/dist/bundled-Cp43MIt0.js +100 -0
- package/dist/bundled-Cp43MIt0.js.map +1 -0
- package/dist/curls.js +133 -0
- package/dist/curls.js.map +1 -0
- package/dist/geometry/curls/bundled.d.ts +16 -0
- package/dist/geometry/curls/index.d.ts +8 -4
- package/dist/geometry/curls/types.d.ts +23 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +160 -152
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +6 -6
- package/dist/index.umd.js.map +1 -1
- package/dist/renderer/types.d.ts +2 -2
- package/dist/types-DEz3mRLI.js +11 -0
- package/dist/types-DEz3mRLI.js.map +1 -0
- package/dist/{webglRenderer-BK-ArG1G.js → webglRenderer-BcNhTqG8.js} +33 -248
- package/dist/webglRenderer-BcNhTqG8.js.map +1 -0
- package/dist/zine.d.ts +15 -3
- package/options.schema.json +2 -2
- package/package.json +12 -2
- package/dist/types-CcPVyhOJ.js +0 -13
- package/dist/types-CcPVyhOJ.js.map +0 -1
- package/dist/webglRenderer-BK-ArG1G.js.map +0 -1
package/dist/renderer/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Spread } from '../engine/spread';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CurlSpec, CurlAnchor } from '../geometry/curls/types';
|
|
3
3
|
/** A decoded, ready-to-paint raster for one page (produced by a content source). */
|
|
4
4
|
export type PageContent = ImageBitmap | HTMLCanvasElement;
|
|
5
5
|
/** Resolved content for the two halves of a spread; null = blank side. */
|
|
@@ -12,7 +12,7 @@ export interface RenderOptions {
|
|
|
12
12
|
/** Render a lone page filling the container (single-page mode), not a half panel. */
|
|
13
13
|
fill?: boolean;
|
|
14
14
|
/** Which page-curl model the WebGL2 renderer bends the leaf with (ignored by CSS). */
|
|
15
|
-
curl?:
|
|
15
|
+
curl?: CurlSpec;
|
|
16
16
|
/** Corner the fold originates from for anchored curls; y in [0,1] (where the reader tapped). */
|
|
17
17
|
anchor?: CurlAnchor;
|
|
18
18
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-DEz3mRLI.js","names":[],"sources":["../src/geometry/curls/types.ts"],"sourcesContent":["/**\n * Curl type identifiers and the deform contract. Kept free of the deform\n * implementations so the engine (and CSS-only builds) can reference the union and the\n * validation list without pulling the WebGL-only curl math into the eager bundle.\n */\nimport type { PageMesh } from './mesh';\n\n/** Where a corner-anchored curl folds from: y in [0,1], 0 = top edge, 1 = bottom edge. */\nexport interface CurlAnchor {\n y: number;\n /**\n * Full-width lone page (single-page mode). The leaf hinges at the container edge and\n * has no facing half to land on — deforms may soften and delay the flop so the curl\n * reads naturally before the renderer dissolves the sheet.\n */\n fill?: boolean;\n}\n\nexport type CurlDeform = (mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor) => void;\n\nexport interface CurlModel {\n readonly deform: CurlDeform;\n /** True when the fold originates at a corner (`anchor.y`) rather than the whole edge. */\n readonly anchored: boolean;\n /**\n * True when the sheet is flat by mid-turn, so a lone page can start dissolving earlier: there\n * is no curl left to read. Defaults to false, which suits a model still bent as it lands.\n */\n readonly flat?: boolean;\n /**\n * False to drop the specular highlight, for a sheet that never bends and so has no curve to\n * catch the light. Defaults to true.\n */\n readonly gloss?: boolean;\n}\n\n/**\n * The curl models carried in the bundle: the default, and the flat turn reduced motion falls\n * back to. Everything else is imported from `@zinejs/core/curls` and passed as a model, so a\n * book that never uses it never pays for it.\n */\nexport type CurlType = 'cone' | 'simple';\n\nexport const CURL_TYPES: CurlType[] = ['cone', 'simple'];\n\n/**\n * Curl models that exist but are not bundled. Named only so passing one as a string can say what\n * to do about it instead of failing as an unknown value.\n */\nexport const IMPORTABLE_CURLS = ['roll', 'leaf', 'flick', 'silk'];\n\nexport const DEFAULT_CURL: CurlType = 'cone';\n\n/** What the `curl` option accepts: a bundled name, or a model imported (or written) by the caller. */\nexport type CurlSpec = CurlType | CurlModel;\n"],"mappings":";AA2CA,IAAa,IAAyB,CAAC,QAAQ,QAAQ,GAM1C,IAAmB;CAAC;CAAQ;CAAQ;CAAS;AAAM,GAEnD,IAAyB"}
|
|
@@ -1,222 +1,7 @@
|
|
|
1
|
-
import { n as e } from "./types-
|
|
2
|
-
|
|
3
|
-
function t(e, t) {
|
|
4
|
-
let n = e + 1, r = t + 1, i = n * r, a = new Float32Array(i * 2);
|
|
5
|
-
for (let i = 0; i < r; i++) for (let r = 0; r < n; r++) {
|
|
6
|
-
let o = i * n + r;
|
|
7
|
-
a[o * 2] = r / e, a[o * 2 + 1] = i / t;
|
|
8
|
-
}
|
|
9
|
-
return {
|
|
10
|
-
cols: e,
|
|
11
|
-
rows: t,
|
|
12
|
-
uvs: a,
|
|
13
|
-
positions: new Float32Array(i * 3),
|
|
14
|
-
normals: new Float32Array(i * 3)
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function n(e) {
|
|
18
|
-
let t = e.cols + 1, n = e.rows + 1, r = e.positions, i = e.normals;
|
|
19
|
-
for (let e = 0; e < n; e++) for (let a = 0; a < t; a++) {
|
|
20
|
-
let o = a > 0 ? a - 1 : a, s = a < t - 1 ? a + 1 : a, c = e > 0 ? e - 1 : e, l = e < n - 1 ? e + 1 : e, u = (e * t + s) * 3, d = (e * t + o) * 3, f = (l * t + a) * 3, p = (c * t + a) * 3, m = r[u] - r[d], h = r[u + 1] - r[d + 1], g = r[u + 2] - r[d + 2], _ = r[f] - r[p], v = r[f + 1] - r[p + 1], y = r[f + 2] - r[p + 2], b = h * y - g * v, x = g * _ - m * y, S = m * v - h * _, C = Math.hypot(b, x, S) || 1;
|
|
21
|
-
b /= C, x /= C, S /= C;
|
|
22
|
-
let w = (e * t + a) * 3;
|
|
23
|
-
i[w] = b, i[w + 1] = x, i[w + 2] = S;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/geometry/curls/roll.ts
|
|
28
|
-
function r(e, t, r, i) {
|
|
29
|
-
let a = e.cols + 1, o = e.rows + 1, s = e.positions, c = Math.max(1e-6, i < .5 ? i * 2 * Math.PI : (1 - (i - .5) * 2) * Math.PI), l = t / c, u = i < .5 ? 0 : -(i - .5) * 2 * Math.PI, d = Math.cos(u), f = Math.sin(u);
|
|
30
|
-
for (let t = 0; t < a; t++) {
|
|
31
|
-
let n = t / e.cols * c, i = Math.sin(n) * l, u = (1 - Math.cos(n)) * l, p = i * d + u * f, m = -i * f + u * d;
|
|
32
|
-
for (let n = 0; n < o; n++) {
|
|
33
|
-
let i = (n * a + t) * 3;
|
|
34
|
-
s[i] = p, s[i + 1] = n / e.rows * r, s[i + 2] = m;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
n(e);
|
|
38
|
-
}
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/geometry/curls/cone.ts
|
|
41
|
-
var i = 30 * Math.PI / 180, a = 44 * Math.PI / 180, o = 1.35, s = .7, c = 1.85, l = 1.15, u = .42, d = 1.08;
|
|
42
|
-
function f(e, t, r, f, p) {
|
|
43
|
-
let m = e.cols + 1, h = e.rows + 1, g = e.positions, _ = !!p.fill, v = Math.sin(Math.PI * f) ** .85, y = _ ? a : i, b = Math.PI / 2 - v * (Math.PI / 2 - y), x = Math.sin(b), S = Math.cos(b), C = 1 / Math.max(x, 1e-6), w = _ ? c : o, T = r * (w + ((_ ? l : s) - w) * v), E = (p.y - .5) * 2, D = E >= 0, O = Math.min(1, Math.abs(E) / u), k = 1 - O, A = D ? r + T : -T, j = Math.hypot(t, .5 * r - A), M = Math.max(j * x, t / Math.PI), N = _ ? f ** +d : f, P = -Math.PI * N, F = Math.cos(P), I = Math.sin(P);
|
|
44
|
-
if (v < 1e-4) {
|
|
45
|
-
for (let n = 0; n < h; n++) {
|
|
46
|
-
let i = n / e.rows * r;
|
|
47
|
-
for (let r = 0; r < m; r++) {
|
|
48
|
-
let a = r / e.cols * t, o = (n * m + r) * 3;
|
|
49
|
-
g[o] = a * F, g[o + 1] = i, g[o + 2] = -a * I;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
n(e);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
for (let n = 0; n < h; n++) {
|
|
56
|
-
let i = n / e.rows * r;
|
|
57
|
-
for (let a = 0; a < m; a++) {
|
|
58
|
-
let o = a / e.cols * t, s = o, c = i, l = 0;
|
|
59
|
-
if (O > 1e-5) {
|
|
60
|
-
let e = D ? r - i : i, t = -T, n = Math.hypot(o, e - t);
|
|
61
|
-
if (n > 1e-8) {
|
|
62
|
-
let e = n * x, i = Math.asin(Math.min(1, Math.max(0, o / n))) * C, a = 1 - Math.cos(i), u = n + t - e * a * x;
|
|
63
|
-
s = e * Math.sin(i), c = D ? r - u : u, l = e * a * S;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
let u = s, d = c, f = l;
|
|
67
|
-
if (k > 1e-5) {
|
|
68
|
-
let e = o / M, t = M * Math.sin(e), n = M * (1 - Math.cos(e));
|
|
69
|
-
u = O * s + k * t, d = O * c + k * i, f = O * l + k * n;
|
|
70
|
-
}
|
|
71
|
-
let p = (n * m + a) * 3;
|
|
72
|
-
g[p] = u * F + f * I, g[p + 1] = d, g[p + 2] = -u * I + f * F;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
n(e);
|
|
76
|
-
}
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/geometry/curls/leaf.ts
|
|
79
|
-
var p = .34, m = 11 * Math.PI / 180, h = 128;
|
|
80
|
-
function g() {
|
|
81
|
-
let e = /* @__PURE__ */ new Float64Array(129), t = /* @__PURE__ */ new Float64Array(129), n = 1 / h, r = (e) => Math.PI * e - .5 * Math.sin(2 * Math.PI * e);
|
|
82
|
-
for (let i = 1; i <= h; i++) {
|
|
83
|
-
let a = r((i - 1) * n), o = r(i * n);
|
|
84
|
-
e[i] = e[i - 1] + .5 * (Math.cos(a) + Math.cos(o)) * n, t[i] = t[i - 1] + .5 * (Math.sin(a) + Math.sin(o)) * n;
|
|
85
|
-
}
|
|
86
|
-
return e[h] = 0, {
|
|
87
|
-
x: e,
|
|
88
|
-
z: t
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
var _ = g(), v = _.z[h];
|
|
92
|
-
function y(e) {
|
|
93
|
-
let t = Math.max(0, Math.min(1, e)) * h, n = Math.min(h - 1, Math.floor(t)), r = t - n;
|
|
94
|
-
return [_.x[n] + (_.x[n + 1] - _.x[n]) * r, _.z[n] + (_.z[n + 1] - _.z[n]) * r];
|
|
95
|
-
}
|
|
96
|
-
function b(e, t, r, i, a) {
|
|
97
|
-
let o = e.cols + 1, s = e.rows + 1, c = e.positions;
|
|
98
|
-
if (i <= 1e-7 || i >= .9999999) {
|
|
99
|
-
let a = i < .5 ? 1 : -1;
|
|
100
|
-
for (let n = 0; n < s; n++) {
|
|
101
|
-
let i = n / e.rows * r;
|
|
102
|
-
for (let r = 0; r < o; r++) {
|
|
103
|
-
let s = (n * o + r) * 3;
|
|
104
|
-
c[s] = a * (r / e.cols) * t, c[s + 1] = i, c[s + 2] = 0;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
n(e);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
let l = Math.sin(Math.PI * i) ** .72, u = Math.max(1e-6, t * p * l), d = Math.max(-1, Math.min(1, (a.y - .5) * 2)) * m * Math.sin(Math.PI * i) ** 1.15, f = Math.cos(d), h = Math.sin(d), g = -h, _ = f, b = Math.abs(h) * r * .5, x = t + b / f, S = x + (-(u + b) / f - x) * i, C = r * .5;
|
|
111
|
-
for (let n = 0; n < s; n++) {
|
|
112
|
-
let i = n / e.rows * r;
|
|
113
|
-
for (let r = 0; r < o; r++) {
|
|
114
|
-
let a = r / e.cols * t, s = a - S, l = i - C, d = s * f + l * h, p = s * g + l * _, m = (n * o + r) * 3;
|
|
115
|
-
if (d <= 0) {
|
|
116
|
-
c[m] = a, c[m + 1] = i, c[m + 2] = 0;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
let b, x;
|
|
120
|
-
if (d < u) {
|
|
121
|
-
let [e, t] = y(d / u);
|
|
122
|
-
b = u * e, x = u * t;
|
|
123
|
-
} else b = -(d - u), x = u * v;
|
|
124
|
-
c[m] = S + g * p + f * b, c[m + 1] = C + _ * p + h * b, c[m + 2] = x;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
n(e);
|
|
128
|
-
}
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region src/geometry/curls/flick.ts
|
|
131
|
-
var x = .45;
|
|
132
|
-
function S(e, t, r, i, a) {
|
|
133
|
-
let o = e.cols + 1, s = e.rows + 1, c = e.cols, l = e.positions, u = t / c, d = Math.PI * i, f = -.52 * Math.sin(2 * Math.PI * i);
|
|
134
|
-
for (let t = 0; t < s; t++) {
|
|
135
|
-
let n = t / e.rows, i = f * (1 + x * Math.abs(n - a.y)), s = n * r, p = 0, m = 0, h = Math.cos(d), g = Math.sin(d);
|
|
136
|
-
l[t * o * 3] = 0, l[t * o * 3 + 1] = s, l[t * o * 3 + 2] = 0;
|
|
137
|
-
for (let e = 1; e < o; e++) {
|
|
138
|
-
let n = e / c, r = d + i * n * n, a = Math.cos(r), f = Math.sin(r);
|
|
139
|
-
p += .5 * (h + a) * u, m += .5 * (g + f) * u, h = a, g = f;
|
|
140
|
-
let _ = (t * o + e) * 3;
|
|
141
|
-
l[_] = p, l[_ + 1] = s, l[_ + 2] = m;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
n(e);
|
|
145
|
-
}
|
|
146
|
-
//#endregion
|
|
147
|
-
//#region src/geometry/curls/silk.ts
|
|
148
|
-
var C = .52, w = .62, T = .5, E = 1.12;
|
|
149
|
-
function D(e, t, r, i, a) {
|
|
150
|
-
let o = e.cols + 1, s = e.rows + 1, c = e.cols, l = e.positions, u = t / c, d = !!a.fill;
|
|
151
|
-
if (i <= 1e-7 || i >= .9999999) {
|
|
152
|
-
let a = i < .5 ? 1 : -1;
|
|
153
|
-
for (let n = 0; n < s; n++) {
|
|
154
|
-
let i = n / e.rows * r;
|
|
155
|
-
for (let r = 0; r < o; r++) {
|
|
156
|
-
let s = (n * o + r) * 3;
|
|
157
|
-
l[s] = a * (r / e.cols) * t, l[s + 1] = i, l[s + 2] = 0;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
n(e);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
let f = Math.PI * (d ? i ** +E : i), p = C * Math.sin(Math.PI * i) ** .8, m = w * Math.sin(Math.PI * i) * (1 - i);
|
|
164
|
-
for (let t = 0; t < s; t++) {
|
|
165
|
-
let n = t / e.rows, i = n * r, s = 1 - T * Math.abs(n - a.y), d = p * (.75 + .25 * s), h = m * s, g = 0, _ = 0, v = Math.cos(f), y = Math.sin(f);
|
|
166
|
-
l[t * o * 3] = 0, l[t * o * 3 + 1] = i, l[t * o * 3 + 2] = 0;
|
|
167
|
-
for (let e = 1; e < o; e++) {
|
|
168
|
-
let n = e / c, r = f + d * Math.sin(2 * Math.PI * n) + h * n * n, a = Math.cos(r), s = Math.sin(r);
|
|
169
|
-
g += .5 * (v + a) * u, _ += .5 * (y + s) * u, v = a, y = s;
|
|
170
|
-
let p = (t * o + e) * 3;
|
|
171
|
-
l[p] = g, l[p + 1] = i, l[p + 2] = _;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
n(e);
|
|
175
|
-
}
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/geometry/curls/simple.ts
|
|
178
|
-
function O(e, t, r, i) {
|
|
179
|
-
let a = e.cols + 1, o = e.rows + 1, s = e.positions, c = i * Math.PI, l = Math.cos(c), u = Math.sin(c);
|
|
180
|
-
for (let n = 0; n < a; n++) {
|
|
181
|
-
let i = n / e.cols * t, c = i * l, d = i * u;
|
|
182
|
-
for (let t = 0; t < o; t++) {
|
|
183
|
-
let i = (t * a + n) * 3;
|
|
184
|
-
s[i] = c, s[i + 1] = t / e.rows * r, s[i + 2] = d;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
n(e);
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region src/geometry/curls/index.ts
|
|
191
|
-
var k = {
|
|
192
|
-
roll: {
|
|
193
|
-
deform: r,
|
|
194
|
-
anchored: !1
|
|
195
|
-
},
|
|
196
|
-
cone: {
|
|
197
|
-
deform: f,
|
|
198
|
-
anchored: !0
|
|
199
|
-
},
|
|
200
|
-
leaf: {
|
|
201
|
-
deform: b,
|
|
202
|
-
anchored: !0
|
|
203
|
-
},
|
|
204
|
-
flick: {
|
|
205
|
-
deform: S,
|
|
206
|
-
anchored: !0
|
|
207
|
-
},
|
|
208
|
-
silk: {
|
|
209
|
-
deform: D,
|
|
210
|
-
anchored: !0
|
|
211
|
-
},
|
|
212
|
-
simple: {
|
|
213
|
-
deform: O,
|
|
214
|
-
anchored: !1
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
//#endregion
|
|
1
|
+
import { n as e } from "./types-DEz3mRLI.js";
|
|
2
|
+
import { o as t, r as n, t as r } from "./bundled-Cp43MIt0.js";
|
|
218
3
|
//#region src/renderer/gl/context.ts
|
|
219
|
-
function
|
|
4
|
+
function i(e) {
|
|
220
5
|
let t = e.getContext("webgl2", {
|
|
221
6
|
alpha: !0,
|
|
222
7
|
premultipliedAlpha: !0,
|
|
@@ -230,17 +15,17 @@ function A(e) {
|
|
|
230
15
|
}
|
|
231
16
|
//#endregion
|
|
232
17
|
//#region src/renderer/gl/program.ts
|
|
233
|
-
function
|
|
18
|
+
function a(e, t, n) {
|
|
234
19
|
let r = e.createProgram();
|
|
235
20
|
if (r === null) throw Error("WebglRenderer: could not create a program.");
|
|
236
|
-
let i =
|
|
21
|
+
let i = o(e, e.VERTEX_SHADER, t), a = o(e, e.FRAGMENT_SHADER, n);
|
|
237
22
|
if (e.attachShader(r, i), e.attachShader(r, a), e.linkProgram(r), e.deleteShader(i), e.deleteShader(a), !e.getProgramParameter(r, e.LINK_STATUS)) {
|
|
238
23
|
let t = e.getProgramInfoLog(r);
|
|
239
24
|
throw e.deleteProgram(r), Error(`WebglRenderer: program link failed: ${t ?? "unknown error"}`);
|
|
240
25
|
}
|
|
241
26
|
return r;
|
|
242
27
|
}
|
|
243
|
-
function
|
|
28
|
+
function o(e, t, n) {
|
|
244
29
|
let r = e.createShader(t);
|
|
245
30
|
if (r === null) throw Error("WebglRenderer: could not create a shader.");
|
|
246
31
|
if (e.shaderSource(r, n), e.compileShader(r), !e.getShaderParameter(r, e.COMPILE_STATUS)) {
|
|
@@ -251,21 +36,21 @@ function M(e, t, n) {
|
|
|
251
36
|
}
|
|
252
37
|
//#endregion
|
|
253
38
|
//#region src/renderer/gl/texture.ts
|
|
254
|
-
function
|
|
39
|
+
function s(e) {
|
|
255
40
|
let t = e.createTexture();
|
|
256
41
|
if (t === null) throw Error("WebglRenderer: could not create a texture.");
|
|
257
42
|
return e.bindTexture(e.TEXTURE_2D, t), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.LINEAR), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.LINEAR), t;
|
|
258
43
|
}
|
|
259
|
-
function
|
|
44
|
+
function c(e, t, n) {
|
|
260
45
|
e.bindTexture(e.TEXTURE_2D, t), e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL, !1), e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !0), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, e.RGBA, e.UNSIGNED_BYTE, n);
|
|
261
46
|
}
|
|
262
47
|
//#endregion
|
|
263
48
|
//#region src/renderer/webglRenderer.ts
|
|
264
|
-
var
|
|
265
|
-
function
|
|
49
|
+
var l = "#version 300 es\nin vec2 aUnit;\nuniform vec2 uViewport;\nuniform vec4 uRect;\nuniform vec3 uView;\nout vec2 vUv;\nvoid main() {\n vec2 px = uRect.xy + aUnit * uRect.zw;\n px = px * uView.x + uView.yz;\n vec2 clip = px / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUnit;\n}", u = "#version 300 es\nprecision mediump float;\nin vec2 vUv;\nuniform sampler2D uTex;\nuniform float uGutterSide; // +1 = spine at right edge, -1 = at left edge, 0 = none\nout vec4 outColor;\nvoid main() {\n vec4 c = texture(uTex, vUv);\n if (uGutterSide != 0.0) {\n float d = uGutterSide > 0.0 ? (1.0 - vUv.x) : vUv.x;\n c.rgb *= mix(0.72, 1.0, smoothstep(0.0, 0.10, d));\n }\n outColor = c;\n}", d = "#version 300 es\nin vec3 aPos; // leaf-local device px: x = bent offset from spine, y 0..H, z depth\nin vec3 aNormal;\nin vec2 aUv;\nuniform vec2 uViewport;\nuniform vec3 uView; // scale, tx, ty\nuniform float uOriginX; // spine x in device px\nuniform float uDir; // +1 leaf to the right of spine, -1 to the left\nuniform float uLeafW; // leaf width in device px (perspective scale)\nout vec2 vUv;\nout float vFacing;\nout float vU;\nvoid main() {\n float bookX = uOriginX + uDir * aPos.x;\n float bookY = aPos.y;\n float Z = aPos.z;\n // Eye distance, in leaf widths. Keeps the perspective resolution-independent. 5.3 puts the\n // roll's deepest point ~14% larger than flat; stronger than that (a nearer eye) balloons the\n // turning sheet and reads more like a fisheye than a page lifting.\n float D = uLeafW * 5.3;\n float persp = D / max(D - Z, 1.0);\n float cx = uViewport.x * 0.5;\n float cy = uViewport.y * 0.5;\n float px = cx + (bookX - cx) * persp;\n float py = cy + (bookY - cy) * min(persp, 1.0); // Y only shrinks (top-safe)\n vec2 p = vec2(px, py) * uView.x + uView.yz;\n vec2 clip = p / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUv;\n vFacing = aNormal.z;\n vU = aUv.x;\n}", f = "#version 300 es\nprecision mediump float;\nin vec2 vUv;\nin float vFacing;\nin float vU;\nuniform sampler2D uFront;\nuniform sampler2D uBack;\nuniform highp float uDir;\nuniform float uGloss; // 0 disables the specular highlight (e.g. the flat 'simple' curl)\nuniform float uAlpha; // leaf opacity; a lone page dissolves into the page landing beneath it\nout vec4 outColor;\nvoid main() {\n // The turn mesh carries its own facing in the normal; the front points at the viewer\n // (normal.z > 0) until the leaf passes vertical, for both flip directions. u->x still\n // flips with uDir so a left-hand leaf reads spine-inward.\n bool showFront = vFacing > 0.0;\n float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;\n vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));\n\n // Fold shading: the sheet darkens sharply where it curves away from the viewer, so the\n // rolled edge reads as a deep crease with a soft highlight riding the ridge.\n float diff = clamp(abs(vFacing), 0.0, 1.0);\n float lit = mix(0.42, 1.0, diff);\n lit *= mix(0.7, 1.0, smoothstep(0.0, 0.12, vU)); // spine crease matches the gutter shadow\n\n // Glossy specular: brightest where the surface tilts ~30 deg from facing the viewer,\n // so a thin glossy highlight rides across the sheet as it rolls.\n float spec = pow(max(cos(acos(diff) - 0.52), 0.0), 220.0) * 0.22 * uGloss;\n\n c.rgb = clamp(c.rgb * lit + spec, 0.0, 1.0);\n // Premultiplied alpha (blendFunc ONE, ONE_MINUS_SRC_ALPHA): scale colour as well as alpha,\n // or fading would brighten the sheet additively instead of dissolving it.\n outColor = vec4(c.rgb * uAlpha, c.a * uAlpha);\n}", p = 28, m = 36, h = .7, g = .8, _ = 4e3, v = 256 * 1024 * 1024;
|
|
50
|
+
function y(e) {
|
|
266
51
|
return e.left === null == (e.right === null) ? 0 : e.right === null ? 1 : -1;
|
|
267
52
|
}
|
|
268
|
-
var
|
|
53
|
+
var b = class {
|
|
269
54
|
#e = null;
|
|
270
55
|
#t = null;
|
|
271
56
|
#n = null;
|
|
@@ -276,7 +61,7 @@ var K = class {
|
|
|
276
61
|
#s = null;
|
|
277
62
|
#c = null;
|
|
278
63
|
#l = 0;
|
|
279
|
-
#u = t(
|
|
64
|
+
#u = t(p, m);
|
|
280
65
|
#d = /* @__PURE__ */ new Map();
|
|
281
66
|
#f = 0;
|
|
282
67
|
#p = null;
|
|
@@ -292,7 +77,7 @@ var K = class {
|
|
|
292
77
|
#y = !1;
|
|
293
78
|
#b = null;
|
|
294
79
|
#x = 0;
|
|
295
|
-
#S = e;
|
|
80
|
+
#S = r[e];
|
|
296
81
|
#C = { y: .5 };
|
|
297
82
|
#w = 0;
|
|
298
83
|
#T = 0;
|
|
@@ -307,7 +92,7 @@ var K = class {
|
|
|
307
92
|
mount(e) {
|
|
308
93
|
this.#e = e;
|
|
309
94
|
let t = e.ownerDocument, n = t.createElement("canvas");
|
|
310
|
-
return n.style.cssText = "display:block;width:100%;height:100%;", e.append(n), this.#t = n, n.addEventListener("webglcontextlost", this.#L), n.addEventListener("webglcontextrestored", this.#R), t.addEventListener("visibilitychange", this.#z), this.#n =
|
|
95
|
+
return n.style.cssText = "display:block;width:100%;height:100%;", e.append(n), this.#t = n, n.addEventListener("webglcontextlost", this.#L), n.addEventListener("webglcontextrestored", this.#R), t.addEventListener("visibilitychange", this.#z), this.#n = i(n), this.#H(), Promise.resolve();
|
|
311
96
|
}
|
|
312
97
|
destroy() {
|
|
313
98
|
this.#V();
|
|
@@ -330,17 +115,17 @@ var K = class {
|
|
|
330
115
|
let t = e.left ?? e.right;
|
|
331
116
|
t && t.height && (this.#D = t.width / t.height);
|
|
332
117
|
}
|
|
333
|
-
beginFlip(e, t,
|
|
334
|
-
let
|
|
335
|
-
this.#x = 0,
|
|
118
|
+
beginFlip(e, t, r, i) {
|
|
119
|
+
let a = i?.fill ?? !1;
|
|
120
|
+
this.#x = 0, i?.curl && (this.#S = n(i.curl)), i?.anchor && (this.#C = i.anchor), this.#T = a ? 0 : y(e), this.#E = a ? 0 : y(t), this.#P(t), a ? this.#b = {
|
|
336
121
|
underLeft: null,
|
|
337
122
|
underRight: null,
|
|
338
123
|
underFull: t.right ?? t.left,
|
|
339
124
|
front: e.right ?? e.left,
|
|
340
125
|
back: e.right ?? e.left,
|
|
341
|
-
dir:
|
|
126
|
+
dir: r === "forward" ? 1 : -1,
|
|
342
127
|
fill: !0
|
|
343
|
-
} :
|
|
128
|
+
} : r === "forward" ? this.#b = {
|
|
344
129
|
underLeft: e.left,
|
|
345
130
|
underRight: t.right,
|
|
346
131
|
underFull: null,
|
|
@@ -397,7 +182,7 @@ var K = class {
|
|
|
397
182
|
}
|
|
398
183
|
#I() {
|
|
399
184
|
let e = this.#F();
|
|
400
|
-
return !this.#y && this.#v !== null &&
|
|
185
|
+
return !this.#y && this.#v !== null && y(this.#v) !== 0 ? {
|
|
401
186
|
x: e.x + e.width / 4,
|
|
402
187
|
y: e.y,
|
|
403
188
|
width: e.width / 2,
|
|
@@ -405,7 +190,7 @@ var K = class {
|
|
|
405
190
|
} : e;
|
|
406
191
|
}
|
|
407
192
|
#L = (e) => {
|
|
408
|
-
e.preventDefault(), this.#A = !0, this.#M === null && (this.#M = setTimeout(() => this.#B(),
|
|
193
|
+
e.preventDefault(), this.#A = !0, this.#M === null && (this.#M = setTimeout(() => this.#B(), _));
|
|
409
194
|
};
|
|
410
195
|
#R = () => {
|
|
411
196
|
this.#V(), this.#A = !1, this.#H(), this.#G();
|
|
@@ -422,7 +207,7 @@ var K = class {
|
|
|
422
207
|
#H() {
|
|
423
208
|
let e = this.#n;
|
|
424
209
|
if (e !== null) {
|
|
425
|
-
this.#r =
|
|
210
|
+
this.#r = a(e, l, u), this.#i = a(e, d, f);
|
|
426
211
|
for (let t of [
|
|
427
212
|
"uViewport",
|
|
428
213
|
"uRect",
|
|
@@ -462,7 +247,7 @@ var K = class {
|
|
|
462
247
|
}
|
|
463
248
|
#W(e, t) {
|
|
464
249
|
let n = [];
|
|
465
|
-
for (let e = 0; e <
|
|
250
|
+
for (let e = 0; e < m; e++) for (let t = 0; t < p; t++) {
|
|
466
251
|
let r = e * 29 + t, i = r + 1, a = r + 29, o = a + 1;
|
|
467
252
|
n.push(r, a, i, i, a, o);
|
|
468
253
|
}
|
|
@@ -494,7 +279,7 @@ var K = class {
|
|
|
494
279
|
}
|
|
495
280
|
#q() {
|
|
496
281
|
let e = this.#O, t = this.#k, n = this.#v;
|
|
497
|
-
this.#w = this.#y ? 0 :
|
|
282
|
+
this.#w = this.#y ? 0 : y(n) * (e / 4), this.#X(), this.#y ? this.#Z({
|
|
498
283
|
x: 0,
|
|
499
284
|
y: 0,
|
|
500
285
|
w: e,
|
|
@@ -530,13 +315,13 @@ var K = class {
|
|
|
530
315
|
h: n
|
|
531
316
|
}, r.underRight, -1)), r.front === null && r.back === null) return;
|
|
532
317
|
let i = r.fill ? t : t / 2, a = r.fill ? r.dir > 0 ? 0 : t : t / 2;
|
|
533
|
-
|
|
318
|
+
this.#S.deform(this.#u, i, n, this.#x, {
|
|
534
319
|
y: this.#C.y,
|
|
535
320
|
fill: r.fill
|
|
536
|
-
}), e.bindBuffer(e.ARRAY_BUFFER, this.#s), e.bufferSubData(e.ARRAY_BUFFER, 0, this.#u.positions), e.bindBuffer(e.ARRAY_BUFFER, this.#c), e.bufferSubData(e.ARRAY_BUFFER, 0, this.#u.normals), this.#Q(e.TEXTURE0, r.front), this.#Q(e.TEXTURE1, r.back), e.activeTexture(e.TEXTURE0), e.useProgram(this.#i), e.bindVertexArray(this.#o), e.uniform2f(this.#h.uViewport ?? null, t, n), e.uniform3f(this.#h.uView ?? null, this.#g.scale, this.#g.tx * this.#_ + this.#w, this.#g.ty * this.#_), e.uniform1f(this.#h.uOriginX ?? null, a), e.uniform1f(this.#h.uDir ?? null, r.dir), e.uniform1f(this.#h.uLeafW ?? null, i), e.uniform1f(this.#h.uGloss ?? null, this.#S ===
|
|
321
|
+
}), e.bindBuffer(e.ARRAY_BUFFER, this.#s), e.bufferSubData(e.ARRAY_BUFFER, 0, this.#u.positions), e.bindBuffer(e.ARRAY_BUFFER, this.#c), e.bufferSubData(e.ARRAY_BUFFER, 0, this.#u.normals), this.#Q(e.TEXTURE0, r.front), this.#Q(e.TEXTURE1, r.back), e.activeTexture(e.TEXTURE0), e.useProgram(this.#i), e.bindVertexArray(this.#o), e.uniform2f(this.#h.uViewport ?? null, t, n), e.uniform3f(this.#h.uView ?? null, this.#g.scale, this.#g.tx * this.#_ + this.#w, this.#g.ty * this.#_), e.uniform1f(this.#h.uOriginX ?? null, a), e.uniform1f(this.#h.uDir ?? null, r.dir), e.uniform1f(this.#h.uLeafW ?? null, i), e.uniform1f(this.#h.uGloss ?? null, this.#S.gloss === !1 ? 0 : 1), e.uniform1f(this.#h.uAlpha ?? null, r.fill ? this.#Y() : 1), e.drawElements(e.TRIANGLES, this.#l, e.UNSIGNED_SHORT, 0);
|
|
537
322
|
}
|
|
538
323
|
#Y() {
|
|
539
|
-
let e = this.#S
|
|
324
|
+
let e = this.#S.flat ? h : g, t = (this.#x - e) / (1 - e);
|
|
540
325
|
return t <= 0 ? 1 : t >= 1 ? 0 : (1 - t) ** 1.5;
|
|
541
326
|
}
|
|
542
327
|
#X() {
|
|
@@ -554,8 +339,8 @@ var K = class {
|
|
|
554
339
|
#$(e) {
|
|
555
340
|
let t = this.#n, n = this.#d.get(e);
|
|
556
341
|
if (n !== void 0) return this.#d.delete(e), this.#d.set(e, n), n.tex;
|
|
557
|
-
let r =
|
|
558
|
-
|
|
342
|
+
let r = s(t);
|
|
343
|
+
c(t, r, e);
|
|
559
344
|
let i = e, a = i.width * i.height * 4;
|
|
560
345
|
return this.#d.set(e, {
|
|
561
346
|
tex: r,
|
|
@@ -565,12 +350,12 @@ var K = class {
|
|
|
565
350
|
#ee() {
|
|
566
351
|
let e = this.#n;
|
|
567
352
|
for (let [t, n] of this.#d) {
|
|
568
|
-
if (this.#f <=
|
|
353
|
+
if (this.#f <= v || this.#d.size <= 4) break;
|
|
569
354
|
this.#d.delete(t), this.#f -= n.bytes, e.deleteTexture(n.tex);
|
|
570
355
|
}
|
|
571
356
|
}
|
|
572
357
|
#te(e) {
|
|
573
|
-
let t =
|
|
358
|
+
let t = s(e);
|
|
574
359
|
return e.bindTexture(e.TEXTURE_2D, t), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, 1, 1, 0, e.RGBA, e.UNSIGNED_BYTE, new Uint8Array([
|
|
575
360
|
0,
|
|
576
361
|
0,
|
|
@@ -587,6 +372,6 @@ var K = class {
|
|
|
587
372
|
}
|
|
588
373
|
};
|
|
589
374
|
//#endregion
|
|
590
|
-
export {
|
|
375
|
+
export { b as WebglRenderer };
|
|
591
376
|
|
|
592
|
-
//# sourceMappingURL=webglRenderer-
|
|
377
|
+
//# sourceMappingURL=webglRenderer-BcNhTqG8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webglRenderer-BcNhTqG8.js","names":["#container","#canvas","#onContextLost","#onContextRestored","#onVisibilityChange","#gl","#buildGlResources","#clearRestoreTimer","#texCache","#blankTex","#quadVao","#curlVao","#flat","#curl","#cacheBytes","#content","#flip","#fatalHandler","#fill","#trackAspect","#render","#pageAspect","#flipT","#curlModel","#anchor","#fromShiftUnit","#toShiftUnit","#view","#bookBox","#contentBox","#contextLost","#restoreTimer","#fatal","#fatalFired","#flatU","#curlU","#makeQuadVao","#buildCurlMesh","#makeBlankTexture","#idxCount","#posBuf","#normBuf","#mesh","#resize","#fitBook","#drawFlip","#drawStatic","#bookW","#bookH","#shiftX","#useFlat","#drawQuad","#bindFace","#dpr","#fillFade","#textureFor","#evictTextures"],"sources":["../src/renderer/gl/context.ts","../src/renderer/gl/program.ts","../src/renderer/gl/texture.ts","../src/renderer/webglRenderer.ts"],"sourcesContent":["/** Create a WebGL2 context, or throw so the caller can fall back to CSS (§8.4). */\nexport function createGlContext(canvas: HTMLCanvasElement): WebGL2RenderingContext {\n const gl = canvas.getContext('webgl2', {\n alpha: true,\n premultipliedAlpha: true,\n antialias: true,\n depth: false,\n stencil: false,\n // We render on demand (not every frame), so the buffer must persist between\n // composites — otherwise the settled page flickers once the flip animation\n // stops and nothing is redrawing each frame.\n preserveDrawingBuffer: true,\n });\n if (gl === null) {\n throw new Error('WebglRenderer: could not create a WebGL2 context.');\n }\n return gl;\n}\n","/** Compile + link a shader program; throws with the driver log on failure. */\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexSrc: string,\n fragmentSrc: string,\n): WebGLProgram {\n const program = gl.createProgram();\n if (program === null) throw new Error('WebglRenderer: could not create a program.');\n\n const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);\n const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n\n // Shaders can be detached/deleted once linked.\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program);\n gl.deleteProgram(program);\n throw new Error(`WebglRenderer: program link failed: ${log ?? 'unknown error'}`);\n }\n return program;\n}\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type);\n if (shader === null) throw new Error('WebglRenderer: could not create a shader.');\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`WebglRenderer: shader compile failed: ${log ?? 'unknown error'}`);\n }\n return shader;\n}\n","/** Create a page texture with clamp + linear filtering (no mipmaps — §8.3). */\nexport function createTexture(gl: WebGL2RenderingContext): WebGLTexture {\n const tex = gl.createTexture();\n if (tex === null) throw new Error('WebglRenderer: could not create a texture.');\n gl.bindTexture(gl.TEXTURE_2D, tex);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n return tex;\n}\n\n/** Upload a decoded page raster (ImageBitmap or canvas) into `tex`. */\nexport function uploadTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n source: TexImageSource,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // No Y flip: the vertex shader maps aUnit.y=0 to the page top and samples the\n // source's top row there. UNPACK_FLIP_Y is honored for <canvas> sources but\n // IGNORED for ImageBitmap, so flipping would invert PDF (canvas) pages while\n // leaving image (ImageBitmap) pages upright. Off keeps both consistent.\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n}\n","// Imported from the leaf modules, not the barrel: the barrel pulls in every curl, which would\n// defeat the point of only bundling the two the engine can name.\nimport { createPageMesh, type PageMesh } from '../geometry/curls/mesh';\nimport { BUNDLED_CURLS, resolveCurl } from '../geometry/curls/bundled';\nimport { DEFAULT_CURL, type CurlAnchor, type CurlModel, type CurlSpec } from '../geometry/curls/types';\nimport type { Spread } from '../engine/spread';\nimport type {\n FlipDirection,\n LayoutMetrics,\n Renderer,\n RenderOptions,\n SpreadContent,\n} from './types';\nimport { createGlContext } from './gl/context';\nimport { createProgram } from './gl/program';\nimport { createTexture, uploadTexture } from './gl/texture';\n\n// Flat program: a page rect (device px, top-left origin) with zoom/pan and a soft\n// permanent gutter shadow toward the spine so the turning leaf's shadow lands into it.\nconst FLAT_VERT = `#version 300 es\nin vec2 aUnit;\nuniform vec2 uViewport;\nuniform vec4 uRect;\nuniform vec3 uView;\nout vec2 vUv;\nvoid main() {\n vec2 px = uRect.xy + aUnit * uRect.zw;\n px = px * uView.x + uView.yz;\n vec2 clip = px / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUnit;\n}`;\n\nconst FLAT_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nuniform sampler2D uTex;\nuniform float uGutterSide; // +1 = spine at right edge, -1 = at left edge, 0 = none\nout vec4 outColor;\nvoid main() {\n vec4 c = texture(uTex, vUv);\n if (uGutterSide != 0.0) {\n float d = uGutterSide > 0.0 ? (1.0 - vUv.x) : vUv.x;\n c.rgb *= mix(0.72, 1.0, smoothstep(0.0, 0.10, d));\n }\n outColor = c;\n}`;\n\n// Curl program: a thin pass-through for the CPU-deformed page mesh (positions and\n// normals come from the selected curl model). It places the leaf, applies a top-safe depth\n// perspective, and lights from the true surface normal.\nconst CURL_VERT = `#version 300 es\nin vec3 aPos; // leaf-local device px: x = bent offset from spine, y 0..H, z depth\nin vec3 aNormal;\nin vec2 aUv;\nuniform vec2 uViewport;\nuniform vec3 uView; // scale, tx, ty\nuniform float uOriginX; // spine x in device px\nuniform float uDir; // +1 leaf to the right of spine, -1 to the left\nuniform float uLeafW; // leaf width in device px (perspective scale)\nout vec2 vUv;\nout float vFacing;\nout float vU;\nvoid main() {\n float bookX = uOriginX + uDir * aPos.x;\n float bookY = aPos.y;\n float Z = aPos.z;\n // Eye distance, in leaf widths. Keeps the perspective resolution-independent. 5.3 puts the\n // roll's deepest point ~14% larger than flat; stronger than that (a nearer eye) balloons the\n // turning sheet and reads more like a fisheye than a page lifting.\n float D = uLeafW * 5.3;\n float persp = D / max(D - Z, 1.0);\n float cx = uViewport.x * 0.5;\n float cy = uViewport.y * 0.5;\n float px = cx + (bookX - cx) * persp;\n float py = cy + (bookY - cy) * min(persp, 1.0); // Y only shrinks (top-safe)\n vec2 p = vec2(px, py) * uView.x + uView.yz;\n vec2 clip = p / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUv;\n vFacing = aNormal.z;\n vU = aUv.x;\n}`;\n\nconst CURL_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nin float vFacing;\nin float vU;\nuniform sampler2D uFront;\nuniform sampler2D uBack;\nuniform highp float uDir;\nuniform float uGloss; // 0 disables the specular highlight (e.g. the flat 'simple' curl)\nuniform float uAlpha; // leaf opacity; a lone page dissolves into the page landing beneath it\nout vec4 outColor;\nvoid main() {\n // The turn mesh carries its own facing in the normal; the front points at the viewer\n // (normal.z > 0) until the leaf passes vertical, for both flip directions. u->x still\n // flips with uDir so a left-hand leaf reads spine-inward.\n bool showFront = vFacing > 0.0;\n float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;\n vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));\n\n // Fold shading: the sheet darkens sharply where it curves away from the viewer, so the\n // rolled edge reads as a deep crease with a soft highlight riding the ridge.\n float diff = clamp(abs(vFacing), 0.0, 1.0);\n float lit = mix(0.42, 1.0, diff);\n lit *= mix(0.7, 1.0, smoothstep(0.0, 0.12, vU)); // spine crease matches the gutter shadow\n\n // Glossy specular: brightest where the surface tilts ~30 deg from facing the viewer,\n // so a thin glossy highlight rides across the sheet as it rolls.\n float spec = pow(max(cos(acos(diff) - 0.52), 0.0), 220.0) * 0.22 * uGloss;\n\n c.rgb = clamp(c.rgb * lit + spec, 0.0, 1.0);\n // Premultiplied alpha (blendFunc ONE, ONE_MINUS_SRC_ALPHA): scale colour as well as alpha,\n // or fading would brighten the sheet additively instead of dissolving it.\n outColor = vec4(c.rgb * uAlpha, c.a * uAlpha);\n}`;\n\nconst GRID_COLS = 28;\nconst GRID_ROWS = 36;\n/** Progress at which a lone page starts dissolving. A full-width leaf has no facing half to\n * flop onto, so instead of landing on empty space it fades into the page arriving beneath it\n * over the rest of the turn. Roll is already flat by mid-turn; curling models stay bent\n * longer, so they dissolve later. The fade itself eases so the leaf settles rather than\n * vanishing while still swinging. */\nconst FILL_FADE_START_ROLL = 0.7;\nconst FILL_FADE_START_CURL = 0.8;\nconst RESTORE_TIMEOUT_MS = 4000;\nconst MAX_TEXTURE_CACHE_BYTES = 256 * 1024 * 1024;\n\ninterface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\ninterface FlipState {\n underLeft: TexImageSource | null;\n underRight: TexImageSource | null;\n underFull: TexImageSource | null; // fill mode\n front: TexImageSource | null;\n back: TexImageSource | null;\n dir: number; // +1 forward (leaf right of spine), -1 backward (left)\n fill: boolean;\n}\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 * GPU renderer (WebGL2, hand-written). The turning leaf is a dense grid deformed on\n * the CPU by the selected curl model (see geometry/curls) and\n * uploaded each frame, lit from real normals; the static pages are flat quads with a\n * gutter shadow. Restores\n * GL state on context loss; signals onFatal (→ CSS fallback) when unrecoverable.\n */\nexport class WebglRenderer implements Renderer {\n #container: HTMLElement | null = null;\n #canvas: HTMLCanvasElement | null = null;\n #gl: WebGL2RenderingContext | null = null;\n\n #flat: WebGLProgram | null = null;\n #curl: WebGLProgram | null = null;\n #quadVao: WebGLVertexArrayObject | null = null;\n #curlVao: WebGLVertexArrayObject | null = null;\n #posBuf: WebGLBuffer | null = null;\n #normBuf: WebGLBuffer | null = null;\n #idxCount = 0;\n #mesh: PageMesh = createPageMesh(GRID_COLS, GRID_ROWS);\n\n #texCache = new Map<TexImageSource, { tex: WebGLTexture; bytes: number }>();\n #cacheBytes = 0;\n #blankTex: WebGLTexture | null = null;\n\n #flatU: Record<string, WebGLUniformLocation | null> = {};\n #curlU: Record<string, WebGLUniformLocation | null> = {};\n\n #view = { scale: 1, tx: 0, ty: 0 };\n #dpr = 1;\n #content: SpreadContent | null = null;\n #fill = false;\n #flip: FlipState | null = null;\n #flipT = 0;\n #curlModel: CurlModel = BUNDLED_CURLS[DEFAULT_CURL];\n #anchor: CurlAnchor = { y: 0.5 };\n // A lone page (cover / book front-back) sits on one half; these center it by shifting\n // the view a quarter-width, interpolated across a flip so the open/close doesn't jump.\n #shiftX = 0;\n #fromShiftUnit = 0;\n #toShiftUnit = 0;\n // Book aspect fit: the 2-page spread is letterboxed to the pages' aspect, centered\n // in the container, so pages fill their halves without stretching. 0 = aspect unknown.\n #pageAspect = 0;\n #bookW = 0;\n #bookH = 0;\n\n #contextLost = false;\n #fatalFired = false;\n #restoreTimer: ReturnType<typeof setTimeout> | null = null;\n #fatalHandler: (() => void) | null = null;\n\n mount(container: HTMLElement): Promise<void> {\n this.#container = container;\n const doc = container.ownerDocument;\n const canvas = doc.createElement('canvas');\n canvas.style.cssText = 'display:block;width:100%;height:100%;';\n container.append(canvas);\n this.#canvas = canvas;\n\n canvas.addEventListener('webglcontextlost', this.#onContextLost as EventListener);\n canvas.addEventListener('webglcontextrestored', this.#onContextRestored as EventListener);\n doc.addEventListener('visibilitychange', this.#onVisibilityChange);\n\n this.#gl = createGlContext(canvas); // throws if unavailable -> engine falls back (§8.4)\n this.#buildGlResources();\n return Promise.resolve();\n }\n\n destroy(): void {\n this.#clearRestoreTimer();\n const canvas = this.#canvas;\n if (canvas !== null) {\n canvas.removeEventListener('webglcontextlost', this.#onContextLost as EventListener);\n canvas.removeEventListener('webglcontextrestored', this.#onContextRestored as EventListener);\n canvas.ownerDocument.removeEventListener('visibilitychange', this.#onVisibilityChange);\n }\n const gl = this.#gl;\n if (gl !== null) {\n for (const cached of this.#texCache.values()) gl.deleteTexture(cached.tex);\n if (this.#blankTex !== null) gl.deleteTexture(this.#blankTex);\n if (this.#quadVao !== null) gl.deleteVertexArray(this.#quadVao);\n if (this.#curlVao !== null) gl.deleteVertexArray(this.#curlVao);\n if (this.#flat !== null) gl.deleteProgram(this.#flat);\n if (this.#curl !== null) gl.deleteProgram(this.#curl);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n canvas?.remove();\n this.#texCache.clear();\n this.#cacheBytes = 0;\n this.#blankTex = null;\n this.#container = null;\n this.#canvas = null;\n this.#gl = null;\n this.#content = null;\n this.#flip = null;\n }\n\n onFatal(handler: () => void): void {\n this.#fatalHandler = handler;\n }\n\n renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void {\n this.#content = content;\n this.#fill = options?.fill ?? false;\n this.#flip = null;\n this.#trackAspect(content);\n this.#render();\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 beginFlip(\n from: SpreadContent,\n to: SpreadContent,\n direction: FlipDirection,\n options?: RenderOptions,\n ): void {\n const fill = options?.fill ?? false;\n this.#flipT = 0;\n if (options?.curl) this.#curlModel = resolveCurl(options.curl);\n if (options?.anchor) this.#anchor = options.anchor;\n this.#fromShiftUnit = fill ? 0 : shiftUnit(from);\n this.#toShiftUnit = fill ? 0 : shiftUnit(to);\n this.#trackAspect(to);\n if (fill) {\n this.#flip = {\n underLeft: null,\n underRight: null,\n underFull: to.right ?? to.left,\n front: from.right ?? from.left,\n // A lone page has no facing \"next\" leaf on its back; show the same page mirrored\n // (the shader flips the back UV) so it reads as the sheet's own reverse side.\n back: from.right ?? from.left,\n dir: direction === 'forward' ? 1 : -1,\n fill: true,\n };\n } else if (direction === 'forward') {\n this.#flip = {\n underLeft: from.left,\n underRight: to.right,\n underFull: null,\n front: from.right,\n back: to.left,\n dir: 1,\n fill: false,\n };\n } else {\n this.#flip = {\n underLeft: to.left,\n underRight: from.right,\n underFull: null,\n front: from.left,\n back: to.right,\n dir: -1,\n fill: false,\n };\n }\n this.#render();\n }\n\n setFlipProgress(t: number, _direction: FlipDirection): void {\n this.#flipT = t;\n this.#render();\n }\n\n setViewTransform(scale: number, x: number, y: number): void {\n this.#view = { scale, tx: x, ty: y };\n this.#render();\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 /** The fitted book rect in container CSS px (letterbox aware) — a stable 2-page area. */\n #bookBox(): { x: number; y: number; width: number; height: number } {\n const el = this.#container;\n const cw = el?.clientWidth ?? 0;\n const ch = el?.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 the draw-time #shiftX centering). This is what the engine hit-tests against. */\n #contentBox(): { x: number; y: number; width: number; height: number } {\n const b = this.#bookBox();\n const lone = !this.#fill && this.#content !== null && shiftUnit(this.#content) !== 0;\n return lone ? { x: b.x + b.width / 4, y: b.y, width: b.width / 2, height: b.height } : b;\n }\n\n // --- context loss / restore ---------------------------------------------------\n\n #onContextLost = (event: Event): void => {\n event.preventDefault();\n this.#contextLost = true;\n if (this.#restoreTimer === null) {\n this.#restoreTimer = setTimeout(() => this.#fatal(), RESTORE_TIMEOUT_MS);\n }\n };\n\n #onContextRestored = (): void => {\n this.#clearRestoreTimer();\n this.#contextLost = false;\n this.#buildGlResources();\n this.#render();\n };\n\n #onVisibilityChange = (): void => {\n if (this.#canvas?.ownerDocument.visibilityState === 'visible') this.#render();\n };\n\n #fatal(): void {\n if (this.#fatalFired) return;\n this.#fatalFired = true;\n this.#fatalHandler?.();\n }\n\n #clearRestoreTimer(): void {\n if (this.#restoreTimer !== null) {\n clearTimeout(this.#restoreTimer);\n this.#restoreTimer = null;\n }\n }\n\n #buildGlResources(): void {\n const gl = this.#gl;\n if (gl === null) return;\n\n this.#flat = createProgram(gl, FLAT_VERT, FLAT_FRAG);\n this.#curl = createProgram(gl, CURL_VERT, CURL_FRAG);\n for (const name of ['uViewport', 'uRect', 'uView', 'uTex', 'uGutterSide']) {\n this.#flatU[name] = gl.getUniformLocation(this.#flat, name);\n }\n for (const name of ['uViewport', 'uView', 'uOriginX', 'uDir', 'uLeafW', 'uFront', 'uBack', 'uGloss', 'uAlpha']) {\n this.#curlU[name] = gl.getUniformLocation(this.#curl, name);\n }\n\n this.#quadVao = this.#makeQuadVao(gl, this.#flat);\n this.#buildCurlMesh(gl, this.#curl);\n\n this.#texCache.clear();\n this.#cacheBytes = 0;\n this.#blankTex = this.#makeBlankTexture(gl);\n gl.useProgram(this.#flat);\n gl.uniform1i(this.#flatU.uTex ?? null, 0);\n gl.useProgram(this.#curl);\n gl.uniform1i(this.#curlU.uFront ?? null, 0);\n gl.uniform1i(this.#curlU.uBack ?? null, 1);\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // premultiplied alpha\n }\n\n #makeQuadVao(gl: WebGL2RenderingContext, program: WebGLProgram): WebGLVertexArrayObject {\n const vao = gl.createVertexArray();\n const buffer = gl.createBuffer();\n if (vao === null || buffer === null) throw new Error('WebglRenderer: could not create buffers.');\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);\n const aUnit = gl.getAttribLocation(program, 'aUnit');\n gl.enableVertexAttribArray(aUnit);\n gl.vertexAttribPointer(aUnit, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n return vao;\n }\n\n #buildCurlMesh(gl: WebGL2RenderingContext, program: WebGLProgram): void {\n const nx = GRID_COLS + 1;\n const indices: number[] = [];\n for (let j = 0; j < GRID_ROWS; j++) {\n for (let i = 0; i < GRID_COLS; i++) {\n const a = j * nx + i;\n const b = a + 1;\n const c = a + nx;\n const d = c + 1;\n indices.push(a, c, b, b, c, d);\n }\n }\n this.#idxCount = indices.length;\n\n const vao = gl.createVertexArray();\n this.#posBuf = gl.createBuffer();\n this.#normBuf = gl.createBuffer();\n const uvBuf = gl.createBuffer();\n const idxBuf = gl.createBuffer();\n if (vao === null || this.#posBuf === null || this.#normBuf === null || uvBuf === null || idxBuf === null) {\n throw new Error('WebglRenderer: could not create mesh buffers.');\n }\n gl.bindVertexArray(vao);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.positions, gl.DYNAMIC_DRAW);\n const aPos = gl.getAttribLocation(program, 'aPos');\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#normBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.normals, gl.DYNAMIC_DRAW);\n const aNormal = gl.getAttribLocation(program, 'aNormal');\n gl.enableVertexAttribArray(aNormal);\n gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.uvs, gl.STATIC_DRAW);\n const aUv = gl.getAttribLocation(program, 'aUv');\n gl.enableVertexAttribArray(aUv);\n gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuf);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);\n\n gl.bindVertexArray(null);\n this.#curlVao = vao;\n }\n\n // --- drawing ------------------------------------------------------------------\n\n #render(): void {\n const gl = this.#gl;\n const canvas = this.#canvas;\n if (gl === null || canvas === null || this.#contextLost || gl.isContextLost()) return;\n this.#resize();\n gl.clear(gl.COLOR_BUFFER_BIT);\n this.#fitBook();\n if (this.#flip !== null) this.#drawFlip();\n else if (this.#content !== null) this.#drawStatic();\n }\n\n /** Letterbox the 2-page book to the page aspect, centered; restricts the GL viewport\n * so pages fill their halves without stretching (the surround stays cleared). */\n #fitBook(): void {\n const gl = this.#gl!;\n const canvas = this.#canvas!;\n const cw = canvas.width;\n const ch = canvas.height;\n if (this.#pageAspect > 0) {\n const bookAspect = (this.#fill ? 1 : 2) * this.#pageAspect;\n if (bookAspect > cw / ch) {\n this.#bookW = cw;\n this.#bookH = Math.round(cw / bookAspect);\n } else {\n this.#bookH = ch;\n this.#bookW = Math.round(ch * bookAspect);\n }\n } else {\n this.#bookW = cw;\n this.#bookH = ch;\n }\n const bx = Math.round((cw - this.#bookW) / 2);\n const by = Math.round((ch - this.#bookH) / 2); // centered, so GL's bottom offset == by\n gl.viewport(bx, by, this.#bookW, this.#bookH);\n }\n\n #drawStatic(): void {\n const w = this.#bookW;\n const h = this.#bookH;\n const content = this.#content!;\n this.#shiftX = this.#fill ? 0 : shiftUnit(content) * (w / 4);\n this.#useFlat();\n if (this.#fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, content.right ?? content.left);\n } else {\n // A lone page (one side null) gets no gutter shadow — it's a standalone, centered page.\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, content.left, content.right ? 1 : 0);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, content.right, content.left ? -1 : 0);\n }\n }\n\n #drawFlip(): void {\n const gl = this.#gl!;\n const w = this.#bookW;\n const h = this.#bookH;\n const flip = this.#flip!;\n\n // Interpolate the lone-page centering across the flip so an opening cover (or a\n // closing back page) slides between centered and spread instead of jumping.\n this.#shiftX = flip.fill\n ? 0\n : (this.#fromShiftUnit + (this.#toShiftUnit - this.#fromShiftUnit) * this.#flipT) * (w / 4);\n\n // Static pages underneath (with gutter shadow).\n this.#useFlat();\n if (flip.fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, flip.underFull);\n } else {\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, flip.underLeft, 1);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, flip.underRight, -1);\n }\n\n if (flip.front === null && flip.back === null) return;\n\n // Deform the leaf mesh with the selected curl model on the CPU, upload it, then draw.\n const leafW = flip.fill ? w : w / 2;\n const originX = flip.fill ? (flip.dir > 0 ? 0 : w) : w / 2;\n this.#curlModel.deform(this.#mesh, leafW, h, this.#flipT, {\n y: this.#anchor.y,\n fill: flip.fill,\n });\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuf);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.#mesh.positions);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#normBuf);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.#mesh.normals);\n\n this.#bindFace(gl.TEXTURE0, flip.front);\n this.#bindFace(gl.TEXTURE1, flip.back);\n gl.activeTexture(gl.TEXTURE0);\n\n gl.useProgram(this.#curl);\n gl.bindVertexArray(this.#curlVao);\n gl.uniform2f(this.#curlU.uViewport ?? null, w, h);\n gl.uniform3f(\n this.#curlU.uView ?? null,\n this.#view.scale,\n this.#view.tx * this.#dpr + this.#shiftX,\n this.#view.ty * this.#dpr,\n );\n gl.uniform1f(this.#curlU.uOriginX ?? null, originX);\n gl.uniform1f(this.#curlU.uDir ?? null, flip.dir);\n gl.uniform1f(this.#curlU.uLeafW ?? null, leafW);\n gl.uniform1f(this.#curlU.uGloss ?? null, this.#curlModel.gloss === false ? 0 : 1);\n gl.uniform1f(this.#curlU.uAlpha ?? null, flip.fill ? this.#fillFade() : 1);\n gl.drawElements(gl.TRIANGLES, this.#idxCount, gl.UNSIGNED_SHORT, 0);\n }\n\n /** Leaf opacity for a lone page: solid until the curl-appropriate fade start, then\n * dissolving to 0 as it lands, so the turn resolves into the destination page rather\n * than onto blank space. The fade eases so opacity hangs longer then finishes cleanly. */\n #fillFade(): number {\n const start = this.#curlModel.flat ? FILL_FADE_START_ROLL : FILL_FADE_START_CURL;\n const over = (this.#flipT - start) / (1 - start);\n if (over <= 0) return 1;\n if (over >= 1) return 0;\n // Ease-out the remaining opacity: stay readable through most of the window, then settle.\n return Math.pow(1 - over, 1.5);\n }\n\n #useFlat(): void {\n const gl = this.#gl!;\n gl.useProgram(this.#flat);\n gl.bindVertexArray(this.#quadVao);\n gl.activeTexture(gl.TEXTURE0);\n gl.uniform2f(this.#flatU.uViewport ?? null, this.#bookW, this.#bookH);\n gl.uniform3f(\n this.#flatU.uView ?? null,\n this.#view.scale,\n this.#view.tx * this.#dpr + this.#shiftX,\n this.#view.ty * this.#dpr,\n );\n }\n\n #drawQuad(rect: Rect, source: TexImageSource | null, gutterSide = 0): void {\n const gl = this.#gl!;\n if (source === null) return;\n this.#bindFace(gl.TEXTURE0, source);\n gl.uniform4f(this.#flatU.uRect ?? null, rect.x, rect.y, rect.w, rect.h);\n gl.uniform1f(this.#flatU.uGutterSide ?? null, gutterSide);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n\n #bindFace(unit: number, source: TexImageSource | null): void {\n const gl = this.#gl!;\n gl.activeTexture(unit);\n gl.bindTexture(gl.TEXTURE_2D, source !== null ? this.#textureFor(source) : this.#blankTex);\n }\n\n #textureFor(source: TexImageSource): WebGLTexture {\n const gl = this.#gl!;\n const existing = this.#texCache.get(source);\n if (existing !== undefined) {\n this.#texCache.delete(source);\n this.#texCache.set(source, existing);\n return existing.tex;\n }\n const tex = createTexture(gl);\n uploadTexture(gl, tex, source);\n const dims = source as unknown as { width: number; height: number };\n const bytes = dims.width * dims.height * 4;\n this.#texCache.set(source, { tex, bytes });\n this.#cacheBytes += bytes;\n this.#evictTextures();\n return tex;\n }\n\n #evictTextures(): void {\n const gl = this.#gl!;\n for (const [src, cached] of this.#texCache) {\n if (this.#cacheBytes <= MAX_TEXTURE_CACHE_BYTES || this.#texCache.size <= 4) break;\n this.#texCache.delete(src);\n this.#cacheBytes -= cached.bytes;\n gl.deleteTexture(cached.tex);\n }\n }\n\n #makeBlankTexture(gl: WebGL2RenderingContext): WebGLTexture {\n const tex = createTexture(gl);\n gl.bindTexture(gl.TEXTURE_2D, tex);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));\n return tex;\n }\n\n #resize(): void {\n const gl = this.#gl;\n const canvas = this.#canvas;\n const el = this.#container;\n if (gl === null || canvas === null || el === null) return;\n this.#dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const w = Math.max(1, Math.round(el.clientWidth * this.#dpr));\n const h = Math.max(1, Math.round(el.clientHeight * this.#dpr));\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w;\n canvas.height = h;\n gl.viewport(0, 0, w, h);\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/zine.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Direction, type Spread, type SpreadMode } from './engine/spread';
|
|
2
2
|
import { type ZineEventMap } from './engine/emitter';
|
|
3
|
-
import { type
|
|
3
|
+
import { type CurlSpec } from './geometry/curls/types';
|
|
4
4
|
import { type RendererOption } from './renderer/select';
|
|
5
5
|
import type { FlipDirection, PageContent } from './renderer/types';
|
|
6
6
|
import type { DownloadInfo, OutlineItem, Source } from './source/types';
|
|
@@ -33,8 +33,20 @@ export interface ZineOptions {
|
|
|
33
33
|
source: Source;
|
|
34
34
|
/** Renderer selection; default 'auto'. */
|
|
35
35
|
renderer?: RendererOption;
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Page-curl model for the WebGL2 renderer. Default `'cone'`.
|
|
38
|
+
*
|
|
39
|
+
* `'cone'` and `'simple'` are bundled and can be named directly. The others are imported and
|
|
40
|
+
* passed as models, so a book that never uses them never carries their math:
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { silk } from '@zinejs/core/curls';
|
|
44
|
+
* new Zine(el, { source, curl: silk });
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* Any object of the same shape works, so a custom curl needs no registration.
|
|
48
|
+
*/
|
|
49
|
+
curl?: CurlSpec;
|
|
38
50
|
/** Fixed container width in px; omit to let the container/CSS drive the size. */
|
|
39
51
|
width?: number;
|
|
40
52
|
/** Fixed container height in px; omit to let the container/CSS drive the size. */
|
package/options.schema.json
CHANGED
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
},
|
|
49
49
|
"curl": {
|
|
50
50
|
"type": "string",
|
|
51
|
-
"enum": ["
|
|
51
|
+
"enum": ["cone", "simple"],
|
|
52
52
|
"default": "cone",
|
|
53
|
-
"description": "Page-curl model for the WebGL2 renderer: 'roll' (roll up then flop over; a lone page turns the same way and dissolves as it lands), '
|
|
53
|
+
"description": "Page-curl model for the WebGL2 renderer. Only the bundled names are valid JSON: 'cone' (natural conical curl, PARC/iBooks-style, anchored at tap; default) and 'simple' (plain flat page turn, no bend). Four more are shipped as importable models rather than names, so a book carries only the curl it uses: 'roll' (roll up then flop over; a lone page turns the same way and dissolves as it lands), 'leaf' (traveling smooth-curvature wave with flat paper before and after the bend, anchored at tap), 'flick' (inertial follow-through: the sheet trails the turn, then swings past and settles, anchored at tap), 'silk' (hand-turned S-curve: spine-driven rotation with body bend + free-edge reverse curl forming a true inflection, early peel lead, anchored at tap). In code, import one from '@zinejs/core/curls' and pass the model; a custom model of the same shape works too."
|
|
54
54
|
},
|
|
55
55
|
"frontCover": {
|
|
56
56
|
"type": "string",
|