liquid-gl 1.0.6 → 2.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/README.md +135 -41
- package/liquidGL.js +2304 -183
- package/package.json +3 -13
package/liquidGL.js
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Author: NaughtyDuk© – https://liquidgl.naughtyduk.com
|
|
6
6
|
* Licence: MIT
|
|
7
|
+
* Version: v2.0.1
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import html2canvas from "html2canvas";
|
|
10
|
-
|
|
11
10
|
const liquidGL = (() => {
|
|
12
11
|
"use strict";
|
|
13
12
|
|
|
13
|
+
const RECAPTURE_INTERVAL_MS = 250;
|
|
14
|
+
|
|
14
15
|
/* --------------------------------------------------
|
|
15
16
|
* Utilities
|
|
16
17
|
* ------------------------------------------------*/
|
|
@@ -65,14 +66,1749 @@ const liquidGL = (() => {
|
|
|
65
66
|
console.error("Program link error", gl.getProgramInfoLog(p));
|
|
66
67
|
return null;
|
|
67
68
|
}
|
|
68
|
-
return p;
|
|
69
|
-
}
|
|
69
|
+
return p;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* --------------------------------------------------
|
|
73
|
+
* NaughtyDOM
|
|
74
|
+
* ------------------------------------------------*/
|
|
75
|
+
const NaughtyDOM = (() => {
|
|
76
|
+
const IDENT = [1, 0, 0, 1, 0, 0];
|
|
77
|
+
const CLIP_OVERFLOW = /^(hidden|clip|scroll|auto)$/;
|
|
78
|
+
|
|
79
|
+
function mul(m, n) {
|
|
80
|
+
return [
|
|
81
|
+
m[0] * n[0] + m[2] * n[1],
|
|
82
|
+
m[1] * n[0] + m[3] * n[1],
|
|
83
|
+
m[0] * n[2] + m[2] * n[3],
|
|
84
|
+
m[1] * n[2] + m[3] * n[3],
|
|
85
|
+
m[0] * n[4] + m[2] * n[5] + m[4],
|
|
86
|
+
m[1] * n[4] + m[3] * n[5] + m[5],
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function apply(m, x, y) {
|
|
91
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function invertLinear(m) {
|
|
95
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
96
|
+
if (!det || !isFinite(det)) return null;
|
|
97
|
+
return [m[3] / det, -m[1] / det, -m[2] / det, m[0] / det, 0, 0];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseMatrix(str) {
|
|
101
|
+
if (!str || str === "none") return null;
|
|
102
|
+
const open = str.indexOf("(");
|
|
103
|
+
if (open === -1) return null;
|
|
104
|
+
const kind = str.slice(0, open);
|
|
105
|
+
const v = str
|
|
106
|
+
.slice(open + 1, str.lastIndexOf(")"))
|
|
107
|
+
.split(",")
|
|
108
|
+
.map((n) => parseFloat(n));
|
|
109
|
+
if (kind === "matrix" && v.length >= 6) {
|
|
110
|
+
return [v[0], v[1], v[2], v[3], v[4], v[5]];
|
|
111
|
+
}
|
|
112
|
+
if (kind === "matrix3d" && v.length >= 16) {
|
|
113
|
+
return [v[0], v[1], v[4], v[5], v[12], v[13]];
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseOrigin(str) {
|
|
119
|
+
if (!str) return [0, 0];
|
|
120
|
+
const p = str.split(" ");
|
|
121
|
+
return [parseFloat(p[0]) || 0, parseFloat(p[1]) || 0];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isTransparent(color) {
|
|
125
|
+
if (!color || color === "transparent" || color === "none") return true;
|
|
126
|
+
const alpha = color.match(
|
|
127
|
+
/^(?:rgba|hsla|hwb|lab|lch|oklab|oklch|color)\([^)]*[,/]\s*([0-9.]+)%?\s*\)$/,
|
|
128
|
+
);
|
|
129
|
+
return alpha ? parseFloat(alpha[1]) === 0 : false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function splitTopLevel(value) {
|
|
133
|
+
const out = [];
|
|
134
|
+
let depth = 0;
|
|
135
|
+
let start = 0;
|
|
136
|
+
for (let i = 0; i < value.length; i++) {
|
|
137
|
+
const c = value[i];
|
|
138
|
+
if (c === "(") depth++;
|
|
139
|
+
else if (c === ")") depth--;
|
|
140
|
+
else if (c === "," && depth === 0) {
|
|
141
|
+
out.push(value.slice(start, i).trim());
|
|
142
|
+
start = i + 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const tail = value.slice(start).trim();
|
|
146
|
+
if (tail) out.push(tail);
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resolveLength(token, basis) {
|
|
151
|
+
if (!token) return 0;
|
|
152
|
+
if (token.indexOf("%") !== -1) {
|
|
153
|
+
return (parseFloat(token) / 100) * basis;
|
|
154
|
+
}
|
|
155
|
+
const n = parseFloat(token);
|
|
156
|
+
return isNaN(n) ? 0 : n;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function cornerRadius(value, w, h) {
|
|
160
|
+
if (!value) return [0, 0];
|
|
161
|
+
const parts = value.split(" ").filter(Boolean);
|
|
162
|
+
const rx = resolveLength(parts[0], w);
|
|
163
|
+
const ry = parts.length > 1 ? resolveLength(parts[1], h) : rx;
|
|
164
|
+
return [Math.max(0, rx), Math.max(0, ry)];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseRadii(style, w, h) {
|
|
168
|
+
const r = [
|
|
169
|
+
cornerRadius(style.borderTopLeftRadius, w, h),
|
|
170
|
+
cornerRadius(style.borderTopRightRadius, w, h),
|
|
171
|
+
cornerRadius(style.borderBottomRightRadius, w, h),
|
|
172
|
+
cornerRadius(style.borderBottomLeftRadius, w, h),
|
|
173
|
+
];
|
|
174
|
+
if (!r.some((c) => c[0] > 0 || c[1] > 0)) return null;
|
|
175
|
+
let f = 1;
|
|
176
|
+
const ratio = (sum, len) => (sum > len && sum > 0 ? len / sum : 1);
|
|
177
|
+
f = Math.min(
|
|
178
|
+
f,
|
|
179
|
+
ratio(r[0][0] + r[1][0], w),
|
|
180
|
+
ratio(r[3][0] + r[2][0], w),
|
|
181
|
+
ratio(r[0][1] + r[3][1], h),
|
|
182
|
+
ratio(r[1][1] + r[2][1], h),
|
|
183
|
+
);
|
|
184
|
+
if (f < 1) {
|
|
185
|
+
for (let i = 0; i < 4; i++) {
|
|
186
|
+
r[i][0] *= f;
|
|
187
|
+
r[i][1] *= f;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return r;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function insetRadii(radii, top, right, bottom, left) {
|
|
194
|
+
if (!radii) return null;
|
|
195
|
+
return [
|
|
196
|
+
[Math.max(0, radii[0][0] - left), Math.max(0, radii[0][1] - top)],
|
|
197
|
+
[Math.max(0, radii[1][0] - right), Math.max(0, radii[1][1] - top)],
|
|
198
|
+
[Math.max(0, radii[2][0] - right), Math.max(0, radii[2][1] - bottom)],
|
|
199
|
+
[Math.max(0, radii[3][0] - left), Math.max(0, radii[3][1] - bottom)],
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function invert(m) {
|
|
204
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
205
|
+
if (!det || !isFinite(det)) return null;
|
|
206
|
+
return [
|
|
207
|
+
m[3] / det,
|
|
208
|
+
-m[1] / det,
|
|
209
|
+
-m[2] / det,
|
|
210
|
+
m[0] / det,
|
|
211
|
+
(m[2] * m[5] - m[3] * m[4]) / det,
|
|
212
|
+
(m[1] * m[4] - m[0] * m[5]) / det,
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const imageCache = new Map();
|
|
217
|
+
const svgCache = new WeakMap();
|
|
218
|
+
let pending = [];
|
|
219
|
+
|
|
220
|
+
function sameOrigin(src) {
|
|
221
|
+
if (/^data:/.test(src) || /^blob:/.test(src)) return true;
|
|
222
|
+
try {
|
|
223
|
+
return new URL(src, location.href).origin === location.origin;
|
|
224
|
+
} catch (e) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function loadImage(src) {
|
|
230
|
+
if (!src) return null;
|
|
231
|
+
if (imageCache.has(src)) return imageCache.get(src);
|
|
232
|
+
const img = new Image();
|
|
233
|
+
const entry = { img, ready: false, failed: false };
|
|
234
|
+
imageCache.set(src, entry);
|
|
235
|
+
if (!sameOrigin(src)) img.crossOrigin = "anonymous";
|
|
236
|
+
const done = new Promise((resolve) => {
|
|
237
|
+
img.onload = () => {
|
|
238
|
+
entry.ready = true;
|
|
239
|
+
resolve();
|
|
240
|
+
};
|
|
241
|
+
img.onerror = () => {
|
|
242
|
+
entry.failed = true;
|
|
243
|
+
resolve();
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
img.src = src;
|
|
247
|
+
if (img.complete && img.naturalWidth) {
|
|
248
|
+
entry.ready = true;
|
|
249
|
+
} else {
|
|
250
|
+
pending.push(done);
|
|
251
|
+
}
|
|
252
|
+
return entry;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const SVG_PAINT = [
|
|
256
|
+
"fill",
|
|
257
|
+
"fill-opacity",
|
|
258
|
+
"fill-rule",
|
|
259
|
+
"stroke",
|
|
260
|
+
"stroke-width",
|
|
261
|
+
"stroke-opacity",
|
|
262
|
+
"stroke-linecap",
|
|
263
|
+
"stroke-linejoin",
|
|
264
|
+
"stroke-dasharray",
|
|
265
|
+
"stroke-dashoffset",
|
|
266
|
+
"opacity",
|
|
267
|
+
"color",
|
|
268
|
+
"stop-color",
|
|
269
|
+
"stop-opacity",
|
|
270
|
+
"font-family",
|
|
271
|
+
"font-size",
|
|
272
|
+
"font-weight",
|
|
273
|
+
"font-style",
|
|
274
|
+
"text-anchor",
|
|
275
|
+
"letter-spacing",
|
|
276
|
+
"display",
|
|
277
|
+
"visibility",
|
|
278
|
+
"transform",
|
|
279
|
+
"transform-origin",
|
|
280
|
+
"mix-blend-mode",
|
|
281
|
+
"clip-path",
|
|
282
|
+
"mask",
|
|
283
|
+
"filter",
|
|
284
|
+
"marker-start",
|
|
285
|
+
"marker-mid",
|
|
286
|
+
"marker-end",
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
function inlineSvgStyles(source, clone) {
|
|
290
|
+
const computed = getComputedStyle(source);
|
|
291
|
+
let css = "";
|
|
292
|
+
for (let i = 0; i < SVG_PAINT.length; i++) {
|
|
293
|
+
const prop = SVG_PAINT[i];
|
|
294
|
+
const value = computed.getPropertyValue(prop);
|
|
295
|
+
if (value) css += `${prop}:${value};`;
|
|
296
|
+
}
|
|
297
|
+
if (css) clone.setAttribute("style", css);
|
|
298
|
+
const sk = source.children;
|
|
299
|
+
const ck = clone.children;
|
|
300
|
+
for (let i = 0; i < sk.length && i < ck.length; i++) {
|
|
301
|
+
inlineSvgStyles(sk[i], ck[i]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function svgToImage(el) {
|
|
306
|
+
const r = el.getBoundingClientRect();
|
|
307
|
+
const signature = `${el.outerHTML.length}|${el.childElementCount}|${Math.round(r.width)}x${Math.round(r.height)}|${el.className}`;
|
|
308
|
+
const cached = svgCache.get(el);
|
|
309
|
+
if (cached && cached.signature === signature) return cached.entry;
|
|
310
|
+
|
|
311
|
+
const clone = el.cloneNode(true);
|
|
312
|
+
inlineSvgStyles(el, clone);
|
|
313
|
+
let html = new XMLSerializer().serializeToString(clone);
|
|
314
|
+
if (
|
|
315
|
+
!/^<svg[^>]*\swidth=/.test(html) ||
|
|
316
|
+
!/^<svg[^>]*\sheight=/.test(html)
|
|
317
|
+
) {
|
|
318
|
+
html = html.replace(
|
|
319
|
+
/^<svg/,
|
|
320
|
+
`<svg width="${r.width}" height="${r.height}"`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (!/xmlns=/.test(html)) {
|
|
324
|
+
html = html.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
325
|
+
}
|
|
326
|
+
const entry = loadImage(
|
|
327
|
+
"data:image/svg+xml;charset=utf-8," + encodeURIComponent(html),
|
|
328
|
+
);
|
|
329
|
+
svgCache.set(el, { signature, entry });
|
|
330
|
+
return entry;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function usableDomImage(el) {
|
|
334
|
+
return (
|
|
335
|
+
el.complete &&
|
|
336
|
+
el.naturalWidth > 0 &&
|
|
337
|
+
(sameOrigin(el.currentSrc || el.src) || !!el.crossOrigin)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function layerUrls(value) {
|
|
342
|
+
if (!value || value === "none") return [];
|
|
343
|
+
const out = [];
|
|
344
|
+
splitTopLevel(value).forEach((layer) => {
|
|
345
|
+
const m = layer.match(/^url\((['"]?)(.*?)\1\)$/);
|
|
346
|
+
if (m) out.push(m[2]);
|
|
347
|
+
});
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const COLOR_TOKEN =
|
|
352
|
+
/^(rgba?\([^)]*\)|hsla?\([^)]*\)|hwb\([^)]*\)|(?:ok)?lab\([^)]*\)|(?:ok)?lch\([^)]*\)|color\([^)]*\)|color-mix\([^)]*\)|#[0-9a-fA-F]{3,8}|[a-zA-Z]+)/;
|
|
353
|
+
|
|
354
|
+
function parseAngle(token) {
|
|
355
|
+
const v = parseFloat(token);
|
|
356
|
+
if (isNaN(v)) return 180;
|
|
357
|
+
if (token.indexOf("turn") !== -1) return v * 360;
|
|
358
|
+
if (token.indexOf("grad") !== -1) return v * 0.9;
|
|
359
|
+
if (token.indexOf("rad") !== -1) return (v * 180) / Math.PI;
|
|
360
|
+
return v;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function sideAngle(spec, w, h) {
|
|
364
|
+
const has = (k) => spec.indexOf(k) !== -1;
|
|
365
|
+
const diag = (Math.atan2(w, h) * 180) / Math.PI;
|
|
366
|
+
if (has("top") && has("right")) return diag;
|
|
367
|
+
if (has("bottom") && has("right")) return 180 - diag;
|
|
368
|
+
if (has("bottom") && has("left")) return 180 + diag;
|
|
369
|
+
if (has("top") && has("left")) return 360 - diag;
|
|
370
|
+
if (has("top")) return 0;
|
|
371
|
+
if (has("right")) return 90;
|
|
372
|
+
if (has("bottom")) return 180;
|
|
373
|
+
if (has("left")) return 270;
|
|
374
|
+
return 180;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function parseStops(parts, length) {
|
|
378
|
+
const stops = [];
|
|
379
|
+
parts.forEach((part) => {
|
|
380
|
+
const m = part.match(COLOR_TOKEN);
|
|
381
|
+
if (!m) return;
|
|
382
|
+
const color = m[0];
|
|
383
|
+
const rest = part.slice(color.length).trim();
|
|
384
|
+
const positions = rest ? rest.split(/\s+/) : [];
|
|
385
|
+
if (!positions.length) {
|
|
386
|
+
stops.push({ color, pos: null });
|
|
387
|
+
} else {
|
|
388
|
+
positions.forEach((p) => {
|
|
389
|
+
const pos =
|
|
390
|
+
p.indexOf("%") !== -1
|
|
391
|
+
? parseFloat(p) / 100
|
|
392
|
+
: length
|
|
393
|
+
? parseFloat(p) / length
|
|
394
|
+
: 0;
|
|
395
|
+
stops.push({ color, pos: isNaN(pos) ? null : pos });
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
if (!stops.length) return stops;
|
|
400
|
+
if (stops[0].pos === null) stops[0].pos = 0;
|
|
401
|
+
if (stops[stops.length - 1].pos === null) {
|
|
402
|
+
stops[stops.length - 1].pos = 1;
|
|
403
|
+
}
|
|
404
|
+
let last = 0;
|
|
405
|
+
for (let i = 0; i < stops.length; i++) {
|
|
406
|
+
if (stops[i].pos === null) {
|
|
407
|
+
let next = i;
|
|
408
|
+
while (next < stops.length && stops[next].pos === null) next++;
|
|
409
|
+
const span = stops[next].pos - last;
|
|
410
|
+
for (let k = i; k < next; k++) {
|
|
411
|
+
stops[k].pos = last + (span * (k - i + 1)) / (next - i + 1);
|
|
412
|
+
}
|
|
413
|
+
i = next - 1;
|
|
414
|
+
}
|
|
415
|
+
last = stops[i].pos;
|
|
416
|
+
if (i > 0 && stops[i].pos < stops[i - 1].pos) {
|
|
417
|
+
stops[i].pos = stops[i - 1].pos;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return stops;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function repeatStops(stops) {
|
|
424
|
+
if (stops.length < 2) return stops;
|
|
425
|
+
const first = stops[0].pos;
|
|
426
|
+
const period = stops[stops.length - 1].pos - first;
|
|
427
|
+
if (period <= 0.0001) return stops;
|
|
428
|
+
const out = [];
|
|
429
|
+
const cycles = Math.min(Math.ceil(1 / period) + 1, 200);
|
|
430
|
+
for (let c = -1; c < cycles; c++) {
|
|
431
|
+
for (let i = 0; i < stops.length; i++) {
|
|
432
|
+
const pos = stops[i].pos + period * c;
|
|
433
|
+
if (pos < -period || pos > 1 + period) continue;
|
|
434
|
+
out.push({
|
|
435
|
+
color: stops[i].color,
|
|
436
|
+
pos: Math.min(1, Math.max(0, pos)),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return out.length ? out : stops;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function resolvePosition(tokens, w, h, iw, ih) {
|
|
444
|
+
let x = "50%";
|
|
445
|
+
let y = "50%";
|
|
446
|
+
if (tokens.length === 1) {
|
|
447
|
+
x = tokens[0];
|
|
448
|
+
y = "50%";
|
|
449
|
+
if (tokens[0] === "top" || tokens[0] === "bottom") {
|
|
450
|
+
y = tokens[0];
|
|
451
|
+
x = "50%";
|
|
452
|
+
}
|
|
453
|
+
} else if (tokens.length >= 2) {
|
|
454
|
+
x = tokens[0];
|
|
455
|
+
y = tokens[1];
|
|
456
|
+
}
|
|
457
|
+
const map = {
|
|
458
|
+
left: "0%",
|
|
459
|
+
top: "0%",
|
|
460
|
+
center: "50%",
|
|
461
|
+
right: "100%",
|
|
462
|
+
bottom: "100%",
|
|
463
|
+
};
|
|
464
|
+
if (map[x] !== undefined) x = map[x];
|
|
465
|
+
if (map[y] !== undefined) y = map[y];
|
|
466
|
+
const px =
|
|
467
|
+
x.indexOf("%") !== -1
|
|
468
|
+
? (parseFloat(x) / 100) * (w - iw)
|
|
469
|
+
: parseFloat(x) || 0;
|
|
470
|
+
const py =
|
|
471
|
+
y.indexOf("%") !== -1
|
|
472
|
+
? (parseFloat(y) / 100) * (h - ih)
|
|
473
|
+
: parseFloat(y) || 0;
|
|
474
|
+
return [px, py];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function makeGradient(ctx, spec, x, y, w, h) {
|
|
478
|
+
const open = spec.indexOf("(");
|
|
479
|
+
const kind = spec.slice(0, open);
|
|
480
|
+
const body = spec.slice(open + 1, spec.lastIndexOf(")"));
|
|
481
|
+
const parts = splitTopLevel(body);
|
|
482
|
+
if (!parts.length) return null;
|
|
483
|
+
const repeating = kind.indexOf("repeating-") === 0;
|
|
484
|
+
const base = kind.replace("repeating-", "");
|
|
485
|
+
|
|
486
|
+
if (base === "linear-gradient") {
|
|
487
|
+
let angle = 180;
|
|
488
|
+
if (/^(to\s|[-0-9.]+(deg|grad|rad|turn))/.test(parts[0])) {
|
|
489
|
+
angle =
|
|
490
|
+
parts[0].indexOf("to ") === 0
|
|
491
|
+
? sideAngle(parts[0], w, h)
|
|
492
|
+
: parseAngle(parts[0]);
|
|
493
|
+
parts.shift();
|
|
494
|
+
}
|
|
495
|
+
const rad = ((angle - 90) * Math.PI) / 180;
|
|
496
|
+
const dx = Math.cos(rad);
|
|
497
|
+
const dy = Math.sin(rad);
|
|
498
|
+
const ar = (angle * Math.PI) / 180;
|
|
499
|
+
const length = Math.abs(w * Math.sin(ar)) + Math.abs(h * Math.cos(ar));
|
|
500
|
+
const cx = x + w / 2;
|
|
501
|
+
const cy = y + h / 2;
|
|
502
|
+
let stops = parseStops(parts, length);
|
|
503
|
+
if (!stops.length) return null;
|
|
504
|
+
if (repeating) stops = repeatStops(stops);
|
|
505
|
+
const g = ctx.createLinearGradient(
|
|
506
|
+
cx - (dx * length) / 2,
|
|
507
|
+
cy - (dy * length) / 2,
|
|
508
|
+
cx + (dx * length) / 2,
|
|
509
|
+
cy + (dy * length) / 2,
|
|
510
|
+
);
|
|
511
|
+
stops.forEach((s) => {
|
|
512
|
+
try {
|
|
513
|
+
g.addColorStop(Math.min(1, Math.max(0, s.pos)), s.color);
|
|
514
|
+
} catch (e) {}
|
|
515
|
+
});
|
|
516
|
+
return { gradient: g };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (base === "radial-gradient") {
|
|
520
|
+
let shape = "ellipse";
|
|
521
|
+
let sizing = "farthest-corner";
|
|
522
|
+
let posTokens = [];
|
|
523
|
+
let explicit = [];
|
|
524
|
+
if (!COLOR_TOKEN.test(parts[0]) || /\bat\b/.test(parts[0])) {
|
|
525
|
+
const head = parts[0];
|
|
526
|
+
const atIndex = head.indexOf(" at ");
|
|
527
|
+
const geom = (atIndex === -1 ? head : head.slice(0, atIndex)).trim();
|
|
528
|
+
if (atIndex !== -1) {
|
|
529
|
+
posTokens = head
|
|
530
|
+
.slice(atIndex + 4)
|
|
531
|
+
.trim()
|
|
532
|
+
.split(/\s+/);
|
|
533
|
+
}
|
|
534
|
+
geom.split(/\s+/).forEach((tok) => {
|
|
535
|
+
if (tok === "circle" || tok === "ellipse") shape = tok;
|
|
536
|
+
else if (/closest|farthest/.test(tok)) sizing = tok;
|
|
537
|
+
else if (tok) explicit.push(tok);
|
|
538
|
+
});
|
|
539
|
+
if (geom || atIndex !== -1) parts.shift();
|
|
540
|
+
}
|
|
541
|
+
const cxy = posTokens.length
|
|
542
|
+
? resolvePosition(posTokens, w, h, 0, 0)
|
|
543
|
+
: [w / 2, h / 2];
|
|
544
|
+
const cx = x + cxy[0];
|
|
545
|
+
const cy = y + cxy[1];
|
|
546
|
+
const lx = cxy[0];
|
|
547
|
+
const ly = cxy[1];
|
|
548
|
+
let rx;
|
|
549
|
+
let ry;
|
|
550
|
+
if (explicit.length) {
|
|
551
|
+
rx = resolveLength(explicit[0], w);
|
|
552
|
+
ry = explicit.length > 1 ? resolveLength(explicit[1], h) : rx;
|
|
553
|
+
} else {
|
|
554
|
+
const dxs = [Math.abs(lx), Math.abs(w - lx)];
|
|
555
|
+
const dys = [Math.abs(ly), Math.abs(h - ly)];
|
|
556
|
+
const near = sizing.indexOf("closest") === 0;
|
|
557
|
+
const sx = near ? Math.min(dxs[0], dxs[1]) : Math.max(dxs[0], dxs[1]);
|
|
558
|
+
const sy = near ? Math.min(dys[0], dys[1]) : Math.max(dys[0], dys[1]);
|
|
559
|
+
if (sizing.indexOf("side") !== -1) {
|
|
560
|
+
rx = sx;
|
|
561
|
+
ry = sy;
|
|
562
|
+
} else {
|
|
563
|
+
rx = Math.sqrt(sx * sx + sy * sy);
|
|
564
|
+
ry = rx;
|
|
565
|
+
if (shape === "ellipse") {
|
|
566
|
+
rx = sx * Math.SQRT2;
|
|
567
|
+
ry = sy * Math.SQRT2;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (shape === "circle") {
|
|
571
|
+
rx =
|
|
572
|
+
sizing.indexOf("closest") === 0
|
|
573
|
+
? Math.min(sx, sy)
|
|
574
|
+
: Math.max(sx, sy);
|
|
575
|
+
if (sizing.indexOf("corner") !== -1)
|
|
576
|
+
rx = Math.sqrt(sx * sx + sy * sy);
|
|
577
|
+
ry = rx;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
rx = Math.max(0.01, rx);
|
|
581
|
+
ry = Math.max(0.01, ry);
|
|
582
|
+
let stops = parseStops(parts, rx);
|
|
583
|
+
if (!stops.length) return null;
|
|
584
|
+
if (repeating) stops = repeatStops(stops);
|
|
585
|
+
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, rx);
|
|
586
|
+
stops.forEach((s) => {
|
|
587
|
+
try {
|
|
588
|
+
g.addColorStop(Math.min(1, Math.max(0, s.pos)), s.color);
|
|
589
|
+
} catch (e) {}
|
|
590
|
+
});
|
|
591
|
+
return { gradient: g, scaleY: ry / rx, cx, cy };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (base === "conic-gradient" && ctx.createConicGradient) {
|
|
595
|
+
let from = 0;
|
|
596
|
+
let posTokens = [];
|
|
597
|
+
if (/^(from\s|at\s)/.test(parts[0])) {
|
|
598
|
+
const head = parts[0];
|
|
599
|
+
const atIndex = head.indexOf(" at ");
|
|
600
|
+
const fromPart = atIndex === -1 ? head : head.slice(0, atIndex);
|
|
601
|
+
if (fromPart.indexOf("from") === 0) {
|
|
602
|
+
from = parseAngle(fromPart.replace("from", "").trim());
|
|
603
|
+
}
|
|
604
|
+
if (atIndex !== -1) {
|
|
605
|
+
posTokens = head
|
|
606
|
+
.slice(atIndex + 4)
|
|
607
|
+
.trim()
|
|
608
|
+
.split(/\s+/);
|
|
609
|
+
}
|
|
610
|
+
parts.shift();
|
|
611
|
+
}
|
|
612
|
+
const cxy = posTokens.length
|
|
613
|
+
? resolvePosition(posTokens, w, h, 0, 0)
|
|
614
|
+
: [w / 2, h / 2];
|
|
615
|
+
let stops = parseStops(parts, 360);
|
|
616
|
+
if (!stops.length) return null;
|
|
617
|
+
if (repeating) stops = repeatStops(stops);
|
|
618
|
+
const g = ctx.createConicGradient(
|
|
619
|
+
((from - 90) * Math.PI) / 180,
|
|
620
|
+
x + cxy[0],
|
|
621
|
+
y + cxy[1],
|
|
622
|
+
);
|
|
623
|
+
stops.forEach((s) => {
|
|
624
|
+
try {
|
|
625
|
+
g.addColorStop(Math.min(1, Math.max(0, s.pos)), s.color);
|
|
626
|
+
} catch (e) {}
|
|
627
|
+
});
|
|
628
|
+
return { gradient: g };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function boxFor(kind, node, style) {
|
|
635
|
+
let x = node.x;
|
|
636
|
+
let y = node.y;
|
|
637
|
+
let w = node.w;
|
|
638
|
+
let h = node.h;
|
|
639
|
+
let radii = node.radii;
|
|
640
|
+
const bt = parseFloat(style.borderTopWidth) || 0;
|
|
641
|
+
const br = parseFloat(style.borderRightWidth) || 0;
|
|
642
|
+
const bb = parseFloat(style.borderBottomWidth) || 0;
|
|
643
|
+
const bl = parseFloat(style.borderLeftWidth) || 0;
|
|
644
|
+
if (kind === "padding-box" || kind === "content-box") {
|
|
645
|
+
x += bl;
|
|
646
|
+
y += bt;
|
|
647
|
+
w -= bl + br;
|
|
648
|
+
h -= bt + bb;
|
|
649
|
+
radii = insetRadii(radii, bt, br, bb, bl);
|
|
650
|
+
if (kind === "content-box") {
|
|
651
|
+
const pt = parseFloat(style.paddingTop) || 0;
|
|
652
|
+
const pr = parseFloat(style.paddingRight) || 0;
|
|
653
|
+
const pb = parseFloat(style.paddingBottom) || 0;
|
|
654
|
+
const pl = parseFloat(style.paddingLeft) || 0;
|
|
655
|
+
x += pl;
|
|
656
|
+
y += pt;
|
|
657
|
+
w -= pl + pr;
|
|
658
|
+
h -= pt + pb;
|
|
659
|
+
radii = insetRadii(radii, pt, pr, pb, pl);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return { x, y, w: Math.max(0, w), h: Math.max(0, h), radii };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function tracePath(ctx, x, y, w, h, radii) {
|
|
666
|
+
if (w <= 0 || h <= 0) return;
|
|
667
|
+
if (!radii) {
|
|
668
|
+
ctx.rect(x, y, w, h);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const [tl, tr, br, bl] = radii;
|
|
672
|
+
const HALF = Math.PI / 2;
|
|
673
|
+
ctx.moveTo(x + tl[0], y);
|
|
674
|
+
ctx.lineTo(x + w - tr[0], y);
|
|
675
|
+
if (tr[0] > 0 || tr[1] > 0) {
|
|
676
|
+
ctx.ellipse(x + w - tr[0], y + tr[1], tr[0], tr[1], 0, -HALF, 0);
|
|
677
|
+
}
|
|
678
|
+
ctx.lineTo(x + w, y + h - br[1]);
|
|
679
|
+
if (br[0] > 0 || br[1] > 0) {
|
|
680
|
+
ctx.ellipse(x + w - br[0], y + h - br[1], br[0], br[1], 0, 0, HALF);
|
|
681
|
+
}
|
|
682
|
+
ctx.lineTo(x + bl[0], y + h);
|
|
683
|
+
if (bl[0] > 0 || bl[1] > 0) {
|
|
684
|
+
ctx.ellipse(x + bl[0], y + h - bl[1], bl[0], bl[1], 0, HALF, Math.PI);
|
|
685
|
+
}
|
|
686
|
+
ctx.lineTo(x, y + tl[1]);
|
|
687
|
+
if (tl[0] > 0 || tl[1] > 0) {
|
|
688
|
+
ctx.ellipse(
|
|
689
|
+
x + tl[0],
|
|
690
|
+
y + tl[1],
|
|
691
|
+
tl[0],
|
|
692
|
+
tl[1],
|
|
693
|
+
0,
|
|
694
|
+
Math.PI,
|
|
695
|
+
Math.PI + HALF,
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
ctx.closePath();
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function borderBoxSize(el, style) {
|
|
702
|
+
let w = parseFloat(style.width);
|
|
703
|
+
let h = parseFloat(style.height);
|
|
704
|
+
if (isNaN(w) || isNaN(h)) {
|
|
705
|
+
if (typeof el.offsetWidth === "number" && el.offsetWidth) {
|
|
706
|
+
return { w: el.offsetWidth, h: el.offsetHeight };
|
|
707
|
+
}
|
|
708
|
+
const r = el.getBoundingClientRect();
|
|
709
|
+
return { w: r.width, h: r.height };
|
|
710
|
+
}
|
|
711
|
+
if (style.boxSizing !== "border-box") {
|
|
712
|
+
w +=
|
|
713
|
+
parseFloat(style.paddingLeft) +
|
|
714
|
+
parseFloat(style.paddingRight) +
|
|
715
|
+
parseFloat(style.borderLeftWidth) +
|
|
716
|
+
parseFloat(style.borderRightWidth);
|
|
717
|
+
h +=
|
|
718
|
+
parseFloat(style.paddingTop) +
|
|
719
|
+
parseFloat(style.paddingBottom) +
|
|
720
|
+
parseFloat(style.borderTopWidth) +
|
|
721
|
+
parseFloat(style.borderBottomWidth);
|
|
722
|
+
}
|
|
723
|
+
return { w, h };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function isStackingContext(el, style) {
|
|
727
|
+
if (style.position !== "static" && style.zIndex !== "auto") return true;
|
|
728
|
+
if (style.position === "fixed" || style.position === "sticky")
|
|
729
|
+
return true;
|
|
730
|
+
if (parseFloat(style.opacity) < 1) return true;
|
|
731
|
+
if (style.transform && style.transform !== "none") return true;
|
|
732
|
+
if (style.filter && style.filter !== "none") return true;
|
|
733
|
+
if (style.isolation === "isolate") return true;
|
|
734
|
+
if (style.mixBlendMode && style.mixBlendMode !== "normal") return true;
|
|
735
|
+
if (/paint|layout|strict|content/.test(style.contain || "")) return true;
|
|
736
|
+
if (style.webkitOverflowScrolling === "touch") return true;
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function measureRuns(el, style) {
|
|
741
|
+
const runs = [];
|
|
742
|
+
if (style.visibility !== "visible") return runs;
|
|
743
|
+
|
|
744
|
+
const transform = style.textTransform;
|
|
745
|
+
const kids = el.childNodes;
|
|
746
|
+
let range = null;
|
|
747
|
+
|
|
748
|
+
for (let i = 0; i < kids.length; i++) {
|
|
749
|
+
const textNode = kids[i];
|
|
750
|
+
if (textNode.nodeType !== 3) continue;
|
|
751
|
+
const raw = textNode.data;
|
|
752
|
+
if (!raw || !raw.trim()) continue;
|
|
753
|
+
if (!range) range = document.createRange();
|
|
754
|
+
|
|
755
|
+
const re = /\S+/g;
|
|
756
|
+
let match;
|
|
757
|
+
while ((match = re.exec(raw)) !== null) {
|
|
758
|
+
try {
|
|
759
|
+
range.setStart(textNode, match.index);
|
|
760
|
+
range.setEnd(textNode, match.index + match[0].length);
|
|
761
|
+
} catch (e) {
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const rects = range.getClientRects();
|
|
765
|
+
if (!rects.length) continue;
|
|
766
|
+
|
|
767
|
+
let text = match[0];
|
|
768
|
+
if (transform === "uppercase") text = text.toUpperCase();
|
|
769
|
+
else if (transform === "lowercase") text = text.toLowerCase();
|
|
770
|
+
else if (transform === "capitalize") {
|
|
771
|
+
text = text.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (rects.length === 1) {
|
|
775
|
+
const r = rects[0];
|
|
776
|
+
runs.push({
|
|
777
|
+
text,
|
|
778
|
+
left: r.left,
|
|
779
|
+
top: r.top,
|
|
780
|
+
right: r.right,
|
|
781
|
+
height: r.height,
|
|
782
|
+
});
|
|
783
|
+
} else {
|
|
784
|
+
const per = Math.ceil(text.length / rects.length);
|
|
785
|
+
for (let k = 0; k < rects.length; k++) {
|
|
786
|
+
const slice = text.substr(k * per, per);
|
|
787
|
+
if (!slice) continue;
|
|
788
|
+
const r = rects[k];
|
|
789
|
+
runs.push({
|
|
790
|
+
text: slice,
|
|
791
|
+
left: r.left,
|
|
792
|
+
top: r.top,
|
|
793
|
+
right: r.right,
|
|
794
|
+
height: r.height,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const tag = el.tagName;
|
|
802
|
+
if (tag === "INPUT" || tag === "TEXTAREA") {
|
|
803
|
+
const value = el.value || el.placeholder;
|
|
804
|
+
if (value) runs.push({ text: value, value: true });
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return runs;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function discoverAssets(el, style) {
|
|
811
|
+
const layers = style.backgroundImage;
|
|
812
|
+
if (layers && layers !== "none") layerUrls(layers).forEach(loadImage);
|
|
813
|
+
const tag = el.tagName;
|
|
814
|
+
if (tag === "IMG") {
|
|
815
|
+
const src = el.currentSrc || el.src;
|
|
816
|
+
if (src && !usableDomImage(el)) loadImage(src);
|
|
817
|
+
} else if (tag === "svg") {
|
|
818
|
+
svgToImage(el);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function buildNode(el, parent, clips, ignore) {
|
|
823
|
+
if (ignore && ignore(el)) return null;
|
|
824
|
+
const style = getComputedStyle(el);
|
|
825
|
+
if (style.display === "none") return null;
|
|
826
|
+
|
|
827
|
+
const size = borderBoxSize(el, style);
|
|
828
|
+
const rect = el.getBoundingClientRect();
|
|
829
|
+
const B = parent ? parent.m : IDENT;
|
|
830
|
+
const Blin = [B[0], B[1], B[2], B[3], 0, 0];
|
|
831
|
+
const own = parseMatrix(style.transform);
|
|
832
|
+
const N = own || IDENT;
|
|
833
|
+
const Nlin = [N[0], N[1], N[2], N[3], 0, 0];
|
|
834
|
+
const BN = mul(Blin, Nlin);
|
|
835
|
+
|
|
836
|
+
let x;
|
|
837
|
+
let y;
|
|
838
|
+
let m;
|
|
839
|
+
if (!own && (!parent || parent.untransformed)) {
|
|
840
|
+
x = rect.left;
|
|
841
|
+
y = rect.top;
|
|
842
|
+
m = IDENT;
|
|
843
|
+
} else {
|
|
844
|
+
const o = parseOrigin(style.transformOrigin);
|
|
845
|
+
const no = apply(Nlin, o[0], o[1]);
|
|
846
|
+
const tv = [o[0] - no[0] + N[4], o[1] - no[1] + N[5]];
|
|
847
|
+
const bt = apply(Blin, tv[0], tv[1]);
|
|
848
|
+
const C = [bt[0] + B[4], bt[1] + B[5]];
|
|
849
|
+
let minX = Infinity;
|
|
850
|
+
let minY = Infinity;
|
|
851
|
+
const corners = [
|
|
852
|
+
[0, 0],
|
|
853
|
+
[size.w, 0],
|
|
854
|
+
[0, size.h],
|
|
855
|
+
[size.w, size.h],
|
|
856
|
+
];
|
|
857
|
+
for (let i = 0; i < corners.length; i++) {
|
|
858
|
+
const p = apply(BN, corners[i][0], corners[i][1]);
|
|
859
|
+
if (p[0] < minX) minX = p[0];
|
|
860
|
+
if (p[1] < minY) minY = p[1];
|
|
861
|
+
}
|
|
862
|
+
const inv = invertLinear(Blin);
|
|
863
|
+
if (!inv) return null;
|
|
864
|
+
const u = apply(inv, rect.left - minX - C[0], rect.top - minY - C[1]);
|
|
865
|
+
x = u[0];
|
|
866
|
+
y = u[1];
|
|
867
|
+
m = [BN[0], BN[1], BN[2], BN[3], C[0], C[1]];
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const radii = parseRadii(style, size.w, size.h);
|
|
871
|
+
const node = {
|
|
872
|
+
el,
|
|
873
|
+
style,
|
|
874
|
+
x,
|
|
875
|
+
y,
|
|
876
|
+
w: size.w,
|
|
877
|
+
h: size.h,
|
|
878
|
+
m,
|
|
879
|
+
radii,
|
|
880
|
+
clips,
|
|
881
|
+
opacity: parseFloat(style.opacity),
|
|
882
|
+
untransformed: !own && (!parent || parent.untransformed),
|
|
883
|
+
children: [],
|
|
884
|
+
runs: measureRuns(el, style),
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
discoverAssets(el, style);
|
|
888
|
+
|
|
889
|
+
if (
|
|
890
|
+
CLIP_OVERFLOW.test(style.overflowX) ||
|
|
891
|
+
CLIP_OVERFLOW.test(style.overflowY)
|
|
892
|
+
) {
|
|
893
|
+
const bt = parseFloat(style.borderTopWidth);
|
|
894
|
+
const br = parseFloat(style.borderRightWidth);
|
|
895
|
+
const bb = parseFloat(style.borderBottomWidth);
|
|
896
|
+
const bl = parseFloat(style.borderLeftWidth);
|
|
897
|
+
node.childClips = clips.concat([
|
|
898
|
+
{
|
|
899
|
+
m,
|
|
900
|
+
x: x + bl,
|
|
901
|
+
y: y + bt,
|
|
902
|
+
w: Math.max(0, size.w - bl - br),
|
|
903
|
+
h: Math.max(0, size.h - bt - bb),
|
|
904
|
+
radii: insetRadii(radii, bt, br, bb, bl),
|
|
905
|
+
},
|
|
906
|
+
]);
|
|
907
|
+
} else {
|
|
908
|
+
node.childClips = clips;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
return node;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function newStack(node) {
|
|
915
|
+
return {
|
|
916
|
+
node,
|
|
917
|
+
negative: [],
|
|
918
|
+
zeroOrAuto: [],
|
|
919
|
+
positive: [],
|
|
920
|
+
floats: [],
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function collect(node, stack, ignore) {
|
|
925
|
+
if (node.el.tagName === "svg") return;
|
|
926
|
+
const kids = node.el.children;
|
|
927
|
+
for (let i = 0; i < kids.length; i++) {
|
|
928
|
+
const child = buildNode(kids[i], node, node.childClips, ignore);
|
|
929
|
+
if (!child) continue;
|
|
930
|
+
const cs = child.style;
|
|
931
|
+
if (isStackingContext(child.el, cs)) {
|
|
932
|
+
const sub = newStack(child);
|
|
933
|
+
collect(child, sub, ignore);
|
|
934
|
+
const z =
|
|
935
|
+
cs.position !== "static" && cs.zIndex !== "auto"
|
|
936
|
+
? parseInt(cs.zIndex, 10) || 0
|
|
937
|
+
: 0;
|
|
938
|
+
if (z < 0) stack.negative.push({ z, sub });
|
|
939
|
+
else if (z > 0) stack.positive.push({ z, sub });
|
|
940
|
+
else stack.zeroOrAuto.push(sub);
|
|
941
|
+
} else if (cs.position !== "static") {
|
|
942
|
+
const sub = newStack(child);
|
|
943
|
+
collect(child, sub, ignore);
|
|
944
|
+
stack.zeroOrAuto.push(sub);
|
|
945
|
+
} else if (cs.float !== "none") {
|
|
946
|
+
const sub = newStack(child);
|
|
947
|
+
collect(child, sub, ignore);
|
|
948
|
+
stack.floats.push(sub);
|
|
949
|
+
} else {
|
|
950
|
+
node.children.push(child);
|
|
951
|
+
collect(child, stack, ignore);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function Painter(ctx, base) {
|
|
957
|
+
this.ctx = ctx;
|
|
958
|
+
this.base = base;
|
|
959
|
+
this.activeClips = null;
|
|
960
|
+
this.alphaStack = [];
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
Painter.prototype.space = function (m) {
|
|
964
|
+
const d = mul(this.base, m);
|
|
965
|
+
this.ctx.setTransform(d[0], d[1], d[2], d[3], d[4], d[5]);
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
Painter.prototype.setClips = function (clips) {
|
|
969
|
+
if (this.activeClips === clips) return;
|
|
970
|
+
const ctx = this.ctx;
|
|
971
|
+
if (this.activeClips !== null) ctx.restore();
|
|
972
|
+
ctx.save();
|
|
973
|
+
for (let i = 0; i < clips.length; i++) {
|
|
974
|
+
const c = clips[i];
|
|
975
|
+
this.space(c.m);
|
|
976
|
+
ctx.beginPath();
|
|
977
|
+
tracePath(ctx, c.x, c.y, c.w, c.h, c.radii);
|
|
978
|
+
ctx.clip();
|
|
979
|
+
}
|
|
980
|
+
this.activeClips = clips;
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
Painter.prototype.release = function () {
|
|
984
|
+
if (this.activeClips !== null) {
|
|
985
|
+
this.ctx.restore();
|
|
986
|
+
this.activeClips = null;
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
Painter.prototype.shadows = function (node) {
|
|
991
|
+
const value = node.style.boxShadow;
|
|
992
|
+
if (!value || value === "none") return;
|
|
993
|
+
const ctx = this.ctx;
|
|
994
|
+
const layers = splitTopLevel(value).reverse();
|
|
995
|
+
for (let i = 0; i < layers.length; i++) {
|
|
996
|
+
const raw = layers[i];
|
|
997
|
+
const inset = raw.indexOf("inset") !== -1;
|
|
998
|
+
const body = raw.replace("inset", "").trim();
|
|
999
|
+
const colorMatch = body.match(
|
|
1000
|
+
/^(rgba?\([^)]*\)|hsla?\([^)]*\)|[a-z]+\([^)]*\)|#[0-9a-f]+|[a-z]+)/i,
|
|
1001
|
+
);
|
|
1002
|
+
if (!colorMatch) continue;
|
|
1003
|
+
const color = colorMatch[0];
|
|
1004
|
+
if (isTransparent(color)) continue;
|
|
1005
|
+
const nums = body
|
|
1006
|
+
.slice(color.length)
|
|
1007
|
+
.trim()
|
|
1008
|
+
.split(/\s+/)
|
|
1009
|
+
.map((n) => parseFloat(n) || 0);
|
|
1010
|
+
const dx = nums[0] || 0;
|
|
1011
|
+
const dy = nums[1] || 0;
|
|
1012
|
+
const blur = nums[2] || 0;
|
|
1013
|
+
const spread = nums[3] || 0;
|
|
1014
|
+
|
|
1015
|
+
ctx.save();
|
|
1016
|
+
this.space(node.m);
|
|
1017
|
+
if (inset) {
|
|
1018
|
+
ctx.beginPath();
|
|
1019
|
+
tracePath(ctx, node.x, node.y, node.w, node.h, node.radii);
|
|
1020
|
+
ctx.clip();
|
|
1021
|
+
const gx = node.x - node.w - 100;
|
|
1022
|
+
const gy = node.y - node.h - 100;
|
|
1023
|
+
ctx.beginPath();
|
|
1024
|
+
ctx.rect(gx, gy, node.w * 3 + 200, node.h * 3 + 200);
|
|
1025
|
+
tracePath(
|
|
1026
|
+
ctx,
|
|
1027
|
+
node.x + spread,
|
|
1028
|
+
node.y + spread,
|
|
1029
|
+
node.w - spread * 2,
|
|
1030
|
+
node.h - spread * 2,
|
|
1031
|
+
insetRadii(node.radii, spread, spread, spread, spread),
|
|
1032
|
+
);
|
|
1033
|
+
ctx.shadowColor = color;
|
|
1034
|
+
ctx.shadowOffsetX = dx;
|
|
1035
|
+
ctx.shadowOffsetY = dy;
|
|
1036
|
+
ctx.shadowBlur = blur;
|
|
1037
|
+
ctx.fillStyle = "#000";
|
|
1038
|
+
ctx.fill("evenodd");
|
|
1039
|
+
} else {
|
|
1040
|
+
ctx.beginPath();
|
|
1041
|
+
tracePath(ctx, node.x, node.y, node.w, node.h, node.radii);
|
|
1042
|
+
const gx = node.x - node.w - blur * 2 - Math.abs(dx) - spread - 100;
|
|
1043
|
+
const gy = node.y - node.h - blur * 2 - Math.abs(dy) - spread - 100;
|
|
1044
|
+
ctx.rect(
|
|
1045
|
+
gx,
|
|
1046
|
+
gy,
|
|
1047
|
+
node.w * 3 + blur * 4 + Math.abs(dx) * 2 + spread * 2 + 200,
|
|
1048
|
+
node.h * 3 + blur * 4 + Math.abs(dy) * 2 + spread * 2 + 200,
|
|
1049
|
+
);
|
|
1050
|
+
ctx.clip("evenodd");
|
|
1051
|
+
ctx.shadowColor = color;
|
|
1052
|
+
ctx.shadowOffsetX = dx;
|
|
1053
|
+
ctx.shadowOffsetY = dy;
|
|
1054
|
+
ctx.shadowBlur = blur;
|
|
1055
|
+
ctx.fillStyle = "#000";
|
|
1056
|
+
ctx.beginPath();
|
|
1057
|
+
tracePath(
|
|
1058
|
+
ctx,
|
|
1059
|
+
node.x - spread,
|
|
1060
|
+
node.y - spread,
|
|
1061
|
+
node.w + spread * 2,
|
|
1062
|
+
node.h + spread * 2,
|
|
1063
|
+
insetRadii(node.radii, -spread, -spread, -spread, -spread),
|
|
1064
|
+
);
|
|
1065
|
+
ctx.fill();
|
|
1066
|
+
}
|
|
1067
|
+
ctx.restore();
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
Painter.prototype.background = function (node) {
|
|
1072
|
+
const style = node.style;
|
|
1073
|
+
const ctx = this.ctx;
|
|
1074
|
+
const images = style.backgroundImage;
|
|
1075
|
+
const hasImages = images && images !== "none";
|
|
1076
|
+
if (isTransparent(style.backgroundColor) && !hasImages) return;
|
|
1077
|
+
|
|
1078
|
+
const clipList = splitTopLevel(style.backgroundClip || "border-box");
|
|
1079
|
+
const clipBox = boxFor(clipList[clipList.length - 1], node, style);
|
|
1080
|
+
if (clipBox.w <= 0 || clipBox.h <= 0) return;
|
|
1081
|
+
|
|
1082
|
+
if (!isTransparent(style.backgroundColor)) {
|
|
1083
|
+
this.space(node.m);
|
|
1084
|
+
ctx.beginPath();
|
|
1085
|
+
tracePath(
|
|
1086
|
+
ctx,
|
|
1087
|
+
clipBox.x,
|
|
1088
|
+
clipBox.y,
|
|
1089
|
+
clipBox.w,
|
|
1090
|
+
clipBox.h,
|
|
1091
|
+
clipBox.radii,
|
|
1092
|
+
);
|
|
1093
|
+
ctx.fillStyle = style.backgroundColor;
|
|
1094
|
+
ctx.fill();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (!hasImages) return;
|
|
1098
|
+
|
|
1099
|
+
const layers = splitTopLevel(images);
|
|
1100
|
+
const originList = splitTopLevel(style.backgroundOrigin || "padding-box");
|
|
1101
|
+
const sizeList = splitTopLevel(style.backgroundSize || "auto");
|
|
1102
|
+
const posList = splitTopLevel(style.backgroundPosition || "0% 0%");
|
|
1103
|
+
const repeatList = splitTopLevel(style.backgroundRepeat || "repeat");
|
|
1104
|
+
const pick = (list, i) => list[i % list.length];
|
|
1105
|
+
|
|
1106
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
1107
|
+
const layer = layers[i];
|
|
1108
|
+
if (!layer || layer === "none") continue;
|
|
1109
|
+
const lClip = boxFor(pick(clipList, i), node, style);
|
|
1110
|
+
const origin = boxFor(pick(originList, i), node, style);
|
|
1111
|
+
if (lClip.w <= 0 || lClip.h <= 0) continue;
|
|
1112
|
+
|
|
1113
|
+
ctx.save();
|
|
1114
|
+
this.space(node.m);
|
|
1115
|
+
ctx.beginPath();
|
|
1116
|
+
tracePath(ctx, lClip.x, lClip.y, lClip.w, lClip.h, lClip.radii);
|
|
1117
|
+
ctx.clip();
|
|
1118
|
+
|
|
1119
|
+
const urlMatch = layer.match(/^url\((['"]?)(.*?)\1\)$/);
|
|
1120
|
+
if (urlMatch) {
|
|
1121
|
+
const entry = imageCache.get(urlMatch[2]);
|
|
1122
|
+
if (entry && entry.ready) {
|
|
1123
|
+
this.tile(
|
|
1124
|
+
entry.img,
|
|
1125
|
+
origin,
|
|
1126
|
+
pick(sizeList, i),
|
|
1127
|
+
pick(posList, i),
|
|
1128
|
+
pick(repeatList, i),
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
} else if (/gradient\(/.test(layer)) {
|
|
1132
|
+
try {
|
|
1133
|
+
const made = makeGradient(
|
|
1134
|
+
ctx,
|
|
1135
|
+
layer,
|
|
1136
|
+
origin.x,
|
|
1137
|
+
origin.y,
|
|
1138
|
+
origin.w,
|
|
1139
|
+
origin.h,
|
|
1140
|
+
);
|
|
1141
|
+
if (made) {
|
|
1142
|
+
ctx.fillStyle = made.gradient;
|
|
1143
|
+
if (made.scaleY && Math.abs(made.scaleY - 1) > 0.001) {
|
|
1144
|
+
const inv = 1 / made.scaleY;
|
|
1145
|
+
ctx.translate(made.cx, made.cy);
|
|
1146
|
+
ctx.scale(1, made.scaleY);
|
|
1147
|
+
ctx.translate(-made.cx, -made.cy);
|
|
1148
|
+
ctx.fillRect(
|
|
1149
|
+
lClip.x,
|
|
1150
|
+
made.cy + (lClip.y - made.cy) * inv,
|
|
1151
|
+
lClip.w,
|
|
1152
|
+
lClip.h * inv,
|
|
1153
|
+
);
|
|
1154
|
+
} else {
|
|
1155
|
+
ctx.fillRect(lClip.x, lClip.y, lClip.w, lClip.h);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
} catch (e) {}
|
|
1159
|
+
}
|
|
1160
|
+
ctx.restore();
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
Painter.prototype.tile = function (img, area, size, position, repeat) {
|
|
1165
|
+
const ctx = this.ctx;
|
|
1166
|
+
const nw = img.naturalWidth || img.width;
|
|
1167
|
+
const nh = img.naturalHeight || img.height;
|
|
1168
|
+
if (!nw || !nh) return;
|
|
1169
|
+
|
|
1170
|
+
let dw;
|
|
1171
|
+
let dh;
|
|
1172
|
+
const ratio = nw / nh;
|
|
1173
|
+
if (size === "cover" || size === "contain") {
|
|
1174
|
+
const areaRatio = area.w / area.h;
|
|
1175
|
+
const wide = size === "cover" ? areaRatio < ratio : areaRatio > ratio;
|
|
1176
|
+
if (wide) {
|
|
1177
|
+
dh = area.h;
|
|
1178
|
+
dw = dh * ratio;
|
|
1179
|
+
} else {
|
|
1180
|
+
dw = area.w;
|
|
1181
|
+
dh = dw / ratio;
|
|
1182
|
+
}
|
|
1183
|
+
} else {
|
|
1184
|
+
const tokens = (size || "auto").split(/\s+/);
|
|
1185
|
+
const sx = tokens[0] || "auto";
|
|
1186
|
+
const sy = tokens[1] || "auto";
|
|
1187
|
+
if (sx === "auto" && sy === "auto") {
|
|
1188
|
+
dw = nw;
|
|
1189
|
+
dh = nh;
|
|
1190
|
+
} else if (sx === "auto") {
|
|
1191
|
+
dh = resolveLength(sy, area.h);
|
|
1192
|
+
dw = dh * ratio;
|
|
1193
|
+
} else if (sy === "auto") {
|
|
1194
|
+
dw = resolveLength(sx, area.w);
|
|
1195
|
+
dh = dw / ratio;
|
|
1196
|
+
} else {
|
|
1197
|
+
dw = resolveLength(sx, area.w);
|
|
1198
|
+
dh = resolveLength(sy, area.h);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (dw <= 0 || dh <= 0) return;
|
|
1202
|
+
|
|
1203
|
+
const offset = resolvePosition(
|
|
1204
|
+
(position || "0% 0%").split(/\s+/),
|
|
1205
|
+
area.w,
|
|
1206
|
+
area.h,
|
|
1207
|
+
dw,
|
|
1208
|
+
dh,
|
|
1209
|
+
);
|
|
1210
|
+
const ox = area.x + offset[0];
|
|
1211
|
+
const oy = area.y + offset[1];
|
|
1212
|
+
|
|
1213
|
+
if (repeat === "no-repeat") {
|
|
1214
|
+
ctx.drawImage(img, ox, oy, dw, dh);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const repeatX = repeat !== "repeat-y";
|
|
1219
|
+
const repeatY = repeat !== "repeat-x";
|
|
1220
|
+
const mode =
|
|
1221
|
+
repeatX && repeatY ? "repeat" : repeatX ? "repeat-x" : "repeat-y";
|
|
1222
|
+
const pattern = ctx.createPattern(img, mode);
|
|
1223
|
+
if (!pattern) return;
|
|
1224
|
+
|
|
1225
|
+
if (pattern.setTransform && typeof DOMMatrix !== "undefined") {
|
|
1226
|
+
pattern.setTransform(new DOMMatrix([dw / nw, 0, 0, dh / nh, ox, oy]));
|
|
1227
|
+
ctx.fillStyle = pattern;
|
|
1228
|
+
ctx.fillRect(
|
|
1229
|
+
repeatX ? area.x : ox,
|
|
1230
|
+
repeatY ? area.y : oy,
|
|
1231
|
+
repeatX ? area.w : dw,
|
|
1232
|
+
repeatY ? area.h : dh,
|
|
1233
|
+
);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
ctx.save();
|
|
1238
|
+
ctx.translate(ox, oy);
|
|
1239
|
+
ctx.scale(dw / nw, dh / nh);
|
|
1240
|
+
ctx.fillStyle = pattern;
|
|
1241
|
+
ctx.fillRect(
|
|
1242
|
+
((repeatX ? area.x : ox) - ox) / (dw / nw),
|
|
1243
|
+
((repeatY ? area.y : oy) - oy) / (dh / nh),
|
|
1244
|
+
(repeatX ? area.w : dw) / (dw / nw),
|
|
1245
|
+
(repeatY ? area.h : dh) / (dh / nh),
|
|
1246
|
+
);
|
|
1247
|
+
ctx.restore();
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
Painter.prototype.replaced = function (node) {
|
|
1251
|
+
const el = node.el;
|
|
1252
|
+
const tag = el.tagName;
|
|
1253
|
+
if (tag === "VIDEO" || tag === "IFRAME") return;
|
|
1254
|
+
|
|
1255
|
+
let source = null;
|
|
1256
|
+
if (tag === "IMG") {
|
|
1257
|
+
if (usableDomImage(el)) {
|
|
1258
|
+
source = el;
|
|
1259
|
+
} else {
|
|
1260
|
+
const entry = imageCache.get(el.currentSrc || el.src);
|
|
1261
|
+
if (entry && entry.ready) source = entry.img;
|
|
1262
|
+
}
|
|
1263
|
+
} else if (tag === "CANVAS") {
|
|
1264
|
+
source = el.width && el.height ? el : null;
|
|
1265
|
+
} else if (tag === "svg") {
|
|
1266
|
+
const cached = svgCache.get(el);
|
|
1267
|
+
if (cached && cached.entry.ready) source = cached.entry.img;
|
|
1268
|
+
}
|
|
1269
|
+
if (!source) return;
|
|
1270
|
+
|
|
1271
|
+
const style = node.style;
|
|
1272
|
+
const box = boxFor("content-box", node, style);
|
|
1273
|
+
if (box.w <= 0 || box.h <= 0) return;
|
|
1274
|
+
|
|
1275
|
+
const nw = source.naturalWidth || source.width || box.w;
|
|
1276
|
+
const nh = source.naturalHeight || source.height || box.h;
|
|
1277
|
+
if (!nw || !nh) return;
|
|
1278
|
+
|
|
1279
|
+
const fit = style.objectFit || "fill";
|
|
1280
|
+
let dw = box.w;
|
|
1281
|
+
let dh = box.h;
|
|
1282
|
+
if (fit !== "fill") {
|
|
1283
|
+
const ratio = nw / nh;
|
|
1284
|
+
const areaRatio = box.w / box.h;
|
|
1285
|
+
if (fit === "contain" || fit === "scale-down") {
|
|
1286
|
+
if (areaRatio > ratio) {
|
|
1287
|
+
dh = box.h;
|
|
1288
|
+
dw = dh * ratio;
|
|
1289
|
+
} else {
|
|
1290
|
+
dw = box.w;
|
|
1291
|
+
dh = dw / ratio;
|
|
1292
|
+
}
|
|
1293
|
+
if (fit === "scale-down" && (dw > nw || dh > nh)) {
|
|
1294
|
+
dw = nw;
|
|
1295
|
+
dh = nh;
|
|
1296
|
+
}
|
|
1297
|
+
} else if (fit === "cover") {
|
|
1298
|
+
if (areaRatio < ratio) {
|
|
1299
|
+
dh = box.h;
|
|
1300
|
+
dw = dh * ratio;
|
|
1301
|
+
} else {
|
|
1302
|
+
dw = box.w;
|
|
1303
|
+
dh = dw / ratio;
|
|
1304
|
+
}
|
|
1305
|
+
} else if (fit === "none") {
|
|
1306
|
+
dw = nw;
|
|
1307
|
+
dh = nh;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const offset = resolvePosition(
|
|
1312
|
+
(style.objectPosition || "50% 50%").split(/\s+/),
|
|
1313
|
+
box.w,
|
|
1314
|
+
box.h,
|
|
1315
|
+
dw,
|
|
1316
|
+
dh,
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
const ctx = this.ctx;
|
|
1320
|
+
ctx.save();
|
|
1321
|
+
this.space(node.m);
|
|
1322
|
+
ctx.beginPath();
|
|
1323
|
+
tracePath(ctx, box.x, box.y, box.w, box.h, box.radii);
|
|
1324
|
+
ctx.clip();
|
|
1325
|
+
try {
|
|
1326
|
+
ctx.drawImage(source, box.x + offset[0], box.y + offset[1], dw, dh);
|
|
1327
|
+
} catch (e) {}
|
|
1328
|
+
ctx.restore();
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
Painter.prototype.text = function (node) {
|
|
1332
|
+
const style = node.style;
|
|
1333
|
+
const ctx = this.ctx;
|
|
1334
|
+
const strokeWidth = parseFloat(style.webkitTextStrokeWidth) || 0;
|
|
1335
|
+
if (isTransparent(style.color) && strokeWidth <= 0) return;
|
|
1336
|
+
|
|
1337
|
+
this.setClips(node.clips);
|
|
1338
|
+
this.space(node.m);
|
|
1339
|
+
|
|
1340
|
+
const fontSize = parseFloat(style.fontSize) || 16;
|
|
1341
|
+
ctx.font = `${style.fontStyle} ${style.fontWeight} ${fontSize}px ${style.fontFamily}`;
|
|
1342
|
+
if ("letterSpacing" in ctx) {
|
|
1343
|
+
ctx.letterSpacing =
|
|
1344
|
+
style.letterSpacing === "normal" ? "0px" : style.letterSpacing;
|
|
1345
|
+
}
|
|
1346
|
+
if ("wordSpacing" in ctx) {
|
|
1347
|
+
ctx.wordSpacing =
|
|
1348
|
+
style.wordSpacing === "normal" ? "0px" : style.wordSpacing;
|
|
1349
|
+
}
|
|
1350
|
+
const rtl = style.direction === "rtl";
|
|
1351
|
+
ctx.direction = rtl ? "rtl" : "ltr";
|
|
1352
|
+
ctx.textAlign = rtl ? "right" : "left";
|
|
1353
|
+
ctx.textBaseline = "alphabetic";
|
|
1354
|
+
ctx.fillStyle = style.color;
|
|
1355
|
+
|
|
1356
|
+
const strokeColor = style.webkitTextStrokeColor;
|
|
1357
|
+
const strokeFirst = (style.paintOrder || "").indexOf("stroke") === 0;
|
|
1358
|
+
|
|
1359
|
+
const shadows = [];
|
|
1360
|
+
if (style.textShadow && style.textShadow !== "none") {
|
|
1361
|
+
splitTopLevel(style.textShadow).forEach((raw) => {
|
|
1362
|
+
const cm = raw.match(COLOR_TOKEN);
|
|
1363
|
+
if (!cm || isTransparent(cm[0])) return;
|
|
1364
|
+
const nums = raw
|
|
1365
|
+
.slice(cm[0].length)
|
|
1366
|
+
.trim()
|
|
1367
|
+
.split(/\s+/)
|
|
1368
|
+
.map((n) => parseFloat(n) || 0);
|
|
1369
|
+
shadows.push({
|
|
1370
|
+
color: cm[0],
|
|
1371
|
+
dx: nums[0] || 0,
|
|
1372
|
+
dy: nums[1] || 0,
|
|
1373
|
+
blur: nums[2] || 0,
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const inv = node.untransformed ? null : invert(node.m);
|
|
1379
|
+
const toLocal = (px, py) => (inv ? apply(inv, px, py) : [px, py]);
|
|
1380
|
+
const scaleY = inv ? Math.hypot(inv[2], inv[3]) : 1;
|
|
1381
|
+
|
|
1382
|
+
const decoration = style.textDecorationLine;
|
|
1383
|
+
const decorate =
|
|
1384
|
+
decoration && decoration !== "none"
|
|
1385
|
+
? {
|
|
1386
|
+
color: style.textDecorationColor || style.color,
|
|
1387
|
+
under: decoration.indexOf("underline") !== -1,
|
|
1388
|
+
through: decoration.indexOf("line-through") !== -1,
|
|
1389
|
+
over: decoration.indexOf("overline") !== -1,
|
|
1390
|
+
}
|
|
1391
|
+
: null;
|
|
1392
|
+
|
|
1393
|
+
const draw = (text, run) => {
|
|
1394
|
+
if (!text) return;
|
|
1395
|
+
const local = toLocal(run.left, run.top);
|
|
1396
|
+
const metrics = ctx.measureText(text);
|
|
1397
|
+
const ascent =
|
|
1398
|
+
metrics.fontBoundingBoxAscent ||
|
|
1399
|
+
metrics.actualBoundingBoxAscent ||
|
|
1400
|
+
fontSize * 0.8;
|
|
1401
|
+
const descent =
|
|
1402
|
+
metrics.fontBoundingBoxDescent ||
|
|
1403
|
+
metrics.actualBoundingBoxDescent ||
|
|
1404
|
+
fontSize * 0.2;
|
|
1405
|
+
const boxH = run.height * scaleY;
|
|
1406
|
+
const baseline = local[1] + (boxH - (ascent + descent)) / 2 + ascent;
|
|
1407
|
+
const anchor = rtl ? toLocal(run.right, run.top)[0] : local[0];
|
|
1408
|
+
|
|
1409
|
+
for (let s = 0; s < shadows.length; s++) {
|
|
1410
|
+
const sh = shadows[s];
|
|
1411
|
+
ctx.save();
|
|
1412
|
+
ctx.shadowColor = sh.color;
|
|
1413
|
+
ctx.shadowOffsetX = sh.dx;
|
|
1414
|
+
ctx.shadowOffsetY = sh.dy;
|
|
1415
|
+
ctx.shadowBlur = sh.blur;
|
|
1416
|
+
ctx.fillText(text, anchor, baseline);
|
|
1417
|
+
ctx.restore();
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
const strokeIt = () => {
|
|
1421
|
+
if (strokeWidth <= 0 || isTransparent(strokeColor)) return;
|
|
1422
|
+
ctx.save();
|
|
1423
|
+
ctx.lineWidth = strokeWidth * 2;
|
|
1424
|
+
ctx.strokeStyle = strokeColor;
|
|
1425
|
+
ctx.lineJoin = "round";
|
|
1426
|
+
ctx.strokeText(text, anchor, baseline);
|
|
1427
|
+
ctx.restore();
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
if (strokeFirst) strokeIt();
|
|
1431
|
+
ctx.fillText(text, anchor, baseline);
|
|
1432
|
+
if (!strokeFirst) strokeIt();
|
|
1433
|
+
|
|
1434
|
+
if (decorate) {
|
|
1435
|
+
const width = metrics.width;
|
|
1436
|
+
const x0 = rtl ? anchor - width : anchor;
|
|
1437
|
+
const thickness = Math.max(1, fontSize / 14);
|
|
1438
|
+
ctx.save();
|
|
1439
|
+
ctx.fillStyle = decorate.color;
|
|
1440
|
+
if (decorate.under) {
|
|
1441
|
+
ctx.fillRect(x0, baseline + thickness * 1.5, width, thickness);
|
|
1442
|
+
}
|
|
1443
|
+
if (decorate.through) {
|
|
1444
|
+
ctx.fillRect(x0, baseline - ascent / 3, width, thickness);
|
|
1445
|
+
}
|
|
1446
|
+
if (decorate.over) {
|
|
1447
|
+
ctx.fillRect(x0, baseline - ascent, width, thickness);
|
|
1448
|
+
}
|
|
1449
|
+
ctx.restore();
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
const runs = node.runs;
|
|
1454
|
+
for (let i = 0; i < runs.length; i++) {
|
|
1455
|
+
const run = runs[i];
|
|
1456
|
+
if (run.value) {
|
|
1457
|
+
const box = boxFor("content-box", node, style);
|
|
1458
|
+
const metrics = ctx.measureText(run.text);
|
|
1459
|
+
const ascent = metrics.fontBoundingBoxAscent || fontSize * 0.8;
|
|
1460
|
+
const descent = metrics.fontBoundingBoxDescent || fontSize * 0.2;
|
|
1461
|
+
const baseline = box.y + (box.h - (ascent + descent)) / 2 + ascent;
|
|
1462
|
+
ctx.fillText(run.text, rtl ? box.x + box.w : box.x, baseline);
|
|
1463
|
+
} else {
|
|
1464
|
+
draw(run.text, run);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
Painter.prototype.borders = function (node) {
|
|
1470
|
+
const style = node.style;
|
|
1471
|
+
const widths = [
|
|
1472
|
+
parseFloat(style.borderTopWidth) || 0,
|
|
1473
|
+
parseFloat(style.borderRightWidth) || 0,
|
|
1474
|
+
parseFloat(style.borderBottomWidth) || 0,
|
|
1475
|
+
parseFloat(style.borderLeftWidth) || 0,
|
|
1476
|
+
];
|
|
1477
|
+
const styles = [
|
|
1478
|
+
style.borderTopStyle,
|
|
1479
|
+
style.borderRightStyle,
|
|
1480
|
+
style.borderBottomStyle,
|
|
1481
|
+
style.borderLeftStyle,
|
|
1482
|
+
];
|
|
1483
|
+
const colors = [
|
|
1484
|
+
style.borderTopColor,
|
|
1485
|
+
style.borderRightColor,
|
|
1486
|
+
style.borderBottomColor,
|
|
1487
|
+
style.borderLeftColor,
|
|
1488
|
+
];
|
|
1489
|
+
let any = false;
|
|
1490
|
+
for (let i = 0; i < 4; i++) {
|
|
1491
|
+
if (
|
|
1492
|
+
widths[i] > 0 &&
|
|
1493
|
+
styles[i] !== "none" &&
|
|
1494
|
+
styles[i] !== "hidden" &&
|
|
1495
|
+
!isTransparent(colors[i])
|
|
1496
|
+
) {
|
|
1497
|
+
any = true;
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (!any) return;
|
|
1502
|
+
|
|
1503
|
+
const ctx = this.ctx;
|
|
1504
|
+
const inner = insetRadii(
|
|
1505
|
+
node.radii,
|
|
1506
|
+
widths[0],
|
|
1507
|
+
widths[1],
|
|
1508
|
+
widths[2],
|
|
1509
|
+
widths[3],
|
|
1510
|
+
);
|
|
1511
|
+
const ix = node.x + widths[3];
|
|
1512
|
+
const iy = node.y + widths[0];
|
|
1513
|
+
const iw = Math.max(0, node.w - widths[1] - widths[3]);
|
|
1514
|
+
const ih = Math.max(0, node.h - widths[0] - widths[2]);
|
|
1515
|
+
|
|
1516
|
+
const uniform =
|
|
1517
|
+
colors[0] === colors[1] &&
|
|
1518
|
+
colors[1] === colors[2] &&
|
|
1519
|
+
colors[2] === colors[3] &&
|
|
1520
|
+
styles[0] === styles[1] &&
|
|
1521
|
+
styles[1] === styles[2] &&
|
|
1522
|
+
styles[2] === styles[3] &&
|
|
1523
|
+
styles[0] === "solid";
|
|
1524
|
+
|
|
1525
|
+
this.space(node.m);
|
|
1526
|
+
|
|
1527
|
+
if (uniform) {
|
|
1528
|
+
ctx.beginPath();
|
|
1529
|
+
tracePath(ctx, node.x, node.y, node.w, node.h, node.radii);
|
|
1530
|
+
tracePath(ctx, ix, iy, iw, ih, inner);
|
|
1531
|
+
ctx.fillStyle = colors[0];
|
|
1532
|
+
ctx.fill("evenodd");
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const wedges = [
|
|
1537
|
+
[
|
|
1538
|
+
[node.x, node.y],
|
|
1539
|
+
[node.x + node.w, node.y],
|
|
1540
|
+
[ix + iw, iy],
|
|
1541
|
+
[ix, iy],
|
|
1542
|
+
],
|
|
1543
|
+
[
|
|
1544
|
+
[node.x + node.w, node.y],
|
|
1545
|
+
[node.x + node.w, node.y + node.h],
|
|
1546
|
+
[ix + iw, iy + ih],
|
|
1547
|
+
[ix + iw, iy],
|
|
1548
|
+
],
|
|
1549
|
+
[
|
|
1550
|
+
[node.x + node.w, node.y + node.h],
|
|
1551
|
+
[node.x, node.y + node.h],
|
|
1552
|
+
[ix, iy + ih],
|
|
1553
|
+
[ix + iw, iy + ih],
|
|
1554
|
+
],
|
|
1555
|
+
[
|
|
1556
|
+
[node.x, node.y + node.h],
|
|
1557
|
+
[node.x, node.y],
|
|
1558
|
+
[ix, iy],
|
|
1559
|
+
[ix, iy + ih],
|
|
1560
|
+
],
|
|
1561
|
+
];
|
|
1562
|
+
|
|
1563
|
+
for (let i = 0; i < 4; i++) {
|
|
1564
|
+
if (
|
|
1565
|
+
widths[i] <= 0 ||
|
|
1566
|
+
styles[i] === "none" ||
|
|
1567
|
+
styles[i] === "hidden" ||
|
|
1568
|
+
isTransparent(colors[i])
|
|
1569
|
+
) {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
ctx.save();
|
|
1573
|
+
this.space(node.m);
|
|
1574
|
+
const wedge = wedges[i];
|
|
1575
|
+
ctx.beginPath();
|
|
1576
|
+
ctx.moveTo(wedge[0][0], wedge[0][1]);
|
|
1577
|
+
for (let p = 1; p < wedge.length; p++) {
|
|
1578
|
+
ctx.lineTo(wedge[p][0], wedge[p][1]);
|
|
1579
|
+
}
|
|
1580
|
+
ctx.closePath();
|
|
1581
|
+
ctx.clip();
|
|
1582
|
+
ctx.beginPath();
|
|
1583
|
+
tracePath(ctx, node.x, node.y, node.w, node.h, node.radii);
|
|
1584
|
+
tracePath(ctx, ix, iy, iw, ih, inner);
|
|
1585
|
+
ctx.fillStyle = colors[i];
|
|
1586
|
+
ctx.fill("evenodd");
|
|
1587
|
+
ctx.restore();
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
Painter.prototype.node = function (node) {
|
|
1592
|
+
if (node.style.visibility !== "visible") return;
|
|
1593
|
+
if (node.w <= 0 || node.h <= 0) return;
|
|
1594
|
+
this.setClips(node.clips);
|
|
1595
|
+
this.shadows(node);
|
|
1596
|
+
this.background(node);
|
|
1597
|
+
this.borders(node);
|
|
1598
|
+
this.replaced(node);
|
|
1599
|
+
};
|
|
1600
|
+
|
|
1601
|
+
const OP_BOX = 0;
|
|
1602
|
+
const OP_TEXT = 1;
|
|
1603
|
+
const OP_ALPHA_PUSH = 2;
|
|
1604
|
+
const OP_ALPHA_POP = 3;
|
|
1605
|
+
|
|
1606
|
+
function emitInFlowBoxes(node, out) {
|
|
1607
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1608
|
+
const child = node.children[i];
|
|
1609
|
+
out.push({ t: OP_BOX, node: child });
|
|
1610
|
+
emitInFlowBoxes(child, out);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function emitInFlowText(node, out) {
|
|
1615
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1616
|
+
const child = node.children[i];
|
|
1617
|
+
if (child.runs.length) out.push({ t: OP_TEXT, node: child });
|
|
1618
|
+
emitInFlowText(child, out);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function emitStack(stack, out) {
|
|
1623
|
+
const alpha = stack.node.opacity;
|
|
1624
|
+
const fade = !isNaN(alpha) && alpha < 1;
|
|
1625
|
+
if (fade) out.push({ t: OP_ALPHA_PUSH, alpha });
|
|
1626
|
+
|
|
1627
|
+
out.push({ t: OP_BOX, node: stack.node });
|
|
1628
|
+
|
|
1629
|
+
const byZ = (a, b) => a.z - b.z;
|
|
1630
|
+
stack.negative.sort(byZ);
|
|
1631
|
+
for (let i = 0; i < stack.negative.length; i++) {
|
|
1632
|
+
emitStack(stack.negative[i].sub, out);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
emitInFlowBoxes(stack.node, out);
|
|
1636
|
+
|
|
1637
|
+
if (stack.node.runs.length) out.push({ t: OP_TEXT, node: stack.node });
|
|
1638
|
+
emitInFlowText(stack.node, out);
|
|
1639
|
+
|
|
1640
|
+
for (let i = 0; i < stack.floats.length; i++) {
|
|
1641
|
+
emitStack(stack.floats[i], out);
|
|
1642
|
+
}
|
|
1643
|
+
for (let i = 0; i < stack.zeroOrAuto.length; i++) {
|
|
1644
|
+
emitStack(stack.zeroOrAuto[i], out);
|
|
1645
|
+
}
|
|
1646
|
+
stack.positive.sort(byZ);
|
|
1647
|
+
for (let i = 0; i < stack.positive.length; i++) {
|
|
1648
|
+
emitStack(stack.positive[i].sub, out);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
if (fade) out.push({ t: OP_ALPHA_POP });
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
Painter.prototype.run = function (ops, from, budgetMs) {
|
|
1655
|
+
const ctx = this.ctx;
|
|
1656
|
+
const deadline = budgetMs ? performance.now() + budgetMs : 0;
|
|
1657
|
+
for (let i = from; i < ops.length; i++) {
|
|
1658
|
+
const op = ops[i];
|
|
1659
|
+
if (op.t === OP_BOX) {
|
|
1660
|
+
this.node(op.node);
|
|
1661
|
+
} else if (op.t === OP_TEXT) {
|
|
1662
|
+
this.text(op.node);
|
|
1663
|
+
} else if (op.t === OP_ALPHA_PUSH) {
|
|
1664
|
+
this.release();
|
|
1665
|
+
this.alphaStack.push(ctx.globalAlpha);
|
|
1666
|
+
ctx.globalAlpha = ctx.globalAlpha * op.alpha;
|
|
1667
|
+
} else {
|
|
1668
|
+
this.release();
|
|
1669
|
+
ctx.globalAlpha = this.alphaStack.pop();
|
|
1670
|
+
}
|
|
1671
|
+
if (deadline && (i & 63) === 0 && performance.now() > deadline) {
|
|
1672
|
+
return i + 1;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return ops.length;
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
function measure(element, options) {
|
|
1679
|
+
const opts = options || {};
|
|
1680
|
+
const scale = opts.scale || 1;
|
|
1681
|
+
const ignore = opts.ignoreElements || null;
|
|
1682
|
+
|
|
1683
|
+
pending = [];
|
|
1684
|
+
const root = buildNode(element, null, [], ignore);
|
|
1685
|
+
if (!root) return null;
|
|
1686
|
+
|
|
1687
|
+
const stack = newStack(root);
|
|
1688
|
+
collect(root, stack, ignore);
|
|
1689
|
+
|
|
1690
|
+
const ops = [];
|
|
1691
|
+
emitStack(stack, ops);
|
|
1692
|
+
|
|
1693
|
+
let base = [scale, 0, 0, scale, -root.x * scale, -root.y * scale];
|
|
1694
|
+
if (!root.untransformed) {
|
|
1695
|
+
const rootInv = invert(root.m);
|
|
1696
|
+
if (rootInv) base = mul(base, rootInv);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
const assets = pending;
|
|
1700
|
+
pending = [];
|
|
1701
|
+
|
|
1702
|
+
return {
|
|
1703
|
+
root,
|
|
1704
|
+
ops,
|
|
1705
|
+
base,
|
|
1706
|
+
scale,
|
|
1707
|
+
assets,
|
|
1708
|
+
width: opts.width != null ? opts.width : root.w,
|
|
1709
|
+
height: opts.height != null ? opts.height : root.h,
|
|
1710
|
+
backgroundColor: opts.backgroundColor || null,
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function prepareCanvas(plan, canvas) {
|
|
1715
|
+
const cw = Math.max(1, Math.round(plan.width * plan.scale));
|
|
1716
|
+
const ch = Math.max(1, Math.round(plan.height * plan.scale));
|
|
1717
|
+
if (canvas.width !== cw) canvas.width = cw;
|
|
1718
|
+
if (canvas.height !== ch) canvas.height = ch;
|
|
1719
|
+
|
|
1720
|
+
const ctx = canvas.getContext("2d");
|
|
1721
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1722
|
+
ctx.globalAlpha = 1;
|
|
1723
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1724
|
+
if (plan.backgroundColor) {
|
|
1725
|
+
ctx.fillStyle = plan.backgroundColor;
|
|
1726
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1727
|
+
}
|
|
1728
|
+
return ctx;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function paint(plan, canvas) {
|
|
1732
|
+
const ctx = prepareCanvas(plan, canvas);
|
|
1733
|
+
const painter = new Painter(ctx, plan.base);
|
|
1734
|
+
painter.run(plan.ops, 0, 0);
|
|
1735
|
+
painter.release();
|
|
1736
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1737
|
+
return canvas;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
function nextTask() {
|
|
1741
|
+
return new Promise((resolve) => {
|
|
1742
|
+
if (typeof requestIdleCallback === "function") {
|
|
1743
|
+
requestIdleCallback(() => resolve(), { timeout: 32 });
|
|
1744
|
+
} else {
|
|
1745
|
+
setTimeout(resolve, 0);
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
async function paintChunked(plan, canvas, budgetMs) {
|
|
1751
|
+
const ctx = prepareCanvas(plan, canvas);
|
|
1752
|
+
const painter = new Painter(ctx, plan.base);
|
|
1753
|
+
let index = 0;
|
|
1754
|
+
while (index < plan.ops.length) {
|
|
1755
|
+
index = painter.run(plan.ops, index, budgetMs);
|
|
1756
|
+
if (index < plan.ops.length) await nextTask();
|
|
1757
|
+
}
|
|
1758
|
+
painter.release();
|
|
1759
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1760
|
+
return canvas;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
function rasterise(element, options) {
|
|
1764
|
+
const opts = options || {};
|
|
1765
|
+
const canvas = opts.canvas || document.createElement("canvas");
|
|
1766
|
+
const plan = measure(element, opts);
|
|
1767
|
+
if (!plan) {
|
|
1768
|
+
canvas.width = Math.max(
|
|
1769
|
+
1,
|
|
1770
|
+
Math.round((opts.width || 1) * (opts.scale || 1)),
|
|
1771
|
+
);
|
|
1772
|
+
canvas.height = Math.max(
|
|
1773
|
+
1,
|
|
1774
|
+
Math.round((opts.height || 1) * (opts.scale || 1)),
|
|
1775
|
+
);
|
|
1776
|
+
return canvas;
|
|
1777
|
+
}
|
|
1778
|
+
return paint(plan, canvas);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
async function rasteriseAsync(element, options) {
|
|
1782
|
+
const opts = options || {};
|
|
1783
|
+
const canvas = opts.canvas || document.createElement("canvas");
|
|
1784
|
+
let plan = measure(element, opts);
|
|
1785
|
+
if (!plan) {
|
|
1786
|
+
canvas.width = Math.max(
|
|
1787
|
+
1,
|
|
1788
|
+
Math.round((opts.width || 1) * (opts.scale || 1)),
|
|
1789
|
+
);
|
|
1790
|
+
canvas.height = Math.max(
|
|
1791
|
+
1,
|
|
1792
|
+
Math.round((opts.height || 1) * (opts.scale || 1)),
|
|
1793
|
+
);
|
|
1794
|
+
return canvas;
|
|
1795
|
+
}
|
|
1796
|
+
if (plan.assets.length) {
|
|
1797
|
+
await Promise.all(plan.assets);
|
|
1798
|
+
plan = measure(element, opts) || plan;
|
|
1799
|
+
}
|
|
1800
|
+
return paintChunked(plan, canvas, opts.budgetMs || 8);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
return { measure, paint, paintChunked, rasterise, rasteriseAsync };
|
|
1804
|
+
})();
|
|
70
1805
|
|
|
71
1806
|
/* --------------------------------------------------
|
|
72
1807
|
* Shared renderer (one per page)
|
|
73
1808
|
* ------------------------------------------------*/
|
|
74
1809
|
class liquidGLRenderer {
|
|
75
1810
|
constructor(snapshotSelector, snapshotResolution = 1.0) {
|
|
1811
|
+
this._naughtyQueued = false;
|
|
76
1812
|
this.canvas = document.createElement("canvas");
|
|
77
1813
|
this.canvas.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;`;
|
|
78
1814
|
this.canvas.setAttribute("data-liquid-ignore", "");
|
|
@@ -138,7 +1874,7 @@ const liquidGL = (() => {
|
|
|
138
1874
|
this._resizeCanvas();
|
|
139
1875
|
this.lenses.forEach((l) => l.updateMetrics());
|
|
140
1876
|
this.captureSnapshot();
|
|
141
|
-
},
|
|
1877
|
+
}, RECAPTURE_INTERVAL_MS);
|
|
142
1878
|
window.addEventListener("resize", onResize, { passive: true });
|
|
143
1879
|
|
|
144
1880
|
if ("ResizeObserver" in window) {
|
|
@@ -157,27 +1893,30 @@ const liquidGL = (() => {
|
|
|
157
1893
|
document.head.appendChild(styleEl);
|
|
158
1894
|
this._dynamicStyleSheet = styleEl.sheet;
|
|
159
1895
|
|
|
1896
|
+
this._snapshotResolution = Math.max(
|
|
1897
|
+
0.1,
|
|
1898
|
+
Math.min(3.0, snapshotResolution),
|
|
1899
|
+
);
|
|
1900
|
+
this._pendingReveal = [];
|
|
1901
|
+
|
|
160
1902
|
this._resizeCanvas();
|
|
161
1903
|
this.captureSnapshot();
|
|
162
1904
|
|
|
163
|
-
this._pendingReveal = [];
|
|
164
|
-
|
|
165
1905
|
/* --------------------------------------------------
|
|
166
1906
|
* Dynamic media (video) support
|
|
167
1907
|
* ------------------------------------------------*/
|
|
168
1908
|
this._videoNodes = Array.from(
|
|
169
|
-
this.snapshotTarget.querySelectorAll("video")
|
|
1909
|
+
this.snapshotTarget.querySelectorAll("video"),
|
|
170
1910
|
);
|
|
171
1911
|
this._videoNodes = this._videoNodes.filter((v) => !this._isIgnored(v));
|
|
172
1912
|
this._tmpCanvas = document.createElement("canvas");
|
|
173
1913
|
this._tmpCtx = this._tmpCanvas.getContext("2d");
|
|
174
1914
|
|
|
175
|
-
this.
|
|
1915
|
+
this._videoFrameState = new WeakMap();
|
|
176
1916
|
|
|
177
|
-
this.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
);
|
|
1917
|
+
this._videoAlphaState = new WeakMap();
|
|
1918
|
+
|
|
1919
|
+
this.canvas.style.opacity = "0";
|
|
181
1920
|
|
|
182
1921
|
this.useExternalTicker = false;
|
|
183
1922
|
|
|
@@ -227,7 +1966,7 @@ const liquidGL = (() => {
|
|
|
227
1966
|
y,
|
|
228
1967
|
gl.RGBA,
|
|
229
1968
|
gl.UNSIGNED_BYTE,
|
|
230
|
-
bmp
|
|
1969
|
+
bmp,
|
|
231
1970
|
);
|
|
232
1971
|
};
|
|
233
1972
|
}
|
|
@@ -244,13 +1983,18 @@ const liquidGL = (() => {
|
|
|
244
1983
|
}`;
|
|
245
1984
|
|
|
246
1985
|
const fsSource = `
|
|
1986
|
+
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
1987
|
+
precision highp float;
|
|
1988
|
+
#else
|
|
247
1989
|
precision mediump float;
|
|
1990
|
+
#endif
|
|
248
1991
|
varying vec2 v_uv;
|
|
249
1992
|
uniform sampler2D u_tex;
|
|
250
1993
|
uniform vec2 u_resolution;
|
|
251
1994
|
uniform vec2 u_textureResolution;
|
|
252
1995
|
uniform vec4 u_bounds;
|
|
253
1996
|
uniform float u_refraction;
|
|
1997
|
+
uniform float u_aberration;
|
|
254
1998
|
uniform float u_bevelDepth;
|
|
255
1999
|
uniform float u_bevelWidth;
|
|
256
2000
|
uniform float u_frost;
|
|
@@ -262,31 +2006,46 @@ const liquidGL = (() => {
|
|
|
262
2006
|
uniform float u_tiltX;
|
|
263
2007
|
uniform float u_tiltY;
|
|
264
2008
|
uniform float u_magnify;
|
|
2009
|
+
uniform vec2 u_subpixel;
|
|
2010
|
+
uniform vec2 u_boxSize;
|
|
265
2011
|
|
|
266
2012
|
float udRoundBox( vec2 p, vec2 b, float r ) {
|
|
267
2013
|
return length(max(abs(p)-b+r,0.0))-r;
|
|
268
2014
|
}
|
|
269
2015
|
|
|
2016
|
+
vec2 cornerNormal( vec2 p, vec2 b, float r, vec2 fallback ) {
|
|
2017
|
+
vec2 q = abs(p) - b + r;
|
|
2018
|
+
vec2 m = max(q, 0.0);
|
|
2019
|
+
float l = length(m);
|
|
2020
|
+
if (l <= 0.0) return fallback;
|
|
2021
|
+
float w = smoothstep(0.0, max(r * 0.5, 1.0), min(m.x, m.y));
|
|
2022
|
+
if (w <= 0.0) return fallback;
|
|
2023
|
+
vec2 s = vec2(p.x < 0.0 ? -1.0 : 1.0, p.y < 0.0 ? -1.0 : 1.0);
|
|
2024
|
+
return normalize(mix(fallback, s * (m / l), w));
|
|
2025
|
+
}
|
|
2026
|
+
|
|
270
2027
|
float random(vec2 st) {
|
|
271
2028
|
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
|
|
272
2029
|
}
|
|
273
2030
|
|
|
274
|
-
float edgeFactor(vec2
|
|
275
|
-
vec2 p_px = (uv - 0.5) * u_resolution;
|
|
276
|
-
vec2 b_px = 0.5 * u_resolution;
|
|
2031
|
+
float edgeFactor(vec2 p_px, vec2 b_px, float radius_px){
|
|
277
2032
|
float d = -udRoundBox(p_px, b_px, radius_px);
|
|
278
|
-
float bevel_px = u_bevelWidth * min(
|
|
2033
|
+
float bevel_px = u_bevelWidth * min(u_boxSize.x, u_boxSize.y);
|
|
279
2034
|
return 1.0 - smoothstep(0.0, bevel_px, d);
|
|
280
2035
|
}
|
|
281
2036
|
void main(){
|
|
282
2037
|
vec2 p = v_uv - 0.5;
|
|
283
2038
|
p.x *= u_resolution.x / u_resolution.y;
|
|
284
2039
|
|
|
285
|
-
|
|
2040
|
+
vec2 p_px = v_uv * u_resolution - u_subpixel - 0.5 * u_boxSize;
|
|
2041
|
+
vec2 b_px = 0.5 * u_boxSize;
|
|
2042
|
+
|
|
2043
|
+
float edge = edgeFactor(p_px, b_px, u_radius);
|
|
286
2044
|
float min_dimension = min(u_resolution.x, u_resolution.y);
|
|
287
2045
|
float offsetAmt = (edge * u_refraction + pow(edge, 10.0) * u_bevelDepth);
|
|
288
2046
|
float centreBlend = smoothstep(0.15, 0.45, length(p));
|
|
289
|
-
vec2
|
|
2047
|
+
vec2 refractDir = cornerNormal(p_px, b_px, u_radius, normalize(p));
|
|
2048
|
+
vec2 offset = refractDir * offsetAmt * centreBlend;
|
|
290
2049
|
|
|
291
2050
|
float tiltRefractionScale = 0.05;
|
|
292
2051
|
vec2 tiltOffset = vec2(tan(radians(u_tiltY)), -tan(radians(u_tiltX))) * tiltRefractionScale;
|
|
@@ -326,6 +2085,12 @@ const liquidGL = (() => {
|
|
|
326
2085
|
refrCol /= 5.0;
|
|
327
2086
|
}
|
|
328
2087
|
|
|
2088
|
+
if (u_aberration > 0.0) {
|
|
2089
|
+
vec2 chroma = offset * u_aberration;
|
|
2090
|
+
refrCol.r = texture2D(u_tex, sampleUV - chroma).r;
|
|
2091
|
+
refrCol.b = texture2D(u_tex, sampleUV + chroma).b;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
329
2094
|
if (refrCol.a < 0.1) {
|
|
330
2095
|
refrCol = baseCol;
|
|
331
2096
|
}
|
|
@@ -336,10 +2101,8 @@ const liquidGL = (() => {
|
|
|
336
2101
|
|
|
337
2102
|
vec4 final = refrCol;
|
|
338
2103
|
|
|
339
|
-
vec2 p_px = (v_uv - 0.5) * u_resolution;
|
|
340
|
-
vec2 b_px = 0.5 * u_resolution;
|
|
341
2104
|
float dmask = udRoundBox(p_px, b_px, u_radius);
|
|
342
|
-
float inShape = 1.0 -
|
|
2105
|
+
float inShape = 1.0 - smoothstep(-0.5, 0.5, dmask);
|
|
343
2106
|
|
|
344
2107
|
if (u_specular) {
|
|
345
2108
|
vec2 lp1 = vec2(sin(u_time*0.2), cos(u_time*0.3))*0.6 + 0.5;
|
|
@@ -370,22 +2133,26 @@ const liquidGL = (() => {
|
|
|
370
2133
|
gl.bufferData(
|
|
371
2134
|
gl.ARRAY_BUFFER,
|
|
372
2135
|
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
|
|
373
|
-
gl.STATIC_DRAW
|
|
2136
|
+
gl.STATIC_DRAW,
|
|
374
2137
|
);
|
|
375
2138
|
|
|
376
2139
|
const posLoc = gl.getAttribLocation(this.program, "a_position");
|
|
377
2140
|
gl.enableVertexAttribArray(posLoc);
|
|
378
2141
|
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
379
2142
|
|
|
2143
|
+
this._posBuf = posBuf;
|
|
2144
|
+
this._posLoc = posLoc;
|
|
2145
|
+
|
|
380
2146
|
this.u = {
|
|
381
2147
|
tex: gl.getUniformLocation(this.program, "u_tex"),
|
|
382
2148
|
res: gl.getUniformLocation(this.program, "u_resolution"),
|
|
383
2149
|
textureResolution: gl.getUniformLocation(
|
|
384
2150
|
this.program,
|
|
385
|
-
"u_textureResolution"
|
|
2151
|
+
"u_textureResolution",
|
|
386
2152
|
),
|
|
387
2153
|
bounds: gl.getUniformLocation(this.program, "u_bounds"),
|
|
388
2154
|
refraction: gl.getUniformLocation(this.program, "u_refraction"),
|
|
2155
|
+
aberration: gl.getUniformLocation(this.program, "u_aberration"),
|
|
389
2156
|
bevelDepth: gl.getUniformLocation(this.program, "u_bevelDepth"),
|
|
390
2157
|
bevelWidth: gl.getUniformLocation(this.program, "u_bevelWidth"),
|
|
391
2158
|
frost: gl.getUniformLocation(this.program, "u_frost"),
|
|
@@ -397,6 +2164,8 @@ const liquidGL = (() => {
|
|
|
397
2164
|
tiltX: gl.getUniformLocation(this.program, "u_tiltX"),
|
|
398
2165
|
tiltY: gl.getUniformLocation(this.program, "u_tiltY"),
|
|
399
2166
|
magnify: gl.getUniformLocation(this.program, "u_magnify"),
|
|
2167
|
+
subpixel: gl.getUniformLocation(this.program, "u_subpixel"),
|
|
2168
|
+
boxSize: gl.getUniformLocation(this.program, "u_boxSize"),
|
|
400
2169
|
};
|
|
401
2170
|
}
|
|
402
2171
|
|
|
@@ -412,7 +2181,7 @@ const liquidGL = (() => {
|
|
|
412
2181
|
|
|
413
2182
|
/* ----------------------------- */
|
|
414
2183
|
async captureSnapshot() {
|
|
415
|
-
if (this._capturing
|
|
2184
|
+
if (this._capturing) return;
|
|
416
2185
|
this._capturing = true;
|
|
417
2186
|
|
|
418
2187
|
const undos = [];
|
|
@@ -420,7 +2189,7 @@ const liquidGL = (() => {
|
|
|
420
2189
|
const attemptCapture = async (
|
|
421
2190
|
attempt = 1,
|
|
422
2191
|
maxAttempts = 3,
|
|
423
|
-
delayMs = 500
|
|
2192
|
+
delayMs = 500,
|
|
424
2193
|
) => {
|
|
425
2194
|
try {
|
|
426
2195
|
const fullW = this.snapshotTarget.scrollWidth;
|
|
@@ -432,13 +2201,24 @@ const liquidGL = (() => {
|
|
|
432
2201
|
let scale = Math.min(
|
|
433
2202
|
this._snapshotResolution,
|
|
434
2203
|
maxTex / fullW,
|
|
435
|
-
maxTex / fullH
|
|
2204
|
+
maxTex / fullH,
|
|
436
2205
|
);
|
|
437
2206
|
|
|
438
2207
|
if (isMobileSafari) {
|
|
439
2208
|
const over = (Math.max(fullW, fullH) * scale) / MAX_MOBILE_DIM;
|
|
440
2209
|
if (over > 1) scale = scale / over;
|
|
441
2210
|
}
|
|
2211
|
+
|
|
2212
|
+
const maxArea = isMobileSafari ? 4096 * 4096 : 16384 * 16384;
|
|
2213
|
+
if (fullW * fullH * scale * scale > maxArea) {
|
|
2214
|
+
scale = Math.sqrt(maxArea / (fullW * fullH));
|
|
2215
|
+
console.warn(
|
|
2216
|
+
`liquidGL: snapshot area capped, resolution reduced to ${scale.toFixed(
|
|
2217
|
+
3,
|
|
2218
|
+
)} for a ${fullW}x${fullH} document.`,
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
442
2222
|
this.scaleFactor = Math.max(0.1, scale);
|
|
443
2223
|
|
|
444
2224
|
this.canvas.style.visibility = "hidden";
|
|
@@ -448,9 +2228,16 @@ const liquidGL = (() => {
|
|
|
448
2228
|
.flatMap((lens) => [lens.el, lens._shadowEl])
|
|
449
2229
|
.filter(Boolean);
|
|
450
2230
|
|
|
2231
|
+
lensElements.forEach((el) => {
|
|
2232
|
+
el.setAttribute("data-liquidgl-hide", "");
|
|
2233
|
+
undos.push(() => {
|
|
2234
|
+
el.removeAttribute("data-liquidgl-hide");
|
|
2235
|
+
});
|
|
2236
|
+
});
|
|
2237
|
+
|
|
451
2238
|
const ignoreElementsFunc = (element) => {
|
|
452
2239
|
if (!element || !element.hasAttribute) return false;
|
|
453
|
-
if (element === this.canvas
|
|
2240
|
+
if (element === this.canvas) {
|
|
454
2241
|
return true;
|
|
455
2242
|
}
|
|
456
2243
|
const style = window.getComputedStyle(element);
|
|
@@ -463,26 +2250,29 @@ const liquidGL = (() => {
|
|
|
463
2250
|
);
|
|
464
2251
|
};
|
|
465
2252
|
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
2253
|
+
const naughtyIgnore = (el) =>
|
|
2254
|
+
ignoreElementsFunc(el) ||
|
|
2255
|
+
(el.hasAttribute && el.hasAttribute("data-liquidgl-hide"));
|
|
2256
|
+
|
|
2257
|
+
const snapCanvas = await NaughtyDOM.rasteriseAsync(
|
|
2258
|
+
this.snapshotTarget,
|
|
2259
|
+
{
|
|
2260
|
+
width: fullW,
|
|
2261
|
+
height: fullH,
|
|
2262
|
+
scale: scale,
|
|
2263
|
+
ignoreElements: naughtyIgnore,
|
|
2264
|
+
},
|
|
2265
|
+
);
|
|
478
2266
|
|
|
479
|
-
this._uploadTexture(snapCanvas)
|
|
2267
|
+
if (!this._uploadTexture(snapCanvas)) {
|
|
2268
|
+
throw new Error("liquidGL: snapshot could not be uploaded.");
|
|
2269
|
+
}
|
|
480
2270
|
return true;
|
|
481
2271
|
} catch (e) {
|
|
482
2272
|
console.error("liquidGL snapshot failed on attempt " + attempt, e);
|
|
483
2273
|
if (attempt < maxAttempts) {
|
|
484
2274
|
console.log(
|
|
485
|
-
`Retrying snapshot capture (${attempt + 1}/${maxAttempts})
|
|
2275
|
+
`Retrying snapshot capture (${attempt + 1}/${maxAttempts})...`,
|
|
486
2276
|
);
|
|
487
2277
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
488
2278
|
return await attemptCapture(attempt + 1, maxAttempts, delayMs);
|
|
@@ -503,27 +2293,39 @@ const liquidGL = (() => {
|
|
|
503
2293
|
|
|
504
2294
|
/* ----------------------------- */
|
|
505
2295
|
_uploadTexture(srcCanvas) {
|
|
506
|
-
if (!srcCanvas)
|
|
2296
|
+
if (!srcCanvas) {
|
|
2297
|
+
console.error("liquidGL: snapshot produced no canvas.");
|
|
2298
|
+
return false;
|
|
2299
|
+
}
|
|
507
2300
|
|
|
508
2301
|
if (!(srcCanvas instanceof HTMLCanvasElement)) {
|
|
509
2302
|
const tmp = document.createElement("canvas");
|
|
510
2303
|
tmp.width = srcCanvas.width || 0;
|
|
511
2304
|
tmp.height = srcCanvas.height || 0;
|
|
512
|
-
if (tmp.width === 0 || tmp.height === 0)
|
|
2305
|
+
if (tmp.width === 0 || tmp.height === 0) {
|
|
2306
|
+
console.error("liquidGL: snapshot canvas has zero dimensions.");
|
|
2307
|
+
return false;
|
|
2308
|
+
}
|
|
513
2309
|
try {
|
|
514
2310
|
const ctx = tmp.getContext("2d");
|
|
515
2311
|
ctx.drawImage(srcCanvas, 0, 0);
|
|
516
2312
|
srcCanvas = tmp;
|
|
517
2313
|
} catch (e) {
|
|
518
|
-
console.
|
|
2314
|
+
console.error(
|
|
519
2315
|
"liquidGL: Unable to convert OffscreenCanvas for upload",
|
|
520
|
-
e
|
|
2316
|
+
e,
|
|
521
2317
|
);
|
|
522
|
-
return;
|
|
2318
|
+
return false;
|
|
523
2319
|
}
|
|
524
2320
|
}
|
|
525
2321
|
|
|
526
|
-
if (srcCanvas.width === 0 || srcCanvas.height === 0)
|
|
2322
|
+
if (srcCanvas.width === 0 || srcCanvas.height === 0) {
|
|
2323
|
+
console.error(
|
|
2324
|
+
"liquidGL: snapshot canvas has zero dimensions, requested area " +
|
|
2325
|
+
`${srcCanvas.width}x${srcCanvas.height} exceeds a browser limit.`,
|
|
2326
|
+
);
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
527
2329
|
this.staticSnapshotCanvas = srcCanvas;
|
|
528
2330
|
const gl = this.gl;
|
|
529
2331
|
if (!this.texture) this.texture = gl.createTexture();
|
|
@@ -535,7 +2337,7 @@ const liquidGL = (() => {
|
|
|
535
2337
|
gl.RGBA,
|
|
536
2338
|
gl.RGBA,
|
|
537
2339
|
gl.UNSIGNED_BYTE,
|
|
538
|
-
srcCanvas
|
|
2340
|
+
srcCanvas,
|
|
539
2341
|
);
|
|
540
2342
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
541
2343
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
@@ -545,12 +2347,18 @@ const liquidGL = (() => {
|
|
|
545
2347
|
this.textureWidth = srcCanvas.width;
|
|
546
2348
|
this.textureHeight = srcCanvas.height;
|
|
547
2349
|
|
|
2350
|
+
if (this._videoFrameState) this._videoFrameState = new WeakMap();
|
|
2351
|
+
|
|
2352
|
+
this._vFboTexture = null;
|
|
2353
|
+
|
|
548
2354
|
this.render();
|
|
549
2355
|
|
|
550
2356
|
if (this._pendingReveal.length) {
|
|
551
2357
|
this._pendingReveal.forEach((ln) => ln._reveal());
|
|
552
2358
|
this._pendingReveal.length = 0;
|
|
553
2359
|
}
|
|
2360
|
+
|
|
2361
|
+
return true;
|
|
554
2362
|
}
|
|
555
2363
|
|
|
556
2364
|
/* ----------------------------- */
|
|
@@ -594,6 +2402,8 @@ const liquidGL = (() => {
|
|
|
594
2402
|
|
|
595
2403
|
this._updateDynamicNodes();
|
|
596
2404
|
|
|
2405
|
+
this._frameSnapRect = this.snapshotTarget.getBoundingClientRect();
|
|
2406
|
+
|
|
597
2407
|
this.lenses.forEach((lens) => {
|
|
598
2408
|
lens.updateMetrics();
|
|
599
2409
|
if (lens._mirrorActive && lens._mirrorClipUpdater) {
|
|
@@ -624,15 +2434,15 @@ const liquidGL = (() => {
|
|
|
624
2434
|
const x = Math.max(0, Math.round(left * dpr) - expand);
|
|
625
2435
|
const y = Math.max(
|
|
626
2436
|
0,
|
|
627
|
-
Math.round(this.canvas.height - (top + height) * dpr) - expand
|
|
2437
|
+
Math.round(this.canvas.height - (top + height) * dpr) - expand,
|
|
628
2438
|
);
|
|
629
2439
|
const w = Math.min(
|
|
630
2440
|
this.canvas.width - x,
|
|
631
|
-
Math.round(width * dpr) + expand * 2
|
|
2441
|
+
Math.round(width * dpr) + expand * 2,
|
|
632
2442
|
);
|
|
633
2443
|
const h = Math.min(
|
|
634
2444
|
this.canvas.height - y,
|
|
635
|
-
Math.round(height * dpr) + expand * 2
|
|
2445
|
+
Math.round(height * dpr) + expand * 2,
|
|
636
2446
|
);
|
|
637
2447
|
if (w > 0 && h > 0) {
|
|
638
2448
|
gl.enable(gl.SCISSOR_TEST);
|
|
@@ -656,22 +2466,29 @@ const liquidGL = (() => {
|
|
|
656
2466
|
let overscrollY = 0;
|
|
657
2467
|
let overscrollX = 0;
|
|
658
2468
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
2469
|
+
const vv = window.visualViewport;
|
|
2470
|
+
if (vv && Math.abs(vv.scale - 1) < 0.01) {
|
|
2471
|
+
overscrollX = vv.offsetLeft;
|
|
2472
|
+
overscrollY = vv.offsetTop;
|
|
662
2473
|
}
|
|
663
2474
|
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
const
|
|
2475
|
+
const leftPx = (rect.left + overscrollX) * dpr;
|
|
2476
|
+
const topPx = (rect.top + overscrollY) * dpr;
|
|
2477
|
+
const x = Math.round(leftPx);
|
|
2478
|
+
const yTop = Math.round(topPx);
|
|
2479
|
+
const w = Math.round(leftPx + rect.width * dpr) - x;
|
|
2480
|
+
const h = Math.round(topPx + rect.height * dpr) - yTop;
|
|
2481
|
+
const y = this.canvas.height - (yTop + h);
|
|
669
2482
|
|
|
670
2483
|
gl.viewport(x, y, w, h);
|
|
671
2484
|
gl.uniform2f(this.u.res, w, h);
|
|
2485
|
+
gl.uniform2f(this.u.subpixel, leftPx - x, topPx - yTop);
|
|
2486
|
+
gl.uniform2f(this.u.boxSize, rect.width * dpr, rect.height * dpr);
|
|
672
2487
|
|
|
673
|
-
const
|
|
674
|
-
|
|
2488
|
+
const snapRect =
|
|
2489
|
+
this._frameSnapRect || this.snapshotTarget.getBoundingClientRect();
|
|
2490
|
+
const docX = rect.left - snapRect.left;
|
|
2491
|
+
const docY = rect.top - snapRect.top;
|
|
675
2492
|
const leftUV = (docX * this.scaleFactor) / this.textureWidth;
|
|
676
2493
|
const topUV = (docY * this.scaleFactor) / this.textureHeight;
|
|
677
2494
|
const wUV = (rect.width * this.scaleFactor) / this.textureWidth;
|
|
@@ -681,9 +2498,10 @@ const liquidGL = (() => {
|
|
|
681
2498
|
gl.uniform2f(
|
|
682
2499
|
this.u.textureResolution,
|
|
683
2500
|
this.textureWidth,
|
|
684
|
-
this.textureHeight
|
|
2501
|
+
this.textureHeight,
|
|
685
2502
|
);
|
|
686
2503
|
gl.uniform1f(this.u.refraction, lens.options.refraction);
|
|
2504
|
+
gl.uniform1f(this.u.aberration, lens.options.aberration || 0);
|
|
687
2505
|
gl.uniform1f(this.u.bevelDepth, lens.options.bevelDepth);
|
|
688
2506
|
gl.uniform1f(this.u.bevelWidth, lens.options.bevelWidth);
|
|
689
2507
|
gl.uniform1f(this.u.frost, lens.options.frost);
|
|
@@ -696,8 +2514,8 @@ const liquidGL = (() => {
|
|
|
696
2514
|
0.001,
|
|
697
2515
|
Math.min(
|
|
698
2516
|
3.0,
|
|
699
|
-
lens.options.magnify !== undefined ? lens.options.magnify : 1.0
|
|
700
|
-
)
|
|
2517
|
+
lens.options.magnify !== undefined ? lens.options.magnify : 1.0,
|
|
2518
|
+
),
|
|
701
2519
|
);
|
|
702
2520
|
gl.uniform1f(this.u.magnify, mag);
|
|
703
2521
|
|
|
@@ -722,6 +2540,166 @@ const liquidGL = (() => {
|
|
|
722
2540
|
ctx.closePath();
|
|
723
2541
|
}
|
|
724
2542
|
|
|
2543
|
+
_initVideoBlit() {
|
|
2544
|
+
if (this._vBlitReady !== undefined) return this._vBlitReady;
|
|
2545
|
+
|
|
2546
|
+
const gl = this.gl;
|
|
2547
|
+
|
|
2548
|
+
const vs = `
|
|
2549
|
+
attribute vec2 a_position;
|
|
2550
|
+
varying vec2 v_uv;
|
|
2551
|
+
void main(){
|
|
2552
|
+
v_uv = (a_position + 1.0) * 0.5;
|
|
2553
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
2554
|
+
}`;
|
|
2555
|
+
|
|
2556
|
+
const fs = `
|
|
2557
|
+
precision mediump float;
|
|
2558
|
+
varying vec2 v_uv;
|
|
2559
|
+
uniform sampler2D u_src;
|
|
2560
|
+
uniform vec4 u_srcRect;
|
|
2561
|
+
void main(){
|
|
2562
|
+
gl_FragColor = texture2D(u_src, u_srcRect.xy + v_uv * u_srcRect.zw);
|
|
2563
|
+
}`;
|
|
2564
|
+
|
|
2565
|
+
const prog = createProgram(gl, vs, fs);
|
|
2566
|
+
if (!prog) {
|
|
2567
|
+
this._vBlitReady = false;
|
|
2568
|
+
return false;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
this._vProg = prog;
|
|
2572
|
+
this._vPosLoc = gl.getAttribLocation(prog, "a_position");
|
|
2573
|
+
this._vU = {
|
|
2574
|
+
src: gl.getUniformLocation(prog, "u_src"),
|
|
2575
|
+
srcRect: gl.getUniformLocation(prog, "u_srcRect"),
|
|
2576
|
+
};
|
|
2577
|
+
|
|
2578
|
+
this._vTex = gl.createTexture();
|
|
2579
|
+
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2580
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2581
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2582
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2583
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2584
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2585
|
+
|
|
2586
|
+
this._vFbo = gl.createFramebuffer();
|
|
2587
|
+
this._vFboTexture = null;
|
|
2588
|
+
|
|
2589
|
+
this._vBlitReady = true;
|
|
2590
|
+
return true;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
_videoIsOpaque(vid) {
|
|
2594
|
+
if (!vid.videoWidth || !vid.videoHeight) return false;
|
|
2595
|
+
|
|
2596
|
+
const key = vid.videoWidth + "x" + vid.videoHeight;
|
|
2597
|
+
const cached = this._videoAlphaState.get(vid);
|
|
2598
|
+
if (cached && cached.key === key) return cached.opaque;
|
|
2599
|
+
|
|
2600
|
+
let opaque = false;
|
|
2601
|
+
try {
|
|
2602
|
+
const probe =
|
|
2603
|
+
this._alphaProbeCanvas ||
|
|
2604
|
+
(this._alphaProbeCanvas = document.createElement("canvas"));
|
|
2605
|
+
const size = 32;
|
|
2606
|
+
probe.width = size;
|
|
2607
|
+
probe.height = size;
|
|
2608
|
+
const ctx = probe.getContext("2d", { willReadFrequently: true });
|
|
2609
|
+
ctx.clearRect(0, 0, size, size);
|
|
2610
|
+
ctx.drawImage(vid, 0, 0, size, size);
|
|
2611
|
+
const data = ctx.getImageData(0, 0, size, size).data;
|
|
2612
|
+
opaque = true;
|
|
2613
|
+
for (let i = 3; i < data.length; i += 4) {
|
|
2614
|
+
if (data[i] !== 255) {
|
|
2615
|
+
opaque = false;
|
|
2616
|
+
break;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
} catch (e) {
|
|
2620
|
+
opaque = false;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
this._videoAlphaState.set(vid, { key, opaque });
|
|
2624
|
+
return opaque;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
_blitVideoToTexture(vid, dstX, dstY, dstW, dstH, srcRect) {
|
|
2628
|
+
const gl = this.gl;
|
|
2629
|
+
|
|
2630
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this._vFbo);
|
|
2631
|
+
|
|
2632
|
+
if (this._vFboTexture !== this.texture) {
|
|
2633
|
+
gl.framebufferTexture2D(
|
|
2634
|
+
gl.FRAMEBUFFER,
|
|
2635
|
+
gl.COLOR_ATTACHMENT0,
|
|
2636
|
+
gl.TEXTURE_2D,
|
|
2637
|
+
this.texture,
|
|
2638
|
+
0,
|
|
2639
|
+
);
|
|
2640
|
+
if (
|
|
2641
|
+
gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE
|
|
2642
|
+
) {
|
|
2643
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2644
|
+
this._vBlitReady = false;
|
|
2645
|
+
return false;
|
|
2646
|
+
}
|
|
2647
|
+
this._vFboTexture = this.texture;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2651
|
+
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2652
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2653
|
+
|
|
2654
|
+
try {
|
|
2655
|
+
gl.texImage2D(
|
|
2656
|
+
gl.TEXTURE_2D,
|
|
2657
|
+
0,
|
|
2658
|
+
gl.RGBA,
|
|
2659
|
+
gl.RGBA,
|
|
2660
|
+
gl.UNSIGNED_BYTE,
|
|
2661
|
+
vid,
|
|
2662
|
+
);
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2665
|
+
this._restoreLensProgramState();
|
|
2666
|
+
return false;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
gl.useProgram(this._vProg);
|
|
2670
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2671
|
+
gl.enableVertexAttribArray(this._vPosLoc);
|
|
2672
|
+
gl.vertexAttribPointer(this._vPosLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2673
|
+
|
|
2674
|
+
gl.uniform1i(this._vU.src, 0);
|
|
2675
|
+
gl.uniform4f(
|
|
2676
|
+
this._vU.srcRect,
|
|
2677
|
+
srcRect.u,
|
|
2678
|
+
srcRect.v,
|
|
2679
|
+
srcRect.uw,
|
|
2680
|
+
srcRect.vh,
|
|
2681
|
+
);
|
|
2682
|
+
|
|
2683
|
+
gl.viewport(dstX, dstY, dstW, dstH);
|
|
2684
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
2685
|
+
|
|
2686
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2687
|
+
this._restoreLensProgramState();
|
|
2688
|
+
|
|
2689
|
+
return true;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
_restoreLensProgramState() {
|
|
2693
|
+
const gl = this.gl;
|
|
2694
|
+
gl.useProgram(this.program);
|
|
2695
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2696
|
+
gl.enableVertexAttribArray(this._posLoc);
|
|
2697
|
+
gl.vertexAttribPointer(this._posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2698
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2699
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2700
|
+
gl.uniform1i(this.u.tex, 0);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
725
2703
|
/* ----------------------------- */
|
|
726
2704
|
_updateDynamicVideos() {
|
|
727
2705
|
if (this._isScrolling && this._scrollUpdateCounter % 2 !== 0) return;
|
|
@@ -755,6 +2733,76 @@ const liquidGL = (() => {
|
|
|
755
2733
|
|
|
756
2734
|
if (drawW <= 0 || drawH <= 0) return;
|
|
757
2735
|
|
|
2736
|
+
const geomKey =
|
|
2737
|
+
Math.round(texX) + "," + Math.round(texY) + "," + drawW + "," + drawH;
|
|
2738
|
+
const prevState = this._videoFrameState.get(vid);
|
|
2739
|
+
if (
|
|
2740
|
+
prevState &&
|
|
2741
|
+
prevState.time === vid.currentTime &&
|
|
2742
|
+
prevState.geom === geomKey
|
|
2743
|
+
) {
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
this._videoFrameState.set(vid, {
|
|
2747
|
+
time: vid.currentTime,
|
|
2748
|
+
geom: geomKey,
|
|
2749
|
+
});
|
|
2750
|
+
|
|
2751
|
+
const drawX = Math.round(texX);
|
|
2752
|
+
const drawY = Math.round(texY);
|
|
2753
|
+
|
|
2754
|
+
const maxW = this.textureWidth;
|
|
2755
|
+
const maxH = this.textureHeight;
|
|
2756
|
+
let dstX = drawX;
|
|
2757
|
+
let dstY = drawY;
|
|
2758
|
+
let srcX = 0,
|
|
2759
|
+
srcY = 0,
|
|
2760
|
+
updW = drawW,
|
|
2761
|
+
updH = drawH;
|
|
2762
|
+
|
|
2763
|
+
if (dstX < 0) {
|
|
2764
|
+
srcX = -dstX;
|
|
2765
|
+
updW += dstX;
|
|
2766
|
+
dstX = 0;
|
|
2767
|
+
}
|
|
2768
|
+
if (dstY < 0) {
|
|
2769
|
+
srcY = -dstY;
|
|
2770
|
+
updH += dstY;
|
|
2771
|
+
dstY = 0;
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
if (dstX + updW > maxW) {
|
|
2775
|
+
updW = maxW - dstX;
|
|
2776
|
+
}
|
|
2777
|
+
if (dstY + updH > maxH) {
|
|
2778
|
+
updH = maxH - dstY;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
if (updW <= 0 || updH <= 0) return;
|
|
2782
|
+
|
|
2783
|
+
const style = window.getComputedStyle(vid);
|
|
2784
|
+
const scaledRadii = {
|
|
2785
|
+
tl: parseFloat(style.borderTopLeftRadius) * this.scaleFactor,
|
|
2786
|
+
tr: parseFloat(style.borderTopRightRadius) * this.scaleFactor,
|
|
2787
|
+
br: parseFloat(style.borderBottomRightRadius) * this.scaleFactor,
|
|
2788
|
+
bl: parseFloat(style.borderBottomLeftRadius) * this.scaleFactor,
|
|
2789
|
+
};
|
|
2790
|
+
const isRounded = Object.values(scaledRadii).some((r) => r > 0);
|
|
2791
|
+
|
|
2792
|
+
if (
|
|
2793
|
+
!isRounded &&
|
|
2794
|
+
this._initVideoBlit() &&
|
|
2795
|
+
this._videoIsOpaque(vid) &&
|
|
2796
|
+
this._blitVideoToTexture(vid, dstX, dstY, updW, updH, {
|
|
2797
|
+
u: srcX / drawW,
|
|
2798
|
+
v: srcY / drawH,
|
|
2799
|
+
uw: updW / drawW,
|
|
2800
|
+
vh: updH / drawH,
|
|
2801
|
+
})
|
|
2802
|
+
) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
758
2806
|
if (
|
|
759
2807
|
this._tmpCanvas.width !== drawW ||
|
|
760
2808
|
this._tmpCanvas.height !== drawH
|
|
@@ -767,20 +2815,12 @@ const liquidGL = (() => {
|
|
|
767
2815
|
this._tmpCtx.save();
|
|
768
2816
|
this._tmpCtx.clearRect(0, 0, drawW, drawH);
|
|
769
2817
|
|
|
770
|
-
|
|
771
|
-
const scaledRadii = {
|
|
772
|
-
tl: parseFloat(style.borderTopLeftRadius) * this.scaleFactor,
|
|
773
|
-
tr: parseFloat(style.borderTopRightRadius) * this.scaleFactor,
|
|
774
|
-
br: parseFloat(style.borderBottomRightRadius) * this.scaleFactor,
|
|
775
|
-
bl: parseFloat(style.borderBottomLeftRadius) * this.scaleFactor,
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
if (Object.values(scaledRadii).some((r) => r > 0)) {
|
|
2818
|
+
if (isRounded) {
|
|
779
2819
|
this._createRoundedRectPath(
|
|
780
2820
|
this._tmpCtx,
|
|
781
2821
|
drawW,
|
|
782
2822
|
drawH,
|
|
783
|
-
scaledRadii
|
|
2823
|
+
scaledRadii,
|
|
784
2824
|
);
|
|
785
2825
|
this._tmpCtx.clip();
|
|
786
2826
|
}
|
|
@@ -794,7 +2834,7 @@ const liquidGL = (() => {
|
|
|
794
2834
|
0,
|
|
795
2835
|
0,
|
|
796
2836
|
drawW,
|
|
797
|
-
drawH
|
|
2837
|
+
drawH,
|
|
798
2838
|
);
|
|
799
2839
|
|
|
800
2840
|
this._tmpCtx.drawImage(vid, 0, 0, drawW, drawH);
|
|
@@ -804,40 +2844,6 @@ const liquidGL = (() => {
|
|
|
804
2844
|
return;
|
|
805
2845
|
}
|
|
806
2846
|
|
|
807
|
-
const drawX = Math.round(texX);
|
|
808
|
-
const drawY = Math.round(texY);
|
|
809
|
-
|
|
810
|
-
if (drawW <= 0 || drawH <= 0) return;
|
|
811
|
-
|
|
812
|
-
const maxW = this.textureWidth;
|
|
813
|
-
const maxH = this.textureHeight;
|
|
814
|
-
let dstX = drawX;
|
|
815
|
-
let dstY = drawY;
|
|
816
|
-
let srcX = 0,
|
|
817
|
-
srcY = 0,
|
|
818
|
-
updW = drawW,
|
|
819
|
-
updH = drawH;
|
|
820
|
-
|
|
821
|
-
if (dstX < 0) {
|
|
822
|
-
srcX = -dstX;
|
|
823
|
-
updW += dstX;
|
|
824
|
-
dstX = 0;
|
|
825
|
-
}
|
|
826
|
-
if (dstY < 0) {
|
|
827
|
-
srcY = -dstY;
|
|
828
|
-
updH += dstY;
|
|
829
|
-
dstY = 0;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (dstX + updW > maxW) {
|
|
833
|
-
updW = maxW - dstX;
|
|
834
|
-
}
|
|
835
|
-
if (dstY + updH > maxH) {
|
|
836
|
-
updH = maxH - dstY;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (updW <= 0 || updH <= 0) return;
|
|
840
|
-
|
|
841
2847
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
842
2848
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
843
2849
|
gl.texSubImage2D(
|
|
@@ -847,7 +2853,7 @@ const liquidGL = (() => {
|
|
|
847
2853
|
dstY,
|
|
848
2854
|
gl.RGBA,
|
|
849
2855
|
gl.UNSIGNED_BYTE,
|
|
850
|
-
this._tmpCanvas
|
|
2856
|
+
this._tmpCanvas,
|
|
851
2857
|
);
|
|
852
2858
|
});
|
|
853
2859
|
}
|
|
@@ -894,7 +2900,7 @@ const liquidGL = (() => {
|
|
|
894
2900
|
xInComposite,
|
|
895
2901
|
yInComposite,
|
|
896
2902
|
wInComposite,
|
|
897
|
-
hInComposite
|
|
2903
|
+
hInComposite,
|
|
898
2904
|
);
|
|
899
2905
|
}
|
|
900
2906
|
});
|
|
@@ -908,27 +2914,37 @@ const liquidGL = (() => {
|
|
|
908
2914
|
if (meta.needsRecapture && !meta._capturing && !this._isScrolling) {
|
|
909
2915
|
meta._capturing = true;
|
|
910
2916
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
2917
|
+
const ignoreDynamic = (n) =>
|
|
2918
|
+
n.tagName === "CANVAS" || n.hasAttribute("data-liquid-ignore");
|
|
2919
|
+
|
|
2920
|
+
if (this._naughtyQueued) {
|
|
2921
|
+
meta._capturing = false;
|
|
2922
|
+
} else {
|
|
2923
|
+
this._naughtyQueued = true;
|
|
2924
|
+
const capture = () => {
|
|
2925
|
+
this._naughtyQueued = false;
|
|
2926
|
+
try {
|
|
2927
|
+
const cv = NaughtyDOM.rasterise(el, {
|
|
2928
|
+
scale: this.scaleFactor,
|
|
2929
|
+
ignoreElements: ignoreDynamic,
|
|
2930
|
+
canvas: meta.naughtyCanvas,
|
|
2931
|
+
});
|
|
2932
|
+
meta.naughtyCanvas = cv;
|
|
2933
|
+
if (cv.width > 0 && cv.height > 0) {
|
|
2934
|
+
meta.lastCapture = cv;
|
|
2935
|
+
meta.needsRecapture = false;
|
|
2936
|
+
}
|
|
2937
|
+
} catch (e) {
|
|
2938
|
+
console.error("liquidGL: Dynamic element capture failed.", e);
|
|
924
2939
|
}
|
|
925
|
-
})
|
|
926
|
-
.catch((e) => {
|
|
927
|
-
console.error("liquidGL: Dynamic element capture failed.", e);
|
|
928
|
-
})
|
|
929
|
-
.finally(() => {
|
|
930
2940
|
meta._capturing = false;
|
|
931
|
-
}
|
|
2941
|
+
};
|
|
2942
|
+
if (typeof requestIdleCallback === "function") {
|
|
2943
|
+
requestIdleCallback(capture, { timeout: 100 });
|
|
2944
|
+
} else {
|
|
2945
|
+
setTimeout(capture, 0);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
932
2948
|
}
|
|
933
2949
|
|
|
934
2950
|
if (meta.lastCapture) {
|
|
@@ -949,7 +2965,7 @@ const liquidGL = (() => {
|
|
|
949
2965
|
0,
|
|
950
2966
|
0,
|
|
951
2967
|
w,
|
|
952
|
-
h
|
|
2968
|
+
h,
|
|
953
2969
|
);
|
|
954
2970
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
955
2971
|
gl.texSubImage2D(
|
|
@@ -959,7 +2975,7 @@ const liquidGL = (() => {
|
|
|
959
2975
|
y,
|
|
960
2976
|
gl.RGBA,
|
|
961
2977
|
gl.UNSIGNED_BYTE,
|
|
962
|
-
eraseCanvas
|
|
2978
|
+
eraseCanvas,
|
|
963
2979
|
);
|
|
964
2980
|
}
|
|
965
2981
|
}
|
|
@@ -1037,7 +3053,7 @@ const liquidGL = (() => {
|
|
|
1037
3053
|
0,
|
|
1038
3054
|
0,
|
|
1039
3055
|
drawW,
|
|
1040
|
-
drawH
|
|
3056
|
+
drawH,
|
|
1041
3057
|
);
|
|
1042
3058
|
compositeVideos(this._compositeCtx, rect);
|
|
1043
3059
|
|
|
@@ -1046,7 +3062,7 @@ const liquidGL = (() => {
|
|
|
1046
3062
|
this._compositeCtx.translate(drawW / 2, drawH / 2);
|
|
1047
3063
|
if (style.transform !== "none") {
|
|
1048
3064
|
this._compositeCtx.transform(
|
|
1049
|
-
...this._parseTransform(style.transform)
|
|
3065
|
+
...this._parseTransform(style.transform),
|
|
1050
3066
|
);
|
|
1051
3067
|
}
|
|
1052
3068
|
this._compositeCtx.translate(-drawW / 2, -drawH / 2);
|
|
@@ -1062,7 +3078,7 @@ const liquidGL = (() => {
|
|
|
1062
3078
|
dstY,
|
|
1063
3079
|
gl.RGBA,
|
|
1064
3080
|
gl.UNSIGNED_BYTE,
|
|
1065
|
-
compositeCanvas
|
|
3081
|
+
compositeCanvas,
|
|
1066
3082
|
);
|
|
1067
3083
|
|
|
1068
3084
|
if (this._workerEnabled && meta._heavyAnim) {
|
|
@@ -1080,7 +3096,7 @@ const liquidGL = (() => {
|
|
|
1080
3096
|
dstX,
|
|
1081
3097
|
dstY,
|
|
1082
3098
|
updW,
|
|
1083
|
-
updH
|
|
3099
|
+
updH,
|
|
1084
3100
|
),
|
|
1085
3101
|
createImageBitmap(meta.lastCapture),
|
|
1086
3102
|
]).then(([snapBmp, dynBmp]) => {
|
|
@@ -1092,7 +3108,7 @@ const liquidGL = (() => {
|
|
|
1092
3108
|
snap: snapBmp,
|
|
1093
3109
|
dyn: dynBmp,
|
|
1094
3110
|
},
|
|
1095
|
-
[snapBmp, dynBmp]
|
|
3111
|
+
[snapBmp, dynBmp],
|
|
1096
3112
|
);
|
|
1097
3113
|
});
|
|
1098
3114
|
meta.prevDrawRect = { x: dstX, y: dstY, w: updW, h: updH };
|
|
@@ -1152,6 +3168,7 @@ const liquidGL = (() => {
|
|
|
1152
3168
|
_capturing: false,
|
|
1153
3169
|
prevDrawRect: null,
|
|
1154
3170
|
lastCapture: null,
|
|
3171
|
+
naughtyCanvas: null,
|
|
1155
3172
|
needsRecapture: true,
|
|
1156
3173
|
hoverClassName: null,
|
|
1157
3174
|
_animating: false,
|
|
@@ -1217,7 +3234,7 @@ const liquidGL = (() => {
|
|
|
1217
3234
|
try {
|
|
1218
3235
|
this._dynamicStyleSheet.insertRule(
|
|
1219
3236
|
rule,
|
|
1220
|
-
this._dynamicStyleSheet.cssRules.length
|
|
3237
|
+
this._dynamicStyleSheet.cssRules.length,
|
|
1221
3238
|
);
|
|
1222
3239
|
m.hoverClassName = className;
|
|
1223
3240
|
el.classList.add(className);
|
|
@@ -1227,7 +3244,7 @@ const liquidGL = (() => {
|
|
|
1227
3244
|
}
|
|
1228
3245
|
setDirty();
|
|
1229
3246
|
},
|
|
1230
|
-
{ passive: true }
|
|
3247
|
+
{ passive: true },
|
|
1231
3248
|
);
|
|
1232
3249
|
|
|
1233
3250
|
el.addEventListener("mouseleave", handleLeave, { passive: true });
|
|
@@ -1247,7 +3264,7 @@ const liquidGL = (() => {
|
|
|
1247
3264
|
if (
|
|
1248
3265
|
meta._heavyAnim &&
|
|
1249
3266
|
!meta._capturing &&
|
|
1250
|
-
ts - meta._lastCaptureTs >
|
|
3267
|
+
ts - meta._lastCaptureTs > RECAPTURE_INTERVAL_MS
|
|
1251
3268
|
) {
|
|
1252
3269
|
meta._lastCaptureTs = ts;
|
|
1253
3270
|
meta.needsRecapture = true;
|
|
@@ -1293,7 +3310,7 @@ const liquidGL = (() => {
|
|
|
1293
3310
|
if (m) m._heavyAnim = true;
|
|
1294
3311
|
startRealtime();
|
|
1295
3312
|
},
|
|
1296
|
-
{ passive: true }
|
|
3313
|
+
{ passive: true },
|
|
1297
3314
|
);
|
|
1298
3315
|
|
|
1299
3316
|
el.addEventListener(
|
|
@@ -1305,7 +3322,7 @@ const liquidGL = (() => {
|
|
|
1305
3322
|
if (!m._animating) startRealtime();
|
|
1306
3323
|
}
|
|
1307
3324
|
},
|
|
1308
|
-
{ passive: true }
|
|
3325
|
+
{ passive: true },
|
|
1309
3326
|
);
|
|
1310
3327
|
|
|
1311
3328
|
const stopRealtime = () => {
|
|
@@ -1454,9 +3471,12 @@ const liquidGL = (() => {
|
|
|
1454
3471
|
let overscrollY = 0;
|
|
1455
3472
|
let overscrollX = 0;
|
|
1456
3473
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
3474
|
+
const vv = window.visualViewport;
|
|
3475
|
+
if (vv) {
|
|
3476
|
+
if (Math.abs(vv.scale - 1) < 0.01) {
|
|
3477
|
+
overscrollX = -vv.offsetLeft;
|
|
3478
|
+
overscrollY = -vv.offsetTop;
|
|
3479
|
+
}
|
|
1460
3480
|
} else {
|
|
1461
3481
|
const bodyStyle = window.getComputedStyle(document.body);
|
|
1462
3482
|
const htmlStyle = window.getComputedStyle(document.documentElement);
|
|
@@ -1641,23 +3661,47 @@ const liquidGL = (() => {
|
|
|
1641
3661
|
const getMaxTilt = () =>
|
|
1642
3662
|
Number.isFinite(this.options.tiltFactor) ? this.options.tiltFactor : 5;
|
|
1643
3663
|
|
|
3664
|
+
const getTiltEase = () =>
|
|
3665
|
+
Number.isFinite(this.options.tiltEase)
|
|
3666
|
+
? Math.max(0, this.options.tiltEase)
|
|
3667
|
+
: 400;
|
|
3668
|
+
|
|
3669
|
+
const TILT_TRACK_MS = 120;
|
|
3670
|
+
|
|
3671
|
+
const setTiltTransition = (ms) => {
|
|
3672
|
+
const value = `transform ${ms}ms cubic-bezier(0.33,1,0.68,1)`;
|
|
3673
|
+
this.el.style.transition = value;
|
|
3674
|
+
if (this._mirror) this._mirror.style.transition = value;
|
|
3675
|
+
if (this._shadowEl) this._shadowEl.style.transition = value;
|
|
3676
|
+
};
|
|
3677
|
+
|
|
3678
|
+
this._clearTiltEnterTimer = () => {
|
|
3679
|
+
if (this._tiltEnterTimer) {
|
|
3680
|
+
clearTimeout(this._tiltEnterTimer);
|
|
3681
|
+
this._tiltEnterTimer = null;
|
|
3682
|
+
}
|
|
3683
|
+
};
|
|
3684
|
+
|
|
1644
3685
|
this._applyTilt = (clientX, clientY) => {
|
|
1645
3686
|
if (!this._tiltInteracting) {
|
|
1646
3687
|
this._tiltInteracting = true;
|
|
1647
|
-
this.el.style.transition =
|
|
1648
|
-
"transform 0.12s cubic-bezier(0.33,1,0.68,1)";
|
|
1649
3688
|
this._createMirrorCanvas();
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
3689
|
+
|
|
3690
|
+
const enterMs = getTiltEase();
|
|
3691
|
+
setTiltTransition(enterMs);
|
|
3692
|
+
|
|
3693
|
+
this._tiltEntering = enterMs > 0;
|
|
3694
|
+
this._clearTiltEnterTimer();
|
|
3695
|
+
if (this._tiltEntering) {
|
|
3696
|
+
this._tiltEnterTimer = setTimeout(() => {
|
|
3697
|
+
this._tiltEntering = false;
|
|
3698
|
+
this._tiltEnterTimer = null;
|
|
3699
|
+
setTiltTransition(TILT_TRACK_MS);
|
|
3700
|
+
}, enterMs);
|
|
1657
3701
|
}
|
|
1658
3702
|
}
|
|
1659
3703
|
|
|
1660
|
-
const r = this._baseRect || this.
|
|
3704
|
+
const r = this._baseRect || this._measureBaseRect();
|
|
1661
3705
|
const cx = r.left + r.width / 2;
|
|
1662
3706
|
const cy = r.top + r.height / 2;
|
|
1663
3707
|
|
|
@@ -1686,8 +3730,13 @@ const liquidGL = (() => {
|
|
|
1686
3730
|
|
|
1687
3731
|
const transformStr = `${overscrollCompensation}${baseTransform}perspective(800px) rotateX(${rotX}deg) rotateY(${rotY}deg)`;
|
|
1688
3732
|
|
|
1689
|
-
this.
|
|
1690
|
-
|
|
3733
|
+
if (this._tiltEntering) {
|
|
3734
|
+
this._easeTiltTo(rotX, rotY, getTiltEase());
|
|
3735
|
+
} else {
|
|
3736
|
+
this._cancelTiltEase();
|
|
3737
|
+
this.tiltX = rotX;
|
|
3738
|
+
this.tiltY = rotY;
|
|
3739
|
+
}
|
|
1691
3740
|
|
|
1692
3741
|
this.el.style.transformOrigin = `50% 50%`;
|
|
1693
3742
|
this.el.style.transform = transformStr;
|
|
@@ -1705,8 +3754,60 @@ const liquidGL = (() => {
|
|
|
1705
3754
|
this.renderer.render();
|
|
1706
3755
|
};
|
|
1707
3756
|
|
|
3757
|
+
this._cancelTiltEase = () => {
|
|
3758
|
+
if (this._tiltEaseRaf) {
|
|
3759
|
+
cancelAnimationFrame(this._tiltEaseRaf);
|
|
3760
|
+
this._tiltEaseRaf = null;
|
|
3761
|
+
}
|
|
3762
|
+
};
|
|
3763
|
+
|
|
3764
|
+
this._easeTiltTo = (toX, toY, duration) => {
|
|
3765
|
+
this._cancelTiltEase();
|
|
3766
|
+
|
|
3767
|
+
const fromX = this.tiltX;
|
|
3768
|
+
const fromY = this.tiltY;
|
|
3769
|
+
if (fromX === toX && fromY === toY) return;
|
|
3770
|
+
|
|
3771
|
+
if (!duration || duration <= 0) {
|
|
3772
|
+
this.tiltX = toX;
|
|
3773
|
+
this.tiltY = toY;
|
|
3774
|
+
this.renderer.render();
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
const start =
|
|
3779
|
+
typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
3780
|
+
|
|
3781
|
+
const step = (now) => {
|
|
3782
|
+
const elapsed =
|
|
3783
|
+
(typeof now === "number"
|
|
3784
|
+
? now
|
|
3785
|
+
: typeof performance !== "undefined"
|
|
3786
|
+
? performance.now()
|
|
3787
|
+
: Date.now()) - start;
|
|
3788
|
+
const t = Math.min(1, elapsed / duration);
|
|
3789
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
3790
|
+
|
|
3791
|
+
this.tiltX = fromX + (toX - fromX) * eased;
|
|
3792
|
+
this.tiltY = fromY + (toY - fromY) * eased;
|
|
3793
|
+
this.renderer.render();
|
|
3794
|
+
|
|
3795
|
+
if (t < 1) {
|
|
3796
|
+
this._tiltEaseRaf = requestAnimationFrame(step);
|
|
3797
|
+
} else {
|
|
3798
|
+
this._tiltEaseRaf = null;
|
|
3799
|
+
this.tiltX = toX;
|
|
3800
|
+
this.tiltY = toY;
|
|
3801
|
+
this.renderer.render();
|
|
3802
|
+
}
|
|
3803
|
+
};
|
|
3804
|
+
|
|
3805
|
+
this._tiltEaseRaf = requestAnimationFrame(step);
|
|
3806
|
+
};
|
|
3807
|
+
|
|
1708
3808
|
this._smoothReset = () => {
|
|
1709
|
-
|
|
3809
|
+
const restMs = getTiltEase();
|
|
3810
|
+
setTiltTransition(restMs);
|
|
1710
3811
|
this.el.style.transformOrigin = `50% 50%`;
|
|
1711
3812
|
const baseRest =
|
|
1712
3813
|
this._savedTransform && this._savedTransform !== "none"
|
|
@@ -1726,13 +3827,11 @@ const liquidGL = (() => {
|
|
|
1726
3827
|
|
|
1727
3828
|
this.el.style.transform = `${overscrollCompensation}${baseRest}perspective(800px) rotateX(0deg) rotateY(0deg)`;
|
|
1728
3829
|
|
|
1729
|
-
this.
|
|
1730
|
-
this.
|
|
1731
|
-
this.
|
|
3830
|
+
this._tiltEntering = false;
|
|
3831
|
+
this._clearTiltEnterTimer();
|
|
3832
|
+
this._easeTiltTo(0, 0, getTiltEase());
|
|
1732
3833
|
|
|
1733
3834
|
if (this._mirror) {
|
|
1734
|
-
this._mirror.style.transition =
|
|
1735
|
-
"transform 0.4s cubic-bezier(0.33, 1, 0.68, 1)";
|
|
1736
3835
|
this._mirror.style.transformOrigin = this._pivotOrigin || "50% 50%";
|
|
1737
3836
|
this._mirror.style.transform = `${baseRest}perspective(800px) rotateX(0deg) rotateY(0deg)`;
|
|
1738
3837
|
const clean = () => {
|
|
@@ -1742,12 +3841,10 @@ const liquidGL = (() => {
|
|
|
1742
3841
|
this._mirror.addEventListener("transitionend", clean, {
|
|
1743
3842
|
once: true,
|
|
1744
3843
|
});
|
|
1745
|
-
this._resetCleanupTimer = setTimeout(clean,
|
|
3844
|
+
this._resetCleanupTimer = setTimeout(clean, restMs + 50);
|
|
1746
3845
|
}
|
|
1747
3846
|
|
|
1748
3847
|
if (this._shadowEl) {
|
|
1749
|
-
this._shadowEl.style.transition =
|
|
1750
|
-
"transform 0.4s cubic-bezier(0.33,1,0.68,1)";
|
|
1751
3848
|
this._shadowEl.style.transformOrigin = `50% 50%`;
|
|
1752
3849
|
this._shadowEl.style.transform = `${baseRest}perspective(800px) rotateX(0deg) rotateY(0deg)`;
|
|
1753
3850
|
}
|
|
@@ -1766,7 +3863,7 @@ const liquidGL = (() => {
|
|
|
1766
3863
|
this._tiltInteracting = false;
|
|
1767
3864
|
this._createMirrorCanvas();
|
|
1768
3865
|
|
|
1769
|
-
const r = this._baseRect || this.
|
|
3866
|
+
const r = this._baseRect || this._measureBaseRect();
|
|
1770
3867
|
const cx = r.left + r.width / 2;
|
|
1771
3868
|
const cy = r.top + r.height / 2;
|
|
1772
3869
|
|
|
@@ -1853,6 +3950,11 @@ const liquidGL = (() => {
|
|
|
1853
3950
|
|
|
1854
3951
|
_unbindTiltHandlers() {
|
|
1855
3952
|
if (!this._tiltHandlersBound) return;
|
|
3953
|
+
if (this._cancelTiltEase) this._cancelTiltEase();
|
|
3954
|
+
if (this._clearTiltEnterTimer) this._clearTiltEnterTimer();
|
|
3955
|
+
this._tiltEntering = false;
|
|
3956
|
+
this.tiltX = 0;
|
|
3957
|
+
this.tiltY = 0;
|
|
1856
3958
|
this.el.removeEventListener("mouseenter", this._onMouseEnter.bind(this));
|
|
1857
3959
|
this.el.removeEventListener("mousemove", this._onMouseMove.bind(this));
|
|
1858
3960
|
document.removeEventListener("mousemove", this._boundCheckLeave);
|
|
@@ -1872,8 +3974,26 @@ const liquidGL = (() => {
|
|
|
1872
3974
|
this.renderer.render();
|
|
1873
3975
|
}
|
|
1874
3976
|
|
|
3977
|
+
_measureBaseRect() {
|
|
3978
|
+
const tilted =
|
|
3979
|
+
this.tiltX !== 0 || this.tiltY !== 0 || !!this._tiltEaseRaf;
|
|
3980
|
+
if (!tilted) return this.el.getBoundingClientRect();
|
|
3981
|
+
|
|
3982
|
+
const prevTransition = this.el.style.transition;
|
|
3983
|
+
const prevTransform = this.el.style.transform;
|
|
3984
|
+
|
|
3985
|
+
this.el.style.transition = "none";
|
|
3986
|
+
this.el.style.transform = this._savedTransform || "";
|
|
3987
|
+
const rect = this.el.getBoundingClientRect();
|
|
3988
|
+
|
|
3989
|
+
this.el.style.transform = prevTransform;
|
|
3990
|
+
this.el.style.transition = prevTransition;
|
|
3991
|
+
|
|
3992
|
+
return rect;
|
|
3993
|
+
}
|
|
3994
|
+
|
|
1875
3995
|
_createMirrorCanvas() {
|
|
1876
|
-
this._baseRect = this.
|
|
3996
|
+
this._baseRect = this._measureBaseRect();
|
|
1877
3997
|
if (this._mirror) return;
|
|
1878
3998
|
this._mirror = document.createElement("canvas");
|
|
1879
3999
|
Object.assign(this._mirror.style, {
|
|
@@ -1891,9 +4011,9 @@ const liquidGL = (() => {
|
|
|
1891
4011
|
|
|
1892
4012
|
const updateClip = () => {
|
|
1893
4013
|
if (this._mirrorActive) {
|
|
1894
|
-
this._baseRect = this._baseRect || this.
|
|
4014
|
+
this._baseRect = this._baseRect || this._measureBaseRect();
|
|
1895
4015
|
}
|
|
1896
|
-
const r = this._baseRect || this.
|
|
4016
|
+
const r = this._baseRect || this._measureBaseRect();
|
|
1897
4017
|
const radius = `${this.radiusCss}px`;
|
|
1898
4018
|
this._mirror.style.clipPath = `inset(${r.top}px ${
|
|
1899
4019
|
innerWidth - r.right
|
|
@@ -1928,12 +4048,13 @@ const liquidGL = (() => {
|
|
|
1928
4048
|
/* --------------------------------------------------
|
|
1929
4049
|
* Public API
|
|
1930
4050
|
* ------------------------------------------------*/
|
|
1931
|
-
|
|
4051
|
+
window.liquidGL = function (userOptions = {}) {
|
|
1932
4052
|
const defaults = {
|
|
1933
4053
|
target: ".liquidGL",
|
|
1934
4054
|
snapshot: "body",
|
|
1935
4055
|
resolution: 2.0,
|
|
1936
4056
|
refraction: 0.01,
|
|
4057
|
+
aberration: 0,
|
|
1937
4058
|
bevelDepth: 0.08,
|
|
1938
4059
|
bevelWidth: 0.15,
|
|
1939
4060
|
frost: 0,
|
|
@@ -1942,6 +4063,7 @@ const liquidGL = (() => {
|
|
|
1942
4063
|
reveal: "fade",
|
|
1943
4064
|
tilt: false,
|
|
1944
4065
|
tiltFactor: 5,
|
|
4066
|
+
tiltEase: 400,
|
|
1945
4067
|
magnify: 1,
|
|
1946
4068
|
on: {},
|
|
1947
4069
|
};
|
|
@@ -1960,7 +4082,7 @@ const liquidGL = (() => {
|
|
|
1960
4082
|
|
|
1961
4083
|
if (noWebGL) {
|
|
1962
4084
|
console.warn(
|
|
1963
|
-
"liquidGL: WebGL not available – falling back to CSS backdrop-filter."
|
|
4085
|
+
"liquidGL: WebGL not available – falling back to CSS backdrop-filter.",
|
|
1964
4086
|
);
|
|
1965
4087
|
const fallbackNodes = document.querySelectorAll(options.target);
|
|
1966
4088
|
fallbackNodes.forEach((node) => {
|
|
@@ -1984,13 +4106,13 @@ const liquidGL = (() => {
|
|
|
1984
4106
|
const nodeList = document.querySelectorAll(options.target);
|
|
1985
4107
|
if (!nodeList || nodeList.length === 0) {
|
|
1986
4108
|
console.warn(
|
|
1987
|
-
`liquidGL: Target element(s) '${options.target}' not found
|
|
4109
|
+
`liquidGL: Target element(s) '${options.target}' not found.`,
|
|
1988
4110
|
);
|
|
1989
4111
|
return;
|
|
1990
4112
|
}
|
|
1991
4113
|
|
|
1992
4114
|
const instances = Array.from(nodeList).map((el) =>
|
|
1993
|
-
renderer.addLens(el, options)
|
|
4115
|
+
renderer.addLens(el, options),
|
|
1994
4116
|
);
|
|
1995
4117
|
|
|
1996
4118
|
if (!renderer._rafId && !renderer.useExternalTicker) {
|
|
@@ -2007,7 +4129,7 @@ const liquidGL = (() => {
|
|
|
2007
4129
|
/* --------------------------------------------------
|
|
2008
4130
|
* Public helper: register elements that need live updates
|
|
2009
4131
|
* ------------------------------------------------*/
|
|
2010
|
-
liquidGL.registerDynamic = function (elements) {
|
|
4132
|
+
window.liquidGL.registerDynamic = function (elements) {
|
|
2011
4133
|
const renderer = window.__liquidGLRenderer__;
|
|
2012
4134
|
if (!renderer || !renderer.addDynamicElement) return;
|
|
2013
4135
|
renderer.addDynamicElement(elements);
|
|
@@ -2019,19 +4141,19 @@ const liquidGL = (() => {
|
|
|
2019
4141
|
/* --------------------------------------------------
|
|
2020
4142
|
* Public helper: Universal smooth scroll / animation sync
|
|
2021
4143
|
* ------------------------------------------------*/
|
|
2022
|
-
liquidGL.syncWith = function (config = {}) {
|
|
4144
|
+
window.liquidGL.syncWith = function (config = {}) {
|
|
2023
4145
|
const renderer = window.__liquidGLRenderer__;
|
|
2024
4146
|
if (!renderer) {
|
|
2025
4147
|
console.warn(
|
|
2026
|
-
"liquidGL: Please initialize liquidGL *before* calling syncWith()."
|
|
4148
|
+
"liquidGL: Please initialize liquidGL *before* calling syncWith().",
|
|
2027
4149
|
);
|
|
2028
4150
|
return;
|
|
2029
4151
|
}
|
|
2030
4152
|
|
|
2031
|
-
const G = window.gsap;
|
|
4153
|
+
const G = config.gsap === false ? null : config.gsap || window.gsap;
|
|
2032
4154
|
const L = window.Lenis;
|
|
2033
4155
|
const LS = window.LocomotiveScroll;
|
|
2034
|
-
const ST = G ? G.ScrollTrigger : null;
|
|
4156
|
+
const ST = G ? config.ScrollTrigger || G.ScrollTrigger : null;
|
|
2035
4157
|
|
|
2036
4158
|
let lenis = config.lenis;
|
|
2037
4159
|
let loco = config.locomotiveScroll;
|
|
@@ -2103,8 +4225,7 @@ const liquidGL = (() => {
|
|
|
2103
4225
|
|
|
2104
4226
|
return { lenis, locomotiveScroll: loco };
|
|
2105
4227
|
};
|
|
2106
|
-
|
|
2107
|
-
return liquidGL;
|
|
4228
|
+
return window.liquidGL;
|
|
2108
4229
|
})();
|
|
2109
4230
|
|
|
2110
4231
|
export default liquidGL;
|