hic-pageflip 1.0.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,186 @@
1
+ /**
2
+ * 2D PageFlip Geometry & Vector Math
3
+ * Based on the classic Flash PageFlip engine & Xerox PARC page curl research.
4
+ */
5
+
6
+ export const Easing = {
7
+ easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
8
+ easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
9
+ easeOutQuad: (t) => 1 - (1 - t) * (1 - t),
10
+ easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
11
+ easeOutBack: (t) => {
12
+ const c1 = 1.70158;
13
+ const c3 = c1 + 1;
14
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
15
+ }
16
+ };
17
+
18
+ /**
19
+ * Constrains dragged point P to respect paper inextensibility (rigidity) relative to spine anchors.
20
+ * @param {number} px - Dragged point X
21
+ * @param {number} py - Dragged point Y
22
+ * @param {number} sx - Start corner X (W or -W)
23
+ * @param {number} sy - Start corner Y (-H/2 to H/2)
24
+ * @param {number} pw - Page width
25
+ * @param {number} ph - Page height
26
+ * @returns {{x: number, y: number}} Constrained (px, py)
27
+ */
28
+ export function constrainPaper(px, py, sx, sy, pw, ph) {
29
+ const topAnchorY = -ph / 2;
30
+ const botAnchorY = ph / 2;
31
+
32
+ // Maximum allowed distance from dragged corner to spine corners
33
+ const maxRadiusTop = Math.hypot(pw, sy - topAnchorY);
34
+ const maxRadiusBot = Math.hypot(pw, botAnchorY - sy);
35
+
36
+ let x = px;
37
+ let y = py;
38
+
39
+ // Constraint 1: Top spine anchor (0, topAnchorY)
40
+ const dTop = Math.hypot(x, y - topAnchorY);
41
+ if (dTop > maxRadiusTop) {
42
+ const scale = maxRadiusTop / dTop;
43
+ x = x * scale;
44
+ y = topAnchorY + (y - topAnchorY) * scale;
45
+ }
46
+
47
+ // Constraint 2: Bottom spine anchor (0, botAnchorY)
48
+ const dBot = Math.hypot(x, y - botAnchorY);
49
+ if (dBot > maxRadiusBot) {
50
+ const scale = maxRadiusBot / dBot;
51
+ x = x * scale;
52
+ y = botAnchorY + (y - botAnchorY) * scale;
53
+ }
54
+
55
+ // Bound within horizontal travel limits
56
+ if (sx > 0) {
57
+ x = Math.max(-pw, Math.min(pw, x));
58
+ } else {
59
+ x = Math.min(pw, Math.max(-pw, x));
60
+ }
61
+
62
+ return { x, y };
63
+ }
64
+
65
+ /**
66
+ * Calculates fold crease line parameters, normal, intersections, and signed distance.
67
+ * @param {number} px - Dragged corner X
68
+ * @param {number} py - Dragged corner Y
69
+ * @param {number} sx - Start corner X
70
+ * @param {number} sy - Start corner Y
71
+ * @param {number} pw - Page width
72
+ * @param {number} ph - Page height
73
+ * @returns {object|null} Fold line geometry
74
+ */
75
+ export function calculateFold(px, py, sx, sy, pw, ph) {
76
+ const vx = px - sx;
77
+ const vy = py - sy;
78
+ const len = Math.hypot(vx, vy);
79
+
80
+ if (len < 0.5) {
81
+ return null; // Fold is negligible / page flat
82
+ }
83
+
84
+ const nx = vx / len;
85
+ const ny = vy / len;
86
+ const cx = (sx + px) / 2;
87
+ const cy = (sy + py) / 2;
88
+ const foldAngle = Math.atan2(ny, nx) + Math.PI / 2;
89
+
90
+ // Page rectangle X range: [0, pw] for right page, [-pw, 0] for left page
91
+ const minX = sx > 0 ? 0 : -pw;
92
+ const maxX = sx > 0 ? pw : 0;
93
+ const minY = -ph / 2;
94
+ const maxY = ph / 2;
95
+
96
+ // Find intersections of line (X - cx)*nx + (Y - cy)*ny = 0 with page box edges
97
+ const intersections = [];
98
+ const eps = 1e-6;
99
+
100
+ // 1. Top edge (Y = minY)
101
+ if (Math.abs(nx) > eps) {
102
+ const ix = cx - ((minY - cy) * ny) / nx;
103
+ if (ix >= minX - eps && ix <= maxX + eps) {
104
+ intersections.push({ x: Math.max(minX, Math.min(maxX, ix)), y: minY, edge: 'top' });
105
+ }
106
+ }
107
+
108
+ // 2. Bottom edge (Y = maxY)
109
+ if (Math.abs(nx) > eps) {
110
+ const ix = cx - ((maxY - cy) * ny) / nx;
111
+ if (ix >= minX - eps && ix <= maxX + eps) {
112
+ intersections.push({ x: Math.max(minX, Math.min(maxX, ix)), y: maxY, edge: 'bottom' });
113
+ }
114
+ }
115
+
116
+ // 3. Outer edge (X = sx)
117
+ if (Math.abs(ny) > eps) {
118
+ const iy = cy - ((sx - cx) * nx) / ny;
119
+ if (iy >= minY - eps && iy <= maxY + eps) {
120
+ intersections.push({ x: sx, y: Math.max(minY, Math.min(maxY, iy)), edge: 'outer' });
121
+ }
122
+ }
123
+
124
+ // 4. Spine edge (X = 0)
125
+ if (Math.abs(ny) > eps) {
126
+ const iy = cy - ((0 - cx) * nx) / ny;
127
+ if (iy >= minY - eps && iy <= maxY + eps) {
128
+ intersections.push({ x: 0, y: Math.max(minY, Math.min(maxY, iy)), edge: 'spine' });
129
+ }
130
+ }
131
+
132
+ // Remove duplicate intersection points that might happen at corners
133
+ const uniqueIntersections = [];
134
+ for (const pt of intersections) {
135
+ const isDup = uniqueIntersections.some(
136
+ (u) => Math.hypot(u.x - pt.x, u.y - pt.y) < 0.1
137
+ );
138
+ if (!isDup) uniqueIntersections.push(pt);
139
+ }
140
+
141
+ return {
142
+ px,
143
+ py,
144
+ sx,
145
+ sy,
146
+ vx,
147
+ vy,
148
+ len,
149
+ nx,
150
+ ny,
151
+ cx,
152
+ cy,
153
+ foldAngle,
154
+ intersections: uniqueIntersections,
155
+ minX,
156
+ maxX,
157
+ minY,
158
+ maxY
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Reflects a 2D point across the fold line.
164
+ */
165
+ export function reflectPoint(x, y, fold) {
166
+ const dist = (x - fold.cx) * fold.nx + (y - fold.cy) * fold.ny;
167
+ return {
168
+ x: x - 2 * dist * fold.nx,
169
+ y: y - 2 * dist * fold.ny
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Returns the signed distance from a point to the fold line.
175
+ * Positive on start corner side S, negative on dragged corner side P.
176
+ */
177
+ export function foldSignedDistance(x, y, fold) {
178
+ return (x - fold.cx) * fold.nx + (y - fold.cy) * fold.ny;
179
+ }
180
+
181
+ /**
182
+ * Clamps value between min and max.
183
+ */
184
+ export function clamp(v, min, max) {
185
+ return Math.max(min, Math.min(max, v));
186
+ }
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pageflip entry point.
3
+ * Exports custom elements (<hic-pageflip>, <hic-pageflip-page>) and core Pageflip engine.
4
+ */
5
+
6
+ export { HICPageflip } from './components/hic-pageflip.js';
7
+ export { HICPageflipPage } from './components/hic-pageflip-page.js';
8
+ export { Pageflip } from './core/pageflip.js';
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "hic-pageflip",
3
+ "version": "1.0.0",
4
+ "description": "Interactive page flip book viewer built with HTML-in-Canvas",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "module": "./index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./components/*": "./components/*",
11
+ "./core/*": "./core/*"
12
+ },
13
+ "keywords": [
14
+ "pageflip",
15
+ "canvas",
16
+ "flipbook",
17
+ "html-in-canvas"
18
+ ],
19
+ "author": {
20
+ "name": "Bramus Van Damme",
21
+ "email": "bramus@bram.us",
22
+ "url": "https://www.bram.us/"
23
+ },
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/bramus/hic-pageflip.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/bramus/hic-pageflip/issues"
31
+ },
32
+ "homepage": "https://hic-pageflip.netlify.app"
33
+ }