@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.
@@ -1,4 +1,5 @@
1
1
  import type { Spread } from '../engine/spread';
2
+ import type { CurlType, CurlAnchor } from '../geometry/curls/types';
2
3
  /** A decoded, ready-to-paint raster for one page (produced by a content source). */
3
4
  export type PageContent = ImageBitmap | HTMLCanvasElement;
4
5
  /** Resolved content for the two halves of a spread; null = blank side. */
@@ -10,6 +11,10 @@ export type FlipDirection = 'forward' | 'backward';
10
11
  export interface RenderOptions {
11
12
  /** Render a lone page filling the container (single-page mode), not a half panel. */
12
13
  fill?: boolean;
14
+ /** Which page-curl model the WebGL2 renderer bends the leaf with (ignored by CSS). */
15
+ curl?: CurlType;
16
+ /** Corner the fold originates from for anchored curls; y in [0,1] (where the reader tapped). */
17
+ anchor?: CurlAnchor;
13
18
  }
14
19
  /** Layout metrics the engine needs for geometry and mode decisions. */
15
20
  export interface LayoutMetrics {
@@ -17,6 +22,23 @@ export interface LayoutMetrics {
17
22
  containerHeight: number;
18
23
  pageWidth: number;
19
24
  pageHeight: number;
25
+ /** The letterboxed book area (aspect-fitted, centered) in container px — a stable 2-page
26
+ * (or 1-page in fill) region used for aspect sizing. Omit to fall back to the full container. */
27
+ book?: {
28
+ x: number;
29
+ y: number;
30
+ width: number;
31
+ height: number;
32
+ };
33
+ /** Where the current spread is actually painted, in container px — equal to `book` for a
34
+ * full spread, but the centered half for a lone page (cover / book front-back). This is the
35
+ * region the engine hit-tests against so taps track the visible page. Omit to fall back. */
36
+ content?: {
37
+ x: number;
38
+ y: number;
39
+ width: number;
40
+ height: number;
41
+ };
20
42
  }
21
43
  /**
22
44
  * The contract every renderer implements (CSS baseline, WebGL2/GPU). The engine
@@ -1,10 +1,11 @@
1
1
  import type { Spread } from '../engine/spread';
2
2
  import type { FlipDirection, LayoutMetrics, Renderer, RenderOptions, SpreadContent } from './types';
3
3
  /**
4
- * GPU renderer (WebGL2, hand-written, no third-party library). The turning leaf is
5
- * a grid mesh bent around a cylinder (driven by the shared flipProgressToPose), the
6
- * static pages are flat textured quads underneath. Restores its GL state in place on
7
- * context loss; if that fails it signals `onFatal` so the engine can fall back to CSS.
4
+ * GPU renderer (WebGL2, hand-written). The turning leaf is a dense grid deformed on
5
+ * the CPU by the selected curl model (see geometry/curls) and
6
+ * uploaded each frame, lit from real normals; the static pages are flat quads with a
7
+ * gutter shadow. Restores
8
+ * GL state on context loss; signals onFatal (→ CSS fallback) when unrecoverable.
8
9
  */
9
10
  export declare class WebglRenderer implements Renderer {
10
11
  #private;
@@ -13,7 +14,7 @@ export declare class WebglRenderer implements Renderer {
13
14
  onFatal(handler: () => void): void;
14
15
  renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void;
15
16
  beginFlip(from: SpreadContent, to: SpreadContent, direction: FlipDirection, options?: RenderOptions): void;
16
- setFlipProgress(t: number, direction: FlipDirection): void;
17
+ setFlipProgress(t: number, _direction: FlipDirection): void;
17
18
  setViewTransform(scale: number, x: number, y: number): void;
18
19
  measure(): LayoutMetrics;
19
20
  }
@@ -0,0 +1,16 @@
1
+ import type { Source } from './types';
2
+ export interface CompositionOptions {
3
+ /** Image URL prepended as a lone front cover (adds a page). */
4
+ frontCover?: string | null;
5
+ /** Image URL appended as a lone back cover (adds a page). */
6
+ backCover?: string | null;
7
+ /** Replace source pages with image URLs, keyed by 0-based source index (negative = from the end). */
8
+ pages?: Record<number, string> | null;
9
+ }
10
+ /**
11
+ * Wrap `base` so image covers/replacements sit alongside it, or return `base`
12
+ * unchanged when nothing is requested. Covers ADD pages (front prepended, back
13
+ * appended); `pages` REPLACE existing source pages. `pages` indexes the base's own
14
+ * pages, so it is unaffected by an added cover.
15
+ */
16
+ export declare function composeSource(base: Source, options: CompositionOptions): Source;
@@ -18,4 +18,10 @@ export interface Source {
18
18
  prefetch(indices: number[]): void;
19
19
  /** Release any decoded resources. */
20
20
  destroy(): void;
21
+ /**
22
+ * Optional: register a handler called when a page's content has been upgraded
23
+ * (e.g. a progressive PDF render swapping low-res for crisp), so the engine can
24
+ * re-render that page if it's on screen.
25
+ */
26
+ onPageUpdate?(handler: (index: number) => void): void;
21
27
  }
@@ -0,0 +1,12 @@
1
+ //#region src/geometry/curls/types.ts
2
+ var e = [
3
+ "roll",
4
+ "cone",
5
+ "leaf",
6
+ "flick",
7
+ "simple"
8
+ ], t = "cone";
9
+ //#endregion
10
+ export { t as n, e as t };
11
+
12
+ //# sourceMappingURL=types-D-rEO00p.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-D-rEO00p.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\nexport type CurlType = 'roll' | 'cone' | 'leaf' | 'flick' | 'simple';\n\nexport const CURL_TYPES: CurlType[] = ['roll', 'cone', 'leaf', 'flick', 'simple'];\n\nexport const DEFAULT_CURL: CurlType = 'cone';\n"],"mappings":";AA4BA,IAAa,IAAyB;CAAC;CAAQ;CAAQ;CAAQ;CAAS;AAAQ,GAEnE,IAAyB"}
@@ -0,0 +1,558 @@
1
+ import { n as e } from "./types-D-rEO00p.js";
2
+ //#region src/geometry/curls/mesh.ts
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/simple.ts
148
+ function C(e, t, r, i) {
149
+ let a = e.cols + 1, o = e.rows + 1, s = e.positions, c = i * Math.PI, l = Math.cos(c), u = Math.sin(c);
150
+ for (let n = 0; n < a; n++) {
151
+ let i = n / e.cols * t, c = i * l, d = i * u;
152
+ for (let t = 0; t < o; t++) {
153
+ let i = (t * a + n) * 3;
154
+ s[i] = c, s[i + 1] = t / e.rows * r, s[i + 2] = d;
155
+ }
156
+ }
157
+ n(e);
158
+ }
159
+ //#endregion
160
+ //#region src/geometry/curls/index.ts
161
+ var w = {
162
+ roll: {
163
+ deform: r,
164
+ anchored: !1
165
+ },
166
+ cone: {
167
+ deform: f,
168
+ anchored: !0
169
+ },
170
+ leaf: {
171
+ deform: b,
172
+ anchored: !0
173
+ },
174
+ flick: {
175
+ deform: S,
176
+ anchored: !0
177
+ },
178
+ simple: {
179
+ deform: C,
180
+ anchored: !1
181
+ }
182
+ };
183
+ //#endregion
184
+ //#region src/renderer/gl/context.ts
185
+ function T(e) {
186
+ let t = e.getContext("webgl2", {
187
+ alpha: !0,
188
+ premultipliedAlpha: !0,
189
+ antialias: !0,
190
+ depth: !1,
191
+ stencil: !1,
192
+ preserveDrawingBuffer: !0
193
+ });
194
+ if (t === null) throw Error("WebglRenderer: could not create a WebGL2 context.");
195
+ return t;
196
+ }
197
+ //#endregion
198
+ //#region src/renderer/gl/program.ts
199
+ function E(e, t, n) {
200
+ let r = e.createProgram();
201
+ if (r === null) throw Error("WebglRenderer: could not create a program.");
202
+ let i = D(e, e.VERTEX_SHADER, t), a = D(e, e.FRAGMENT_SHADER, n);
203
+ if (e.attachShader(r, i), e.attachShader(r, a), e.linkProgram(r), e.deleteShader(i), e.deleteShader(a), !e.getProgramParameter(r, e.LINK_STATUS)) {
204
+ let t = e.getProgramInfoLog(r);
205
+ throw e.deleteProgram(r), Error(`WebglRenderer: program link failed: ${t ?? "unknown error"}`);
206
+ }
207
+ return r;
208
+ }
209
+ function D(e, t, n) {
210
+ let r = e.createShader(t);
211
+ if (r === null) throw Error("WebglRenderer: could not create a shader.");
212
+ if (e.shaderSource(r, n), e.compileShader(r), !e.getShaderParameter(r, e.COMPILE_STATUS)) {
213
+ let t = e.getShaderInfoLog(r);
214
+ throw e.deleteShader(r), Error(`WebglRenderer: shader compile failed: ${t ?? "unknown error"}`);
215
+ }
216
+ return r;
217
+ }
218
+ //#endregion
219
+ //#region src/renderer/gl/texture.ts
220
+ function O(e) {
221
+ let t = e.createTexture();
222
+ if (t === null) throw Error("WebglRenderer: could not create a texture.");
223
+ 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;
224
+ }
225
+ function k(e, t, n) {
226
+ 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);
227
+ }
228
+ //#endregion
229
+ //#region src/renderer/webglRenderer.ts
230
+ var A = "#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}", j = "#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}", M = "#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 = "#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, F = 36, I = .7, L = .8, R = 4e3, z = 256 * 1024 * 1024;
231
+ function B(e) {
232
+ return e.left === null == (e.right === null) ? 0 : e.right === null ? 1 : -1;
233
+ }
234
+ var V = class {
235
+ #e = null;
236
+ #t = null;
237
+ #n = null;
238
+ #r = null;
239
+ #i = null;
240
+ #a = null;
241
+ #o = null;
242
+ #s = null;
243
+ #c = null;
244
+ #l = 0;
245
+ #u = t(P, F);
246
+ #d = /* @__PURE__ */ new Map();
247
+ #f = 0;
248
+ #p = null;
249
+ #m = {};
250
+ #h = {};
251
+ #g = {
252
+ scale: 1,
253
+ tx: 0,
254
+ ty: 0
255
+ };
256
+ #_ = 1;
257
+ #v = null;
258
+ #y = !1;
259
+ #b = null;
260
+ #x = 0;
261
+ #S = e;
262
+ #C = { y: .5 };
263
+ #w = 0;
264
+ #T = 0;
265
+ #E = 0;
266
+ #D = 0;
267
+ #O = 0;
268
+ #k = 0;
269
+ #A = !1;
270
+ #j = !1;
271
+ #M = null;
272
+ #N = null;
273
+ mount(e) {
274
+ this.#e = e;
275
+ let t = e.ownerDocument, n = t.createElement("canvas");
276
+ 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 = T(n), this.#H(), Promise.resolve();
277
+ }
278
+ destroy() {
279
+ this.#V();
280
+ let e = this.#t;
281
+ e !== null && (e.removeEventListener("webglcontextlost", this.#L), e.removeEventListener("webglcontextrestored", this.#R), e.ownerDocument.removeEventListener("visibilitychange", this.#z));
282
+ let t = this.#n;
283
+ if (t !== null) {
284
+ for (let e of this.#d.values()) t.deleteTexture(e.tex);
285
+ this.#p !== null && t.deleteTexture(this.#p), this.#a !== null && t.deleteVertexArray(this.#a), this.#o !== null && t.deleteVertexArray(this.#o), this.#r !== null && t.deleteProgram(this.#r), this.#i !== null && t.deleteProgram(this.#i), t.getExtension("WEBGL_lose_context")?.loseContext();
286
+ }
287
+ e?.remove(), this.#d.clear(), this.#f = 0, this.#p = null, this.#e = null, this.#t = null, this.#n = null, this.#v = null, this.#b = null;
288
+ }
289
+ onFatal(e) {
290
+ this.#N = e;
291
+ }
292
+ renderSpread(e, t, n) {
293
+ this.#v = t, this.#y = n?.fill ?? !1, this.#b = null, this.#P(t), this.#G();
294
+ }
295
+ #P(e) {
296
+ let t = e.left ?? e.right;
297
+ t && t.height && (this.#D = t.width / t.height);
298
+ }
299
+ beginFlip(e, t, n, r) {
300
+ let i = r?.fill ?? !1;
301
+ this.#x = 0, r?.curl && (this.#S = r.curl), r?.anchor && (this.#C = r.anchor), this.#T = i ? 0 : B(e), this.#E = i ? 0 : B(t), this.#P(t), i ? this.#b = {
302
+ underLeft: null,
303
+ underRight: null,
304
+ underFull: t.right ?? t.left,
305
+ front: e.right ?? e.left,
306
+ back: e.right ?? e.left,
307
+ dir: n === "forward" ? 1 : -1,
308
+ fill: !0
309
+ } : n === "forward" ? this.#b = {
310
+ underLeft: e.left,
311
+ underRight: t.right,
312
+ underFull: null,
313
+ front: e.right,
314
+ back: t.left,
315
+ dir: 1,
316
+ fill: !1
317
+ } : this.#b = {
318
+ underLeft: t.left,
319
+ underRight: e.right,
320
+ underFull: null,
321
+ front: e.left,
322
+ back: t.right,
323
+ dir: -1,
324
+ fill: !1
325
+ }, this.#G();
326
+ }
327
+ setFlipProgress(e, t) {
328
+ this.#x = e, this.#G();
329
+ }
330
+ setViewTransform(e, t, n) {
331
+ this.#g = {
332
+ scale: e,
333
+ tx: t,
334
+ ty: n
335
+ }, this.#G();
336
+ }
337
+ measure() {
338
+ let e = this.#e, t = e?.clientWidth ?? 0, n = e?.clientHeight ?? 0, r = this.#D > 0, i = r ? this.#F() : void 0, a = r ? this.#I() : void 0;
339
+ return {
340
+ containerWidth: t,
341
+ containerHeight: n,
342
+ pageWidth: t / 2,
343
+ pageHeight: n,
344
+ book: i,
345
+ content: a
346
+ };
347
+ }
348
+ #F() {
349
+ let e = this.#e, t = e?.clientWidth ?? 0, n = e?.clientHeight ?? 0;
350
+ if (this.#D <= 0 || t <= 0 || n <= 0) return {
351
+ x: 0,
352
+ y: 0,
353
+ width: t,
354
+ height: n
355
+ };
356
+ let r = (this.#y ? 1 : 2) * this.#D, i = t, a = n;
357
+ return r > t / n ? a = t / r : i = n * r, {
358
+ x: (t - i) / 2,
359
+ y: (n - a) / 2,
360
+ width: i,
361
+ height: a
362
+ };
363
+ }
364
+ #I() {
365
+ let e = this.#F();
366
+ return !this.#y && this.#v !== null && B(this.#v) !== 0 ? {
367
+ x: e.x + e.width / 4,
368
+ y: e.y,
369
+ width: e.width / 2,
370
+ height: e.height
371
+ } : e;
372
+ }
373
+ #L = (e) => {
374
+ e.preventDefault(), this.#A = !0, this.#M === null && (this.#M = setTimeout(() => this.#B(), R));
375
+ };
376
+ #R = () => {
377
+ this.#V(), this.#A = !1, this.#H(), this.#G();
378
+ };
379
+ #z = () => {
380
+ this.#t?.ownerDocument.visibilityState === "visible" && this.#G();
381
+ };
382
+ #B() {
383
+ this.#j || (this.#j = !0, this.#N?.());
384
+ }
385
+ #V() {
386
+ this.#M !== null && (clearTimeout(this.#M), this.#M = null);
387
+ }
388
+ #H() {
389
+ let e = this.#n;
390
+ if (e !== null) {
391
+ this.#r = E(e, A, j), this.#i = E(e, M, N);
392
+ for (let t of [
393
+ "uViewport",
394
+ "uRect",
395
+ "uView",
396
+ "uTex",
397
+ "uGutterSide"
398
+ ]) this.#m[t] = e.getUniformLocation(this.#r, t);
399
+ for (let t of [
400
+ "uViewport",
401
+ "uView",
402
+ "uOriginX",
403
+ "uDir",
404
+ "uLeafW",
405
+ "uFront",
406
+ "uBack",
407
+ "uGloss",
408
+ "uAlpha"
409
+ ]) this.#h[t] = e.getUniformLocation(this.#i, t);
410
+ this.#a = this.#U(e, this.#r), this.#W(e, this.#i), this.#d.clear(), this.#f = 0, this.#p = this.#te(e), e.useProgram(this.#r), e.uniform1i(this.#m.uTex ?? null, 0), e.useProgram(this.#i), e.uniform1i(this.#h.uFront ?? null, 0), e.uniform1i(this.#h.uBack ?? null, 1), e.clearColor(0, 0, 0, 0), e.enable(e.BLEND), e.blendFunc(e.ONE, e.ONE_MINUS_SRC_ALPHA);
411
+ }
412
+ }
413
+ #U(e, t) {
414
+ let n = e.createVertexArray(), r = e.createBuffer();
415
+ if (n === null || r === null) throw Error("WebglRenderer: could not create buffers.");
416
+ e.bindVertexArray(n), e.bindBuffer(e.ARRAY_BUFFER, r), e.bufferData(e.ARRAY_BUFFER, new Float32Array([
417
+ 0,
418
+ 0,
419
+ 1,
420
+ 0,
421
+ 0,
422
+ 1,
423
+ 1,
424
+ 1
425
+ ]), e.STATIC_DRAW);
426
+ let i = e.getAttribLocation(t, "aUnit");
427
+ return e.enableVertexAttribArray(i), e.vertexAttribPointer(i, 2, e.FLOAT, !1, 0, 0), e.bindVertexArray(null), n;
428
+ }
429
+ #W(e, t) {
430
+ let n = [];
431
+ for (let e = 0; e < F; e++) for (let t = 0; t < P; t++) {
432
+ let r = e * 29 + t, i = r + 1, a = r + 29, o = a + 1;
433
+ n.push(r, a, i, i, a, o);
434
+ }
435
+ this.#l = n.length;
436
+ let r = e.createVertexArray();
437
+ this.#s = e.createBuffer(), this.#c = e.createBuffer();
438
+ let i = e.createBuffer(), a = e.createBuffer();
439
+ if (r === null || this.#s === null || this.#c === null || i === null || a === null) throw Error("WebglRenderer: could not create mesh buffers.");
440
+ e.bindVertexArray(r), e.bindBuffer(e.ARRAY_BUFFER, this.#s), e.bufferData(e.ARRAY_BUFFER, this.#u.positions, e.DYNAMIC_DRAW);
441
+ let o = e.getAttribLocation(t, "aPos");
442
+ e.enableVertexAttribArray(o), e.vertexAttribPointer(o, 3, e.FLOAT, !1, 0, 0), e.bindBuffer(e.ARRAY_BUFFER, this.#c), e.bufferData(e.ARRAY_BUFFER, this.#u.normals, e.DYNAMIC_DRAW);
443
+ let s = e.getAttribLocation(t, "aNormal");
444
+ e.enableVertexAttribArray(s), e.vertexAttribPointer(s, 3, e.FLOAT, !1, 0, 0), e.bindBuffer(e.ARRAY_BUFFER, i), e.bufferData(e.ARRAY_BUFFER, this.#u.uvs, e.STATIC_DRAW);
445
+ let c = e.getAttribLocation(t, "aUv");
446
+ e.enableVertexAttribArray(c), e.vertexAttribPointer(c, 2, e.FLOAT, !1, 0, 0), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, a), e.bufferData(e.ELEMENT_ARRAY_BUFFER, new Uint16Array(n), e.STATIC_DRAW), e.bindVertexArray(null), this.#o = r;
447
+ }
448
+ #G() {
449
+ let e = this.#n, t = this.#t;
450
+ e === null || t === null || this.#A || e.isContextLost() || (this.#ne(), e.clear(e.COLOR_BUFFER_BIT), this.#K(), this.#b === null ? this.#v !== null && this.#q() : this.#J());
451
+ }
452
+ #K() {
453
+ let e = this.#n, t = this.#t, n = t.width, r = t.height;
454
+ if (this.#D > 0) {
455
+ let e = (this.#y ? 1 : 2) * this.#D;
456
+ e > n / r ? (this.#O = n, this.#k = Math.round(n / e)) : (this.#k = r, this.#O = Math.round(r * e));
457
+ } else this.#O = n, this.#k = r;
458
+ let i = Math.round((n - this.#O) / 2), a = Math.round((r - this.#k) / 2);
459
+ e.viewport(i, a, this.#O, this.#k);
460
+ }
461
+ #q() {
462
+ let e = this.#O, t = this.#k, n = this.#v;
463
+ this.#w = this.#y ? 0 : B(n) * (e / 4), this.#X(), this.#y ? this.#Z({
464
+ x: 0,
465
+ y: 0,
466
+ w: e,
467
+ h: t
468
+ }, n.right ?? n.left) : (this.#Z({
469
+ x: 0,
470
+ y: 0,
471
+ w: e / 2,
472
+ h: t
473
+ }, n.left, +!!n.right), this.#Z({
474
+ x: e / 2,
475
+ y: 0,
476
+ w: e / 2,
477
+ h: t
478
+ }, n.right, n.left ? -1 : 0));
479
+ }
480
+ #J() {
481
+ let e = this.#n, t = this.#O, n = this.#k, r = this.#b;
482
+ if (this.#w = r.fill ? 0 : (this.#T + (this.#E - this.#T) * this.#x) * (t / 4), this.#X(), r.fill ? this.#Z({
483
+ x: 0,
484
+ y: 0,
485
+ w: t,
486
+ h: n
487
+ }, r.underFull) : (this.#Z({
488
+ x: 0,
489
+ y: 0,
490
+ w: t / 2,
491
+ h: n
492
+ }, r.underLeft, 1), this.#Z({
493
+ x: t / 2,
494
+ y: 0,
495
+ w: t / 2,
496
+ h: n
497
+ }, r.underRight, -1)), r.front === null && r.back === null) return;
498
+ let i = r.fill ? t : t / 2, a = r.fill ? r.dir > 0 ? 0 : t : t / 2;
499
+ w[this.#S].deform(this.#u, i, n, this.#x, {
500
+ y: this.#C.y,
501
+ fill: r.fill
502
+ }), 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 === "simple" ? 0 : 1), e.uniform1f(this.#h.uAlpha ?? null, r.fill ? this.#Y() : 1), e.drawElements(e.TRIANGLES, this.#l, e.UNSIGNED_SHORT, 0);
503
+ }
504
+ #Y() {
505
+ let e = this.#S === "roll" || this.#S === "simple" ? I : L, t = (this.#x - e) / (1 - e);
506
+ return t <= 0 ? 1 : t >= 1 ? 0 : (1 - t) ** 1.5;
507
+ }
508
+ #X() {
509
+ let e = this.#n;
510
+ e.useProgram(this.#r), e.bindVertexArray(this.#a), e.activeTexture(e.TEXTURE0), e.uniform2f(this.#m.uViewport ?? null, this.#O, this.#k), e.uniform3f(this.#m.uView ?? null, this.#g.scale, this.#g.tx * this.#_ + this.#w, this.#g.ty * this.#_);
511
+ }
512
+ #Z(e, t, n = 0) {
513
+ let r = this.#n;
514
+ t !== null && (this.#Q(r.TEXTURE0, t), r.uniform4f(this.#m.uRect ?? null, e.x, e.y, e.w, e.h), r.uniform1f(this.#m.uGutterSide ?? null, n), r.drawArrays(r.TRIANGLE_STRIP, 0, 4));
515
+ }
516
+ #Q(e, t) {
517
+ let n = this.#n;
518
+ n.activeTexture(e), n.bindTexture(n.TEXTURE_2D, t === null ? this.#p : this.#$(t));
519
+ }
520
+ #$(e) {
521
+ let t = this.#n, n = this.#d.get(e);
522
+ if (n !== void 0) return this.#d.delete(e), this.#d.set(e, n), n.tex;
523
+ let r = O(t);
524
+ k(t, r, e);
525
+ let i = e, a = i.width * i.height * 4;
526
+ return this.#d.set(e, {
527
+ tex: r,
528
+ bytes: a
529
+ }), this.#f += a, this.#ee(), r;
530
+ }
531
+ #ee() {
532
+ let e = this.#n;
533
+ for (let [t, n] of this.#d) {
534
+ if (this.#f <= z || this.#d.size <= 4) break;
535
+ this.#d.delete(t), this.#f -= n.bytes, e.deleteTexture(n.tex);
536
+ }
537
+ }
538
+ #te(e) {
539
+ let t = O(e);
540
+ 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([
541
+ 0,
542
+ 0,
543
+ 0,
544
+ 0
545
+ ])), t;
546
+ }
547
+ #ne() {
548
+ let e = this.#n, t = this.#t, n = this.#e;
549
+ if (e === null || t === null || n === null) return;
550
+ this.#_ = typeof devicePixelRatio == "number" ? devicePixelRatio : 1;
551
+ let r = Math.max(1, Math.round(n.clientWidth * this.#_)), i = Math.max(1, Math.round(n.clientHeight * this.#_));
552
+ (t.width !== r || t.height !== i) && (t.width = r, t.height = i, e.viewport(0, 0, r, i));
553
+ }
554
+ };
555
+ //#endregion
556
+ export { V as WebglRenderer };
557
+
558
+ //# sourceMappingURL=webglRenderer-Bg6VF4CW.js.map