p5.zine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/zine.js ADDED
@@ -0,0 +1,1003 @@
1
+ import { jsPDF } from "jspdf";
2
+ import { resolvePageDimensions } from "./page-size.js";
3
+
4
+ const PAGE_DEFS = [
5
+ { key: "cover", label: "cover", pageSize: "single", widthMultiplier: 1 },
6
+ { key: "one", label: "one", pageSize: "full", widthMultiplier: 2 },
7
+ { key: "two", label: "two", pageSize: "full", widthMultiplier: 2 },
8
+ { key: "three", label: "three", pageSize: "full", widthMultiplier: 2 },
9
+ { key: "back", label: "back", pageSize: "single", widthMultiplier: 1 },
10
+ ];
11
+
12
+ const DEFAULTS = {
13
+ maxSinglePageWidth: 400,
14
+ rWidth: 198,
15
+ rHeight: 306,
16
+ pWidth: 800,
17
+ frameRate: 10,
18
+ pixelDensity: 1,
19
+ };
20
+
21
+ const PREVIEW_MODES = {
22
+ CANVAS: "canvas",
23
+ PAGES: "pages",
24
+ };
25
+
26
+ let activeZine = null;
27
+
28
+ function resolveP5Instance(candidate) {
29
+ if (typeof window !== "undefined" && candidate === window) {
30
+ if (typeof p5 !== "undefined" && p5.instance) {
31
+ return p5.instance;
32
+ }
33
+ }
34
+ return candidate;
35
+ }
36
+
37
+ class ZinePage {
38
+ constructor(p, def, pWidth, pHeight) {
39
+ this.key = def.key;
40
+ this.label = def.label;
41
+ this.pageSize = def.pageSize;
42
+ this.widthMultiplier = def.widthMultiplier;
43
+ this.graphics = p.createGraphics(pWidth * def.widthMultiplier, pHeight);
44
+ this.graphics.pageSize = def.pageSize;
45
+ this.canvasEl = this.graphics.canvas || this.graphics.elt || null;
46
+ this.wrapper = null;
47
+ this.labelEl = null;
48
+ }
49
+ }
50
+
51
+ class ZineManager {
52
+ constructor(p) {
53
+ this.p = resolveP5Instance(p);
54
+ this.canvas = null;
55
+ this.pages = [];
56
+ this.pageMap = new Map();
57
+ this.graphicsMap = new Map();
58
+ this.pageContainer = null;
59
+ this.borderYes = true;
60
+ this.previewMode = PREVIEW_MODES.PAGES;
61
+ this.lastPointer = null;
62
+ this.pointerBound = false;
63
+
64
+ this.maxSinglePageWidth = DEFAULTS.maxSinglePageWidth;
65
+ this.rWidth = DEFAULTS.rWidth;
66
+ this.rHeight = DEFAULTS.rHeight;
67
+ this.pageRatio = this.rWidth / this.rHeight;
68
+ this.pWidth = DEFAULTS.pWidth;
69
+ this.pHeight = this.pWidth / this.pageRatio;
70
+ this.pageDPI = 96;
71
+
72
+ this.aWidth = 0;
73
+ this.aHeight = 0;
74
+ this.gap = 0;
75
+ this.frameRate = DEFAULTS.frameRate;
76
+ this.pixelDensity = DEFAULTS.pixelDensity;
77
+ this.downloadBackground = "#ffffff";
78
+ this.downloadFormat = "png";
79
+ this.mousePadding = 0;
80
+ this.clampMouse = true;
81
+ }
82
+
83
+ init() {
84
+ this.applyConfig();
85
+ this.createRootCanvas();
86
+
87
+ if (this.previewMode === PREVIEW_MODES.CANVAS) {
88
+ this.updateAdaptiveWidth();
89
+ }
90
+
91
+ this.createPages();
92
+ this.applyCoreSettings();
93
+ this.updateTitle();
94
+ this.updateDownloadLabels();
95
+ this.initCapture();
96
+
97
+ if (this.previewMode === PREVIEW_MODES.CANVAS) {
98
+ this.resizeCanvas();
99
+ } else {
100
+ this.mountPages();
101
+ this.layoutPages();
102
+ this.bindPointerTracking();
103
+ }
104
+ }
105
+
106
+ applyConfig() {
107
+ if (typeof window === "undefined") {
108
+ return;
109
+ }
110
+
111
+ if (typeof window.zine === "object" && window.zine !== null) {
112
+ this.frameRate =
113
+ typeof window.zine.frameRate !== "undefined"
114
+ ? window.zine.frameRate
115
+ : this.frameRate;
116
+ this.pixelDensity =
117
+ typeof window.zine.pixelDensity !== "undefined"
118
+ ? window.zine.pixelDensity
119
+ : this.pixelDensity;
120
+ }
121
+
122
+ const { width, height, dpi } = resolvePageDimensions(window.zine || {}, {
123
+ width: this.pWidth,
124
+ height: this.pHeight,
125
+ ratio: this.pageRatio,
126
+ });
127
+
128
+ if (typeof width === "number" && typeof height === "number") {
129
+ this.pWidth = width;
130
+ this.pHeight = height;
131
+ this.pageRatio = width / height;
132
+ }
133
+ if (typeof dpi === "number") {
134
+ this.pageDPI = dpi;
135
+ }
136
+
137
+ const previewMode = window.zine?.preview;
138
+ if (previewMode === PREVIEW_MODES.CANVAS) {
139
+ this.previewMode = PREVIEW_MODES.CANVAS;
140
+ } else if (previewMode === PREVIEW_MODES.PAGES || previewMode === "dom") {
141
+ this.previewMode = PREVIEW_MODES.PAGES;
142
+ }
143
+
144
+ if (typeof window.zine?.downloadBackground !== "undefined") {
145
+ this.downloadBackground = window.zine.downloadBackground;
146
+ }
147
+
148
+ if (typeof window.zine?.downloadFormat === "string") {
149
+ const format = window.zine.downloadFormat.toLowerCase();
150
+ if (format === "png" || format === "jpg" || format === "jpeg") {
151
+ this.downloadFormat = format === "jpeg" ? "jpg" : format;
152
+ }
153
+ }
154
+
155
+ if (typeof window.zine?.mousePadding === "number") {
156
+ this.mousePadding = window.zine.mousePadding;
157
+ }
158
+
159
+ if (typeof window.zine?.mouseClamp === "boolean") {
160
+ this.clampMouse = window.zine.mouseClamp;
161
+ }
162
+ }
163
+
164
+ createRootCanvas() {
165
+ if (this.previewMode === PREVIEW_MODES.CANVAS) {
166
+ const windowW = this.getWindowWidth();
167
+ this.canvas = this.p.createCanvas(windowW - 17, windowW * 3.3);
168
+ this.canvas.parent("#myCanvas");
169
+ return;
170
+ }
171
+
172
+ this.canvas = this.p.createCanvas(1, 1);
173
+ this.canvas.hide();
174
+ }
175
+
176
+ getWindowWidth() {
177
+ if (typeof windowWidth !== "undefined") {
178
+ return windowWidth;
179
+ }
180
+ if (typeof this.p.windowWidth !== "undefined") {
181
+ return this.p.windowWidth;
182
+ }
183
+ if (typeof window !== "undefined") {
184
+ return window.innerWidth;
185
+ }
186
+ return this.p.width || 0;
187
+ }
188
+
189
+ getMaxCanvasWidth() {
190
+ return (this.maxSinglePageWidth * 2) / 0.7;
191
+ }
192
+
193
+ createPages() {
194
+ this.pages = PAGE_DEFS.map(
195
+ (def) => new ZinePage(this.p, def, this.pWidth, this.pHeight),
196
+ );
197
+ this.pageMap = new Map(this.pages.map((page) => [page.key, page]));
198
+ this.graphicsMap = new Map(this.pages.map((page) => [page.graphics, page]));
199
+
200
+ this.pages.forEach((page) => {
201
+ if (typeof page.graphics.pixelDensity === "function") {
202
+ page.graphics.pixelDensity(this.pixelDensity);
203
+ }
204
+ if (typeof page.graphics.frameRate === "function") {
205
+ page.graphics.frameRate(this.frameRate);
206
+ }
207
+ });
208
+
209
+ const graphics = this.pages.map((page) => page.graphics);
210
+ this.pages.forEach((page) => {
211
+ this.p[page.key] = page.graphics;
212
+ });
213
+
214
+ this.p.all = graphics;
215
+ }
216
+
217
+ mountPages() {
218
+ if (typeof document === "undefined") {
219
+ return;
220
+ }
221
+
222
+ const container = document.querySelector("#myCanvas") || document.body;
223
+ this.pageContainer = container;
224
+ container.classList.add("zine-pages");
225
+
226
+ container.querySelectorAll(".zine-page").forEach((node) => node.remove());
227
+
228
+ this.pages.forEach((page) => {
229
+ const canvasEl =
230
+ page.canvasEl || page.graphics.canvas || page.graphics.elt;
231
+ if (!canvasEl) {
232
+ return;
233
+ }
234
+
235
+ const wrapper = document.createElement("div");
236
+ wrapper.className = "zine-page";
237
+
238
+ const label = document.createElement("div");
239
+ label.className = "zine-page-label";
240
+ label.textContent = page.label || "";
241
+
242
+ canvasEl.classList.add("zine-page-canvas");
243
+ canvasEl.style.display = "block";
244
+
245
+ wrapper.appendChild(label);
246
+ wrapper.appendChild(canvasEl);
247
+ container.appendChild(wrapper);
248
+
249
+ page.canvasEl = canvasEl;
250
+ page.wrapper = wrapper;
251
+ page.labelEl = label;
252
+ });
253
+
254
+ this.setPreviewChromeVisible(this.borderYes);
255
+ }
256
+
257
+ layoutPages() {
258
+ if (!this.pageContainer) {
259
+ return;
260
+ }
261
+
262
+ const containerWidth =
263
+ this.pageContainer.clientWidth ||
264
+ (typeof window !== "undefined" ? window.innerWidth : 0);
265
+ const padding = 32;
266
+ const availableWidth = Math.max(0, containerWidth - padding);
267
+
268
+ this.pages.forEach((page) => {
269
+ const canvasEl = page.canvasEl;
270
+ if (!canvasEl) {
271
+ return;
272
+ }
273
+
274
+ const scale =
275
+ availableWidth > 0
276
+ ? Math.min(1, availableWidth / page.graphics.width)
277
+ : 1;
278
+ const width = Math.round(page.graphics.width * scale);
279
+ const height = Math.round(page.graphics.height * scale);
280
+
281
+ canvasEl.style.width = `${width}px`;
282
+ canvasEl.style.height = `${height}px`;
283
+
284
+ if (page.wrapper) {
285
+ page.wrapper.style.width = `${width}px`;
286
+ }
287
+ });
288
+ }
289
+
290
+ setPreviewChromeVisible(isVisible) {
291
+ if (!this.pageContainer) {
292
+ return;
293
+ }
294
+
295
+ this.pageContainer.classList.toggle("zine-preview-hidden", !isVisible);
296
+ }
297
+
298
+ bindPointerTracking() {
299
+ if (this.pointerBound || typeof window === "undefined") {
300
+ return;
301
+ }
302
+
303
+ this.pointerBound = true;
304
+ const handler = (event) => {
305
+ this.lastPointer = { x: event.clientX, y: event.clientY };
306
+ };
307
+
308
+ window.addEventListener("pointermove", handler);
309
+ window.addEventListener("pointerdown", handler);
310
+ this.pointerHandler = handler;
311
+ }
312
+
313
+ applyCoreSettings() {
314
+ this.p.pixelDensity(this.pixelDensity);
315
+ this.p.frameRate(this.frameRate);
316
+ this.p.angleMode(this.p.DEGREES);
317
+ this.p.noStroke();
318
+ }
319
+
320
+ updateAdaptiveWidth() {
321
+ if (this.previewMode !== PREVIEW_MODES.CANVAS) {
322
+ return;
323
+ }
324
+
325
+ this.aWidth = Math.min((this.p.width / 2) * 0.7, this.maxSinglePageWidth);
326
+ this.aHeight = this.pageRatio ? this.aWidth / this.pageRatio : this.aWidth;
327
+ this.gap = this.aWidth / 4;
328
+ }
329
+
330
+ resizeCanvas() {
331
+ if (this.previewMode !== PREVIEW_MODES.CANVAS) {
332
+ this.layoutPages();
333
+ return;
334
+ }
335
+
336
+ const windowW = this.getWindowWidth();
337
+ const maxCanvasWidth = this.getMaxCanvasWidth();
338
+
339
+ if (windowW >= maxCanvasWidth) {
340
+ this.p.resizeCanvas(maxCanvasWidth, maxCanvasWidth * 3.3);
341
+ } else {
342
+ this.p.resizeCanvas(windowW - 17, windowW * 3.3);
343
+ }
344
+ }
345
+
346
+ updateTitle() {
347
+ if (typeof window === "undefined") {
348
+ return;
349
+ }
350
+
351
+ if (typeof window.zine === "object" && window.zine !== null) {
352
+ const titleEl = document.querySelector("#genTitle");
353
+ const authorEl = document.querySelector("#author");
354
+ const descEl = document.querySelector("#des");
355
+
356
+ if (titleEl) {
357
+ titleEl.innerHTML = window.zine?.title || "Default Title";
358
+ }
359
+ if (authorEl) {
360
+ authorEl.innerHTML = window.zine?.author
361
+ ? `by <a href="${window.zine?.personalUrl || "#"}">${
362
+ window.zine?.author
363
+ }</a>`
364
+ : "by Unknown Author";
365
+ }
366
+ if (descEl) {
367
+ descEl.innerHTML =
368
+ window.zine?.description || "No description available.";
369
+ }
370
+ }
371
+ }
372
+
373
+ updateDownloadLabels() {
374
+ if (typeof document === "undefined") {
375
+ return;
376
+ }
377
+
378
+ const downloadButton = document.getElementById("download-jpg");
379
+ if (!downloadButton) {
380
+ return;
381
+ }
382
+
383
+ const extension = this.downloadFormat === "png" ? "png" : "jpg";
384
+ downloadButton.value = `*download .${extension}*`;
385
+ }
386
+
387
+ initCapture() {
388
+ if (typeof window === "undefined") {
389
+ return;
390
+ }
391
+
392
+ if (
393
+ typeof window.zine !== "undefined" &&
394
+ (window.zine?.cam === false ||
395
+ window.zine?.cam === "false" ||
396
+ window.zine?.cam === "False" ||
397
+ window.zine?.cam === "FALSE")
398
+ ) {
399
+ return;
400
+ }
401
+
402
+ this.p.selfie = this.p.createCapture(this.p.VIDEO);
403
+ this.p.selfie.hide();
404
+ }
405
+
406
+ getLayout() {
407
+ if (this.previewMode !== PREVIEW_MODES.CANVAS) {
408
+ return [];
409
+ }
410
+
411
+ const canvasWidth = this.p.width;
412
+ return this.pages.map((page, index) => {
413
+ const isSingle = page.pageSize === "single";
414
+ const width = this.aWidth * (isSingle ? 1 : 2);
415
+ const height = this.aHeight;
416
+ const x = canvasWidth / 2 - (isSingle ? this.aWidth / 2 : this.aWidth);
417
+ const y = this.gap + (this.aHeight + this.gap) * index;
418
+ return { page, index, x, y, width, height, isSingle };
419
+ });
420
+ }
421
+
422
+ drawPreview() {
423
+ if (this.previewMode !== PREVIEW_MODES.CANVAS || !this.borderYes) {
424
+ return;
425
+ }
426
+
427
+ const p = this.p;
428
+ const layout = this.getLayout();
429
+
430
+ p.push();
431
+
432
+ layout.forEach((item) => {
433
+ p.image(item.page.graphics, item.x, item.y, item.width, item.height);
434
+ });
435
+
436
+ p.noStroke();
437
+ p.fill("#ed225d");
438
+ p.textSize(15);
439
+ p.textFont("monospace");
440
+ layout.forEach((item) => {
441
+ if (item.page.label) {
442
+ p.text(item.page.label, item.x, item.y - 10);
443
+ }
444
+ });
445
+
446
+ p.noFill();
447
+ p.stroke("#ed225d");
448
+ p.strokeWeight(1);
449
+ layout.forEach((item) => {
450
+ if (item.isSingle) {
451
+ p.rect(item.x, item.y, this.aWidth, this.aHeight);
452
+ } else {
453
+ p.rect(item.x, item.y, this.aWidth, this.aHeight);
454
+ p.rect(item.x + this.aWidth, item.y, this.aWidth, this.aHeight);
455
+ }
456
+ });
457
+
458
+ p.pop();
459
+ }
460
+
461
+ setPagesPixelDensity(num) {
462
+ this.pages.forEach((page) => {
463
+ page.graphics.pixelDensity(num);
464
+ });
465
+ }
466
+
467
+ applyPreviewPixelDensity() {
468
+ const target = this.pixelDensity;
469
+ if (
470
+ typeof this.p.pixelDensity === "function" &&
471
+ this.p.pixelDensity() !== target
472
+ ) {
473
+ this.p.pixelDensity(target);
474
+ }
475
+
476
+ this.pages.forEach((page) => {
477
+ if (typeof page.graphics.pixelDensity === "function") {
478
+ if (page.graphics.pixelDensity() !== target) {
479
+ page.graphics.pixelDensity(target);
480
+ }
481
+ }
482
+ if (typeof page.graphics.frameRate === "function") {
483
+ page.graphics.frameRate(this.frameRate);
484
+ }
485
+ });
486
+ }
487
+
488
+ updateMousePositions() {
489
+ const pages = this.pages.map((page) => page.graphics);
490
+ this.updateMousePositionsFor(pages);
491
+ }
492
+
493
+ updateMousePositionsFor(pages) {
494
+ if (!pages || pages.length === 0) {
495
+ return;
496
+ }
497
+
498
+ const padding = this.mousePadding ?? 0;
499
+
500
+ if (this.previewMode === PREVIEW_MODES.CANVAS) {
501
+ if (this.aWidth === 0) {
502
+ return;
503
+ }
504
+
505
+ const ratioer = this.pWidth / this.aWidth;
506
+
507
+ pages.forEach((page, index) => {
508
+ if (!page) {
509
+ return;
510
+ }
511
+
512
+ const isSingle = page.width === this.pWidth;
513
+ const baseX =
514
+ this.p.width / 2 - (isSingle ? this.aWidth / 2 : this.aWidth);
515
+ const baseY = this.gap + (this.aHeight + this.gap) * index;
516
+
517
+ const minOffset = this.clampMouse ? 0 : -padding;
518
+ const maxX = this.clampMouse ? page.width : page.width + padding;
519
+ const maxY = this.clampMouse ? page.height : page.height + padding;
520
+
521
+ const localX = (this.p.mouseX - baseX) * ratioer;
522
+ const localY = (this.p.mouseY - baseY) * ratioer;
523
+ const isInside =
524
+ localX >= 0 &&
525
+ localX <= page.width &&
526
+ localY >= 0 &&
527
+ localY <= page.height;
528
+
529
+ page.mouseHere = isInside;
530
+ if (!isInside) {
531
+ page.mouseX = null;
532
+ page.mouseY = null;
533
+ return;
534
+ }
535
+
536
+ page.mouseX = this.p.constrain(localX, minOffset, maxX);
537
+ page.mouseY = this.p.constrain(localY, minOffset, maxY);
538
+ });
539
+
540
+ return;
541
+ }
542
+
543
+ this.updateMousePositionsFromPointer(pages);
544
+ }
545
+
546
+ updateMousePositionsFromPointer(pages) {
547
+ if (!this.lastPointer) {
548
+ return;
549
+ }
550
+
551
+ const padding = this.mousePadding ?? 0;
552
+
553
+ pages.forEach((graphics) => {
554
+ const page = this.graphicsMap.get(graphics);
555
+ const canvasEl = page?.canvasEl;
556
+ if (!canvasEl) {
557
+ return;
558
+ }
559
+
560
+ const rect = canvasEl.getBoundingClientRect();
561
+ if (rect.width === 0 || rect.height === 0) {
562
+ return;
563
+ }
564
+
565
+ const scaleX = graphics.width / rect.width;
566
+ const scaleY = graphics.height / rect.height;
567
+ const localX = (this.lastPointer.x - rect.left) * scaleX;
568
+ const localY = (this.lastPointer.y - rect.top) * scaleY;
569
+
570
+ const minOffset = this.clampMouse ? 0 : -padding;
571
+ const maxX = this.clampMouse ? graphics.width : graphics.width + padding;
572
+ const maxY = this.clampMouse
573
+ ? graphics.height
574
+ : graphics.height + padding;
575
+
576
+ const isInside =
577
+ this.lastPointer.x >= rect.left &&
578
+ this.lastPointer.x <= rect.right &&
579
+ this.lastPointer.y >= rect.top &&
580
+ this.lastPointer.y <= rect.bottom;
581
+
582
+ graphics.mouseHere = isInside;
583
+ if (!isInside) {
584
+ graphics.mouseX = null;
585
+ graphics.mouseY = null;
586
+ return;
587
+ }
588
+
589
+ graphics.mouseX = this.p.constrain(localX, minOffset, maxX);
590
+ graphics.mouseY = this.p.constrain(localY, minOffset, maxY);
591
+ });
592
+ }
593
+
594
+ getPage(key) {
595
+ return this.pageMap.get(key)?.graphics || null;
596
+ }
597
+
598
+ getDownloadBackground() {
599
+ if (
600
+ this.downloadBackground === null ||
601
+ this.downloadBackground === false ||
602
+ this.downloadBackground === "transparent" ||
603
+ this.downloadBackground === "none"
604
+ ) {
605
+ return null;
606
+ }
607
+
608
+ return this.downloadBackground || "#ffffff";
609
+ }
610
+
611
+ buildPageExportGraphic(source) {
612
+ const pg = this.p.createGraphics(source.width, source.height);
613
+ pg.pixelDensity(this.pixelDensity);
614
+
615
+ const bg = this.getDownloadBackground();
616
+ if (bg) {
617
+ pg.background(bg);
618
+ } else {
619
+ pg.clear();
620
+ }
621
+
622
+ pg.image(source, 0, 0);
623
+ return pg;
624
+ }
625
+
626
+ drawPrintLayout(target, useDegrees) {
627
+ const printRotate = useDegrees ? 90 : target.HALF_PI;
628
+ const cover = this.getPage("cover");
629
+ const one = this.getPage("one");
630
+ const two = this.getPage("two");
631
+ const three = this.getPage("three");
632
+ const back = this.getPage("back");
633
+
634
+ target.push();
635
+ target.rotate(printRotate);
636
+ target.translate(target.height - this.pWidth, -this.pHeight);
637
+ target.image(cover, 0, 0);
638
+ target.pop();
639
+
640
+ target.push();
641
+ target.rotate(-printRotate);
642
+ target.translate(-target.height, this.pHeight);
643
+ target.image(one, 0, 0);
644
+ target.pop();
645
+
646
+ target.push();
647
+ target.rotate(-printRotate);
648
+ target.translate(-target.height / 2, this.pHeight);
649
+ target.image(two, 0, 0);
650
+ target.pop();
651
+
652
+ target.push();
653
+ target.rotate(printRotate);
654
+ target.translate(0, -this.pHeight);
655
+ target.image(three, 0, 0);
656
+ target.pop();
657
+
658
+ target.push();
659
+ target.rotate(printRotate);
660
+ target.translate(target.height - this.pWidth * 2, -this.pHeight);
661
+ target.image(back, 0, 0);
662
+ target.pop();
663
+ }
664
+
665
+ buildPrintGraphics() {
666
+ const pg = this.p.createGraphics(this.pHeight * 2, this.pWidth * 4);
667
+ const useDegrees = this.p.angleMode() === "degrees";
668
+
669
+ pg.angleMode(useDegrees ? pg.DEGREES : pg.RADIANS);
670
+ pg.pixelDensity(this.pixelDensity);
671
+ const bg = this.getDownloadBackground();
672
+ if (bg) {
673
+ pg.background(bg);
674
+ } else {
675
+ pg.clear();
676
+ }
677
+
678
+ this.drawPrintLayout(pg, useDegrees);
679
+
680
+ return pg;
681
+ }
682
+
683
+ printSetting() {
684
+ const p = this.p;
685
+ const useDegrees = p.angleMode() === "degrees";
686
+ const previousSize = { width: p.width, height: p.height };
687
+
688
+ p.push();
689
+ p.resizeCanvas(this.pHeight * 2, this.pWidth * 4);
690
+ p.clear();
691
+ p.background(255);
692
+
693
+ this.drawPrintLayout(p, useDegrees);
694
+
695
+ p.pop();
696
+
697
+ if (this.previewMode !== PREVIEW_MODES.CANVAS) {
698
+ p.resizeCanvas(previousSize.width, previousSize.height);
699
+ }
700
+ }
701
+
702
+ downloadJPG() {
703
+ this.borderYes = false;
704
+ this.setPreviewChromeVisible(false);
705
+
706
+ const stamp = `${this.p.hour()}${this.p.minute()}${this.p.second()}`;
707
+ const extension = this.downloadFormat === "png" ? "png" : "jpg";
708
+ const pages = [
709
+ { page: this.getPage("cover"), index: 0 },
710
+ { page: this.getPage("one"), index: 1 },
711
+ { page: this.getPage("two"), index: 2 },
712
+ { page: this.getPage("three"), index: 3 },
713
+ { page: this.getPage("back"), index: 4 },
714
+ ];
715
+
716
+ pages.forEach(({ page, index }) => {
717
+ if (!page) {
718
+ return;
719
+ }
720
+
721
+ const exportGraphic = this.buildPageExportGraphic(page);
722
+ exportGraphic.save(`${index}-genZ${stamp}.${extension}`);
723
+ if (typeof exportGraphic.remove === "function") {
724
+ exportGraphic.remove();
725
+ }
726
+ });
727
+
728
+ this.borderYes = true;
729
+ this.setPreviewChromeVisible(true);
730
+ }
731
+
732
+ downloadPDF() {
733
+ this.borderYes = false;
734
+ this.setPreviewChromeVisible(false);
735
+
736
+ try {
737
+ const printGraphics = this.buildPrintGraphics();
738
+ const canvasEl =
739
+ printGraphics?.canvas || printGraphics?.elt || printGraphics;
740
+
741
+ if (!canvasEl) {
742
+ return;
743
+ }
744
+
745
+ const imgData = canvasEl.toDataURL("image/jpeg", 1.0);
746
+ const paperWidthPx = this.pHeight * 2;
747
+ const paperHeightPx = this.pWidth * 4;
748
+ const dpi = this.pageDPI || 96;
749
+ const paperWidthIn = paperWidthPx / dpi;
750
+ const paperHeightIn = paperHeightPx / dpi;
751
+ const orientation = paperWidthIn > paperHeightIn ? "l" : "p";
752
+
753
+ const pdf = new jsPDF({
754
+ orientation,
755
+ unit: "in",
756
+ format: [paperWidthIn, paperHeightIn],
757
+ });
758
+ pdf.addImage(imgData, "JPEG", 0, 0, paperWidthIn, paperHeightIn);
759
+
760
+ let fileName;
761
+ if (
762
+ typeof window !== "undefined" &&
763
+ typeof window.zine === "object" &&
764
+ window.zine !== null &&
765
+ typeof window.zine?.title !== "undefined"
766
+ ) {
767
+ fileName = window.zine.title;
768
+ } else {
769
+ fileName = "(gen)zine";
770
+ }
771
+
772
+ if (
773
+ /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
774
+ navigator.userAgent.toLowerCase(),
775
+ )
776
+ ) {
777
+ window.open(pdf.output("bloburl", { filename: fileName }));
778
+ } else {
779
+ pdf.save(`${fileName}.pdf`);
780
+ }
781
+
782
+ if (typeof printGraphics?.remove === "function") {
783
+ printGraphics.remove();
784
+ }
785
+ } finally {
786
+ this.borderYes = true;
787
+ this.setPreviewChromeVisible(true);
788
+ }
789
+ }
790
+ }
791
+
792
+ export function zineAddon(p5, fn, lifecycles) {
793
+ fn.selfie = null;
794
+ PAGE_DEFS.forEach((def) => {
795
+ fn[def.key] = null;
796
+ });
797
+ fn.all = [];
798
+
799
+ fn.initZine = function () {
800
+ const instance = resolveP5Instance(this);
801
+ instance._zine = new ZineManager(instance);
802
+ activeZine = instance._zine;
803
+ instance._zine.init();
804
+ };
805
+
806
+ fn.setupZine = function () {
807
+ const instance = resolveP5Instance(this);
808
+ if (!instance._zine) {
809
+ return;
810
+ }
811
+ instance._zine.applyPreviewPixelDensity();
812
+ instance._zine.updateMousePositions();
813
+ };
814
+
815
+ fn.drawZine = function () {
816
+ const instance = resolveP5Instance(this);
817
+ if (!instance._zine) {
818
+ return;
819
+ }
820
+ instance._zine.drawPreview();
821
+ };
822
+
823
+ fn.mousePosition = function () {
824
+ const instance = resolveP5Instance(this);
825
+ if (!instance._zine) {
826
+ return;
827
+ }
828
+ instance._zine.updateMousePositions();
829
+ };
830
+
831
+ fn.changePixelDensity = function (num) {
832
+ const instance = resolveP5Instance(this);
833
+ if (!instance._zine) {
834
+ return;
835
+ }
836
+ instance._zine.setPagesPixelDensity(num);
837
+ };
838
+
839
+ fn.zinePageSize = function (width, height, options = {}) {
840
+ if (typeof window === "undefined") {
841
+ return;
842
+ }
843
+
844
+ if (this._zine) {
845
+ console.warn(
846
+ "zinePageSize() should be called before setup; reload to apply changes.",
847
+ );
848
+ return;
849
+ }
850
+
851
+ window.zine =
852
+ typeof window.zine === "object" && window.zine !== null ? window.zine : {};
853
+
854
+ if (typeof width !== "undefined") {
855
+ window.zine.pageWidth = width;
856
+ }
857
+ if (typeof height !== "undefined") {
858
+ window.zine.pageHeight = height;
859
+ }
860
+
861
+ delete window.zine.paperWidth;
862
+ delete window.zine.paperHeight;
863
+ delete window.zine.paperSize;
864
+
865
+ if (options && typeof options === "object") {
866
+ if (typeof options.dpi === "number") {
867
+ window.zine.pageDPI = options.dpi;
868
+ }
869
+ if (typeof options.unit === "string") {
870
+ window.zine.pageUnit = options.unit;
871
+ }
872
+ }
873
+ };
874
+
875
+ fn.zinePaperSize = function (width, height, options = {}) {
876
+ if (typeof window === "undefined") {
877
+ return;
878
+ }
879
+
880
+ if (this._zine) {
881
+ console.warn(
882
+ "zinePaperSize() should be called before setup; reload to apply changes.",
883
+ );
884
+ return;
885
+ }
886
+
887
+ window.zine =
888
+ typeof window.zine === "object" && window.zine !== null ? window.zine : {};
889
+
890
+ if (typeof width !== "undefined") {
891
+ window.zine.paperWidth = width;
892
+ }
893
+ if (typeof height !== "undefined") {
894
+ window.zine.paperHeight = height;
895
+ }
896
+
897
+ delete window.zine.pageWidth;
898
+ delete window.zine.pageHeight;
899
+ delete window.zine.pageSize;
900
+
901
+ if (options && typeof options === "object") {
902
+ if (typeof options.dpi === "number") {
903
+ window.zine.paperDPI = options.dpi;
904
+ }
905
+ if (typeof options.unit === "string") {
906
+ window.zine.paperUnit = options.unit;
907
+ }
908
+ }
909
+ };
910
+
911
+ fn.calMousePos = function (arr) {
912
+ const instance = resolveP5Instance(this);
913
+ if (!instance._zine) {
914
+ return;
915
+ }
916
+ instance._zine.updateMousePositionsFor(
917
+ arr || instance._zine.pages.map((p) => p.graphics),
918
+ );
919
+ };
920
+
921
+ fn.changeTitle = function () {
922
+ const instance = resolveP5Instance(this);
923
+ if (!instance._zine) {
924
+ return;
925
+ }
926
+ instance._zine.updateTitle();
927
+ };
928
+
929
+ fn.updateAdaptiveWidth = function () {
930
+ const instance = resolveP5Instance(this);
931
+ if (!instance._zine) {
932
+ return;
933
+ }
934
+ instance._zine.updateAdaptiveWidth();
935
+ };
936
+
937
+ fn.resizeZineCanvas = function () {
938
+ const instance = resolveP5Instance(this);
939
+ if (!instance._zine) {
940
+ return;
941
+ }
942
+ instance._zine.resizeCanvas();
943
+ };
944
+
945
+ fn.printSetting = function () {
946
+ const instance = resolveP5Instance(this);
947
+ if (!instance._zine) {
948
+ return;
949
+ }
950
+ instance._zine.printSetting();
951
+ };
952
+
953
+ fn.windowResized = function () {
954
+ const instance = resolveP5Instance(this);
955
+ if (!instance._zine) {
956
+ return;
957
+ }
958
+
959
+ if (instance._zine.previewMode === PREVIEW_MODES.CANVAS) {
960
+ instance._zine.updateAdaptiveWidth();
961
+ }
962
+ instance._zine.resizeCanvas();
963
+ };
964
+
965
+ if (lifecycles) {
966
+ lifecycles.postsetup = function () {
967
+ if (typeof this.initZine === "function") {
968
+ this.initZine();
969
+ }
970
+ };
971
+ lifecycles.predraw = function () {
972
+ if (typeof this.setupZine === "function") {
973
+ this.setupZine();
974
+ }
975
+ };
976
+ lifecycles.postdraw = function () {
977
+ if (typeof this.drawZine === "function") {
978
+ this.drawZine();
979
+ }
980
+ };
981
+ }
982
+ }
983
+
984
+ document.addEventListener("DOMContentLoaded", function () {
985
+ document
986
+ .getElementById("download-jpg")
987
+ .addEventListener("click", downloadJPG);
988
+ document
989
+ .getElementById("download-pdf")
990
+ .addEventListener("click", downloadPDF);
991
+ });
992
+
993
+ function downloadJPG() {
994
+ if (activeZine) {
995
+ activeZine.downloadJPG();
996
+ }
997
+ }
998
+
999
+ function downloadPDF() {
1000
+ if (activeZine) {
1001
+ activeZine.downloadPDF();
1002
+ }
1003
+ }