q5 2.24.4 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 2.24
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
- Q5.prototype.registerPreloadMethod = (n, fn) => (Q5.prototype[n] = fn[n]);
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.24';
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._loader = new Promise((resolve, reject) => {
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._loader;
1361
+ delete g.promise;
1346
1362
  resolve(g);
1347
1363
  };
1348
1364
  img.onerror = reject;
1349
1365
  });
1350
- $._preloadPromises.push(g._loader);
1366
+ $._preloadPromises.push(g.promise);
1351
1367
 
1352
1368
  g.src = img.src = url;
1353
1369
 
1354
- if (!$._usePreload) return g._loader;
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._loader = (async () => {
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._loader;
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._loader);
1830
+ $._preloadPromises.push(f.promise);
1816
1831
  $.textFont(name);
1817
- if (!$._usePreload) return f._loader;
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 (useCache || genTextImage) {
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
- if (genTextImage) return img;
1939
- return $.textImage(img, x, y);
1947
+ return img;
1940
1948
  } else img.clear();
1941
1949
  }
1942
1950
  }
@@ -1964,7 +1972,7 @@ Q5.renderers.c2d.text = ($, q) => {
1964
1972
  lines = wrapped;
1965
1973
  }
1966
1974
 
1967
- if (!useCache && !genTextImage) {
1975
+ if (!genTextImage) {
1968
1976
  tX = x;
1969
1977
  tY = y;
1970
1978
  } else {
@@ -2025,12 +2033,12 @@ Q5.renderers.c2d.text = ($, q) => {
2025
2033
 
2026
2034
  if (!$._fillSet) ctx.fillStyle = ogFill;
2027
2035
 
2028
- if (useCache || genTextImage) {
2036
+ if (genTextImage) {
2029
2037
  styleHashes.push(styleHash);
2030
2038
  (cache[str] ??= {})[styleHash] = img;
2031
2039
 
2032
2040
  cacheSize++;
2033
- if (cacheSize > cacheMax) {
2041
+ if (cacheSize > $._textCacheMaxSize) {
2034
2042
  let half = Math.ceil(cacheSize / 2);
2035
2043
  let hashes = styleHashes.splice(0, half);
2036
2044
  for (let s in cache) {
@@ -2040,8 +2048,7 @@ Q5.renderers.c2d.text = ($, q) => {
2040
2048
  cacheSize -= half;
2041
2049
  }
2042
2050
 
2043
- if (genTextImage) return img;
2044
- $.textImage(img, x, y);
2051
+ return img;
2045
2052
  }
2046
2053
  };
2047
2054
 
@@ -2936,16 +2943,16 @@ Q5.modules.dom = ($, q) => {
2936
2943
  };
2937
2944
 
2938
2945
  if (src) {
2939
- el._loader = new Promise((resolve) => {
2946
+ el.promise = new Promise((resolve) => {
2940
2947
  el.addEventListener('loadeddata', () => {
2941
2948
  el._load();
2942
2949
  resolve(el);
2943
2950
  });
2944
2951
  el.src = src;
2945
2952
  });
2946
- $._preloadPromises.push(el._loader);
2953
+ $._preloadPromises.push(el.promise);
2947
2954
 
2948
- if (!$._usePreload) return el._loader;
2955
+ if (!$._usePreload) return el.promise;
2949
2956
  }
2950
2957
  return el;
2951
2958
  };
@@ -2972,7 +2979,7 @@ Q5.modules.dom = ($, q) => {
2972
2979
  vid.pixels = g.pixels;
2973
2980
  g.remove();
2974
2981
  };
2975
- vid._loader = (async () => {
2982
+ vid.promise = (async () => {
2976
2983
  let stream;
2977
2984
  try {
2978
2985
  stream = await navigator.mediaDevices.getUserMedia(constraints);
@@ -2987,9 +2994,9 @@ Q5.modules.dom = ($, q) => {
2987
2994
  if (cb) cb(vid);
2988
2995
  return vid;
2989
2996
  })();
2990
- $._preloadPromises.push(vid._loader);
2997
+ $._preloadPromises.push(vid.promise);
2991
2998
 
2992
- if (!$._usePreload) return vid._loader;
2999
+ if (!$._usePreload) return vid.promise;
2993
3000
  return vid;
2994
3001
  };
2995
3002
 
@@ -4068,28 +4075,28 @@ Q5.modules.sound = ($, q) => {
4068
4075
  let s = new Q5.Sound();
4069
4076
  sounds.push(s);
4070
4077
 
4071
- s._loader = (async () => {
4078
+ s.promise = (async () => {
4072
4079
  let err;
4073
4080
  try {
4074
4081
  await s.load(url);
4075
4082
  } catch (e) {
4076
4083
  err = e;
4077
4084
  }
4078
- delete s._loader;
4085
+ delete s.promise;
4079
4086
  if (err) throw err;
4080
4087
  if (cb) cb(s);
4081
4088
  return s;
4082
4089
  })();
4083
- $._preloadPromises.push(s._loader);
4090
+ $._preloadPromises.push(s.promise);
4084
4091
 
4085
- if (!$._usePreload) return s._loader;
4092
+ if (!$._usePreload) return s.promise;
4086
4093
  return s;
4087
4094
  };
4088
4095
 
4089
4096
  $.loadAudio = (url, cb) => {
4090
4097
  let a = new Audio(url);
4091
4098
  a.crossOrigin = 'Anonymous';
4092
- a._loader = new Promise((resolve, reject) => {
4099
+ a.promise = new Promise((resolve, reject) => {
4093
4100
  a.addEventListener('canplaythrough', () => {
4094
4101
  if (!a.loaded) {
4095
4102
  a.loaded = true;
@@ -4100,9 +4107,9 @@ Q5.modules.sound = ($, q) => {
4100
4107
  a.addEventListener('suspend', resolve);
4101
4108
  a.addEventListener('error', reject);
4102
4109
  });
4103
- $._preloadPromises.push(a._loader);
4110
+ $._preloadPromises.push(a.promise);
4104
4111
 
4105
- if (!$._usePreload) return a._loader;
4112
+ if (!$._usePreload) return a.promise;
4106
4113
  return a;
4107
4114
  };
4108
4115
 
@@ -4269,7 +4276,7 @@ Q5.Sound = class {
4269
4276
  Q5.modules.util = ($, q) => {
4270
4277
  $._loadFile = (url, cb, type) => {
4271
4278
  let ret = {};
4272
- ret._loader = new Promise((resolve, reject) => {
4279
+ ret.promise = new Promise((resolve, reject) => {
4273
4280
  fetch(url)
4274
4281
  .then((res) => {
4275
4282
  if (!res.ok) {
@@ -4283,7 +4290,7 @@ Q5.modules.util = ($, q) => {
4283
4290
  if (type == 'csv') f = $.CSV.parse(f);
4284
4291
  if (typeof f == 'string') ret.text = f;
4285
4292
  else Object.assign(ret, f);
4286
- delete ret._loader;
4293
+ delete ret.promise;
4287
4294
  if (cb) cb(f);
4288
4295
  resolve(f);
4289
4296
  });
@@ -4295,14 +4302,15 @@ Q5.modules.util = ($, q) => {
4295
4302
  $.loadJSON = (url, cb) => $._loadFile(url, cb, 'json');
4296
4303
  $.loadCSV = (url, cb) => $._loadFile(url, cb, 'csv');
4297
4304
 
4298
- const imgRegex = /(jpe?g|png|gif|webp|avif|svg)/,
4299
- fontRegex = /(ttf|otf|woff2?|eot|json)/,
4300
- audioRegex = /(wav|flac|mp3|ogg|m4a|aac|aiff|weba)/;
4305
+ const imgRegex = /(jpe?g|png|gif|webp|avif|svg)/i,
4306
+ fontRegex = /(ttf|otf|woff2?|eot|json)/i,
4307
+ fontCategoryRegex = /(serif|sans-serif|monospace|cursive|fantasy)/i,
4308
+ audioRegex = /(wav|flac|mp3|ogg|m4a|aac|aiff|weba)/i;
4301
4309
 
4302
4310
  $.load = function (...urls) {
4303
4311
  if (Array.isArray(urls[0])) urls = urls[0];
4304
4312
 
4305
- let loaders = [];
4313
+ let promises = [];
4306
4314
 
4307
4315
  for (let url of urls) {
4308
4316
  let ext = url.split('.').pop().toLowerCase();
@@ -4314,18 +4322,18 @@ Q5.modules.util = ($, q) => {
4314
4322
  obj = $.loadCSV(url);
4315
4323
  } else if (imgRegex.test(ext)) {
4316
4324
  obj = $.loadImage(url);
4317
- } else if (fontRegex.test(ext)) {
4325
+ } else if (fontRegex.test(ext) || fontCategoryRegex.test(url)) {
4318
4326
  obj = $.loadFont(url);
4319
4327
  } else if (audioRegex.test(ext)) {
4320
4328
  obj = $.loadSound(url);
4321
4329
  } else {
4322
4330
  obj = $.loadText(url);
4323
4331
  }
4324
- loaders.push(obj._loader);
4332
+ promises.push(obj.promise);
4325
4333
  }
4326
4334
 
4327
- if (urls.length == 1) return loaders[0];
4328
- return Promise.all(loaders);
4335
+ if (urls.length == 1) return promises[0];
4336
+ return Promise.all(promises);
4329
4337
  };
4330
4338
 
4331
4339
  async function saveFile(data, name, ext) {
@@ -4830,6 +4838,8 @@ struct Q5 {
4830
4838
  $._frameA = frameA = Q5.device.createTexture({ size, format, usage });
4831
4839
  $._frameB = frameB = Q5.device.createTexture({ size, format, usage });
4832
4840
 
4841
+ $.canvas.texture = frameA;
4842
+
4833
4843
  $._frameShaderCode =
4834
4844
  $._baseShaderCode +
4835
4845
  /* wgsl */ `
@@ -5720,6 +5730,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
5720
5730
  $.rectMode = (x) => ($._rectMode = x);
5721
5731
 
5722
5732
  $.rect = (x, y, w, h, rr = 0) => {
5733
+ h ??= w;
5723
5734
  let [l, r, t, b] = $._calcBox(x, y, w, h, $._rectMode);
5724
5735
  let ci, ti;
5725
5736
  if ($._matrixDirty) $._saveMatrix();
@@ -6694,7 +6705,8 @@ struct FragParams {
6694
6705
  @location(0) texCoord : vec2f,
6695
6706
  @location(1) fillColor : vec4f,
6696
6707
  @location(2) strokeColor : vec4f,
6697
- @location(3) strokeWeight : f32
6708
+ @location(3) strokeWeight : f32,
6709
+ @location(4) edge : f32
6698
6710
  }
6699
6711
  struct Char {
6700
6712
  texOffset: vec2f,
@@ -6708,7 +6720,8 @@ struct Text {
6708
6720
  matrixIndex: f32,
6709
6721
  fillIndex: f32,
6710
6722
  strokeIndex: f32,
6711
- strokeWeight: f32
6723
+ strokeWeight: f32,
6724
+ edge: f32
6712
6725
  }
6713
6726
 
6714
6727
  @group(0) @binding(0) var<uniform> q: Q5;
@@ -6769,12 +6782,13 @@ fn vertexMain(v : VertexParams) -> FragParams {
6769
6782
  f.fillColor = colors[i32(text.fillIndex)];
6770
6783
  f.strokeColor = colors[i32(text.strokeIndex)];
6771
6784
  f.strokeWeight = text.strokeWeight;
6785
+ f.edge = text.edge;
6772
6786
  return f;
6773
6787
  }
6774
6788
 
6775
6789
  @fragment
6776
6790
  fn fragMain(f : FragParams) -> @location(0) vec4f {
6777
- let edge = 0.5;
6791
+ let edge = f.edge;
6778
6792
  let dist = calcDist(f.texCoord, edge);
6779
6793
 
6780
6794
  if (f.strokeWeight == 0.0) {
@@ -6986,26 +7000,27 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
6986
7000
 
6987
7001
  $.loadFont = (url, cb) => {
6988
7002
  let ext = url.slice(url.lastIndexOf('.') + 1);
7003
+ if (url == ext) return $._loadDefaultFont(url, cb);
6989
7004
  if (ext != 'json') return $._g.loadFont(url, cb);
6990
7005
  let fontName = url.slice(url.lastIndexOf('/') + 1, url.lastIndexOf('-'));
6991
7006
  let f = { family: fontName };
6992
- f._loader = createFont(url, fontName, () => {
6993
- delete f._loader;
7007
+ f.promise = createFont(url, fontName, () => {
7008
+ delete f.promise;
6994
7009
  if (cb) cb(f);
6995
7010
  });
6996
- $._preloadPromises.push(f._loader);
7011
+ $._preloadPromises.push(f.promise);
6997
7012
 
6998
- if (!$._usePreload) return f._loader;
7013
+ if (!$._usePreload) return f.promise;
6999
7014
  return f;
7000
7015
  };
7001
7016
 
7002
- $._loadDefaultFont = (fontName) => {
7017
+ $._loadDefaultFont = (fontName, cb) => {
7003
7018
  fonts[fontName] = null;
7004
- if (navigator.onLine) {
7005
- $.loadFont(`https://q5js.org/fonts/${fontName}-msdf.json`);
7006
- } else {
7007
- $.loadFont(`/node_modules/q5/builtinFonts/${fontName}-msdf.json`);
7019
+ let url = `https://q5js.org/fonts/${fontName}-msdf.json`;
7020
+ if (!navigator.onLine) {
7021
+ url = `/node_modules/q5/builtinFonts/${fontName}-msdf.json`;
7008
7022
  }
7023
+ return $.loadFont(url, cb);
7009
7024
  };
7010
7025
 
7011
7026
  $._textSize = 18;
@@ -7021,7 +7036,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
7021
7036
  if (typeof fontName != 'string') fontName = fontName.family;
7022
7037
  let font = fonts[fontName];
7023
7038
  if (font) $._font = font;
7024
- else if (font === undefined) $._loadDefaultFont(fontName);
7039
+ else if (font === undefined) return $._loadDefaultFont(fontName);
7025
7040
  };
7026
7041
 
7027
7042
  $.textSize = (size) => {
@@ -7033,6 +7048,33 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
7033
7048
  }
7034
7049
  };
7035
7050
 
7051
+ let weights = {
7052
+ thin: 100,
7053
+ extralight: 200,
7054
+ light: 300,
7055
+ normal: 400,
7056
+ regular: 400,
7057
+ medium: 500,
7058
+ semibold: 600,
7059
+ bold: 700,
7060
+ bolder: 800,
7061
+ extrabold: 800,
7062
+ black: 900,
7063
+ heavy: 900
7064
+ };
7065
+
7066
+ // ranges from 0.35 (black) to 0.65 (thin)
7067
+ $._textEdge = 0.5;
7068
+
7069
+ $.textWeight = (weight) => {
7070
+ if (!weight) return $._textWeight;
7071
+ if (typeof weight == 'string') {
7072
+ weight = weights[weight.toLowerCase().replace(/[ _-]/g, '')];
7073
+ if (!weight) throw new Error(`Invalid font weight: ${weight}`);
7074
+ }
7075
+ $._textEdge = 0.6875 - weight * 0.000375;
7076
+ };
7077
+
7036
7078
  $.textLeading = (lineHeight) => {
7037
7079
  $._font.lineHeight = leading = lineHeight;
7038
7080
  leadDiff = leading - $._textSize;
@@ -7191,12 +7233,12 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
7191
7233
 
7192
7234
  txt[0] = x;
7193
7235
  txt[1] = -y;
7194
- txt[2] = $._textSize / 44;
7236
+ txt[2] = $._textSize / 42;
7195
7237
  txt[3] = $._matrixIndex;
7196
7238
  txt[4] = $._doFill && $._fillSet ? $._fill : 0;
7197
7239
  txt[5] = $._stroke;
7198
7240
  txt[6] = $._doStroke && $._strokeSet ? $._strokeWeight : 0;
7199
- txt[7] = 0; // padding
7241
+ txt[7] = $._textEdge;
7200
7242
 
7201
7243
  textStack.push(txt);
7202
7244
  $._drawStack.push($._textPL, measurements.printedCharCount, $._font.index);