@zinejs/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,306 @@
1
+ import { t as e } from "./flipProgressToPose-DZWSuKoE.js";
2
+ //#region src/renderer/gl/context.ts
3
+ function t(e) {
4
+ let t = e.getContext("webgl2", {
5
+ alpha: !0,
6
+ premultipliedAlpha: !0,
7
+ antialias: !0,
8
+ depth: !1,
9
+ stencil: !1,
10
+ preserveDrawingBuffer: !0
11
+ });
12
+ if (t === null) throw Error("WebglRenderer: could not create a WebGL2 context.");
13
+ return t;
14
+ }
15
+ //#endregion
16
+ //#region src/renderer/gl/program.ts
17
+ function n(e, t, n) {
18
+ let i = e.createProgram();
19
+ if (i === null) throw Error("WebglRenderer: could not create a program.");
20
+ let a = r(e, e.VERTEX_SHADER, t), o = r(e, e.FRAGMENT_SHADER, n);
21
+ if (e.attachShader(i, a), e.attachShader(i, o), e.linkProgram(i), e.deleteShader(a), e.deleteShader(o), !e.getProgramParameter(i, e.LINK_STATUS)) {
22
+ let t = e.getProgramInfoLog(i);
23
+ throw e.deleteProgram(i), Error(`WebglRenderer: program link failed: ${t ?? "unknown error"}`);
24
+ }
25
+ return i;
26
+ }
27
+ function r(e, t, n) {
28
+ let r = e.createShader(t);
29
+ if (r === null) throw Error("WebglRenderer: could not create a shader.");
30
+ if (e.shaderSource(r, n), e.compileShader(r), !e.getShaderParameter(r, e.COMPILE_STATUS)) {
31
+ let t = e.getShaderInfoLog(r);
32
+ throw e.deleteShader(r), Error(`WebglRenderer: shader compile failed: ${t ?? "unknown error"}`);
33
+ }
34
+ return r;
35
+ }
36
+ //#endregion
37
+ //#region src/renderer/gl/texture.ts
38
+ function i(e) {
39
+ let t = e.createTexture();
40
+ if (t === null) throw Error("WebglRenderer: could not create a texture.");
41
+ 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;
42
+ }
43
+ function a(e, t, n) {
44
+ 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);
45
+ }
46
+ //#endregion
47
+ //#region src/renderer/webglRenderer.ts
48
+ var o = "#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}", s = "#version 300 es\nprecision mediump float;\nin vec2 vUv;\nuniform sampler2D uTex;\nuniform float uGutterSide; // +1 = spine at the right edge, -1 = at the left edge, 0 = none\nout vec4 outColor;\nvoid main() {\n vec4 c = texture(uTex, vUv);\n if (uGutterSide != 0.0) {\n // Soft, permanent gutter shadow: darken toward the spine edge where the pages\n // meet, so the turning leaf's spine shadow lands into it instead of vanishing.\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}", c = "#version 300 es\nin vec2 aUnit;\nuniform vec2 uViewport;\nuniform vec4 uLeaf; // spineX, y, width, height (device px)\nuniform vec3 uView; // scale, tx, ty\nuniform float uAngle; // spine rotation 0..PI\nuniform float uCurl; // 0..1 bend amount\nuniform float uDir; // +1 leaf sweeps from +x side, -1 from -x side\nout vec2 vUv;\nout float vU;\nout float vLight; // surface facing: +1 toward viewer, 0 edge-on, -1 away\nconst float CAM = 3.0; // perspective camera distance, in page widths (bigger = subtler)\nvoid main() {\n float u = aUnit.x;\n float W = uLeaf.z;\n // Cylinder wrap of the turning sheet, rotated about the spine (vertical y axis).\n float bend = uCurl * 1.5;\n float phi, xw, zw;\n if (bend > 0.001) {\n float R = W / bend;\n phi = u * bend;\n xw = R * sin(phi);\n zw = R * (1.0 - cos(phi));\n } else {\n phi = 0.0;\n xw = u * W;\n zw = 0.0;\n }\n float X = xw * cos(uAngle) - zw * sin(uAngle); // in-plane\n float Z = xw * sin(uAngle) + zw * cos(uAngle); // depth (+ toward viewer)\n float px = uLeaf.x + uDir * X;\n float py = uLeaf.y + aUnit.y * uLeaf.w;\n // Perspective by depth. X may bulge toward the viewer for 3D pop; Y only ever\n // SHRINKS (persp clamped <= 1) about the page's own vertical center, so the top\n // is never pushed past the frame (that was the hidden-top bug).\n float D = W * CAM;\n float persp = D / max(D - Z, 1.0);\n float cx = uViewport.x * 0.5;\n float cy = uLeaf.y + 0.5 * uLeaf.w;\n px = cx + (px - cx) * persp;\n py = cy + (py - cy) * min(persp, 1.0);\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 = aUnit;\n vU = u;\n vLight = cos(phi + uAngle);\n}", l = "#version 300 es\nprecision mediump float;\nin vec2 vUv;\nin float vU;\nin float vLight;\nuniform sampler2D uFront;\nuniform sampler2D uBack;\nuniform highp float uDir; // must match the vertex stage's default highp\nout vec4 outColor;\nvoid main() {\n // A backward flip (uDir < 0) mirrors the mesh horizontally, which flips both the\n // projected winding (gl_FrontFacing) and the page's u->x mapping; undo both here.\n float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;\n // Front/back from the sheet's smooth facing (vLight), NOT gl_FrontFacing: the\n // winding-based test flickers per-pixel at grazing angles as the curl overlaps.\n bool showFront = vLight > 0.0;\n vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));\n\n // Diffuse curl shading from the (smooth) facing magnitude: face-on bright, edge-on dark.\n float diff = clamp(abs(vLight), 0.0, 1.0);\n float lit = mix(0.72, 1.0, diff); // ambient floor, never crushed\n float sheen = smoothstep(0.6, 0.95, diff) * (1.0 - diff) * 0.5; // soft crest highlight\n // Spine crease uses the SAME curve as the static gutter shadow, so when the leaf\n // lands the shadow is continuous instead of popping away.\n lit *= mix(0.72, 1.0, smoothstep(0.0, 0.10, vU));\n\n c.rgb = clamp(c.rgb * lit + sheen, 0.0, 1.0);\n outColor = c;\n}", u = 24, d = 4e3, f = 256 * 1024 * 1024, p = class {
49
+ #e = null;
50
+ #t = null;
51
+ #n = null;
52
+ #r = null;
53
+ #i = null;
54
+ #a = null;
55
+ #o = null;
56
+ #s = 0;
57
+ #c = /* @__PURE__ */ new Map();
58
+ #l = 0;
59
+ #u = null;
60
+ #d = {};
61
+ #f = {};
62
+ #p = {
63
+ scale: 1,
64
+ tx: 0,
65
+ ty: 0
66
+ };
67
+ #m = 1;
68
+ #h = null;
69
+ #g = !1;
70
+ #_ = null;
71
+ #v = 0;
72
+ #y = "forward";
73
+ #b = !1;
74
+ #x = !1;
75
+ #S = null;
76
+ #C = null;
77
+ mount(e) {
78
+ this.#e = e;
79
+ let n = e.ownerDocument, r = n.createElement("canvas");
80
+ return r.style.cssText = "display:block;width:100%;height:100%;", e.append(r), this.#t = r, r.addEventListener("webglcontextlost", this.#w), r.addEventListener("webglcontextrestored", this.#T), n.addEventListener("visibilitychange", this.#E), this.#n = t(r), this.#k(), Promise.resolve();
81
+ }
82
+ destroy() {
83
+ this.#O();
84
+ let e = this.#t;
85
+ e !== null && (e.removeEventListener("webglcontextlost", this.#w), e.removeEventListener("webglcontextrestored", this.#T), e.ownerDocument.removeEventListener("visibilitychange", this.#E));
86
+ let t = this.#n;
87
+ if (t !== null) {
88
+ for (let e of this.#c.values()) t.deleteTexture(e.tex);
89
+ this.#u !== null && t.deleteTexture(this.#u), 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();
90
+ }
91
+ e?.remove(), this.#c.clear(), this.#l = 0, this.#u = null, this.#e = null, this.#t = null, this.#n = null, this.#h = null, this.#_ = null;
92
+ }
93
+ onFatal(e) {
94
+ this.#C = e;
95
+ }
96
+ renderSpread(e, t, n) {
97
+ this.#h = t, this.#g = n?.fill ?? !1, this.#_ = null, this.#A();
98
+ }
99
+ beginFlip(e, t, n, r) {
100
+ let i = r?.fill ?? !1;
101
+ this.#y = n, this.#v = 0, i ? this.#_ = {
102
+ underLeft: null,
103
+ underRight: null,
104
+ underFull: t.right ?? t.left,
105
+ front: e.right ?? e.left,
106
+ back: t.right ?? t.left,
107
+ dir: n === "forward" ? 1 : -1,
108
+ spineAtStart: n === "forward" ? 0 : 1,
109
+ fill: !0
110
+ } : n === "forward" ? this.#_ = {
111
+ underLeft: e.left,
112
+ underRight: t.right,
113
+ underFull: null,
114
+ front: e.right,
115
+ back: t.left,
116
+ dir: 1,
117
+ spineAtStart: .5,
118
+ fill: !1
119
+ } : this.#_ = {
120
+ underLeft: t.left,
121
+ underRight: e.right,
122
+ underFull: null,
123
+ front: e.left,
124
+ back: t.right,
125
+ dir: -1,
126
+ spineAtStart: .5,
127
+ fill: !1
128
+ }, this.#A();
129
+ }
130
+ setFlipProgress(e, t) {
131
+ this.#v = e, this.#y = t, this.#A();
132
+ }
133
+ setViewTransform(e, t, n) {
134
+ this.#p = {
135
+ scale: e,
136
+ tx: t,
137
+ ty: n
138
+ }, this.#A();
139
+ }
140
+ measure() {
141
+ let e = this.#e, t = e?.clientWidth ?? 0, n = e?.clientHeight ?? 0;
142
+ return {
143
+ containerWidth: t,
144
+ containerHeight: n,
145
+ pageWidth: t / 2,
146
+ pageHeight: n
147
+ };
148
+ }
149
+ #w = (e) => {
150
+ e.preventDefault(), this.#b = !0, this.#S === null && (this.#S = setTimeout(() => this.#D(), d));
151
+ };
152
+ #T = () => {
153
+ this.#O(), this.#b = !1, this.#k(), this.#A();
154
+ };
155
+ #E = () => {
156
+ this.#t?.ownerDocument.visibilityState === "visible" && this.#A();
157
+ };
158
+ #D() {
159
+ this.#x || (this.#x = !0, this.#C?.());
160
+ }
161
+ #O() {
162
+ this.#S !== null && (clearTimeout(this.#S), this.#S = null);
163
+ }
164
+ #k() {
165
+ let e = this.#n;
166
+ if (e === null) return;
167
+ this.#r = n(e, o, s), this.#i = n(e, c, l);
168
+ for (let t of [
169
+ "uViewport",
170
+ "uRect",
171
+ "uView",
172
+ "uTex",
173
+ "uGutterSide"
174
+ ]) this.#d[t] = e.getUniformLocation(this.#r, t);
175
+ for (let t of [
176
+ "uViewport",
177
+ "uLeaf",
178
+ "uView",
179
+ "uAngle",
180
+ "uCurl",
181
+ "uDir",
182
+ "uFront",
183
+ "uBack"
184
+ ]) this.#f[t] = e.getUniformLocation(this.#i, t);
185
+ this.#a = this.#z(e, this.#r, new Float32Array([
186
+ 0,
187
+ 0,
188
+ 1,
189
+ 0,
190
+ 0,
191
+ 1,
192
+ 1,
193
+ 1
194
+ ]));
195
+ let t = [];
196
+ for (let e = 0; e <= u; e++) {
197
+ let n = e / u;
198
+ t.push(n, 0, n, 1);
199
+ }
200
+ this.#s = 50, this.#o = this.#z(e, this.#i, new Float32Array(t)), this.#c.clear(), this.#l = 0, this.#u = this.#R(e), e.useProgram(this.#r), e.uniform1i(this.#d.uTex ?? null, 0), e.useProgram(this.#i), e.uniform1i(this.#f.uFront ?? null, 0), e.uniform1i(this.#f.uBack ?? null, 1), e.clearColor(0, 0, 0, 0), e.enable(e.BLEND), e.blendFunc(e.ONE, e.ONE_MINUS_SRC_ALPHA);
201
+ }
202
+ #A() {
203
+ let e = this.#n, t = this.#t;
204
+ e === null || t === null || this.#b || e.isContextLost() || (this.#B(), e.clear(e.COLOR_BUFFER_BIT), this.#_ === null ? this.#h !== null && this.#j() : this.#M());
205
+ }
206
+ #j() {
207
+ let e = this.#t, t = e.width, n = e.height;
208
+ this.#N();
209
+ let r = this.#h;
210
+ this.#g ? this.#P({
211
+ x: 0,
212
+ y: 0,
213
+ w: t,
214
+ h: n
215
+ }, r.right ?? r.left) : (this.#P({
216
+ x: 0,
217
+ y: 0,
218
+ w: t / 2,
219
+ h: n
220
+ }, r.left, 1), this.#P({
221
+ x: t / 2,
222
+ y: 0,
223
+ w: t / 2,
224
+ h: n
225
+ }, r.right, -1));
226
+ }
227
+ #M() {
228
+ let t = this.#n, n = this.#t, r = n.width, i = n.height, a = this.#_, o = e(this.#v);
229
+ if (this.#N(), a.fill ? this.#P({
230
+ x: 0,
231
+ y: 0,
232
+ w: r,
233
+ h: i
234
+ }, a.underFull) : (this.#P({
235
+ x: 0,
236
+ y: 0,
237
+ w: r / 2,
238
+ h: i
239
+ }, a.underLeft, 1), this.#P({
240
+ x: r / 2,
241
+ y: 0,
242
+ w: r / 2,
243
+ h: i
244
+ }, a.underRight, -1)), a.front === null && a.back === null) return;
245
+ this.#F(t.TEXTURE0, a.front), this.#F(t.TEXTURE1, a.back), t.activeTexture(t.TEXTURE0);
246
+ let s = a.fill ? r : r / 2, c = a.spineAtStart * r;
247
+ t.useProgram(this.#i), t.bindVertexArray(this.#o), t.uniform2f(this.#f.uViewport ?? null, r, i), t.uniform3f(this.#f.uView ?? null, this.#p.scale, this.#p.tx * this.#m, this.#p.ty * this.#m), t.uniform4f(this.#f.uLeaf ?? null, c, 0, s, i), t.uniform1f(this.#f.uAngle ?? null, o.angle), t.uniform1f(this.#f.uCurl ?? null, o.curl), t.uniform1f(this.#f.uDir ?? null, a.dir), t.drawArrays(t.TRIANGLE_STRIP, 0, this.#s);
248
+ }
249
+ #N() {
250
+ let e = this.#n, t = this.#t;
251
+ e.useProgram(this.#r), e.bindVertexArray(this.#a), e.activeTexture(e.TEXTURE0), e.uniform2f(this.#d.uViewport ?? null, t.width, t.height), e.uniform3f(this.#d.uView ?? null, this.#p.scale, this.#p.tx * this.#m, this.#p.ty * this.#m);
252
+ }
253
+ #P(e, t, n = 0) {
254
+ let r = this.#n;
255
+ t !== null && (this.#F(r.TEXTURE0, t), r.uniform4f(this.#d.uRect ?? null, e.x, e.y, e.w, e.h), r.uniform1f(this.#d.uGutterSide ?? null, n), r.drawArrays(r.TRIANGLE_STRIP, 0, 4));
256
+ }
257
+ #F(e, t) {
258
+ let n = this.#n;
259
+ n.activeTexture(e), n.bindTexture(n.TEXTURE_2D, t === null ? this.#u : this.#I(t));
260
+ }
261
+ #I(e) {
262
+ let t = this.#n, n = this.#c.get(e);
263
+ if (n !== void 0) return this.#c.delete(e), this.#c.set(e, n), n.tex;
264
+ let r = i(t);
265
+ a(t, r, e);
266
+ let o = e, s = o.width * o.height * 4;
267
+ return this.#c.set(e, {
268
+ tex: r,
269
+ bytes: s
270
+ }), this.#l += s, this.#L(), r;
271
+ }
272
+ #L() {
273
+ let e = this.#n;
274
+ for (let [t, n] of this.#c) {
275
+ if (this.#l <= f || this.#c.size <= 4) break;
276
+ this.#c.delete(t), this.#l -= n.bytes, e.deleteTexture(n.tex);
277
+ }
278
+ }
279
+ #R(e) {
280
+ let t = i(e);
281
+ 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([
282
+ 0,
283
+ 0,
284
+ 0,
285
+ 0
286
+ ])), t;
287
+ }
288
+ #z(e, t, n) {
289
+ let r = e.createVertexArray(), i = e.createBuffer();
290
+ if (r === null || i === null) throw Error("WebglRenderer: could not create buffers.");
291
+ e.bindVertexArray(r), e.bindBuffer(e.ARRAY_BUFFER, i), e.bufferData(e.ARRAY_BUFFER, n, e.STATIC_DRAW);
292
+ let a = e.getAttribLocation(t, "aUnit");
293
+ return e.enableVertexAttribArray(a), e.vertexAttribPointer(a, 2, e.FLOAT, !1, 0, 0), e.bindVertexArray(null), r;
294
+ }
295
+ #B() {
296
+ let e = this.#n, t = this.#t, n = this.#e;
297
+ if (e === null || t === null || n === null) return;
298
+ this.#m = typeof devicePixelRatio == "number" ? devicePixelRatio : 1;
299
+ let r = Math.max(1, Math.round(n.clientWidth * this.#m)), i = Math.max(1, Math.round(n.clientHeight * this.#m));
300
+ (t.width !== r || t.height !== i) && (t.width = r, t.height = i, e.viewport(0, 0, r, i));
301
+ }
302
+ };
303
+ //#endregion
304
+ export { p as WebglRenderer };
305
+
306
+ //# sourceMappingURL=webglRenderer-D-1UyC-B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webglRenderer-D-1UyC-B.js","names":["#container","#canvas","#onContextLost","#onContextRestored","#onVisibilityChange","#gl","#buildGlResources","#clearRestoreTimer","#texCache","#blankTex","#quadVao","#gridVao","#flat","#curl","#cacheBytes","#content","#flip","#fatalHandler","#fill","#render","#flipDir","#flipT","#view","#contextLost","#restoreTimer","#fatal","#fatalFired","#flatU","#curlU","#makeVao","#gridCount","#makeBlankTexture","#resize","#drawFlip","#drawStatic","#useFlat","#drawQuad","#bindFace","#dpr","#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","import { flipProgressToPose } from '../geometry/flipProgressToPose';\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: maps a unit quad to a page rect (device px, top-left origin) with\n// zoom/pan. aUnit.y=0 is the page top and samples the source's top row (no flip).\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 the right edge, -1 = at the left edge, 0 = none\nout vec4 outColor;\nvoid main() {\n vec4 c = texture(uTex, vUv);\n if (uGutterSide != 0.0) {\n // Soft, permanent gutter shadow: darken toward the spine edge where the pages\n // meet, so the turning leaf's spine shadow lands into it instead of vanishing.\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: the turning leaf. u runs from the spine (0) to the free edge (1).\n// The page wraps around a vertical cylinder (radius shrinks with uCurl) and rotates\n// about the spine by uAngle; orthographic (no perspective yet).\nconst CURL_VERT = `#version 300 es\nin vec2 aUnit;\nuniform vec2 uViewport;\nuniform vec4 uLeaf; // spineX, y, width, height (device px)\nuniform vec3 uView; // scale, tx, ty\nuniform float uAngle; // spine rotation 0..PI\nuniform float uCurl; // 0..1 bend amount\nuniform float uDir; // +1 leaf sweeps from +x side, -1 from -x side\nout vec2 vUv;\nout float vU;\nout float vLight; // surface facing: +1 toward viewer, 0 edge-on, -1 away\nconst float CAM = 3.0; // perspective camera distance, in page widths (bigger = subtler)\nvoid main() {\n float u = aUnit.x;\n float W = uLeaf.z;\n // Cylinder wrap of the turning sheet, rotated about the spine (vertical y axis).\n float bend = uCurl * 1.5;\n float phi, xw, zw;\n if (bend > 0.001) {\n float R = W / bend;\n phi = u * bend;\n xw = R * sin(phi);\n zw = R * (1.0 - cos(phi));\n } else {\n phi = 0.0;\n xw = u * W;\n zw = 0.0;\n }\n float X = xw * cos(uAngle) - zw * sin(uAngle); // in-plane\n float Z = xw * sin(uAngle) + zw * cos(uAngle); // depth (+ toward viewer)\n float px = uLeaf.x + uDir * X;\n float py = uLeaf.y + aUnit.y * uLeaf.w;\n // Perspective by depth. X may bulge toward the viewer for 3D pop; Y only ever\n // SHRINKS (persp clamped <= 1) about the page's own vertical center, so the top\n // is never pushed past the frame (that was the hidden-top bug).\n float D = W * CAM;\n float persp = D / max(D - Z, 1.0);\n float cx = uViewport.x * 0.5;\n float cy = uLeaf.y + 0.5 * uLeaf.w;\n px = cx + (px - cx) * persp;\n py = cy + (py - cy) * min(persp, 1.0);\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 = aUnit;\n vU = u;\n vLight = cos(phi + uAngle);\n}`;\n\n// Per-fragment facing: whichever side of the curling sheet faces the viewer shows\n// its own page, so mid-curl you see a sliver of the next page (continuous handoff).\nconst CURL_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nin float vU;\nin float vLight;\nuniform sampler2D uFront;\nuniform sampler2D uBack;\nuniform highp float uDir; // must match the vertex stage's default highp\nout vec4 outColor;\nvoid main() {\n // A backward flip (uDir < 0) mirrors the mesh horizontally, which flips both the\n // projected winding (gl_FrontFacing) and the page's u->x mapping; undo both here.\n float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;\n // Front/back from the sheet's smooth facing (vLight), NOT gl_FrontFacing: the\n // winding-based test flickers per-pixel at grazing angles as the curl overlaps.\n bool showFront = vLight > 0.0;\n vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));\n\n // Diffuse curl shading from the (smooth) facing magnitude: face-on bright, edge-on dark.\n float diff = clamp(abs(vLight), 0.0, 1.0);\n float lit = mix(0.72, 1.0, diff); // ambient floor, never crushed\n float sheen = smoothstep(0.6, 0.95, diff) * (1.0 - diff) * 0.5; // soft crest highlight\n // Spine crease uses the SAME curve as the static gutter shadow, so when the leaf\n // lands the shadow is continuous instead of popping away.\n lit *= mix(0.72, 1.0, smoothstep(0.0, 0.10, vU));\n\n c.rgb = clamp(c.rgb * lit + sheen, 0.0, 1.0);\n outColor = c;\n}`;\n\nconst GRID_COLS = 24;\n// If a lost context is not restored within this window, give up and let the engine\n// fall back to the CSS renderer (§8.4).\nconst RESTORE_TIMEOUT_MS = 4000;\n// GPU texture LRU ceiling (§9). Page textures are cached by content and evicted\n// least-recently-used beyond this, so flips/zooms don't re-upload every frame.\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 / -1 for the curl sweep\n spineAtStart: number; // 0 = spine at left edge, 0.5 = center, 1 = right edge\n fill: boolean;\n}\n\n/**\n * GPU renderer (WebGL2, hand-written, no third-party library). The turning leaf is\n * a grid mesh bent around a cylinder (driven by the shared flipProgressToPose), the\n * static pages are flat textured quads underneath. Restores its GL state in place on\n * context loss; if that fails it signals `onFatal` so the engine can fall back to CSS.\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 #gridVao: WebGLVertexArrayObject | null = null;\n #gridCount = 0;\n #texCache = new Map<TexImageSource, { tex: WebGLTexture; bytes: number }>();\n #cacheBytes = 0;\n #blankTex: WebGLTexture | null = null; // 1x1 transparent, for blank leaf faces\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 #flipDir: FlipDirection = 'forward';\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 // Fill the container as a plain block, so it doesn't depend on the container\n // being positioned (an absolute canvas would escape a static container).\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\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.#gridVao !== null) gl.deleteVertexArray(this.#gridVao);\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.#render();\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.#flipDir = direction;\n this.#flipT = 0;\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 back: to.right ?? to.left,\n dir: direction === 'forward' ? 1 : -1,\n spineAtStart: direction === 'forward' ? 0 : 1,\n fill: true,\n };\n } else if (direction === 'forward') {\n // Right page lifts and swings left about the spine; left stays, right reveals `to`.\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 spineAtStart: 0.5,\n fill: false,\n };\n } else {\n // Left page swings right about the spine; right stays, left reveals `to`.\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 spineAtStart: 0.5,\n fill: false,\n };\n }\n this.#render();\n }\n\n setFlipProgress(t: number, direction: FlipDirection): void {\n this.#flipT = t;\n this.#flipDir = direction;\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 return { containerWidth: w, containerHeight: h, pageWidth: w / 2, pageHeight: h };\n }\n\n // --- context loss / restore ---------------------------------------------------\n\n #onContextLost = (event: Event): void => {\n event.preventDefault(); // required, or the browser won't fire 'restored'\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(); // old GL objects are gone; rebuild on the same context\n this.#render(); // CPU-side content/flip/view were retained, so re-upload + repaint\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', 'uLeaf', 'uView', 'uAngle', 'uCurl', 'uDir', 'uFront', 'uBack']) {\n this.#curlU[name] = gl.getUniformLocation(this.#curl, name);\n }\n\n this.#quadVao = this.#makeVao(gl, this.#flat, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]));\n\n // Grid strip across the width (columns subdivided, 2 rows) for a smooth bend.\n const grid: number[] = [];\n for (let c = 0; c <= GRID_COLS; c++) {\n const u = c / GRID_COLS;\n grid.push(u, 0, u, 1);\n }\n this.#gridCount = (GRID_COLS + 1) * 2;\n this.#gridVao = this.#makeVao(gl, this.#curl, new Float32Array(grid));\n\n // A (re)created context has no live textures; drop the cache and remake the blank.\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 // --- 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 if (this.#flip !== null) this.#drawFlip();\n else if (this.#content !== null) this.#drawStatic();\n }\n\n #drawStatic(): void {\n const canvas = this.#canvas!;\n const w = canvas.width;\n const h = canvas.height;\n this.#useFlat();\n const content = this.#content!;\n if (this.#fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, content.right ?? content.left);\n } else {\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, content.left, 1);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, content.right, -1);\n }\n }\n\n #drawFlip(): void {\n const gl = this.#gl!;\n const canvas = this.#canvas!;\n const w = canvas.width;\n const h = canvas.height;\n const flip = this.#flip!;\n const pose = flipProgressToPose(this.#flipT);\n\n // Static pages underneath.\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 // Turning leaf: both faces uploaded, per-fragment facing picks front vs back.\n if (flip.front === null && flip.back === null) return;\n this.#bindFace(gl.TEXTURE0, flip.front);\n this.#bindFace(gl.TEXTURE1, flip.back);\n gl.activeTexture(gl.TEXTURE0);\n\n const leafW = flip.fill ? w : w / 2;\n const spineX = flip.spineAtStart * w;\n gl.useProgram(this.#curl);\n gl.bindVertexArray(this.#gridVao);\n gl.uniform2f(this.#curlU.uViewport ?? null, w, h);\n gl.uniform3f(this.#curlU.uView ?? null, this.#view.scale, this.#view.tx * this.#dpr, this.#view.ty * this.#dpr);\n gl.uniform4f(this.#curlU.uLeaf ?? null, spineX, 0, leafW, h);\n gl.uniform1f(this.#curlU.uAngle ?? null, pose.angle);\n gl.uniform1f(this.#curlU.uCurl ?? null, pose.curl);\n gl.uniform1f(this.#curlU.uDir ?? null, flip.dir);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.#gridCount);\n }\n\n #useFlat(): void {\n const gl = this.#gl!;\n const canvas = this.#canvas!;\n gl.useProgram(this.#flat);\n gl.bindVertexArray(this.#quadVao);\n gl.activeTexture(gl.TEXTURE0);\n gl.uniform2f(this.#flatU.uViewport ?? null, canvas.width, canvas.height);\n gl.uniform3f(this.#flatU.uView ?? null, this.#view.scale, this.#view.tx * this.#dpr, this.#view.ty * this.#dpr);\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 /** Bind the texture for `source` (a blank 1x1 when null) to a texture unit. */\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 /** Page texture cached by content: uploaded once, reused across frames, LRU-capped. */\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); // move to most-recently-used\n this.#texCache.set(source, existing);\n return existing.tex;\n }\n const tex = createTexture(gl); // binds + uploads on the active unit set by the caller\n uploadTexture(gl, tex, source);\n const dims = source as unknown as { width: number; height: number }; // page rasters have both\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 /** Drop least-recently-used page textures past the byte ceiling (keep a small floor). */\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 #makeVao(gl: WebGL2RenderingContext, program: WebGLProgram, data: Float32Array): 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, data, 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 #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 ADDED
@@ -0,0 +1,84 @@
1
+ import { type Direction } from './engine/spread';
2
+ import { type ZineEventMap } from './engine/emitter';
3
+ import { type RendererOption } from './renderer/select';
4
+ import type { FlipDirection } from './renderer/types';
5
+ import type { Source } from './source/types';
6
+ export interface ZoomOptions {
7
+ /** Whether zooming is allowed at all; default true. */
8
+ enabled?: boolean;
9
+ /** Maximum zoom scale; default 4. */
10
+ max?: number;
11
+ /** Zoom on Ctrl/⌘ + wheel (also how trackpad pinch arrives on desktop); default true. */
12
+ wheel?: boolean;
13
+ /** Zoom levels cycled by double-click (wraps to the first); `false` disables. Default [1, 2, 4]. */
14
+ doubleClick?: number[] | false;
15
+ /**
16
+ * Listen for double-click zoom inside the click-to-flip zones. Default: off in 'edge' mode
17
+ * (edges flip instantly, no delay), on in 'half' mode. Turning it on in 'edge' mode makes
18
+ * clicks wait out `clickFlipDelay` so a double-click can preempt them with a zoom.
19
+ */
20
+ doubleClickInFlipZone?: boolean;
21
+ }
22
+ export interface ZineOptions {
23
+ /** Content source (e.g. an ImageSource). Required. */
24
+ source: Source;
25
+ /** Renderer selection; default 'auto'. */
26
+ renderer?: RendererOption;
27
+ /** Fixed container width in px; omit to let the container/CSS drive the size. */
28
+ width?: number;
29
+ /** Fixed container height in px; omit to let the container/CSS drive the size. */
30
+ height?: number;
31
+ /** Reading direction; default 'ltr'. */
32
+ direction?: Direction;
33
+ /** First page is a lone cover; default false. */
34
+ cover?: boolean;
35
+ /** Page to open on; default 0. */
36
+ startPage?: number;
37
+ /** Flip animation duration in ms; default 500. */
38
+ flipDuration?: number;
39
+ /** How a tap/click turns pages: near an edge ('edge'), by page half ('half'), or 'off'. Default 'edge'. */
40
+ clickToFlip?: 'edge' | 'half' | 'off';
41
+ /** Edge-zone size in px per side, used when clickToFlip is 'edge'. Default 64. */
42
+ clickZoneSize?: number;
43
+ /**
44
+ * Delay (ms) a click waits before flipping, so a double-click can preempt it with a zoom.
45
+ * Omit for auto: 0 when double-click zoom is inactive, 250 when it's active.
46
+ */
47
+ clickFlipDelay?: number;
48
+ /** Zoom behavior. */
49
+ zoom?: ZoomOptions;
50
+ /** Container widths below this (px) switch to one page per spread; default 640. */
51
+ singlePageThreshold?: number;
52
+ }
53
+ /**
54
+ * The public flipbook. Composes the engine (spreads, virtualizer, events), a
55
+ * lazily-selected renderer, and a content source. Construction is synchronous
56
+ * but setup is async — await `ready` (or listen for the `ready` event) before
57
+ * driving it.
58
+ */
59
+ export declare class Zine {
60
+ #private;
61
+ /** Stable seek API: drive the fold to a fixed progress without animating (visual regression). */
62
+ readonly debug: {
63
+ setFlipProgress: (t: number, direction: FlipDirection) => void;
64
+ };
65
+ constructor(container: HTMLElement, options: ZineOptions);
66
+ /** Resolves once the renderer is mounted and the first spread is painted. */
67
+ get ready(): Promise<void>;
68
+ getPageCount(): number;
69
+ getPage(): number;
70
+ flipNext(): void;
71
+ flipPrev(): void;
72
+ flipTo(page: number): void;
73
+ getZoom(): number;
74
+ /** Zoom to `scale` (clamped to [1, zoom.max]), keeping `center` (container-local) fixed. */
75
+ setZoom(scale: number, center?: {
76
+ x: number;
77
+ y: number;
78
+ }): void;
79
+ resetZoom(): void;
80
+ on<K extends keyof ZineEventMap>(event: K, listener: (payload: ZineEventMap[K]) => void): () => void;
81
+ destroy(): void;
82
+ /** Re-measure and re-render; call after the container resizes. */
83
+ update(): void;
84
+ }
@@ -0,0 +1,106 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://zinejs.dev/options.schema.json",
4
+ "title": "ZineOptions",
5
+ "description": "Constructor options for the Zine flipbook: new Zine(container, options).",
6
+ "type": "object",
7
+ "required": ["source"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "source": {
11
+ "type": "object",
12
+ "description": "Content source resolving page indices to rasters, e.g. new ImageSource(urls). Required; a runtime object, not JSON-serializable."
13
+ },
14
+ "width": {
15
+ "type": "number",
16
+ "minimum": 1,
17
+ "description": "Fixed container width in px; omit to let the container/CSS drive the size."
18
+ },
19
+ "height": {
20
+ "type": "number",
21
+ "minimum": 1,
22
+ "description": "Fixed container height in px; omit to let the container/CSS drive the size."
23
+ },
24
+ "renderer": {
25
+ "description": "Which renderer to use. Default 'auto' (prefer GPU, else CSS).",
26
+ "default": "auto",
27
+ "oneOf": [
28
+ { "type": "string", "enum": ["auto", "css", "webgl2"] },
29
+ {
30
+ "type": "array",
31
+ "items": { "type": "string", "enum": ["css", "webgl2"] },
32
+ "description": "Ordered fallback list; the first available renderer wins."
33
+ },
34
+ { "type": "object", "description": "A custom Renderer instance." }
35
+ ]
36
+ },
37
+ "direction": {
38
+ "type": "string",
39
+ "enum": ["ltr", "rtl"],
40
+ "default": "ltr",
41
+ "description": "Reading direction."
42
+ },
43
+ "cover": {
44
+ "type": "boolean",
45
+ "default": false,
46
+ "description": "Treat the first page as a lone cover."
47
+ },
48
+ "startPage": {
49
+ "type": "integer",
50
+ "minimum": 0,
51
+ "default": 0,
52
+ "description": "Zero-based page to open on; must be within the source's page count."
53
+ },
54
+ "flipDuration": {
55
+ "type": "number",
56
+ "minimum": 0,
57
+ "default": 500,
58
+ "description": "Flip animation duration in milliseconds."
59
+ },
60
+ "clickToFlip": {
61
+ "type": "string",
62
+ "enum": ["edge", "half", "off"],
63
+ "default": "edge",
64
+ "description": "How a tap/click turns pages: near an edge, by page half, or off."
65
+ },
66
+ "clickZoneSize": {
67
+ "type": "number",
68
+ "minimum": 0,
69
+ "default": 64,
70
+ "description": "Edge-zone size in px per side, used when clickToFlip is 'edge'."
71
+ },
72
+ "clickFlipDelay": {
73
+ "type": "number",
74
+ "minimum": 0,
75
+ "description": "Delay (ms) before a click flips, to let a double-click zoom preempt it. Omit for auto (0 when double-click zoom is inactive, 250 when active)."
76
+ },
77
+ "zoom": {
78
+ "type": "object",
79
+ "description": "Zoom behavior.",
80
+ "additionalProperties": false,
81
+ "properties": {
82
+ "enabled": { "type": "boolean", "default": true, "description": "Whether zooming is allowed." },
83
+ "max": { "type": "number", "minimum": 1, "default": 4, "description": "Maximum zoom scale." },
84
+ "wheel": { "type": "boolean", "default": true, "description": "Zoom on Ctrl/Cmd + wheel." },
85
+ "doubleClick": {
86
+ "default": [1, 2, 4],
87
+ "description": "Zoom levels cycled by double-click (wraps); false to disable.",
88
+ "oneOf": [
89
+ { "type": "boolean", "enum": [false] },
90
+ { "type": "array", "items": { "type": "number", "minimum": 1 } }
91
+ ]
92
+ },
93
+ "doubleClickInFlipZone": {
94
+ "type": "boolean",
95
+ "description": "Listen for double-click zoom inside click-to-flip zones. Default: off in 'edge' mode, on in 'half'. Enabling it makes clicks wait out clickFlipDelay."
96
+ }
97
+ }
98
+ },
99
+ "singlePageThreshold": {
100
+ "type": "number",
101
+ "minimum": 0,
102
+ "default": 640,
103
+ "description": "Container widths (px) below this switch to one page per spread."
104
+ }
105
+ }
106
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@zinejs/core",
3
+ "version": "0.1.0",
4
+ "description": "Framework-agnostic flipbook engine with CSS and WebGL2 renderers and no dependencies.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "license": "PolyForm-Noncommercial-1.0.0",
8
+ "keywords": [
9
+ "flipbook",
10
+ "page-flip",
11
+ "pageflip",
12
+ "book",
13
+ "webgl",
14
+ "viewer",
15
+ "zinejs"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/awecode/zinejs.git",
20
+ "directory": "packages/core"
21
+ },
22
+ "homepage": "https://github.com/awecode/zinejs#readme",
23
+ "bugs": "https://github.com/awecode/zinejs/issues",
24
+ "files": [
25
+ "dist",
26
+ "options.schema.json"
27
+ ],
28
+ "types": "./dist/index.d.ts",
29
+ "module": "./dist/index.js",
30
+ "main": "./dist/index.umd.cjs",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js",
35
+ "require": "./dist/index.umd.cjs"
36
+ },
37
+ "./options.schema.json": "./options.schema.json"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "size-limit": [
43
+ {
44
+ "name": "core (ESM, gzip, excl. lazy renderer chunks)",
45
+ "path": "dist/index.js",
46
+ "limit": "15 kB",
47
+ "gzip": true
48
+ },
49
+ {
50
+ "name": "CSS renderer chunk (lazy, gzip)",
51
+ "path": "dist/cssRenderer-*.js",
52
+ "limit": "12 kB",
53
+ "gzip": true
54
+ },
55
+ {
56
+ "name": "WebGL2 renderer chunk (lazy, gzip)",
57
+ "path": "dist/webglRenderer-*.js",
58
+ "limit": "12 kB",
59
+ "gzip": true
60
+ }
61
+ ],
62
+ "scripts": {
63
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.build.json",
64
+ "typecheck": "tsc --noEmit -p tsconfig.json",
65
+ "test": "pnpm typecheck && vitest run",
66
+ "size": "pnpm build && size-limit"
67
+ }
68
+ }