q5 3.5.2 → 3.6.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/.prettierignore +1 -0
- package/README.md +1 -1
- package/defs/q5-c2d.d.ts +3950 -0
- package/deno.json +1 -1
- package/package.json +2 -1
- package/q5.d.ts +2649 -3027
- package/q5.js +405 -234
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 3.
|
|
3
|
+
* @version 3.6
|
|
4
4
|
* @author quinton-ashley
|
|
5
5
|
* @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
|
|
6
6
|
* @license LGPL-3.0
|
|
@@ -79,15 +79,19 @@ function Q5(scope, parent, renderer) {
|
|
|
79
79
|
$.deviceOrientation = window.screen?.orientation?.type;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
$.
|
|
83
|
-
$.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
$._loaders = [];
|
|
83
|
+
$.loadAll = () => {
|
|
84
|
+
let loaders = $._loaders;
|
|
85
|
+
if ($._g) loaders = loaders.concat($._g._loaders);
|
|
86
|
+
return Promise.all(loaders);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
$.isPreloadSupported = () => true;
|
|
90
|
+
$.disablePreload = () => ($._disablePreload = true);
|
|
87
91
|
|
|
88
92
|
const resolvers = [];
|
|
89
93
|
$._incrementPreload = () => {
|
|
90
|
-
$.
|
|
94
|
+
$._loaders.push(new Promise((resolve) => resolvers.push(resolve)));
|
|
91
95
|
};
|
|
92
96
|
$._decrementPreload = () => {
|
|
93
97
|
if (resolvers.length) resolvers.pop()();
|
|
@@ -289,7 +293,7 @@ function Q5(scope, parent, renderer) {
|
|
|
289
293
|
for (let name of userFns) $[name] ??= () => {};
|
|
290
294
|
|
|
291
295
|
if ($._isGlobal) {
|
|
292
|
-
for (let name of ['setup', 'update', 'draw', ...userFns]) {
|
|
296
|
+
for (let name of ['setup', 'update', 'draw', 'drawFrame', ...userFns]) {
|
|
293
297
|
if (Q5[name]) $[name] = Q5[name];
|
|
294
298
|
else {
|
|
295
299
|
Object.defineProperty(Q5, name, {
|
|
@@ -341,16 +345,13 @@ function Q5(scope, parent, renderer) {
|
|
|
341
345
|
new Promise((resolve) => {
|
|
342
346
|
setTimeout(() => {
|
|
343
347
|
// if not loading
|
|
344
|
-
if (!$.
|
|
348
|
+
if (!$._loaders.length) resolve();
|
|
345
349
|
}, 500);
|
|
346
350
|
})
|
|
347
351
|
]);
|
|
348
352
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (t.setup?.constructor.name == 'AsyncFunction') {
|
|
353
|
-
$.usePromiseLoading();
|
|
353
|
+
if (!$._disablePreload) {
|
|
354
|
+
await $.loadAll();
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
$.setup ??= t.setup || (() => {});
|
|
@@ -374,7 +375,7 @@ function Q5(scope, parent, renderer) {
|
|
|
374
375
|
|
|
375
376
|
Q5.instances.push($);
|
|
376
377
|
|
|
377
|
-
if (autoLoaded) start();
|
|
378
|
+
if (autoLoaded || Q5._esm) start();
|
|
378
379
|
else setTimeout(start, 32);
|
|
379
380
|
}
|
|
380
381
|
|
|
@@ -400,14 +401,14 @@ Q5.hooks = {
|
|
|
400
401
|
remove: []
|
|
401
402
|
};
|
|
402
403
|
|
|
403
|
-
Q5.addHook = (
|
|
404
|
+
Q5.addHook = (lifecycle, fn) => Q5.hooks[lifecycle].push(fn);
|
|
404
405
|
|
|
405
406
|
// p5 v2 compat
|
|
406
407
|
Q5.registerAddon = (addon) => {
|
|
407
408
|
let lifecycles = {};
|
|
408
409
|
addon(Q5, Q5.prototype, lifecycles);
|
|
409
|
-
for (let
|
|
410
|
-
Q5.hooks[
|
|
410
|
+
for (let l in lifecycles) {
|
|
411
|
+
Q5.hooks[l].push(lifecycles[l]);
|
|
411
412
|
}
|
|
412
413
|
};
|
|
413
414
|
|
|
@@ -437,16 +438,20 @@ function createCanvas(w, h, opt) {
|
|
|
437
438
|
}
|
|
438
439
|
}
|
|
439
440
|
|
|
440
|
-
if (Q5._server)
|
|
441
|
+
if (Q5._server) {
|
|
442
|
+
global.q5 = global.Q5 = Q5;
|
|
443
|
+
global.p5 ??= Q5;
|
|
444
|
+
}
|
|
441
445
|
|
|
442
446
|
if (typeof window == 'object') {
|
|
443
|
-
window.
|
|
447
|
+
window.q5 = window.Q5 = Q5;
|
|
448
|
+
window.p5 ??= Q5;
|
|
444
449
|
window.createCanvas = createCanvas;
|
|
445
450
|
window.C2D = 'c2d';
|
|
446
451
|
window.WEBGPU = 'webgpu';
|
|
447
452
|
} else global.window = 0;
|
|
448
453
|
|
|
449
|
-
Q5.version = Q5.VERSION = '3.
|
|
454
|
+
Q5.version = Q5.VERSION = '3.6';
|
|
450
455
|
|
|
451
456
|
if (typeof document == 'object') {
|
|
452
457
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1388,11 +1393,10 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1388
1393
|
let imgData = null,
|
|
1389
1394
|
pixels = null;
|
|
1390
1395
|
|
|
1391
|
-
$.createImage = (w, h, opt) => {
|
|
1392
|
-
opt ??=
|
|
1393
|
-
opt.
|
|
1394
|
-
|
|
1395
|
-
return new Q5.Image($, w, h, opt);
|
|
1396
|
+
$.createImage = (w, h, opt = {}) => {
|
|
1397
|
+
opt.colorSpace ??= $.canvas.colorSpace;
|
|
1398
|
+
opt.defaultImageScale ??= $._defaultImageScale;
|
|
1399
|
+
return new Q5.Image(w, h, opt);
|
|
1396
1400
|
};
|
|
1397
1401
|
|
|
1398
1402
|
$.loadImage = function (url, cb, opt) {
|
|
@@ -1406,7 +1410,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1406
1410
|
if (typeof last == 'object') {
|
|
1407
1411
|
opt = last;
|
|
1408
1412
|
cb = null;
|
|
1409
|
-
} else opt =
|
|
1413
|
+
} else opt = undefined;
|
|
1410
1414
|
|
|
1411
1415
|
let g = $.createImage(1, 1, opt);
|
|
1412
1416
|
let pd = g._pixelDensity;
|
|
@@ -1416,6 +1420,10 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1416
1420
|
|
|
1417
1421
|
g.promise = new Promise((resolve, reject) => {
|
|
1418
1422
|
img.onload = () => {
|
|
1423
|
+
delete g.promise;
|
|
1424
|
+
delete g.then;
|
|
1425
|
+
if (g._usedAwait) g = $.createImage(1, 1, opt);
|
|
1426
|
+
|
|
1419
1427
|
img._pixelDensity = pd;
|
|
1420
1428
|
g.defaultWidth = img.width * $._defaultImageScale;
|
|
1421
1429
|
g.defaultHeight = img.height * $._defaultImageScale;
|
|
@@ -1425,16 +1433,19 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1425
1433
|
|
|
1426
1434
|
g.ctx.drawImage(img, 0, 0);
|
|
1427
1435
|
if (cb) cb(g);
|
|
1428
|
-
delete g.promise;
|
|
1429
1436
|
resolve(g);
|
|
1430
1437
|
};
|
|
1431
1438
|
img.onerror = reject;
|
|
1432
1439
|
});
|
|
1433
|
-
$.
|
|
1440
|
+
$._loaders.push(g.promise);
|
|
1434
1441
|
|
|
1435
|
-
|
|
1442
|
+
// then only runs when the user awaits the instance
|
|
1443
|
+
g.then = (resolve, reject) => {
|
|
1444
|
+
g._usedAwait = true;
|
|
1445
|
+
return g.promise.then(resolve, reject);
|
|
1446
|
+
};
|
|
1436
1447
|
|
|
1437
|
-
|
|
1448
|
+
g.src = img.src = url;
|
|
1438
1449
|
return g;
|
|
1439
1450
|
};
|
|
1440
1451
|
|
|
@@ -1695,7 +1706,9 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1695
1706
|
};
|
|
1696
1707
|
|
|
1697
1708
|
Q5.Image = class {
|
|
1698
|
-
constructor(
|
|
1709
|
+
constructor(w, h, opt = {}) {
|
|
1710
|
+
opt.alpha ??= true;
|
|
1711
|
+
opt.colorSpace ??= Q5.canvasOptions.colorSpace;
|
|
1699
1712
|
let $ = this;
|
|
1700
1713
|
$._isImage = true;
|
|
1701
1714
|
$.canvas = $.ctx = $.drawingContext = null;
|
|
@@ -1706,7 +1719,7 @@ Q5.Image = class {
|
|
|
1706
1719
|
if (r[m]) r[m]($, $);
|
|
1707
1720
|
}
|
|
1708
1721
|
$._pixelDensity = opt.pixelDensity || 1;
|
|
1709
|
-
$._defaultImageScale = opt.defaultImageScale ||
|
|
1722
|
+
$._defaultImageScale = opt.defaultImageScale || 2;
|
|
1710
1723
|
$.createCanvas(w, h, opt);
|
|
1711
1724
|
let scale = $._pixelDensity * $._defaultImageScale;
|
|
1712
1725
|
$.defaultWidth = w * scale;
|
|
@@ -1878,13 +1891,11 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1878
1891
|
emphasis = 'normal',
|
|
1879
1892
|
weight = 'normal',
|
|
1880
1893
|
styleHash = 0,
|
|
1881
|
-
styleHashes = [],
|
|
1882
1894
|
genTextImage = false,
|
|
1883
1895
|
cacheSize = 0;
|
|
1884
1896
|
$._fontMod = false;
|
|
1885
1897
|
|
|
1886
1898
|
let cache = ($._textCache = {});
|
|
1887
|
-
$._textCacheMaxSize = 12000;
|
|
1888
1899
|
|
|
1889
1900
|
$.loadFont = (url, cb) => {
|
|
1890
1901
|
let f;
|
|
@@ -1894,25 +1905,29 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1894
1905
|
} else {
|
|
1895
1906
|
let name = url.split('/').pop().split('.')[0].replace(' ', '');
|
|
1896
1907
|
|
|
1897
|
-
f =
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1908
|
+
f = { family: name };
|
|
1909
|
+
let ff = new FontFace(name, `url(${encodeURI(url)})`);
|
|
1910
|
+
document.fonts.add(ff);
|
|
1911
|
+
|
|
1912
|
+
f.promise = new Promise((resolve, reject) => {
|
|
1913
|
+
ff.load()
|
|
1914
|
+
.then(() => {
|
|
1915
|
+
delete f.promise;
|
|
1916
|
+
delete f.then;
|
|
1917
|
+
if (cb) cb(ff);
|
|
1918
|
+
resolve(ff);
|
|
1919
|
+
})
|
|
1920
|
+
.catch((err) => {
|
|
1921
|
+
reject(err);
|
|
1922
|
+
});
|
|
1923
|
+
});
|
|
1911
1924
|
}
|
|
1912
|
-
|
|
1913
|
-
$._preloadPromises.push(f.promise);
|
|
1925
|
+
$._loaders.push(f.promise);
|
|
1914
1926
|
$.textFont(f.family);
|
|
1915
|
-
|
|
1927
|
+
f.then = (resolve, reject) => {
|
|
1928
|
+
f._usedAwait = true;
|
|
1929
|
+
return f.promise.then(resolve, reject);
|
|
1930
|
+
};
|
|
1916
1931
|
return f;
|
|
1917
1932
|
};
|
|
1918
1933
|
|
|
@@ -1980,8 +1995,13 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1980
1995
|
}
|
|
1981
1996
|
}
|
|
1982
1997
|
|
|
1998
|
+
if (f._usedAwait) {
|
|
1999
|
+
f = { family: fontFamily };
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1983
2002
|
f.faces = loadedFaces;
|
|
1984
2003
|
delete f.promise;
|
|
2004
|
+
delete f.then;
|
|
1985
2005
|
if (cb) cb(f);
|
|
1986
2006
|
return f;
|
|
1987
2007
|
} catch (e) {
|
|
@@ -2086,7 +2106,7 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2086
2106
|
if (str === undefined || (!$._doFill && !$._doStroke)) return;
|
|
2087
2107
|
str = str.toString();
|
|
2088
2108
|
let ctx = $.ctx;
|
|
2089
|
-
let img, colorStyle;
|
|
2109
|
+
let img, colorStyle, styleCache, colorCache, recycling;
|
|
2090
2110
|
|
|
2091
2111
|
if ($._fontMod) $._updateFont();
|
|
2092
2112
|
|
|
@@ -2094,14 +2114,23 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2094
2114
|
if (styleHash == -1) updateStyleHash();
|
|
2095
2115
|
colorStyle = $._fill + $._stroke + $._strokeWeight;
|
|
2096
2116
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2117
|
+
styleCache = cache[str];
|
|
2118
|
+
if (styleCache) colorCache = styleCache[styleHash];
|
|
2119
|
+
else styleCache = cache[str] = {};
|
|
2100
2120
|
|
|
2101
|
-
if (
|
|
2102
|
-
img =
|
|
2121
|
+
if (colorCache) {
|
|
2122
|
+
img = colorCache[colorStyle];
|
|
2103
2123
|
if (img) return img;
|
|
2104
|
-
|
|
2124
|
+
|
|
2125
|
+
if (colorCache.size >= 4) {
|
|
2126
|
+
for (let recycleKey in colorCache) {
|
|
2127
|
+
img = colorCache[recycleKey];
|
|
2128
|
+
delete colorCache[recycleKey];
|
|
2129
|
+
break;
|
|
2130
|
+
}
|
|
2131
|
+
recycling = true;
|
|
2132
|
+
}
|
|
2133
|
+
} else colorCache = styleCache[styleHash] = {};
|
|
2105
2134
|
}
|
|
2106
2135
|
|
|
2107
2136
|
if (str.indexOf('\n') == -1) lines[0] = str;
|
|
@@ -2169,6 +2198,8 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2169
2198
|
img._bottom = img._top + ascent + leading * (lines.length - 1);
|
|
2170
2199
|
img._leading = leading;
|
|
2171
2200
|
} else {
|
|
2201
|
+
let cnv = img.canvas;
|
|
2202
|
+
img.ctx.clearRect(0, 0, cnv.width, cnv.height);
|
|
2172
2203
|
img.modified = true;
|
|
2173
2204
|
}
|
|
2174
2205
|
|
|
@@ -2199,19 +2230,33 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2199
2230
|
if (!$._fillSet) ctx.fillStyle = ogFill;
|
|
2200
2231
|
|
|
2201
2232
|
if (genTextImage) {
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2233
|
+
colorCache[colorStyle] = img;
|
|
2234
|
+
|
|
2235
|
+
if (!recycling) {
|
|
2236
|
+
if (!colorCache.size) {
|
|
2237
|
+
Object.defineProperty(colorCache, 'size', {
|
|
2238
|
+
writable: true,
|
|
2239
|
+
enumerable: false
|
|
2240
|
+
});
|
|
2241
|
+
colorCache.size = 0;
|
|
2242
|
+
}
|
|
2243
|
+
colorCache.size++;
|
|
2244
|
+
cacheSize++;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
if (cacheSize > Q5.MAX_TEXT_IMAGES) {
|
|
2248
|
+
for (const str in cache) {
|
|
2249
|
+
styleCache = cache[str];
|
|
2250
|
+
for (const hash in styleCache) {
|
|
2251
|
+
colorCache = styleCache[hash];
|
|
2252
|
+
for (let c in colorCache) {
|
|
2253
|
+
let _img = colorCache[c];
|
|
2254
|
+
if (_img._texture) _img._texture.destroy();
|
|
2255
|
+
delete colorCache[c];
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2213
2258
|
}
|
|
2214
|
-
cacheSize
|
|
2259
|
+
cacheSize = 0;
|
|
2215
2260
|
}
|
|
2216
2261
|
return img;
|
|
2217
2262
|
}
|
|
@@ -2239,6 +2284,7 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2239
2284
|
};
|
|
2240
2285
|
|
|
2241
2286
|
Q5.fonts = [];
|
|
2287
|
+
Q5.MAX_TEXT_IMAGES = 5000;
|
|
2242
2288
|
Q5.modules.color = ($, q) => {
|
|
2243
2289
|
$.RGB = $.RGBA = $.RGBHDR = $._colorMode = 'rgb';
|
|
2244
2290
|
$.HSL = 'hsl';
|
|
@@ -2275,6 +2321,7 @@ Q5.modules.color = ($, q) => {
|
|
|
2275
2321
|
black: [0, 0, 0],
|
|
2276
2322
|
blue: [0, 0, 255],
|
|
2277
2323
|
brown: [165, 42, 42],
|
|
2324
|
+
coral: [255, 127, 80],
|
|
2278
2325
|
crimson: [220, 20, 60],
|
|
2279
2326
|
cyan: [0, 255, 255],
|
|
2280
2327
|
darkviolet: [148, 0, 211],
|
|
@@ -3112,29 +3159,38 @@ Q5.modules.dom = ($, q) => {
|
|
|
3112
3159
|
|
|
3113
3160
|
$.createSpan = (content) => $.createEl('span', content);
|
|
3114
3161
|
|
|
3162
|
+
function initVideo(el) {
|
|
3163
|
+
el.width ||= el.videoWidth;
|
|
3164
|
+
el.height ||= el.videoHeight;
|
|
3165
|
+
el.defaultWidth = el.width * $._defaultImageScale;
|
|
3166
|
+
el.defaultHeight = el.height * $._defaultImageScale;
|
|
3167
|
+
el.ready = true;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3115
3170
|
$.createVideo = (src) => {
|
|
3116
3171
|
let el = $.createEl('video');
|
|
3117
3172
|
el.crossOrigin = 'anonymous';
|
|
3118
3173
|
|
|
3119
|
-
el._load = () => {
|
|
3120
|
-
el.width ||= el.videoWidth;
|
|
3121
|
-
el.height ||= el.videoHeight;
|
|
3122
|
-
el.defaultWidth = el.width * $._defaultImageScale;
|
|
3123
|
-
el.defaultHeight = el.height * $._defaultImageScale;
|
|
3124
|
-
el.ready = true;
|
|
3125
|
-
};
|
|
3126
|
-
|
|
3127
3174
|
if (src) {
|
|
3128
3175
|
el.promise = new Promise((resolve) => {
|
|
3129
3176
|
el.addEventListener('loadeddata', () => {
|
|
3130
|
-
el.
|
|
3177
|
+
delete el.promise;
|
|
3178
|
+
delete el.then;
|
|
3179
|
+
if (el._usedAwait) {
|
|
3180
|
+
el = $.createEl('video');
|
|
3181
|
+
el.crossOrigin = 'anonymous';
|
|
3182
|
+
el.src = src;
|
|
3183
|
+
}
|
|
3184
|
+
initVideo(el);
|
|
3131
3185
|
resolve(el);
|
|
3132
3186
|
});
|
|
3133
3187
|
el.src = src;
|
|
3134
3188
|
});
|
|
3135
|
-
$.
|
|
3136
|
-
|
|
3137
|
-
|
|
3189
|
+
$._loaders.push(el.promise);
|
|
3190
|
+
el.then = (resolve, reject) => {
|
|
3191
|
+
el._usedAwait = true;
|
|
3192
|
+
return el.promise.then(resolve, reject);
|
|
3193
|
+
};
|
|
3138
3194
|
}
|
|
3139
3195
|
return el;
|
|
3140
3196
|
};
|
|
@@ -3149,18 +3205,22 @@ Q5.modules.dom = ($, q) => {
|
|
|
3149
3205
|
constraints.video.facingMode ??= 'user';
|
|
3150
3206
|
|
|
3151
3207
|
let vid = $.createVideo();
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
vid.
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3208
|
+
|
|
3209
|
+
function extendVideo(vid) {
|
|
3210
|
+
vid.playsinline = vid.autoplay = true;
|
|
3211
|
+
if (flipped) {
|
|
3212
|
+
vid.flipped = true;
|
|
3213
|
+
vid.style.transform = 'scale(-1, 1)';
|
|
3214
|
+
}
|
|
3215
|
+
vid.loadPixels = () => {
|
|
3216
|
+
let g = $.createGraphics(vid.videoWidth, vid.videoHeight, { renderer: 'c2d' });
|
|
3217
|
+
g.image(vid, 0, 0);
|
|
3218
|
+
g.loadPixels();
|
|
3219
|
+
vid.pixels = g.pixels;
|
|
3220
|
+
g.remove();
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3164
3224
|
vid.promise = (async () => {
|
|
3165
3225
|
let stream;
|
|
3166
3226
|
try {
|
|
@@ -3169,16 +3229,26 @@ Q5.modules.dom = ($, q) => {
|
|
|
3169
3229
|
throw e;
|
|
3170
3230
|
}
|
|
3171
3231
|
|
|
3232
|
+
delete vid.promise;
|
|
3233
|
+
delete vid.then;
|
|
3234
|
+
if (vid._usedAwait) {
|
|
3235
|
+
vid = $.createVideo();
|
|
3236
|
+
}
|
|
3237
|
+
extendVideo(vid);
|
|
3238
|
+
|
|
3172
3239
|
vid.srcObject = stream;
|
|
3173
3240
|
await new Promise((resolve) => vid.addEventListener('loadeddata', resolve));
|
|
3174
3241
|
|
|
3175
|
-
vid
|
|
3242
|
+
initVideo(vid);
|
|
3176
3243
|
if (cb) cb(vid);
|
|
3177
3244
|
return vid;
|
|
3178
3245
|
})();
|
|
3179
|
-
$.
|
|
3246
|
+
$._loaders.push(vid.promise);
|
|
3180
3247
|
|
|
3181
|
-
|
|
3248
|
+
vid.then = (resolve, reject) => {
|
|
3249
|
+
vid._usedAwait = true;
|
|
3250
|
+
return vid.promise.then(resolve, reject);
|
|
3251
|
+
};
|
|
3182
3252
|
return vid;
|
|
3183
3253
|
};
|
|
3184
3254
|
|
|
@@ -3258,12 +3328,12 @@ Q5.modules.fes = ($) => {
|
|
|
3258
3328
|
}
|
|
3259
3329
|
}
|
|
3260
3330
|
|
|
3261
|
-
if (Q5.online != false && typeof navigator != undefined && navigator.onLine) {
|
|
3331
|
+
if ($._isGlobal && Q5.online != false && typeof navigator != undefined && navigator.onLine) {
|
|
3262
3332
|
async function checkLatestVersion() {
|
|
3263
3333
|
try {
|
|
3264
|
-
let
|
|
3265
|
-
if (!
|
|
3266
|
-
let data = await
|
|
3334
|
+
let res = await fetch('https://data.jsdelivr.com/v1/package/npm/q5');
|
|
3335
|
+
if (!res.ok) return;
|
|
3336
|
+
let data = await res.json();
|
|
3267
3337
|
let l = data.tags.latest;
|
|
3268
3338
|
l = l.slice(0, l.lastIndexOf('.'));
|
|
3269
3339
|
if (l != Q5.version) {
|
|
@@ -4352,6 +4422,11 @@ Q5.modules.sound = ($, q) => {
|
|
|
4352
4422
|
sounds.push(s);
|
|
4353
4423
|
|
|
4354
4424
|
s.promise = (async () => {
|
|
4425
|
+
if (s._usedAwait) {
|
|
4426
|
+
sounds.splice(sounds.indexOf(s), 1);
|
|
4427
|
+
s = new Q5.Sound();
|
|
4428
|
+
sounds.push(s);
|
|
4429
|
+
}
|
|
4355
4430
|
let err;
|
|
4356
4431
|
try {
|
|
4357
4432
|
await s.load(url);
|
|
@@ -4359,13 +4434,17 @@ Q5.modules.sound = ($, q) => {
|
|
|
4359
4434
|
err = e;
|
|
4360
4435
|
}
|
|
4361
4436
|
delete s.promise;
|
|
4437
|
+
delete s.then;
|
|
4362
4438
|
if (err) throw err;
|
|
4363
4439
|
if (cb) cb(s);
|
|
4364
4440
|
return s;
|
|
4365
4441
|
})();
|
|
4366
|
-
$.
|
|
4442
|
+
$._loaders.push(s.promise);
|
|
4367
4443
|
|
|
4368
|
-
|
|
4444
|
+
s.then = (resolve, reject) => {
|
|
4445
|
+
s._usedAwait = true;
|
|
4446
|
+
return s.promise.then(resolve, reject);
|
|
4447
|
+
};
|
|
4369
4448
|
return s;
|
|
4370
4449
|
};
|
|
4371
4450
|
|
|
@@ -4374,19 +4453,30 @@ Q5.modules.sound = ($, q) => {
|
|
|
4374
4453
|
a._isAudio = true;
|
|
4375
4454
|
a.crossOrigin = 'Anonymous';
|
|
4376
4455
|
a.promise = new Promise((resolve, reject) => {
|
|
4377
|
-
|
|
4456
|
+
function loaded() {
|
|
4378
4457
|
if (!a.loaded) {
|
|
4458
|
+
delete a.promise;
|
|
4459
|
+
delete a.then;
|
|
4460
|
+
if (a._usedAwait) {
|
|
4461
|
+
a = new Audio(url);
|
|
4462
|
+
a._isAudio = true;
|
|
4463
|
+
a.crossOrigin = 'Anonymous';
|
|
4464
|
+
}
|
|
4379
4465
|
a.loaded = true;
|
|
4380
4466
|
if (cb) cb(a);
|
|
4381
4467
|
resolve(a);
|
|
4382
4468
|
}
|
|
4383
|
-
}
|
|
4384
|
-
a.addEventListener('
|
|
4469
|
+
}
|
|
4470
|
+
a.addEventListener('canplay', loaded);
|
|
4471
|
+
a.addEventListener('suspend', loaded);
|
|
4385
4472
|
a.addEventListener('error', reject);
|
|
4386
4473
|
});
|
|
4387
|
-
$.
|
|
4474
|
+
$._loaders.push(a.promise);
|
|
4388
4475
|
|
|
4389
|
-
|
|
4476
|
+
a.then = (resolve, reject) => {
|
|
4477
|
+
a._usedAwait = true;
|
|
4478
|
+
return a.promise.then(resolve, reject);
|
|
4479
|
+
};
|
|
4390
4480
|
return a;
|
|
4391
4481
|
};
|
|
4392
4482
|
|
|
@@ -4571,27 +4661,30 @@ Q5.Sound = class {
|
|
|
4571
4661
|
Q5.modules.util = ($, q) => {
|
|
4572
4662
|
$._loadFile = (url, cb, type) => {
|
|
4573
4663
|
let ret = {};
|
|
4574
|
-
ret.promise =
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
$.
|
|
4594
|
-
|
|
4664
|
+
ret.promise = fetch(url)
|
|
4665
|
+
.then((res) => {
|
|
4666
|
+
if (!res.ok) {
|
|
4667
|
+
reject('error loading file');
|
|
4668
|
+
return null;
|
|
4669
|
+
}
|
|
4670
|
+
return type == 'json' ? res.json() : res.text();
|
|
4671
|
+
})
|
|
4672
|
+
.then((f) => {
|
|
4673
|
+
if (type == 'csv') f = Q5.CSV.parse(f);
|
|
4674
|
+
|
|
4675
|
+
if (typeof f == 'string') ret.text = f;
|
|
4676
|
+
else Object.assign(ret, f);
|
|
4677
|
+
|
|
4678
|
+
delete ret.promise;
|
|
4679
|
+
delete ret.then;
|
|
4680
|
+
if (cb) cb(f);
|
|
4681
|
+
return f;
|
|
4682
|
+
});
|
|
4683
|
+
$._loaders.push(ret.promise);
|
|
4684
|
+
|
|
4685
|
+
ret.then = (resolve, reject) => {
|
|
4686
|
+
return ret.promise.then(resolve, reject);
|
|
4687
|
+
};
|
|
4595
4688
|
return ret;
|
|
4596
4689
|
};
|
|
4597
4690
|
|
|
@@ -4607,11 +4700,15 @@ Q5.modules.util = ($, q) => {
|
|
|
4607
4700
|
let xml = new DOMParser().parseFromString(text, 'application/xml');
|
|
4608
4701
|
ret.DOM = xml;
|
|
4609
4702
|
delete ret.promise;
|
|
4703
|
+
delete ret.then;
|
|
4610
4704
|
if (cb) cb(xml);
|
|
4611
4705
|
return xml;
|
|
4612
4706
|
});
|
|
4613
|
-
$.
|
|
4614
|
-
|
|
4707
|
+
$._loaders.push(ret.promise);
|
|
4708
|
+
|
|
4709
|
+
ret.then = (resolve, reject) => {
|
|
4710
|
+
return ret.promise.then(resolve, reject);
|
|
4711
|
+
};
|
|
4615
4712
|
return ret;
|
|
4616
4713
|
};
|
|
4617
4714
|
|
|
@@ -4623,7 +4720,7 @@ Q5.modules.util = ($, q) => {
|
|
|
4623
4720
|
$.load = function (...urls) {
|
|
4624
4721
|
if (Array.isArray(urls[0])) urls = urls[0];
|
|
4625
4722
|
|
|
4626
|
-
let
|
|
4723
|
+
let thenables = [];
|
|
4627
4724
|
|
|
4628
4725
|
for (let url of urls) {
|
|
4629
4726
|
let ext = url.split('.').pop().toLowerCase();
|
|
@@ -4644,11 +4741,11 @@ Q5.modules.util = ($, q) => {
|
|
|
4644
4741
|
} else {
|
|
4645
4742
|
obj = $.loadText(url);
|
|
4646
4743
|
}
|
|
4647
|
-
|
|
4744
|
+
thenables.push(obj);
|
|
4648
4745
|
}
|
|
4649
4746
|
|
|
4650
|
-
if (urls.length == 1) return
|
|
4651
|
-
return Promise.all(
|
|
4747
|
+
if (urls.length == 1) return thenables[0];
|
|
4748
|
+
return Promise.all(thenables);
|
|
4652
4749
|
};
|
|
4653
4750
|
|
|
4654
4751
|
async function saveFile(data, name, ext) {
|
|
@@ -4751,8 +4848,13 @@ Q5.Vector = class {
|
|
|
4751
4848
|
this.z = z || 0;
|
|
4752
4849
|
this._isVector = true;
|
|
4753
4850
|
this._$ = $ || window;
|
|
4754
|
-
|
|
4755
|
-
|
|
4851
|
+
|
|
4852
|
+
// managed by the user to avoid redundant calculations
|
|
4853
|
+
this._useCache = false;
|
|
4854
|
+
this._mag = 0;
|
|
4855
|
+
this._magCached = false;
|
|
4856
|
+
this._direction = 0;
|
|
4857
|
+
this._directionCached = false;
|
|
4756
4858
|
}
|
|
4757
4859
|
|
|
4758
4860
|
set(x, y, z) {
|
|
@@ -4774,11 +4876,6 @@ Q5.Vector = class {
|
|
|
4774
4876
|
return { x: x, y: x, z: x };
|
|
4775
4877
|
}
|
|
4776
4878
|
|
|
4777
|
-
_calcNorm() {
|
|
4778
|
-
this._cnsq = this.x * this.x + this.y * this.y + this.z * this.z;
|
|
4779
|
-
this._cn = Math.sqrt(this._cnsq);
|
|
4780
|
-
}
|
|
4781
|
-
|
|
4782
4879
|
add() {
|
|
4783
4880
|
let u = this._arg2v(...arguments);
|
|
4784
4881
|
this.x += u.x;
|
|
@@ -4822,14 +4919,71 @@ Q5.Vector = class {
|
|
|
4822
4919
|
return this;
|
|
4823
4920
|
}
|
|
4824
4921
|
|
|
4922
|
+
_calcMag() {
|
|
4923
|
+
const x = this.x,
|
|
4924
|
+
y = this.y,
|
|
4925
|
+
z = this.z;
|
|
4926
|
+
this._mag = Math.sqrt(x * x + y * y + z * z);
|
|
4927
|
+
this._magCached = this._useCache;
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4825
4930
|
mag() {
|
|
4826
|
-
this.
|
|
4827
|
-
return this.
|
|
4931
|
+
if (!this._magCached) this._calcMag();
|
|
4932
|
+
return this._mag;
|
|
4828
4933
|
}
|
|
4829
4934
|
|
|
4830
4935
|
magSq() {
|
|
4831
|
-
this.
|
|
4832
|
-
|
|
4936
|
+
if (this._magCached) return this._mag * this._mag;
|
|
4937
|
+
const x = this.x,
|
|
4938
|
+
y = this.y,
|
|
4939
|
+
z = this.z;
|
|
4940
|
+
return x * x + y * y + z * z;
|
|
4941
|
+
}
|
|
4942
|
+
setMag(m) {
|
|
4943
|
+
if (!this._magCached) this._calcMag();
|
|
4944
|
+
let n = this._mag;
|
|
4945
|
+
if (n == 0) {
|
|
4946
|
+
const dir = this.direction();
|
|
4947
|
+
this.x = m * this._$.cos(dir);
|
|
4948
|
+
this.y = m * this._$.sin(dir);
|
|
4949
|
+
} else {
|
|
4950
|
+
let t = m / n;
|
|
4951
|
+
this.x *= t;
|
|
4952
|
+
this.y *= t;
|
|
4953
|
+
this.z *= t;
|
|
4954
|
+
}
|
|
4955
|
+
this._mag = m;
|
|
4956
|
+
this._magCached = this._useCache;
|
|
4957
|
+
return this;
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
direction() {
|
|
4961
|
+
if (!this._directionCached) {
|
|
4962
|
+
const x = this.x,
|
|
4963
|
+
y = this.y;
|
|
4964
|
+
if (x || y) this._direction = this._$.atan2(this.y, this.x);
|
|
4965
|
+
this._directionCached = this._useCache;
|
|
4966
|
+
}
|
|
4967
|
+
return this._direction;
|
|
4968
|
+
}
|
|
4969
|
+
|
|
4970
|
+
setDirection(ang) {
|
|
4971
|
+
let mag = this.mag();
|
|
4972
|
+
if (mag) {
|
|
4973
|
+
this.x = mag * this._$.cos(ang);
|
|
4974
|
+
this.y = mag * this._$.sin(ang);
|
|
4975
|
+
}
|
|
4976
|
+
this._direction = ang;
|
|
4977
|
+
this._directionCached = this._useCache;
|
|
4978
|
+
return this;
|
|
4979
|
+
}
|
|
4980
|
+
|
|
4981
|
+
heading() {
|
|
4982
|
+
return this.direction();
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
setHeading(ang) {
|
|
4986
|
+
return this.setDirection(ang);
|
|
4833
4987
|
}
|
|
4834
4988
|
|
|
4835
4989
|
dot() {
|
|
@@ -4857,55 +5011,32 @@ Q5.Vector = class {
|
|
|
4857
5011
|
}
|
|
4858
5012
|
|
|
4859
5013
|
normalize() {
|
|
4860
|
-
this.
|
|
4861
|
-
let n = this.
|
|
5014
|
+
if (!this._magCached) this._calcMag();
|
|
5015
|
+
let n = this._mag;
|
|
4862
5016
|
if (n != 0) {
|
|
4863
5017
|
this.x /= n;
|
|
4864
5018
|
this.y /= n;
|
|
4865
5019
|
this.z /= n;
|
|
4866
5020
|
}
|
|
4867
|
-
this.
|
|
4868
|
-
this.
|
|
5021
|
+
this._mag = 1;
|
|
5022
|
+
this._magCached = this._useCache;
|
|
4869
5023
|
return this;
|
|
4870
5024
|
}
|
|
4871
5025
|
|
|
4872
5026
|
limit(m) {
|
|
4873
|
-
this.
|
|
4874
|
-
let n = this.
|
|
5027
|
+
if (!this._magCached) this._calcMag();
|
|
5028
|
+
let n = this._mag;
|
|
4875
5029
|
if (n > m) {
|
|
4876
5030
|
let t = m / n;
|
|
4877
5031
|
this.x *= t;
|
|
4878
5032
|
this.y *= t;
|
|
4879
5033
|
this.z *= t;
|
|
4880
|
-
this.
|
|
4881
|
-
this.
|
|
5034
|
+
this._mag = m;
|
|
5035
|
+
this._magCached = this._useCache;
|
|
4882
5036
|
}
|
|
4883
5037
|
return this;
|
|
4884
5038
|
}
|
|
4885
5039
|
|
|
4886
|
-
setMag(m) {
|
|
4887
|
-
this._calcNorm();
|
|
4888
|
-
let n = this._cn;
|
|
4889
|
-
let t = m / n;
|
|
4890
|
-
this.x *= t;
|
|
4891
|
-
this.y *= t;
|
|
4892
|
-
this.z *= t;
|
|
4893
|
-
this._cn = m;
|
|
4894
|
-
this._cnsq = m * m;
|
|
4895
|
-
return this;
|
|
4896
|
-
}
|
|
4897
|
-
|
|
4898
|
-
heading() {
|
|
4899
|
-
return this._$.atan2(this.y, this.x);
|
|
4900
|
-
}
|
|
4901
|
-
|
|
4902
|
-
setHeading(ang) {
|
|
4903
|
-
let mag = this.mag();
|
|
4904
|
-
this.x = mag * this._$.cos(ang);
|
|
4905
|
-
this.y = mag * this._$.sin(ang);
|
|
4906
|
-
return this;
|
|
4907
|
-
}
|
|
4908
|
-
|
|
4909
5040
|
rotate(ang) {
|
|
4910
5041
|
let costh = this._$.cos(ang);
|
|
4911
5042
|
let sinth = this._$.sin(ang);
|
|
@@ -4989,8 +5120,8 @@ Q5.Vector = class {
|
|
|
4989
5120
|
|
|
4990
5121
|
fromAngle(th, l) {
|
|
4991
5122
|
if (l === undefined) l = 1;
|
|
4992
|
-
this.
|
|
4993
|
-
this.
|
|
5123
|
+
this._mag = l;
|
|
5124
|
+
this._magCached = this._useCache;
|
|
4994
5125
|
this.x = l * this._$.cos(th);
|
|
4995
5126
|
this.y = l * this._$.sin(th);
|
|
4996
5127
|
this.z = 0;
|
|
@@ -4999,8 +5130,8 @@ Q5.Vector = class {
|
|
|
4999
5130
|
|
|
5000
5131
|
fromAngles(th, ph, l) {
|
|
5001
5132
|
if (l === undefined) l = 1;
|
|
5002
|
-
this.
|
|
5003
|
-
this.
|
|
5133
|
+
this._mag = l;
|
|
5134
|
+
this._magCached = this._useCache;
|
|
5004
5135
|
const cosph = this._$.cos(ph);
|
|
5005
5136
|
const sinph = this._$.sin(ph);
|
|
5006
5137
|
const costh = this._$.cos(th);
|
|
@@ -5012,12 +5143,14 @@ Q5.Vector = class {
|
|
|
5012
5143
|
}
|
|
5013
5144
|
|
|
5014
5145
|
random2D() {
|
|
5015
|
-
this.
|
|
5146
|
+
this._mag = 1;
|
|
5147
|
+
this._magCached = this._useCache;
|
|
5016
5148
|
return this.fromAngle(Math.random() * Math.PI * 2);
|
|
5017
5149
|
}
|
|
5018
5150
|
|
|
5019
5151
|
random3D() {
|
|
5020
|
-
this.
|
|
5152
|
+
this._mag = 1;
|
|
5153
|
+
this._magCached = this._useCache;
|
|
5021
5154
|
return this.fromAngles(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2);
|
|
5022
5155
|
}
|
|
5023
5156
|
|
|
@@ -5035,7 +5168,7 @@ Q5.Vector.equals = (v, u, epsilon) => v.equals(u, epsilon);
|
|
|
5035
5168
|
Q5.Vector.lerp = (v, u, amt) => v.copy().lerp(u, amt);
|
|
5036
5169
|
Q5.Vector.slerp = (v, u, amt) => v.copy().slerp(u, amt);
|
|
5037
5170
|
Q5.Vector.limit = (v, m) => v.copy().limit(m);
|
|
5038
|
-
Q5.Vector.
|
|
5171
|
+
Q5.Vector.direction = (v) => this._$.atan2(v.y, v.x);
|
|
5039
5172
|
Q5.Vector.magSq = (v) => v.x * v.x + v.y * v.y + v.z * v.z;
|
|
5040
5173
|
Q5.Vector.mag = (v) => Math.sqrt(Q5.Vector.magSq(v));
|
|
5041
5174
|
Q5.Vector.mult = (v, u) => v.copy().mult(u);
|
|
@@ -5532,8 +5665,18 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5532
5665
|
if (args.length == 1) m = args[0];
|
|
5533
5666
|
else m = args;
|
|
5534
5667
|
|
|
5535
|
-
if (m.length
|
|
5536
|
-
|
|
5668
|
+
if (m.length <= 6) {
|
|
5669
|
+
const a = m[0],
|
|
5670
|
+
b = m[1],
|
|
5671
|
+
c = m[2],
|
|
5672
|
+
d = m[3],
|
|
5673
|
+
e = m[4] || 0,
|
|
5674
|
+
f = m[5] || 0;
|
|
5675
|
+
// Convert Canvas2D [a,b,c,d,e,f] (column-major 3x3: [a,b,0, c,d,0, e,f,1])
|
|
5676
|
+
m = [a, b, 0, c, d, 0, e, f, 1];
|
|
5677
|
+
}
|
|
5678
|
+
if (m.length <= 9) {
|
|
5679
|
+
// convert 3x3 matrix to 4x4 layout used internally
|
|
5537
5680
|
m = [m[0], m[1], 0, m[2], m[3], m[4], 0, m[5], 0, 0, 1, 0, m[6], m[7], 0, m[8]];
|
|
5538
5681
|
} else if (m.length != 16) {
|
|
5539
5682
|
throw new Error('Matrix must be a 3x3 or 4x4 array.');
|
|
@@ -6674,10 +6817,10 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6674
6817
|
hw = w;
|
|
6675
6818
|
hh = h;
|
|
6676
6819
|
} else if (_rectMode == 'corners') {
|
|
6677
|
-
hw = (
|
|
6678
|
-
hh = (
|
|
6679
|
-
x
|
|
6680
|
-
y
|
|
6820
|
+
hw = Math.abs((w - x) / 2);
|
|
6821
|
+
hh = Math.abs((h - y) / 2);
|
|
6822
|
+
x = (x + w) / 2;
|
|
6823
|
+
y = (y + h) / 2;
|
|
6681
6824
|
}
|
|
6682
6825
|
}
|
|
6683
6826
|
rectModeCache[0] = x;
|
|
@@ -6696,7 +6839,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6696
6839
|
addRect(x, y, hw, hh, rr, doStroke ? sw : 0, doFill ? fillIdx : 0);
|
|
6697
6840
|
};
|
|
6698
6841
|
|
|
6699
|
-
$.square = (x, y, s) => $.rect(x, y, s, s);
|
|
6842
|
+
$.square = (x, y, s, rr) => $.rect(x, y, s, s, rr);
|
|
6700
6843
|
|
|
6701
6844
|
function addCapsule(x1, y1, x2, y2, r, strokeW, fillCapsule) {
|
|
6702
6845
|
let dx = x2 - x1,
|
|
@@ -6982,8 +7125,8 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6982
7125
|
} else if (_ellipseMode == 'corners') {
|
|
6983
7126
|
x = (x + w) / 2;
|
|
6984
7127
|
y = (y + h) / 2;
|
|
6985
|
-
a =
|
|
6986
|
-
b =
|
|
7128
|
+
a = w - x;
|
|
7129
|
+
b = h - y;
|
|
6987
7130
|
}
|
|
6988
7131
|
ellipseModeCache[0] = x;
|
|
6989
7132
|
ellipseModeCache[1] = y;
|
|
@@ -7338,9 +7481,9 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
7338
7481
|
};
|
|
7339
7482
|
|
|
7340
7483
|
$.loadImage = (src, cb) => {
|
|
7341
|
-
let g = $._g.loadImage(src, () => {
|
|
7342
|
-
$._makeDrawable(
|
|
7343
|
-
if (cb) cb(
|
|
7484
|
+
let g = $._g.loadImage(src, (img) => {
|
|
7485
|
+
$._makeDrawable(img);
|
|
7486
|
+
if (cb) cb(img);
|
|
7344
7487
|
});
|
|
7345
7488
|
return g;
|
|
7346
7489
|
};
|
|
@@ -7707,18 +7850,27 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7707
7850
|
|
|
7708
7851
|
let fontsArr = [];
|
|
7709
7852
|
let fonts = {};
|
|
7853
|
+
let fontSet;
|
|
7710
7854
|
|
|
7711
|
-
async function createFont(
|
|
7712
|
-
let
|
|
7713
|
-
if (res.status == 404) return '';
|
|
7714
|
-
|
|
7715
|
-
let atlas = await res.json();
|
|
7855
|
+
async function createFont(url, fontName, cb) {
|
|
7856
|
+
let baseUrl = url.substring(0, url.lastIndexOf('-'));
|
|
7716
7857
|
|
|
7717
|
-
|
|
7718
|
-
let
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7858
|
+
// load atlas and image in parallel
|
|
7859
|
+
let atlas, img;
|
|
7860
|
+
try {
|
|
7861
|
+
[atlas, img] = await Promise.all([
|
|
7862
|
+
fetch(url).then((res) => {
|
|
7863
|
+
if (res.status == 404) throw new Error('404');
|
|
7864
|
+
return res.json();
|
|
7865
|
+
}),
|
|
7866
|
+
fetch(baseUrl + '.png')
|
|
7867
|
+
.then((res) => res.blob())
|
|
7868
|
+
.then((blob) => createImageBitmap(blob))
|
|
7869
|
+
]);
|
|
7870
|
+
} catch (error) {
|
|
7871
|
+
console.error('Error loading font:', error);
|
|
7872
|
+
return '';
|
|
7873
|
+
}
|
|
7722
7874
|
|
|
7723
7875
|
// convert image to texture
|
|
7724
7876
|
let imgSize = [img.width, img.height, 1];
|
|
@@ -7786,44 +7938,54 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7786
7938
|
}
|
|
7787
7939
|
}
|
|
7788
7940
|
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7941
|
+
let _font = new MsdfFont(fontBindGroup, atlas.common.lineHeight, chars, kernings);
|
|
7942
|
+
_font.index = fontsArr.length;
|
|
7943
|
+
fontsArr.push(_font);
|
|
7944
|
+
fonts[fontName] = _font;
|
|
7945
|
+
$._font = _font;
|
|
7794
7946
|
|
|
7795
7947
|
if (cb) cb(fontName);
|
|
7948
|
+
return { family: fontName };
|
|
7796
7949
|
}
|
|
7797
7950
|
|
|
7798
|
-
$.loadFont = (url, cb) => {
|
|
7951
|
+
$.loadFont = (url = 'sans-serif', cb) => {
|
|
7952
|
+
fontSet = true;
|
|
7799
7953
|
if (url.startsWith('https://fonts.googleapis.com/css')) {
|
|
7800
7954
|
return $._g.loadFont(url, cb);
|
|
7801
7955
|
}
|
|
7802
7956
|
|
|
7803
7957
|
let ext = url.slice(url.lastIndexOf('.') + 1);
|
|
7804
|
-
|
|
7958
|
+
|
|
7959
|
+
// if not a url, assume it's one of q5's MSDF fonts
|
|
7960
|
+
if (url == ext) {
|
|
7961
|
+
let fontName = url;
|
|
7962
|
+
fonts[fontName] = null;
|
|
7963
|
+
url = `https://q5js.org/fonts/${fontName}-msdf.json`;
|
|
7964
|
+
if (Q5.online == false || !navigator.onLine) {
|
|
7965
|
+
url = `/node_modules/q5/builtinFonts/${fontName}-msdf.json`;
|
|
7966
|
+
}
|
|
7967
|
+
ext = 'json';
|
|
7968
|
+
}
|
|
7969
|
+
|
|
7805
7970
|
if (ext != 'json') return $._g.loadFont(url, cb);
|
|
7971
|
+
|
|
7806
7972
|
let fontName = url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('-'));
|
|
7807
7973
|
let f = { family: fontName };
|
|
7808
7974
|
f.promise = createFont(url, fontName, () => {
|
|
7809
7975
|
delete f.promise;
|
|
7976
|
+
delete f.then;
|
|
7977
|
+
if (f._usedAwait) f = { family: fontName };
|
|
7810
7978
|
if (cb) cb(f);
|
|
7811
7979
|
});
|
|
7812
|
-
$.
|
|
7980
|
+
$._loaders.push(f.promise);
|
|
7813
7981
|
|
|
7814
|
-
|
|
7982
|
+
f.then = (resolve, reject) => {
|
|
7983
|
+
f._usedAwait = true;
|
|
7984
|
+
return f.promise.then(resolve, reject);
|
|
7985
|
+
};
|
|
7815
7986
|
return f;
|
|
7816
7987
|
};
|
|
7817
7988
|
|
|
7818
|
-
$._loadDefaultFont = (fontName, cb) => {
|
|
7819
|
-
fonts[fontName] = null;
|
|
7820
|
-
let url = `https://q5js.org/fonts/${fontName}-msdf.json`;
|
|
7821
|
-
if (Q5.online == false || !navigator.onLine) {
|
|
7822
|
-
url = `/node_modules/q5/builtinFonts/${fontName}-msdf.json`;
|
|
7823
|
-
}
|
|
7824
|
-
return $.loadFont(url, cb);
|
|
7825
|
-
};
|
|
7826
|
-
|
|
7827
7989
|
let _textSize = 18,
|
|
7828
7990
|
_textAlign = 'left',
|
|
7829
7991
|
_textBaseline = 'alphabetic',
|
|
@@ -7832,12 +7994,16 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7832
7994
|
leadDiff = 4.5,
|
|
7833
7995
|
leadPercent = 1.25;
|
|
7834
7996
|
|
|
7997
|
+
let categories = ['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui'];
|
|
7998
|
+
|
|
7835
7999
|
$.textFont = (fontName) => {
|
|
7836
8000
|
if (!fontName) return $._font;
|
|
8001
|
+
fontSet = true;
|
|
7837
8002
|
if (typeof fontName != 'string') fontName = fontName.family;
|
|
7838
8003
|
let font = fonts[fontName];
|
|
7839
8004
|
if (font) $._font = font;
|
|
7840
|
-
|
|
8005
|
+
// if it's a font category or not a WebGPU font, set the Canvas2D font
|
|
8006
|
+
else if (categories[fontName] || font === undefined) $._g.textFont(fontName);
|
|
7841
8007
|
};
|
|
7842
8008
|
|
|
7843
8009
|
$.textSize = (size) => {
|
|
@@ -7895,8 +8061,8 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7895
8061
|
let lineWidthsCache = new Array(100);
|
|
7896
8062
|
|
|
7897
8063
|
// Reusable buffers for text data to avoid creating new arrays
|
|
7898
|
-
let charDataBuffer = new Float32Array(
|
|
7899
|
-
let textDataBuffer = new Float32Array(
|
|
8064
|
+
let charDataBuffer = new Float32Array(Q5.MAX_CHARS * 4); // reusable buffer for char data
|
|
8065
|
+
let textDataBuffer = new Float32Array(Q5.MAX_TEXTS * 8); // reusable buffer for text metadata
|
|
7900
8066
|
|
|
7901
8067
|
let measureText = (font, text, charCallback) => {
|
|
7902
8068
|
let maxWidth = 0,
|
|
@@ -7948,18 +8114,21 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7948
8114
|
};
|
|
7949
8115
|
|
|
7950
8116
|
$.text = (str, x, y, w, h) => {
|
|
7951
|
-
if (!$._font) {
|
|
7952
|
-
// if the default font hasn't been loaded yet, try to load it
|
|
7953
|
-
if ($._font !== null) $.textFont('sans-serif');
|
|
7954
|
-
if (_textSize >= 1) return $.textImage(str, x, y, w, h);
|
|
7955
|
-
}
|
|
7956
|
-
|
|
7957
8117
|
if (_textSize < 1) return;
|
|
7958
8118
|
|
|
7959
8119
|
let type = typeof str;
|
|
7960
8120
|
if (type != 'string') {
|
|
7961
8121
|
if (type == 'object') str = str.toString();
|
|
7962
8122
|
else str = str + '';
|
|
8123
|
+
} else if (!str.length) return;
|
|
8124
|
+
|
|
8125
|
+
// if not using an MSDF font
|
|
8126
|
+
if (!$._font) {
|
|
8127
|
+
// if no font is set, lazy load the default MSDF font
|
|
8128
|
+
if (!fontSet) $.loadFont();
|
|
8129
|
+
// use Canvas2D text rendering
|
|
8130
|
+
let img = $.createTextImage(str, w, h);
|
|
8131
|
+
return $.textImage(img, x, y);
|
|
7963
8132
|
}
|
|
7964
8133
|
|
|
7965
8134
|
if (str.length > w) {
|
|
@@ -8196,6 +8365,8 @@ Q5.BLUR = 8;
|
|
|
8196
8365
|
Q5.MAX_TRANSFORMS = 1e7;
|
|
8197
8366
|
Q5.MAX_RECTS = 200200;
|
|
8198
8367
|
Q5.MAX_ELLIPSES = 200200;
|
|
8368
|
+
Q5.MAX_CHARS = 100000;
|
|
8369
|
+
Q5.MAX_TEXTS = 10000;
|
|
8199
8370
|
|
|
8200
8371
|
Q5.initWebGPU = async () => {
|
|
8201
8372
|
if (!navigator.gpu) {
|