q5 2.24.4 → 2.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/deno.json +1 -1
- package/package.json +1 -1
- package/q5.d.ts +193 -37
- package/q5.js +172 -105
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.25
|
|
4
4
|
* @author quinton-ashley, Tezumie, and LingDong-
|
|
5
5
|
* @license LGPL-3.0
|
|
6
6
|
* @class Q5
|
|
@@ -99,7 +99,6 @@ function Q5(scope, parent, renderer) {
|
|
|
99
99
|
let nextTS = ts + $._targetFrameDuration;
|
|
100
100
|
let frameDelay = nextTS - performance.now();
|
|
101
101
|
while (frameDelay < 0) frameDelay += $._targetFrameDuration;
|
|
102
|
-
log(frameDelay);
|
|
103
102
|
looper = setTimeout(() => $._draw(nextTS), frameDelay);
|
|
104
103
|
}
|
|
105
104
|
} else if ($.frameCount && !$._redraw) return;
|
|
@@ -223,11 +222,23 @@ function Q5(scope, parent, renderer) {
|
|
|
223
222
|
if (n[0] != '_' && typeof $[n] == 'function') $[n] = fn.bind($);
|
|
224
223
|
}
|
|
225
224
|
|
|
225
|
+
for (let [n, fn] of Object.entries(Q5.preloadMethods)) {
|
|
226
|
+
$[n] = function () {
|
|
227
|
+
$._incrementPreload();
|
|
228
|
+
return fn.apply($, arguments);
|
|
229
|
+
// fn is responsible for calling $._decrementPreload
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
226
233
|
if (scope == 'global') {
|
|
227
234
|
let props = Object.getOwnPropertyNames($);
|
|
228
235
|
for (let p of props) {
|
|
229
236
|
if (p[0] != '_') globalScope[p] = $[p];
|
|
230
237
|
}
|
|
238
|
+
// to support p5.sound
|
|
239
|
+
for (let p of ['_incrementPreload', '_decrementPreload']) {
|
|
240
|
+
globalScope[p] = $[p];
|
|
241
|
+
}
|
|
231
242
|
}
|
|
232
243
|
|
|
233
244
|
if (typeof scope == 'function') scope($);
|
|
@@ -335,7 +346,9 @@ Q5.methods = {
|
|
|
335
346
|
remove: []
|
|
336
347
|
};
|
|
337
348
|
Q5.prototype.registerMethod = (m, fn) => Q5.methods[m].push(fn);
|
|
338
|
-
|
|
349
|
+
|
|
350
|
+
Q5.preloadMethods = {};
|
|
351
|
+
Q5.prototype.registerPreloadMethod = (n, fn) => (Q5.preloadMethods[n] = fn[n]);
|
|
339
352
|
|
|
340
353
|
if (Q5._server) global.p5 ??= global.Q5 = Q5;
|
|
341
354
|
|
|
@@ -349,7 +362,7 @@ function createCanvas(w, h, opt) {
|
|
|
349
362
|
}
|
|
350
363
|
}
|
|
351
364
|
|
|
352
|
-
Q5.version = Q5.VERSION = '2.
|
|
365
|
+
Q5.version = Q5.VERSION = '2.25';
|
|
353
366
|
|
|
354
367
|
if (typeof document == 'object') {
|
|
355
368
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -943,6 +956,9 @@ Q5.renderers.c2d.shapes = ($) => {
|
|
|
943
956
|
w /= 2;
|
|
944
957
|
h /= 2;
|
|
945
958
|
|
|
959
|
+
w = Math.abs(w);
|
|
960
|
+
h = Math.abs(h);
|
|
961
|
+
|
|
946
962
|
if (!$._doFill && mode == $.PIE_OPEN) mode = $.CHORD_OPEN;
|
|
947
963
|
|
|
948
964
|
$.ctx.beginPath();
|
|
@@ -984,7 +1000,7 @@ Q5.renderers.c2d.shapes = ($) => {
|
|
|
984
1000
|
|
|
985
1001
|
function ellipse(x, y, w, h) {
|
|
986
1002
|
$.ctx.beginPath();
|
|
987
|
-
$.ctx.ellipse(x, y, w / 2, h / 2, 0, 0, TAU);
|
|
1003
|
+
$.ctx.ellipse(x, y, Math.abs(w / 2), Math.abs(h / 2), 0, 0, TAU);
|
|
988
1004
|
ink();
|
|
989
1005
|
}
|
|
990
1006
|
|
|
@@ -1015,7 +1031,7 @@ Q5.renderers.c2d.shapes = ($) => {
|
|
|
1015
1031
|
d *= $._da;
|
|
1016
1032
|
}
|
|
1017
1033
|
$.ctx.beginPath();
|
|
1018
|
-
$.ctx.arc(x, y, d / 2, 0, TAU);
|
|
1034
|
+
$.ctx.arc(x, y, Math.abs(d / 2), 0, TAU);
|
|
1019
1035
|
ink();
|
|
1020
1036
|
} else $.ellipse(x, y, d, d);
|
|
1021
1037
|
};
|
|
@@ -1331,7 +1347,7 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1331
1347
|
let img = new window.Image();
|
|
1332
1348
|
img.crossOrigin = 'Anonymous';
|
|
1333
1349
|
|
|
1334
|
-
g.
|
|
1350
|
+
g.promise = new Promise((resolve, reject) => {
|
|
1335
1351
|
img.onload = () => {
|
|
1336
1352
|
img._pixelDensity = pd;
|
|
1337
1353
|
g.defaultWidth = img.width * $._defaultImageScale;
|
|
@@ -1342,16 +1358,16 @@ Q5.renderers.c2d.image = ($, q) => {
|
|
|
1342
1358
|
|
|
1343
1359
|
g.ctx.drawImage(img, 0, 0);
|
|
1344
1360
|
if (cb) cb(g);
|
|
1345
|
-
delete g.
|
|
1361
|
+
delete g.promise;
|
|
1346
1362
|
resolve(g);
|
|
1347
1363
|
};
|
|
1348
1364
|
img.onerror = reject;
|
|
1349
1365
|
});
|
|
1350
|
-
$._preloadPromises.push(g.
|
|
1366
|
+
$._preloadPromises.push(g.promise);
|
|
1351
1367
|
|
|
1352
1368
|
g.src = img.src = url;
|
|
1353
1369
|
|
|
1354
|
-
if (!$._usePreload) return g.
|
|
1370
|
+
if (!$._usePreload) return g.promise;
|
|
1355
1371
|
return g;
|
|
1356
1372
|
};
|
|
1357
1373
|
|
|
@@ -1788,33 +1804,32 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1788
1804
|
fontMod = false,
|
|
1789
1805
|
styleHash = 0,
|
|
1790
1806
|
styleHashes = [],
|
|
1791
|
-
useCache = false,
|
|
1792
1807
|
genTextImage = false,
|
|
1793
|
-
cacheSize = 0
|
|
1794
|
-
cacheMax = 12000;
|
|
1808
|
+
cacheSize = 0;
|
|
1795
1809
|
|
|
1796
1810
|
let cache = ($._textCache = {});
|
|
1811
|
+
$._textCacheMaxSize = 12000;
|
|
1797
1812
|
|
|
1798
1813
|
$.loadFont = (url, cb) => {
|
|
1799
1814
|
let name = url.split('/').pop().split('.')[0].replace(' ', '');
|
|
1800
1815
|
|
|
1801
1816
|
let f = new FontFace(name, `url(${url})`);
|
|
1802
1817
|
document.fonts.add(f);
|
|
1803
|
-
f.
|
|
1818
|
+
f.promise = (async () => {
|
|
1804
1819
|
let err;
|
|
1805
1820
|
try {
|
|
1806
1821
|
await f.load();
|
|
1807
1822
|
} catch (e) {
|
|
1808
1823
|
err = e;
|
|
1809
1824
|
}
|
|
1810
|
-
delete f.
|
|
1825
|
+
delete f.promise;
|
|
1811
1826
|
if (err) throw err;
|
|
1812
1827
|
if (cb) cb(f);
|
|
1813
1828
|
return f;
|
|
1814
1829
|
})();
|
|
1815
|
-
$._preloadPromises.push(f.
|
|
1830
|
+
$._preloadPromises.push(f.promise);
|
|
1816
1831
|
$.textFont(name);
|
|
1817
|
-
if (!$._usePreload) return f.
|
|
1832
|
+
if (!$._usePreload) return f.promise;
|
|
1818
1833
|
return f;
|
|
1819
1834
|
};
|
|
1820
1835
|
|
|
@@ -1900,12 +1915,6 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1900
1915
|
styleHash = hash >>> 0;
|
|
1901
1916
|
};
|
|
1902
1917
|
|
|
1903
|
-
$.textCache = (enable, maxSize) => {
|
|
1904
|
-
if (maxSize) cacheMax = maxSize;
|
|
1905
|
-
if (enable !== undefined) useCache = enable;
|
|
1906
|
-
return useCache;
|
|
1907
|
-
};
|
|
1908
|
-
|
|
1909
1918
|
$.createTextImage = (str, w, h) => {
|
|
1910
1919
|
genTextImage = true;
|
|
1911
1920
|
let img = $.text(str, 0, 0, w, h);
|
|
@@ -1927,7 +1936,7 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1927
1936
|
|
|
1928
1937
|
if (fontMod) updateFont();
|
|
1929
1938
|
|
|
1930
|
-
if (
|
|
1939
|
+
if (genTextImage) {
|
|
1931
1940
|
if (styleHash == -1) updateStyleHash();
|
|
1932
1941
|
|
|
1933
1942
|
img = cache[str];
|
|
@@ -1935,8 +1944,7 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1935
1944
|
|
|
1936
1945
|
if (img) {
|
|
1937
1946
|
if (img._fill == $._fill && img._stroke == $._stroke && img._strokeWeight == $._strokeWeight) {
|
|
1938
|
-
|
|
1939
|
-
return $.textImage(img, x, y);
|
|
1947
|
+
return img;
|
|
1940
1948
|
} else img.clear();
|
|
1941
1949
|
}
|
|
1942
1950
|
}
|
|
@@ -1964,12 +1972,14 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1964
1972
|
lines = wrapped;
|
|
1965
1973
|
}
|
|
1966
1974
|
|
|
1967
|
-
if (!
|
|
1975
|
+
if (!genTextImage) {
|
|
1968
1976
|
tX = x;
|
|
1969
1977
|
tY = y;
|
|
1978
|
+
if ($._textBaseline == 'middle') tY -= leading * (lines.length - 1) * 0.5;
|
|
1979
|
+
else if ($._textBaseline == 'bottom') tY -= leading * (lines.length - 1);
|
|
1970
1980
|
} else {
|
|
1971
1981
|
tX = 0;
|
|
1972
|
-
tY = leading
|
|
1982
|
+
tY = leading;
|
|
1973
1983
|
|
|
1974
1984
|
if (!img) {
|
|
1975
1985
|
let ogBaseline = $.ctx.textBaseline;
|
|
@@ -1981,15 +1991,24 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
1981
1991
|
|
|
1982
1992
|
$.ctx.textBaseline = ogBaseline;
|
|
1983
1993
|
|
|
1984
|
-
|
|
1994
|
+
let maxWidth = 0;
|
|
1995
|
+
for (let line of lines) {
|
|
1996
|
+
let lineWidth = ctx.measureText(line).width;
|
|
1997
|
+
if (lineWidth > maxWidth) maxWidth = lineWidth;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
let imgW = Math.ceil(maxWidth),
|
|
2001
|
+
imgH = Math.ceil(leading * lines.length + descent);
|
|
2002
|
+
|
|
2003
|
+
img = $.createImage.call($, imgW, imgH, {
|
|
1985
2004
|
pixelDensity: $._pixelDensity
|
|
1986
2005
|
});
|
|
1987
2006
|
|
|
1988
2007
|
img._ascent = ascent;
|
|
1989
2008
|
img._descent = descent;
|
|
1990
2009
|
img._top = descent + leadDiff;
|
|
1991
|
-
img._middle = img._top + ascent * 0.5;
|
|
1992
|
-
img._bottom = img._top + ascent;
|
|
2010
|
+
img._middle = img._top + ascent * 0.5 + leading * (lines.length - 1) * 0.5;
|
|
2011
|
+
img._bottom = img._top + ascent + leading * (lines.length - 1);
|
|
1993
2012
|
img._leading = leading;
|
|
1994
2013
|
} else {
|
|
1995
2014
|
img.modified = true;
|
|
@@ -2025,12 +2044,12 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2025
2044
|
|
|
2026
2045
|
if (!$._fillSet) ctx.fillStyle = ogFill;
|
|
2027
2046
|
|
|
2028
|
-
if (
|
|
2047
|
+
if (genTextImage) {
|
|
2029
2048
|
styleHashes.push(styleHash);
|
|
2030
2049
|
(cache[str] ??= {})[styleHash] = img;
|
|
2031
2050
|
|
|
2032
2051
|
cacheSize++;
|
|
2033
|
-
if (cacheSize >
|
|
2052
|
+
if (cacheSize > $._textCacheMaxSize) {
|
|
2034
2053
|
let half = Math.ceil(cacheSize / 2);
|
|
2035
2054
|
let hashes = styleHashes.splice(0, half);
|
|
2036
2055
|
for (let s in cache) {
|
|
@@ -2040,8 +2059,8 @@ Q5.renderers.c2d.text = ($, q) => {
|
|
|
2040
2059
|
cacheSize -= half;
|
|
2041
2060
|
}
|
|
2042
2061
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2062
|
+
$.save(img);
|
|
2063
|
+
return img;
|
|
2045
2064
|
}
|
|
2046
2065
|
};
|
|
2047
2066
|
|
|
@@ -2556,10 +2575,8 @@ Q5.modules.display = ($) => {
|
|
|
2556
2575
|
|
|
2557
2576
|
let c = $.canvas;
|
|
2558
2577
|
|
|
2559
|
-
$.CENTERED = 'centered';
|
|
2560
|
-
$.FULLSCREEN = 'fullscreen';
|
|
2561
2578
|
$.MAXED = 'maxed';
|
|
2562
|
-
|
|
2579
|
+
$.SMOOTH = 'smooth';
|
|
2563
2580
|
$.PIXELATED = 'pixelated';
|
|
2564
2581
|
|
|
2565
2582
|
if (Q5._instanceCount == 0 && !Q5._server) {
|
|
@@ -2583,15 +2600,13 @@ html, body {
|
|
|
2583
2600
|
-webkit-font-smoothing: none;
|
|
2584
2601
|
}
|
|
2585
2602
|
.q5-centered,
|
|
2586
|
-
.q5-maxed
|
|
2587
|
-
.q5-fullscreen {
|
|
2603
|
+
.q5-maxed {
|
|
2588
2604
|
display: flex;
|
|
2589
2605
|
align-items: center;
|
|
2590
2606
|
justify-content: center;
|
|
2591
2607
|
}
|
|
2592
2608
|
main.q5-centered,
|
|
2593
|
-
main.q5-maxed
|
|
2594
|
-
.q5-fullscreen {
|
|
2609
|
+
main.q5-maxed {
|
|
2595
2610
|
height: 100vh;
|
|
2596
2611
|
}
|
|
2597
2612
|
main {
|
|
@@ -2612,8 +2627,8 @@ main {
|
|
|
2612
2627
|
if ($.noSmooth) $.noSmooth();
|
|
2613
2628
|
if ($.textFont) $.textFont('monospace');
|
|
2614
2629
|
}
|
|
2615
|
-
if (c.displayMode == '
|
|
2616
|
-
p.classList.remove('q5-centered', 'q5-maxed'
|
|
2630
|
+
if (c.displayMode == 'normal') {
|
|
2631
|
+
p.classList.remove('q5-centered', 'q5-maxed');
|
|
2617
2632
|
s.width = c.w * c.displayScale + 'px';
|
|
2618
2633
|
s.height = c.h * c.displayScale + 'px';
|
|
2619
2634
|
} else {
|
|
@@ -2641,6 +2656,7 @@ main {
|
|
|
2641
2656
|
if (typeof displayScale == 'string') {
|
|
2642
2657
|
displayScale = parseFloat(displayScale.slice(1));
|
|
2643
2658
|
}
|
|
2659
|
+
if (displayMode == 'fullscreen') displayMode = 'maxed';
|
|
2644
2660
|
if (displayMode == 'center') displayMode = 'centered';
|
|
2645
2661
|
Object.assign(c, { displayMode, renderQuality, displayScale });
|
|
2646
2662
|
if ($.ctx) $.pushStyles();
|
|
@@ -2649,7 +2665,7 @@ main {
|
|
|
2649
2665
|
};
|
|
2650
2666
|
|
|
2651
2667
|
$.fullscreen = (v) => {
|
|
2652
|
-
if (v
|
|
2668
|
+
if (v == undefined) return document.fullscreenElement;
|
|
2653
2669
|
if (v) document.body.requestFullscreen();
|
|
2654
2670
|
else document.body.exitFullscreen();
|
|
2655
2671
|
};
|
|
@@ -2936,16 +2952,16 @@ Q5.modules.dom = ($, q) => {
|
|
|
2936
2952
|
};
|
|
2937
2953
|
|
|
2938
2954
|
if (src) {
|
|
2939
|
-
el.
|
|
2955
|
+
el.promise = new Promise((resolve) => {
|
|
2940
2956
|
el.addEventListener('loadeddata', () => {
|
|
2941
2957
|
el._load();
|
|
2942
2958
|
resolve(el);
|
|
2943
2959
|
});
|
|
2944
2960
|
el.src = src;
|
|
2945
2961
|
});
|
|
2946
|
-
$._preloadPromises.push(el.
|
|
2962
|
+
$._preloadPromises.push(el.promise);
|
|
2947
2963
|
|
|
2948
|
-
if (!$._usePreload) return el.
|
|
2964
|
+
if (!$._usePreload) return el.promise;
|
|
2949
2965
|
}
|
|
2950
2966
|
return el;
|
|
2951
2967
|
};
|
|
@@ -2972,7 +2988,7 @@ Q5.modules.dom = ($, q) => {
|
|
|
2972
2988
|
vid.pixels = g.pixels;
|
|
2973
2989
|
g.remove();
|
|
2974
2990
|
};
|
|
2975
|
-
vid.
|
|
2991
|
+
vid.promise = (async () => {
|
|
2976
2992
|
let stream;
|
|
2977
2993
|
try {
|
|
2978
2994
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
@@ -2987,9 +3003,9 @@ Q5.modules.dom = ($, q) => {
|
|
|
2987
3003
|
if (cb) cb(vid);
|
|
2988
3004
|
return vid;
|
|
2989
3005
|
})();
|
|
2990
|
-
$._preloadPromises.push(vid.
|
|
3006
|
+
$._preloadPromises.push(vid.promise);
|
|
2991
3007
|
|
|
2992
|
-
if (!$._usePreload) return vid.
|
|
3008
|
+
if (!$._usePreload) return vid.promise;
|
|
2993
3009
|
return vid;
|
|
2994
3010
|
};
|
|
2995
3011
|
|
|
@@ -4068,28 +4084,28 @@ Q5.modules.sound = ($, q) => {
|
|
|
4068
4084
|
let s = new Q5.Sound();
|
|
4069
4085
|
sounds.push(s);
|
|
4070
4086
|
|
|
4071
|
-
s.
|
|
4087
|
+
s.promise = (async () => {
|
|
4072
4088
|
let err;
|
|
4073
4089
|
try {
|
|
4074
4090
|
await s.load(url);
|
|
4075
4091
|
} catch (e) {
|
|
4076
4092
|
err = e;
|
|
4077
4093
|
}
|
|
4078
|
-
delete s.
|
|
4094
|
+
delete s.promise;
|
|
4079
4095
|
if (err) throw err;
|
|
4080
4096
|
if (cb) cb(s);
|
|
4081
4097
|
return s;
|
|
4082
4098
|
})();
|
|
4083
|
-
$._preloadPromises.push(s.
|
|
4099
|
+
$._preloadPromises.push(s.promise);
|
|
4084
4100
|
|
|
4085
|
-
if (!$._usePreload) return s.
|
|
4101
|
+
if (!$._usePreload) return s.promise;
|
|
4086
4102
|
return s;
|
|
4087
4103
|
};
|
|
4088
4104
|
|
|
4089
4105
|
$.loadAudio = (url, cb) => {
|
|
4090
4106
|
let a = new Audio(url);
|
|
4091
4107
|
a.crossOrigin = 'Anonymous';
|
|
4092
|
-
a.
|
|
4108
|
+
a.promise = new Promise((resolve, reject) => {
|
|
4093
4109
|
a.addEventListener('canplaythrough', () => {
|
|
4094
4110
|
if (!a.loaded) {
|
|
4095
4111
|
a.loaded = true;
|
|
@@ -4100,9 +4116,9 @@ Q5.modules.sound = ($, q) => {
|
|
|
4100
4116
|
a.addEventListener('suspend', resolve);
|
|
4101
4117
|
a.addEventListener('error', reject);
|
|
4102
4118
|
});
|
|
4103
|
-
$._preloadPromises.push(a.
|
|
4119
|
+
$._preloadPromises.push(a.promise);
|
|
4104
4120
|
|
|
4105
|
-
if (!$._usePreload) return a.
|
|
4121
|
+
if (!$._usePreload) return a.promise;
|
|
4106
4122
|
return a;
|
|
4107
4123
|
};
|
|
4108
4124
|
|
|
@@ -4269,7 +4285,7 @@ Q5.Sound = class {
|
|
|
4269
4285
|
Q5.modules.util = ($, q) => {
|
|
4270
4286
|
$._loadFile = (url, cb, type) => {
|
|
4271
4287
|
let ret = {};
|
|
4272
|
-
ret.
|
|
4288
|
+
ret.promise = new Promise((resolve, reject) => {
|
|
4273
4289
|
fetch(url)
|
|
4274
4290
|
.then((res) => {
|
|
4275
4291
|
if (!res.ok) {
|
|
@@ -4283,7 +4299,7 @@ Q5.modules.util = ($, q) => {
|
|
|
4283
4299
|
if (type == 'csv') f = $.CSV.parse(f);
|
|
4284
4300
|
if (typeof f == 'string') ret.text = f;
|
|
4285
4301
|
else Object.assign(ret, f);
|
|
4286
|
-
delete ret.
|
|
4302
|
+
delete ret.promise;
|
|
4287
4303
|
if (cb) cb(f);
|
|
4288
4304
|
resolve(f);
|
|
4289
4305
|
});
|
|
@@ -4295,14 +4311,15 @@ Q5.modules.util = ($, q) => {
|
|
|
4295
4311
|
$.loadJSON = (url, cb) => $._loadFile(url, cb, 'json');
|
|
4296
4312
|
$.loadCSV = (url, cb) => $._loadFile(url, cb, 'csv');
|
|
4297
4313
|
|
|
4298
|
-
const imgRegex = /(jpe?g|png|gif|webp|avif|svg)
|
|
4299
|
-
fontRegex = /(ttf|otf|woff2?|eot|json)
|
|
4300
|
-
|
|
4314
|
+
const imgRegex = /(jpe?g|png|gif|webp|avif|svg)/i,
|
|
4315
|
+
fontRegex = /(ttf|otf|woff2?|eot|json)/i,
|
|
4316
|
+
fontCategoryRegex = /(serif|sans-serif|monospace|cursive|fantasy)/i,
|
|
4317
|
+
audioRegex = /(wav|flac|mp3|ogg|m4a|aac|aiff|weba)/i;
|
|
4301
4318
|
|
|
4302
4319
|
$.load = function (...urls) {
|
|
4303
4320
|
if (Array.isArray(urls[0])) urls = urls[0];
|
|
4304
4321
|
|
|
4305
|
-
let
|
|
4322
|
+
let promises = [];
|
|
4306
4323
|
|
|
4307
4324
|
for (let url of urls) {
|
|
4308
4325
|
let ext = url.split('.').pop().toLowerCase();
|
|
@@ -4314,25 +4331,29 @@ Q5.modules.util = ($, q) => {
|
|
|
4314
4331
|
obj = $.loadCSV(url);
|
|
4315
4332
|
} else if (imgRegex.test(ext)) {
|
|
4316
4333
|
obj = $.loadImage(url);
|
|
4317
|
-
} else if (fontRegex.test(ext)) {
|
|
4334
|
+
} else if (fontRegex.test(ext) || fontCategoryRegex.test(url)) {
|
|
4318
4335
|
obj = $.loadFont(url);
|
|
4319
4336
|
} else if (audioRegex.test(ext)) {
|
|
4320
4337
|
obj = $.loadSound(url);
|
|
4321
4338
|
} else {
|
|
4322
4339
|
obj = $.loadText(url);
|
|
4323
4340
|
}
|
|
4324
|
-
|
|
4341
|
+
promises.push(obj.promise);
|
|
4325
4342
|
}
|
|
4326
4343
|
|
|
4327
|
-
if (urls.length == 1) return
|
|
4328
|
-
return Promise.all(
|
|
4344
|
+
if (urls.length == 1) return promises[0];
|
|
4345
|
+
return Promise.all(promises);
|
|
4329
4346
|
};
|
|
4330
4347
|
|
|
4331
4348
|
async function saveFile(data, name, ext) {
|
|
4332
4349
|
name = name || 'untitled';
|
|
4333
4350
|
ext = ext || 'png';
|
|
4334
4351
|
if (imgRegex.test(ext)) {
|
|
4335
|
-
|
|
4352
|
+
if ($.canvas?.renderer == 'webgpu' && data.canvas.renderer == 'c2d') {
|
|
4353
|
+
data = await $._g._saveCanvas(data, ext);
|
|
4354
|
+
} else {
|
|
4355
|
+
data = await $._saveCanvas(data, ext);
|
|
4356
|
+
}
|
|
4336
4357
|
} else {
|
|
4337
4358
|
let type = 'text/plain';
|
|
4338
4359
|
if (ext == 'json') {
|
|
@@ -4830,6 +4851,8 @@ struct Q5 {
|
|
|
4830
4851
|
$._frameA = frameA = Q5.device.createTexture({ size, format, usage });
|
|
4831
4852
|
$._frameB = frameB = Q5.device.createTexture({ size, format, usage });
|
|
4832
4853
|
|
|
4854
|
+
$.canvas.texture = frameA;
|
|
4855
|
+
|
|
4833
4856
|
$._frameShaderCode =
|
|
4834
4857
|
$._baseShaderCode +
|
|
4835
4858
|
/* wgsl */ `
|
|
@@ -4930,16 +4953,25 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
4930
4953
|
createMainView();
|
|
4931
4954
|
};
|
|
4932
4955
|
|
|
4956
|
+
let usingRGB = true,
|
|
4957
|
+
colorFormat = 1;
|
|
4958
|
+
|
|
4959
|
+
let cm = $.colorMode;
|
|
4960
|
+
$.colorMode = function () {
|
|
4961
|
+
cm(...arguments);
|
|
4962
|
+
usingRGB = $._colorMode == 'rgb';
|
|
4963
|
+
colorFormat = $._colorFormat;
|
|
4964
|
+
};
|
|
4965
|
+
|
|
4933
4966
|
let addColor = (r, g, b, a) => {
|
|
4934
|
-
|
|
4935
|
-
if (typeof r == 'string' || $._colorMode != 'rgb') {
|
|
4967
|
+
if (typeof r === 'string' || usingRGB === false) {
|
|
4936
4968
|
r = $.color(r, g, b, a);
|
|
4937
|
-
} else if (b
|
|
4969
|
+
} else if (b === undefined) {
|
|
4938
4970
|
// grayscale mode `fill(1, 0.5)`
|
|
4939
|
-
a = g ??
|
|
4971
|
+
a = g ?? colorFormat;
|
|
4940
4972
|
g = b = r;
|
|
4941
4973
|
}
|
|
4942
|
-
a ??=
|
|
4974
|
+
a ??= colorFormat;
|
|
4943
4975
|
if (r._q5Color) {
|
|
4944
4976
|
let c = r;
|
|
4945
4977
|
if (c.r != undefined) ({ r, g, b, a } = c);
|
|
@@ -4952,7 +4984,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
4952
4984
|
}
|
|
4953
4985
|
}
|
|
4954
4986
|
|
|
4955
|
-
if (
|
|
4987
|
+
if (colorFormat === 255) {
|
|
4956
4988
|
r /= 255;
|
|
4957
4989
|
g /= 255;
|
|
4958
4990
|
b /= 255;
|
|
@@ -5720,6 +5752,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5720
5752
|
$.rectMode = (x) => ($._rectMode = x);
|
|
5721
5753
|
|
|
5722
5754
|
$.rect = (x, y, w, h, rr = 0) => {
|
|
5755
|
+
h ??= w;
|
|
5723
5756
|
let [l, r, t, b] = $._calcBox(x, y, w, h, $._rectMode);
|
|
5724
5757
|
let ci, ti;
|
|
5725
5758
|
if ($._matrixDirty) $._saveMatrix();
|
|
@@ -6396,8 +6429,10 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6396
6429
|
|
|
6397
6430
|
$._textureBindGroups = [];
|
|
6398
6431
|
|
|
6399
|
-
$._saveCanvas = async (
|
|
6400
|
-
|
|
6432
|
+
$._saveCanvas = async (img, ext) => {
|
|
6433
|
+
if (img._graphics && img._drawStack?.length) img._completeFrame();
|
|
6434
|
+
|
|
6435
|
+
let texture = img.texture,
|
|
6401
6436
|
w = texture.width,
|
|
6402
6437
|
h = texture.height,
|
|
6403
6438
|
bytesPerRow = Math.ceil((w * 4) / 256) * 256;
|
|
@@ -6418,7 +6453,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6418
6453
|
await buffer.mapAsync(GPUMapMode.READ);
|
|
6419
6454
|
|
|
6420
6455
|
let pad = new Uint8Array(buffer.getMappedRange());
|
|
6421
|
-
data = new Uint8Array(w * h * 4); // unpadded data
|
|
6456
|
+
let data = new Uint8Array(w * h * 4); // unpadded data
|
|
6422
6457
|
|
|
6423
6458
|
// Remove padding from each row and swap BGR to RGB
|
|
6424
6459
|
for (let y = 0; y < h; y++) {
|
|
@@ -6513,7 +6548,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6513
6548
|
let g = new Q5Image(...arguments);
|
|
6514
6549
|
if (w > 1 && h > 1) {
|
|
6515
6550
|
$._addTexture(g);
|
|
6516
|
-
|
|
6551
|
+
img.modified = true;
|
|
6517
6552
|
}
|
|
6518
6553
|
return g;
|
|
6519
6554
|
};
|
|
@@ -6536,6 +6571,8 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6536
6571
|
$._addTexture(g, g._frameA);
|
|
6537
6572
|
$._addTexture(g, g._frameB);
|
|
6538
6573
|
g._beginRender();
|
|
6574
|
+
} else {
|
|
6575
|
+
g.modified = true;
|
|
6539
6576
|
}
|
|
6540
6577
|
return g;
|
|
6541
6578
|
};
|
|
@@ -6571,17 +6608,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6571
6608
|
h = cnv.height,
|
|
6572
6609
|
pd = img._pixelDensity || 1;
|
|
6573
6610
|
|
|
6574
|
-
if (img._graphics)
|
|
6575
|
-
let g = img;
|
|
6576
|
-
if (g.drawStack.length) {
|
|
6577
|
-
g._render();
|
|
6578
|
-
g._finishRender();
|
|
6579
|
-
g.textureIndex += g.frameCount % 2 == 0 ? -1 : 1;
|
|
6580
|
-
g.resetMatrix();
|
|
6581
|
-
g._beginRender();
|
|
6582
|
-
g.frameCount++;
|
|
6583
|
-
}
|
|
6584
|
-
}
|
|
6611
|
+
if (img._graphics && img._drawStack?.length) img._completeFrame();
|
|
6585
6612
|
|
|
6586
6613
|
if (img.modified) {
|
|
6587
6614
|
Q5.device.queue.copyExternalImageToTexture(
|
|
@@ -6638,6 +6665,15 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6638
6665
|
}
|
|
6639
6666
|
};
|
|
6640
6667
|
|
|
6668
|
+
$._completeFrame = () => {
|
|
6669
|
+
$._render();
|
|
6670
|
+
$._finishRender();
|
|
6671
|
+
$.textureIndex += $.frameCount % 2 == 0 ? -1 : 1;
|
|
6672
|
+
$.resetMatrix();
|
|
6673
|
+
$._beginRender();
|
|
6674
|
+
$.frameCount++;
|
|
6675
|
+
};
|
|
6676
|
+
|
|
6641
6677
|
$._hooks.preRender.push(() => {
|
|
6642
6678
|
if (!vertIndex) return;
|
|
6643
6679
|
|
|
@@ -6694,7 +6730,8 @@ struct FragParams {
|
|
|
6694
6730
|
@location(0) texCoord : vec2f,
|
|
6695
6731
|
@location(1) fillColor : vec4f,
|
|
6696
6732
|
@location(2) strokeColor : vec4f,
|
|
6697
|
-
@location(3) strokeWeight : f32
|
|
6733
|
+
@location(3) strokeWeight : f32,
|
|
6734
|
+
@location(4) edge : f32
|
|
6698
6735
|
}
|
|
6699
6736
|
struct Char {
|
|
6700
6737
|
texOffset: vec2f,
|
|
@@ -6708,7 +6745,8 @@ struct Text {
|
|
|
6708
6745
|
matrixIndex: f32,
|
|
6709
6746
|
fillIndex: f32,
|
|
6710
6747
|
strokeIndex: f32,
|
|
6711
|
-
strokeWeight: f32
|
|
6748
|
+
strokeWeight: f32,
|
|
6749
|
+
edge: f32
|
|
6712
6750
|
}
|
|
6713
6751
|
|
|
6714
6752
|
@group(0) @binding(0) var<uniform> q: Q5;
|
|
@@ -6769,12 +6807,13 @@ fn vertexMain(v : VertexParams) -> FragParams {
|
|
|
6769
6807
|
f.fillColor = colors[i32(text.fillIndex)];
|
|
6770
6808
|
f.strokeColor = colors[i32(text.strokeIndex)];
|
|
6771
6809
|
f.strokeWeight = text.strokeWeight;
|
|
6810
|
+
f.edge = text.edge;
|
|
6772
6811
|
return f;
|
|
6773
6812
|
}
|
|
6774
6813
|
|
|
6775
6814
|
@fragment
|
|
6776
6815
|
fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
6777
|
-
let edge =
|
|
6816
|
+
let edge = f.edge;
|
|
6778
6817
|
let dist = calcDist(f.texCoord, edge);
|
|
6779
6818
|
|
|
6780
6819
|
if (f.strokeWeight == 0.0) {
|
|
@@ -6986,26 +7025,27 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
6986
7025
|
|
|
6987
7026
|
$.loadFont = (url, cb) => {
|
|
6988
7027
|
let ext = url.slice(url.lastIndexOf('.') + 1);
|
|
7028
|
+
if (url == ext) return $._loadDefaultFont(url, cb);
|
|
6989
7029
|
if (ext != 'json') return $._g.loadFont(url, cb);
|
|
6990
7030
|
let fontName = url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('-'));
|
|
6991
7031
|
let f = { family: fontName };
|
|
6992
|
-
f.
|
|
6993
|
-
delete f.
|
|
7032
|
+
f.promise = createFont(url, fontName, () => {
|
|
7033
|
+
delete f.promise;
|
|
6994
7034
|
if (cb) cb(f);
|
|
6995
7035
|
});
|
|
6996
|
-
$._preloadPromises.push(f.
|
|
7036
|
+
$._preloadPromises.push(f.promise);
|
|
6997
7037
|
|
|
6998
|
-
if (!$._usePreload) return f.
|
|
7038
|
+
if (!$._usePreload) return f.promise;
|
|
6999
7039
|
return f;
|
|
7000
7040
|
};
|
|
7001
7041
|
|
|
7002
|
-
$._loadDefaultFont = (fontName) => {
|
|
7042
|
+
$._loadDefaultFont = (fontName, cb) => {
|
|
7003
7043
|
fonts[fontName] = null;
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
$.loadFont(`/node_modules/q5/builtinFonts/${fontName}-msdf.json`);
|
|
7044
|
+
let url = `https://q5js.org/fonts/${fontName}-msdf.json`;
|
|
7045
|
+
if (!navigator.onLine) {
|
|
7046
|
+
url = `/node_modules/q5/builtinFonts/${fontName}-msdf.json`;
|
|
7008
7047
|
}
|
|
7048
|
+
return $.loadFont(url, cb);
|
|
7009
7049
|
};
|
|
7010
7050
|
|
|
7011
7051
|
$._textSize = 18;
|
|
@@ -7021,7 +7061,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7021
7061
|
if (typeof fontName != 'string') fontName = fontName.family;
|
|
7022
7062
|
let font = fonts[fontName];
|
|
7023
7063
|
if (font) $._font = font;
|
|
7024
|
-
else if (font === undefined) $._loadDefaultFont(fontName);
|
|
7064
|
+
else if (font === undefined) return $._loadDefaultFont(fontName);
|
|
7025
7065
|
};
|
|
7026
7066
|
|
|
7027
7067
|
$.textSize = (size) => {
|
|
@@ -7033,6 +7073,33 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7033
7073
|
}
|
|
7034
7074
|
};
|
|
7035
7075
|
|
|
7076
|
+
let weights = {
|
|
7077
|
+
thin: 100,
|
|
7078
|
+
extralight: 200,
|
|
7079
|
+
light: 300,
|
|
7080
|
+
normal: 400,
|
|
7081
|
+
regular: 400,
|
|
7082
|
+
medium: 500,
|
|
7083
|
+
semibold: 600,
|
|
7084
|
+
bold: 700,
|
|
7085
|
+
bolder: 800,
|
|
7086
|
+
extrabold: 800,
|
|
7087
|
+
black: 900,
|
|
7088
|
+
heavy: 900
|
|
7089
|
+
};
|
|
7090
|
+
|
|
7091
|
+
// ranges from 0.35 (black) to 0.65 (thin)
|
|
7092
|
+
$._textEdge = 0.5;
|
|
7093
|
+
|
|
7094
|
+
$.textWeight = (weight) => {
|
|
7095
|
+
if (!weight) return $._textWeight;
|
|
7096
|
+
if (typeof weight == 'string') {
|
|
7097
|
+
weight = weights[weight.toLowerCase().replace(/[ _-]/g, '')];
|
|
7098
|
+
if (!weight) throw new Error(`Invalid font weight: ${weight}`);
|
|
7099
|
+
}
|
|
7100
|
+
$._textEdge = 0.6875 - weight * 0.000375;
|
|
7101
|
+
};
|
|
7102
|
+
|
|
7036
7103
|
$.textLeading = (lineHeight) => {
|
|
7037
7104
|
$._font.lineHeight = leading = lineHeight;
|
|
7038
7105
|
leadDiff = leading - $._textSize;
|
|
@@ -7191,12 +7258,12 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
|
|
|
7191
7258
|
|
|
7192
7259
|
txt[0] = x;
|
|
7193
7260
|
txt[1] = -y;
|
|
7194
|
-
txt[2] = $._textSize /
|
|
7261
|
+
txt[2] = $._textSize / 42;
|
|
7195
7262
|
txt[3] = $._matrixIndex;
|
|
7196
7263
|
txt[4] = $._doFill && $._fillSet ? $._fill : 0;
|
|
7197
7264
|
txt[5] = $._stroke;
|
|
7198
7265
|
txt[6] = $._doStroke && $._strokeSet ? $._strokeWeight : 0;
|
|
7199
|
-
txt[7] =
|
|
7266
|
+
txt[7] = $._textEdge;
|
|
7200
7267
|
|
|
7201
7268
|
textStack.push(txt);
|
|
7202
7269
|
$._drawStack.push($._textPL, measurements.printedCharCount, $._font.index);
|