liquid-gl 2.0.0 → 2.0.2
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 +38 -18
- package/liquidGL.js +1814 -59
- package/package.json +1 -4
package/liquidGL.js
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Author: NaughtyDuk© – https://liquidgl.naughtyduk.com
|
|
6
6
|
* Licence: MIT
|
|
7
|
-
* Version: v2.0.
|
|
7
|
+
* Version: v2.0.2
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import html2canvas from "html2canvas";
|
|
11
|
-
|
|
12
10
|
const liquidGL = (() => {
|
|
13
11
|
"use strict";
|
|
14
12
|
|
|
@@ -71,11 +69,1746 @@ const liquidGL = (() => {
|
|
|
71
69
|
return p;
|
|
72
70
|
}
|
|
73
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
|
+
})();
|
|
1805
|
+
|
|
74
1806
|
/* --------------------------------------------------
|
|
75
1807
|
* Shared renderer (one per page)
|
|
76
1808
|
* ------------------------------------------------*/
|
|
77
1809
|
class liquidGLRenderer {
|
|
78
1810
|
constructor(snapshotSelector, snapshotResolution = 1.0) {
|
|
1811
|
+
this._naughtyQueued = false;
|
|
79
1812
|
this.canvas = document.createElement("canvas");
|
|
80
1813
|
this.canvas.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;`;
|
|
81
1814
|
this.canvas.setAttribute("data-liquid-ignore", "");
|
|
@@ -273,6 +2006,8 @@ const liquidGL = (() => {
|
|
|
273
2006
|
uniform float u_tiltX;
|
|
274
2007
|
uniform float u_tiltY;
|
|
275
2008
|
uniform float u_magnify;
|
|
2009
|
+
uniform vec2 u_subpixel;
|
|
2010
|
+
uniform vec2 u_boxSize;
|
|
276
2011
|
|
|
277
2012
|
float udRoundBox( vec2 p, vec2 b, float r ) {
|
|
278
2013
|
return length(max(abs(p)-b+r,0.0))-r;
|
|
@@ -293,27 +2028,23 @@ const liquidGL = (() => {
|
|
|
293
2028
|
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
|
|
294
2029
|
}
|
|
295
2030
|
|
|
296
|
-
float edgeFactor(vec2
|
|
297
|
-
vec2 p_px = (uv - 0.5) * u_resolution;
|
|
298
|
-
vec2 b_px = 0.5 * u_resolution;
|
|
2031
|
+
float edgeFactor(vec2 p_px, vec2 b_px, float radius_px){
|
|
299
2032
|
float d = -udRoundBox(p_px, b_px, radius_px);
|
|
300
|
-
float bevel_px = u_bevelWidth * min(
|
|
2033
|
+
float bevel_px = u_bevelWidth * min(u_boxSize.x, u_boxSize.y);
|
|
301
2034
|
return 1.0 - smoothstep(0.0, bevel_px, d);
|
|
302
2035
|
}
|
|
303
2036
|
void main(){
|
|
304
2037
|
vec2 p = v_uv - 0.5;
|
|
305
2038
|
p.x *= u_resolution.x / u_resolution.y;
|
|
306
2039
|
|
|
307
|
-
|
|
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);
|
|
308
2044
|
float min_dimension = min(u_resolution.x, u_resolution.y);
|
|
309
2045
|
float offsetAmt = (edge * u_refraction + pow(edge, 10.0) * u_bevelDepth);
|
|
310
2046
|
float centreBlend = smoothstep(0.15, 0.45, length(p));
|
|
311
|
-
vec2 refractDir = cornerNormal(
|
|
312
|
-
(v_uv - 0.5) * u_resolution,
|
|
313
|
-
0.5 * u_resolution,
|
|
314
|
-
u_radius,
|
|
315
|
-
normalize(p)
|
|
316
|
-
);
|
|
2047
|
+
vec2 refractDir = cornerNormal(p_px, b_px, u_radius, normalize(p));
|
|
317
2048
|
vec2 offset = refractDir * offsetAmt * centreBlend;
|
|
318
2049
|
|
|
319
2050
|
float tiltRefractionScale = 0.05;
|
|
@@ -370,10 +2101,8 @@ const liquidGL = (() => {
|
|
|
370
2101
|
|
|
371
2102
|
vec4 final = refrCol;
|
|
372
2103
|
|
|
373
|
-
vec2 p_px = (v_uv - 0.5) * u_resolution;
|
|
374
|
-
vec2 b_px = 0.5 * u_resolution;
|
|
375
2104
|
float dmask = udRoundBox(p_px, b_px, u_radius);
|
|
376
|
-
float inShape =
|
|
2105
|
+
float inShape = 1.0 - smoothstep(-0.5, 0.5, dmask);
|
|
377
2106
|
|
|
378
2107
|
if (u_specular) {
|
|
379
2108
|
vec2 lp1 = vec2(sin(u_time*0.2), cos(u_time*0.3))*0.6 + 0.5;
|
|
@@ -435,6 +2164,8 @@ const liquidGL = (() => {
|
|
|
435
2164
|
tiltX: gl.getUniformLocation(this.program, "u_tiltX"),
|
|
436
2165
|
tiltY: gl.getUniformLocation(this.program, "u_tiltY"),
|
|
437
2166
|
magnify: gl.getUniformLocation(this.program, "u_magnify"),
|
|
2167
|
+
subpixel: gl.getUniformLocation(this.program, "u_subpixel"),
|
|
2168
|
+
boxSize: gl.getUniformLocation(this.program, "u_boxSize"),
|
|
438
2169
|
};
|
|
439
2170
|
}
|
|
440
2171
|
|
|
@@ -450,7 +2181,7 @@ const liquidGL = (() => {
|
|
|
450
2181
|
|
|
451
2182
|
/* ----------------------------- */
|
|
452
2183
|
async captureSnapshot() {
|
|
453
|
-
if (this._capturing
|
|
2184
|
+
if (this._capturing) return;
|
|
454
2185
|
this._capturing = true;
|
|
455
2186
|
|
|
456
2187
|
const undos = [];
|
|
@@ -519,25 +2250,19 @@ const liquidGL = (() => {
|
|
|
519
2250
|
);
|
|
520
2251
|
};
|
|
521
2252
|
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
onclone: (clonedDoc) => {
|
|
534
|
-
clonedDoc
|
|
535
|
-
.querySelectorAll("[data-liquidgl-hide]")
|
|
536
|
-
.forEach((el) => {
|
|
537
|
-
el.style.visibility = "hidden";
|
|
538
|
-
});
|
|
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,
|
|
539
2264
|
},
|
|
540
|
-
|
|
2265
|
+
);
|
|
541
2266
|
|
|
542
2267
|
if (!this._uploadTexture(snapCanvas)) {
|
|
543
2268
|
throw new Error("liquidGL: snapshot could not be uploaded.");
|
|
@@ -659,6 +2384,10 @@ const liquidGL = (() => {
|
|
|
659
2384
|
const gl = this.gl;
|
|
660
2385
|
if (!this.texture) return;
|
|
661
2386
|
|
|
2387
|
+
this.lenses.forEach((ln) => {
|
|
2388
|
+
if (ln._isSticky && !ln._mirrorActive) ln.updateMetrics();
|
|
2389
|
+
});
|
|
2390
|
+
|
|
662
2391
|
if (this._isScrolling) {
|
|
663
2392
|
this._scrollUpdateCounter++;
|
|
664
2393
|
}
|
|
@@ -757,6 +2486,8 @@ const liquidGL = (() => {
|
|
|
757
2486
|
|
|
758
2487
|
gl.viewport(x, y, w, h);
|
|
759
2488
|
gl.uniform2f(this.u.res, w, h);
|
|
2489
|
+
gl.uniform2f(this.u.subpixel, leftPx - x, topPx - yTop);
|
|
2490
|
+
gl.uniform2f(this.u.boxSize, rect.width * dpr, rect.height * dpr);
|
|
760
2491
|
|
|
761
2492
|
const snapRect =
|
|
762
2493
|
this._frameSnapRect || this.snapshotTarget.getBoundingClientRect();
|
|
@@ -1187,27 +2918,37 @@ const liquidGL = (() => {
|
|
|
1187
2918
|
if (meta.needsRecapture && !meta._capturing && !this._isScrolling) {
|
|
1188
2919
|
meta._capturing = true;
|
|
1189
2920
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
2921
|
+
const ignoreDynamic = (n) =>
|
|
2922
|
+
n.tagName === "CANVAS" || n.hasAttribute("data-liquid-ignore");
|
|
2923
|
+
|
|
2924
|
+
if (this._naughtyQueued) {
|
|
2925
|
+
meta._capturing = false;
|
|
2926
|
+
} else {
|
|
2927
|
+
this._naughtyQueued = true;
|
|
2928
|
+
const capture = () => {
|
|
2929
|
+
this._naughtyQueued = false;
|
|
2930
|
+
try {
|
|
2931
|
+
const cv = NaughtyDOM.rasterise(el, {
|
|
2932
|
+
scale: this.scaleFactor,
|
|
2933
|
+
ignoreElements: ignoreDynamic,
|
|
2934
|
+
canvas: meta.naughtyCanvas,
|
|
2935
|
+
});
|
|
2936
|
+
meta.naughtyCanvas = cv;
|
|
2937
|
+
if (cv.width > 0 && cv.height > 0) {
|
|
2938
|
+
meta.lastCapture = cv;
|
|
2939
|
+
meta.needsRecapture = false;
|
|
2940
|
+
}
|
|
2941
|
+
} catch (e) {
|
|
2942
|
+
console.error("liquidGL: Dynamic element capture failed.", e);
|
|
1203
2943
|
}
|
|
1204
|
-
})
|
|
1205
|
-
.catch((e) => {
|
|
1206
|
-
console.error("liquidGL: Dynamic element capture failed.", e);
|
|
1207
|
-
})
|
|
1208
|
-
.finally(() => {
|
|
1209
2944
|
meta._capturing = false;
|
|
1210
|
-
}
|
|
2945
|
+
};
|
|
2946
|
+
if (typeof requestIdleCallback === "function") {
|
|
2947
|
+
requestIdleCallback(capture, { timeout: 100 });
|
|
2948
|
+
} else {
|
|
2949
|
+
setTimeout(capture, 0);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
1211
2952
|
}
|
|
1212
2953
|
|
|
1213
2954
|
if (meta.lastCapture) {
|
|
@@ -1431,6 +3172,7 @@ const liquidGL = (() => {
|
|
|
1431
3172
|
_capturing: false,
|
|
1432
3173
|
prevDrawRect: null,
|
|
1433
3174
|
lastCapture: null,
|
|
3175
|
+
naughtyCanvas: null,
|
|
1434
3176
|
needsRecapture: true,
|
|
1435
3177
|
hoverClassName: null,
|
|
1436
3178
|
_animating: false,
|
|
@@ -1663,6 +3405,8 @@ const liquidGL = (() => {
|
|
|
1663
3405
|
? "relative"
|
|
1664
3406
|
: this.el.style.position;
|
|
1665
3407
|
|
|
3408
|
+
this._isSticky = /sticky/.test(window.getComputedStyle(this.el).position);
|
|
3409
|
+
|
|
1666
3410
|
const bgCol = window.getComputedStyle(this.el).backgroundColor;
|
|
1667
3411
|
const rgbaMatch = bgCol.match(/rgba?\(([^)]+)\)/);
|
|
1668
3412
|
this._bgColorComponents = null;
|
|
@@ -1700,6 +3444,17 @@ const liquidGL = (() => {
|
|
|
1700
3444
|
? this._baseRect
|
|
1701
3445
|
: this.el.getBoundingClientRect();
|
|
1702
3446
|
|
|
3447
|
+
const prev = this.rectPx;
|
|
3448
|
+
if (
|
|
3449
|
+
prev &&
|
|
3450
|
+
rect.left === prev.left &&
|
|
3451
|
+
rect.top === prev.top &&
|
|
3452
|
+
rect.width === prev.width &&
|
|
3453
|
+
rect.height === prev.height
|
|
3454
|
+
) {
|
|
3455
|
+
return;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
1703
3458
|
this.rectPx = {
|
|
1704
3459
|
left: rect.left,
|
|
1705
3460
|
top: rect.top,
|
|
@@ -2310,7 +4065,7 @@ const liquidGL = (() => {
|
|
|
2310
4065
|
/* --------------------------------------------------
|
|
2311
4066
|
* Public API
|
|
2312
4067
|
* ------------------------------------------------*/
|
|
2313
|
-
|
|
4068
|
+
window.liquidGL = function (userOptions = {}) {
|
|
2314
4069
|
const defaults = {
|
|
2315
4070
|
target: ".liquidGL",
|
|
2316
4071
|
snapshot: "body",
|
|
@@ -2391,7 +4146,7 @@ const liquidGL = (() => {
|
|
|
2391
4146
|
/* --------------------------------------------------
|
|
2392
4147
|
* Public helper: register elements that need live updates
|
|
2393
4148
|
* ------------------------------------------------*/
|
|
2394
|
-
liquidGL.registerDynamic = function (elements) {
|
|
4149
|
+
window.liquidGL.registerDynamic = function (elements) {
|
|
2395
4150
|
const renderer = window.__liquidGLRenderer__;
|
|
2396
4151
|
if (!renderer || !renderer.addDynamicElement) return;
|
|
2397
4152
|
renderer.addDynamicElement(elements);
|
|
@@ -2403,7 +4158,7 @@ const liquidGL = (() => {
|
|
|
2403
4158
|
/* --------------------------------------------------
|
|
2404
4159
|
* Public helper: Universal smooth scroll / animation sync
|
|
2405
4160
|
* ------------------------------------------------*/
|
|
2406
|
-
liquidGL.syncWith = function (config = {}) {
|
|
4161
|
+
window.liquidGL.syncWith = function (config = {}) {
|
|
2407
4162
|
const renderer = window.__liquidGLRenderer__;
|
|
2408
4163
|
if (!renderer) {
|
|
2409
4164
|
console.warn(
|
|
@@ -2487,7 +4242,7 @@ const liquidGL = (() => {
|
|
|
2487
4242
|
|
|
2488
4243
|
return { lenis, locomotiveScroll: loco };
|
|
2489
4244
|
};
|
|
2490
|
-
return liquidGL;
|
|
4245
|
+
return window.liquidGL;
|
|
2491
4246
|
})();
|
|
2492
4247
|
|
|
2493
4248
|
export default liquidGL;
|