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.
@@ -0,0 +1,27 @@
1
+ name: Build and Deploy
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v3
18
+
19
+ - name: Set up Node.js
20
+ uses: actions/setup-node@v3
21
+ with:
22
+ node-version: '16'
23
+
24
+ - name: Install dependencies and build
25
+ run: |
26
+ npm install
27
+ npm run build
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ # p5.zine
2
+
3
+ p5.zine is a **browser‑first** library on top of p5.js for making printable zines. It creates five `p5.Graphics` pages (cover, one, two, three, back) and provides helpers for layout, export, and preview.
4
+
5
+ > ✅ **Intended usage:** load via `<script>` tags (CDN or local file).
6
+ > ❌ **Not meant** to be imported as an ES module in bundlers.
7
+
8
+ ## Quick start (CDN, easiest)
9
+
10
+ ### p5 2.x
11
+
12
+ ```html
13
+ <script src="https://cdn.jsdelivr.net/npm/p5@2.2.0/lib/p5.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/p5.zine@1.0.1/dist/p5.zine.js"></script>
15
+ <script>
16
+ function setup() {}
17
+ function draw() {
18
+ cover.background(255);
19
+ cover.text("Hello", 40, 60);
20
+
21
+ one.background(240);
22
+ if (one.mouseHere) {
23
+ one.ellipse(one.mouseX, one.mouseY, 40);
24
+ }
25
+ }
26
+ </script>
27
+ ```
28
+
29
+ ### p5 1.x (legacy)
30
+
31
+ ```html
32
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/p5.js"></script>
33
+ <script src="https://cdn.jsdelivr.net/npm/p5.zine@1.0.1/dist/p5.zine.legacy.js"></script>
34
+ ```
35
+
36
+ ## Table of contents
37
+
38
+ - [Quick start (CDN, easiest)](#quick-start-cdn-easiest)
39
+ - [Bundles (p5 2.x + 1.x)](#bundles-p5-2x--1x)
40
+ - [Local dev / preview](#local-dev--preview)
41
+ - [Pages](#pages)
42
+ - [Configuration (window.zine)](#configuration-windowzine)
43
+ - [Preview modes](#preview-modes)
44
+ - [Mouse handling](#mouse-handling)
45
+ - [Download background](#download-background)
46
+ - [Page size vs. paper size](#page-size-vs-paper-size)
47
+ - [Export](#export)
48
+ - [Custom helpers (custom.js)](#custom-helpers-customjs)
49
+ - [CDN usage (advanced)](#cdn-usage-advanced)
50
+ - [Releases & versioning (maintainers)](#releases--versioning-maintainers)
51
+ - [Architecture (1.x + 2.x addons)](#architecture-1x--2x-addons)
52
+ - [Internal defaults](#internal-defaults)
53
+
54
+ ## Bundles (p5 2.x + 1.x)
55
+
56
+ The build outputs two bundles:
57
+
58
+ - `dist/p5.zine.js` → **p5 2.x** (`p5.registerAddon`)
59
+ - `dist/p5.zine.legacy.js` → **p5 1.x** (`p5.prototype` + `registerMethod`)
60
+
61
+ ## Local dev / preview
62
+
63
+ ```bash
64
+ npm run dev
65
+ ```
66
+
67
+ Open:
68
+
69
+ - `http://localhost:3000/test/index.html` (p5 2.x)
70
+ - `http://localhost:3000/test/index.legacy.html` (p5 1.x)
71
+ - `http://localhost:3000/test/compat.html` (side‑by‑side)
72
+
73
+ Other scripts:
74
+
75
+ ```bash
76
+ npm run build
77
+ npm run preview
78
+ npm run test
79
+ ```
80
+
81
+ ## Pages
82
+
83
+ The library exposes five `p5.Graphics` pages:
84
+
85
+ - `cover`
86
+ - `one`
87
+ - `two`
88
+ - `three`
89
+ - `back`
90
+
91
+ Each page is its own canvas. In the default preview mode, these canvases are mounted in the DOM and scaled responsively.
92
+
93
+ ## Configuration (`window.zine`)
94
+
95
+ Set options **before** `setup()` runs:
96
+
97
+ ```js
98
+ window.zine = {
99
+ title: "My Zine",
100
+ author: "Your Name",
101
+ personalUrl: "https://example.com",
102
+ description: "Short description",
103
+
104
+ cam: true, // set false to disable webcam capture
105
+ frameRate: 10,
106
+ pixelDensity: 1,
107
+
108
+ preview: "pages", // "pages" (default) or "canvas"
109
+
110
+ downloadFormat: "png", // "png" (default) or "jpg"
111
+ downloadBackground: "#ffffff", // default white; use "transparent" for none
112
+
113
+ mouseClamp: true, // keep mouse within each page
114
+ mousePadding: 0, // when mouseClamp is false, allow extra padding
115
+
116
+ // page size (single page)
117
+ pageWidth: "8.5in",
118
+ pageHeight: "11in",
119
+ // or pageSize: { width: "8.5in", height: "11in" }
120
+
121
+ // paper size (imposition)
122
+ // paperWidth: "11in",
123
+ // paperHeight: "17in",
124
+ // or paperSize: { width: "11in", height: "17in" }
125
+
126
+ // DPI + units (used for inches/cm/mm/pt conversions)
127
+ pageDPI: 96,
128
+ pageUnit: "in"
129
+ };
130
+ ```
131
+
132
+ ## Preview modes
133
+
134
+ - `preview: "pages"` (default) mounts each page canvas in the DOM and scales them responsively.
135
+ - `preview: "canvas"` uses the legacy big preview canvas.
136
+
137
+ ## Mouse handling
138
+
139
+ `page.mouseX/page.mouseY` are only populated when the pointer is over that page’s canvas.
140
+ When the pointer is outside, they become `null` and `page.mouseHere` is `false`.
141
+
142
+ ## Download background
143
+
144
+ Exports are rendered to an offscreen graphics buffer. By default a white background is applied so JPEGs are not black.
145
+ Set `downloadBackground: "transparent"` to keep transparency (only works with PNG).
146
+
147
+ ## Page size vs. paper size
148
+
149
+ ### `zinePageSize(width, height, options)`
150
+ Sets the **base size for each single page** (cover/back). Full spreads are 2× width.
151
+
152
+ ```js
153
+ function setup() {
154
+ zinePageSize("8.5in", "11in", { dpi: 96 });
155
+ }
156
+ ```
157
+
158
+ ### `zinePaperSize(width, height, options)`
159
+ Sets the **full paper size** (imposition) and derives the page size:
160
+
161
+ - `pageWidth = paperHeight / 4`
162
+ - `pageHeight = paperWidth / 2`
163
+
164
+ ```js
165
+ function setup() {
166
+ zinePaperSize("11in", "17in", { dpi: 300 });
167
+ }
168
+ ```
169
+
170
+ If you call both helpers, **the last call wins**.
171
+
172
+ Supported units: `px`, `in`, `cm`, `mm`, `pt`. If you pass a number (or unitless string), it’s treated as pixels unless you set `pageUnit`.
173
+
174
+ ## Export
175
+
176
+ ### JPG / PNG
177
+
178
+ The “download” button exports each page as an image. The extension matches `downloadFormat`.
179
+
180
+ ### PDF
181
+
182
+ PDFs are generated using the imposition layout. The PDF page size is derived from your
183
+ `pageDPI` / `paperDPI` so the PDF matches the paper dimensions instead of forcing Letter.
184
+
185
+ ## Custom helpers (custom.js)
186
+
187
+ These helpers are available on both `p5` and `p5.Graphics`:
188
+
189
+ - `Background`
190
+ - `selfieBackground`
191
+ - `rightCamera`
192
+ - `randomLayout`
193
+ - `gridLayout`
194
+ - `glitchLayout`
195
+ - `leftBackground`
196
+ - `rightBackground`
197
+ - `fullPage`
198
+ - `leftPage`
199
+ - `rightPage`
200
+ - `textSet`
201
+ - `textBox`
202
+
203
+ ## CDN usage (advanced)
204
+
205
+ If you want a pinned version:
206
+
207
+ ```html
208
+ <script src="https://cdn.jsdelivr.net/npm/p5.zine@1.0.1/dist/p5.zine.js"></script>
209
+ ```
210
+
211
+ If you omit the version, CDN will serve the latest tag (which can include breaking changes).
212
+
213
+ ## Releases & versioning (maintainers)
214
+
215
+ This project publishes to npm primarily to power CDN distribution. If you’re a maintainer:
216
+
217
+ ```bash
218
+ npm version patch # or minor / major
219
+ npm run build
220
+ npm publish
221
+ ```
222
+
223
+ If you want automated releases, I can set up:
224
+
225
+ - GitHub Actions for `npm publish` on tag
226
+ - Auto‑generated changelog
227
+ - Release notes from conventional commits
228
+
229
+ ## Architecture (1.x + 2.x addons)
230
+
231
+ Core addon logic lives in:
232
+
233
+ - `src/zine.js`
234
+ - `src/custom.js`
235
+
236
+ Adapters bridge to each runtime:
237
+
238
+ - `src/adapters/p5-2.js` → uses `p5.registerAddon(...)` (p5 2.x)
239
+ - `src/adapters/p5-1.js` → patches `p5.prototype` and maps lifecycles to `registerMethod(...)` (p5 1.x)
240
+
241
+ Entry points:
242
+
243
+ - `src/index.modern.js` → modern bundle
244
+ - `src/index.legacy.js` → legacy bundle
245
+
246
+ ## Internal defaults
247
+
248
+ ```js
249
+ const DEFAULTS = {
250
+ maxSinglePageWidth: 400,
251
+ rWidth: 198,
252
+ rHeight: 306,
253
+ pWidth: 800,
254
+ frameRate: 10,
255
+ pixelDensity: 1
256
+ };
257
+ ```
258
+
259
+ - `maxSinglePageWidth`: cap for the legacy preview canvas.
260
+ - `rWidth` / `rHeight`: reference ratio used to compute page height from width.
261
+ - `pWidth`: base width for a single page (pixels).
262
+
263
+ If you want these exposed as public config, say the word and I’ll wire them up.
package/build.js ADDED
@@ -0,0 +1,77 @@
1
+ const esbuild = require("esbuild");
2
+
3
+ // Define the banner content
4
+ const banner = `/* ==========================================================
5
+ *
6
+ * p5.zine
7
+ * Copyright (c) 2026 Munus Shih, Tuan Huang, Iley Cao
8
+ *
9
+ * Licensed under GNU General Public License.
10
+ * https://www.gnu.org/licenses
11
+ ===========================================================*/`;
12
+
13
+ const args = new Set(process.argv.slice(2));
14
+ const isWatch = args.has("--watch");
15
+ const isServe = args.has("--serve");
16
+ const isDev = args.has("--dev") || isWatch || isServe;
17
+
18
+ const host = process.env.HOST || "localhost";
19
+ const port = process.env.PORT ? Number(process.env.PORT) : 3000;
20
+
21
+ const buildOptions = {
22
+ entryPoints: {
23
+ "p5.zine": "src/index.modern.js",
24
+ "p5.zine.legacy": "src/index.legacy.js",
25
+ },
26
+ bundle: true,
27
+ minify: !isDev,
28
+ sourcemap: isDev ? "inline" : false,
29
+ outdir: "dist",
30
+ legalComments: "inline",
31
+ banner: {
32
+ js: banner,
33
+ },
34
+ logLevel: "info",
35
+ color: true,
36
+ loader: {
37
+ ".html": "text",
38
+ ".css": "text",
39
+ },
40
+ };
41
+
42
+ async function run() {
43
+ if (!isWatch && !isServe) {
44
+ try {
45
+ await esbuild.build(buildOptions);
46
+ console.log("Build successful! 🎉");
47
+ } catch (error) {
48
+ console.error("❌ Build failed: ", error);
49
+ process.exit(1);
50
+ }
51
+ return;
52
+ }
53
+
54
+ const ctx = await esbuild.context(buildOptions);
55
+
56
+ if (isServe) {
57
+ const server = await ctx.serve({
58
+ servedir: ".",
59
+ host,
60
+ port,
61
+ });
62
+
63
+ console.log(
64
+ `Serving on http://${server.host}:${server.port} (open /test/index.html or /test/index.legacy.html)`,
65
+ );
66
+ }
67
+
68
+ if (isWatch) {
69
+ await ctx.watch();
70
+ console.log("Watching for changes...");
71
+ }
72
+ }
73
+
74
+ run().catch((error) => {
75
+ console.error("❌ Build failed: ", error);
76
+ process.exit(1);
77
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "p5.zine",
3
+ "version": "0.0.1",
4
+ "description": "p5.zine is an open-sourced and friendly library for anyone curious about creative code and zine-making.",
5
+ "main": "main.js",
6
+ "scripts": {
7
+ "build": "node build.js",
8
+ "dev": "node build.js --watch --serve --dev",
9
+ "preview": "node build.js --serve --dev",
10
+ "test": "node build.js && node scripts/smoke-test.js",
11
+ "publish": "npm publish --access public"
12
+ },
13
+ "publishConfig": {
14
+ "registry": "https://registry.npmjs.org/"
15
+ },
16
+ "keywords": [
17
+ "p5.js",
18
+ "zine",
19
+ "creative coding",
20
+ "zine-making",
21
+ "zine-coding"
22
+ ],
23
+ "author": "Munus Shih, Tuan Huang, Iley Cao",
24
+ "license": "GPL-3.0-only",
25
+ "devDependencies": {
26
+ "esbuild": "^0.24.0"
27
+ },
28
+ "dependencies": {
29
+ "jspdf": "^2.5.2"
30
+ }
31
+ }
@@ -0,0 +1,53 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const assert = require("assert");
4
+
5
+ const root = path.resolve(__dirname, "..");
6
+ const distModern = path.join(root, "dist", "p5.zine.js");
7
+ const distLegacy = path.join(root, "dist", "p5.zine.legacy.js");
8
+ const testModern = path.join(root, "test", "index.html");
9
+ const testLegacy = path.join(root, "test", "index.legacy.html");
10
+
11
+ function fileExists(filePath) {
12
+ try {
13
+ fs.accessSync(filePath, fs.constants.F_OK);
14
+ return true;
15
+ } catch (error) {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ assert.ok(fileExists(distModern), "dist/p5.zine.js does not exist. Run npm run build first.");
21
+ assert.ok(fileExists(distLegacy), "dist/p5.zine.legacy.js does not exist. Run npm run build first.");
22
+
23
+ const modernStat = fs.statSync(distModern);
24
+ const legacyStat = fs.statSync(distLegacy);
25
+ assert.ok(modernStat.size > 0, "dist/p5.zine.js is empty.");
26
+ assert.ok(legacyStat.size > 0, "dist/p5.zine.legacy.js is empty.");
27
+
28
+ const modernContent = fs.readFileSync(distModern, "utf8");
29
+ const legacyContent = fs.readFileSync(distLegacy, "utf8");
30
+ assert.ok(
31
+ modernContent.includes("p5.zine"),
32
+ "dist/p5.zine.js does not include expected library banner/string."
33
+ );
34
+ assert.ok(
35
+ legacyContent.includes("p5.zine"),
36
+ "dist/p5.zine.legacy.js does not include expected library banner/string."
37
+ );
38
+
39
+ assert.ok(fileExists(testModern), "test/index.html is missing.");
40
+ assert.ok(fileExists(testLegacy), "test/index.legacy.html is missing.");
41
+
42
+ const testModernHtml = fs.readFileSync(testModern, "utf8");
43
+ const testLegacyHtml = fs.readFileSync(testLegacy, "utf8");
44
+ assert.ok(
45
+ testModernHtml.includes("../dist/p5.zine.js"),
46
+ "test/index.html does not reference ../dist/p5.zine.js."
47
+ );
48
+ assert.ok(
49
+ testLegacyHtml.includes("../dist/p5.zine.legacy.js"),
50
+ "test/index.legacy.html does not reference ../dist/p5.zine.legacy.js."
51
+ );
52
+
53
+ console.log("Smoke test passed ✅");
@@ -0,0 +1,65 @@
1
+ import { zineAddon } from "../zine.js";
2
+ import { customAddon } from "../custom.js";
3
+
4
+ function registerLegacyHooks(lifecycles) {
5
+ if (typeof p5 === "undefined") {
6
+ return;
7
+ }
8
+ if (typeof p5.prototype.registerMethod !== "function") {
9
+ return;
10
+ }
11
+
12
+ if (lifecycles.presetup) {
13
+ p5.prototype.registerMethod("beforeSetup", lifecycles.presetup);
14
+ }
15
+ if (lifecycles.postsetup) {
16
+ p5.prototype.registerMethod("afterSetup", lifecycles.postsetup);
17
+ }
18
+ if (lifecycles.predraw) {
19
+ p5.prototype.registerMethod("pre", lifecycles.predraw);
20
+ }
21
+ if (lifecycles.postdraw) {
22
+ p5.prototype.registerMethod("post", lifecycles.postdraw);
23
+ }
24
+ if (lifecycles.remove) {
25
+ p5.prototype.registerMethod("remove", lifecycles.remove);
26
+ }
27
+ }
28
+
29
+ function registerLegacyAddon() {
30
+ if (typeof p5 === "undefined") {
31
+ return;
32
+ }
33
+
34
+ const lifecycles = {};
35
+ zineAddon(p5, p5.prototype, lifecycles);
36
+ customAddon(p5, p5.prototype);
37
+ const originalInitZine = p5.prototype.initZine;
38
+ p5.prototype.initZine = function() {
39
+ if (typeof originalInitZine === "function") {
40
+ originalInitZine.call(this);
41
+ }
42
+
43
+ if (typeof window === "undefined") {
44
+ return;
45
+ }
46
+
47
+ const pages = ["cover", "one", "two", "three", "back"];
48
+ pages.forEach((key) => {
49
+ if (this[key]) {
50
+ window[key] = this[key];
51
+ }
52
+ });
53
+
54
+ if (this.selfie) {
55
+ window.selfie = this.selfie;
56
+ }
57
+
58
+ if (this.all) {
59
+ window.all = this.all;
60
+ }
61
+ };
62
+ registerLegacyHooks(lifecycles);
63
+ }
64
+
65
+ registerLegacyAddon();
@@ -0,0 +1,46 @@
1
+ import { zineAddon } from "../zine.js";
2
+ import { customAddon } from "../custom.js";
3
+
4
+ function registerLegacyHooks(lifecycles) {
5
+ if (typeof p5 === "undefined") {
6
+ return;
7
+ }
8
+ if (typeof p5.prototype.registerMethod !== "function") {
9
+ return;
10
+ }
11
+
12
+ if (lifecycles.presetup) {
13
+ p5.prototype.registerMethod("beforeSetup", lifecycles.presetup);
14
+ }
15
+ if (lifecycles.postsetup) {
16
+ p5.prototype.registerMethod("afterSetup", lifecycles.postsetup);
17
+ }
18
+ if (lifecycles.predraw) {
19
+ p5.prototype.registerMethod("pre", lifecycles.predraw);
20
+ }
21
+ if (lifecycles.postdraw) {
22
+ p5.prototype.registerMethod("post", lifecycles.postdraw);
23
+ }
24
+ if (lifecycles.remove) {
25
+ p5.prototype.registerMethod("remove", lifecycles.remove);
26
+ }
27
+ }
28
+
29
+ function registerModernAddon() {
30
+ if (typeof p5 === "undefined") {
31
+ return;
32
+ }
33
+
34
+ if (typeof p5.registerAddon === "function") {
35
+ p5.registerAddon(zineAddon);
36
+ p5.registerAddon(customAddon);
37
+ return;
38
+ }
39
+
40
+ const lifecycles = {};
41
+ zineAddon(p5, p5.prototype, lifecycles);
42
+ customAddon(p5, p5.prototype);
43
+ registerLegacyHooks(lifecycles);
44
+ }
45
+
46
+ registerModernAddon();