q5 3.9.4 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 3.9
3
+ * @version 4.0
4
4
  * @author quinton-ashley
5
5
  * @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
6
6
  * @license LGPL-3.0
@@ -469,7 +469,7 @@ if (typeof window == 'object') {
469
469
  window.WEBGPU = 'webgpu';
470
470
  } else global.window = 0;
471
471
 
472
- Q5.version = Q5.VERSION = '3.9';
472
+ Q5.version = Q5.VERSION = '4.0';
473
473
 
474
474
  if (typeof document == 'object') {
475
475
  document.addEventListener('DOMContentLoaded', () => {
@@ -638,7 +638,7 @@ Q5.modules.canvas = ($, q) => {
638
638
 
639
639
  let m = Q5._libMap;
640
640
 
641
- if (m.width) {
641
+ if (m?.width) {
642
642
  q[m.width] = w;
643
643
  q[m.height] = h;
644
644
  q[m.halfWidth] = q.halfWidth;
@@ -1603,7 +1603,11 @@ Q5.renderers.c2d.image = ($, q) => {
1603
1603
  $._retint = true;
1604
1604
 
1605
1605
  if ($._owner?._makeDrawable) {
1606
- $._texture.destroy();
1606
+ if ($._owner._texturesToDestroy && $._texture) {
1607
+ $._owner._texturesToDestroy.push($._texture);
1608
+ } else {
1609
+ $._texture.destroy();
1610
+ }
1607
1611
  delete $._texture;
1608
1612
  $._owner._makeDrawable($);
1609
1613
  }
@@ -1776,24 +1780,26 @@ Q5.Image = class {
1776
1780
  delete $.createCanvas;
1777
1781
  $._loop = false;
1778
1782
 
1779
- let libMap = Q5._libMap;
1780
- let imgFns = [
1781
- 'copy',
1782
- 'filter',
1783
- 'get',
1784
- 'set',
1785
- 'resize',
1786
- 'mask',
1787
- 'trim',
1788
- 'inset',
1789
- 'pixels',
1790
- 'loadPixels',
1791
- 'updatePixels',
1792
- 'smooth',
1793
- 'noSmooth'
1794
- ];
1795
- for (let name of imgFns) {
1796
- if (libMap[name]) $[libMap[name]] = $[name];
1783
+ let m = Q5._libMap;
1784
+ if (m) {
1785
+ let imgFns = [
1786
+ 'copy',
1787
+ 'filter',
1788
+ 'get',
1789
+ 'set',
1790
+ 'resize',
1791
+ 'mask',
1792
+ 'trim',
1793
+ 'inset',
1794
+ 'pixels',
1795
+ 'loadPixels',
1796
+ 'updatePixels',
1797
+ 'smooth',
1798
+ 'noSmooth'
1799
+ ];
1800
+ for (let name of imgFns) {
1801
+ if (m[name]) $[m[name]] = $[name];
1802
+ }
1797
1803
  }
1798
1804
  }
1799
1805
  get w() {
@@ -2101,7 +2107,7 @@ Q5.renderers.c2d.text = ($, q) => {
2101
2107
 
2102
2108
  $.textStyle = (x) => {
2103
2109
  if (!x) return emphasis;
2104
- emphasis = x;
2110
+ $._textStyle = emphasis = x;
2105
2111
  $._fontMod = true;
2106
2112
  styleHash = -1;
2107
2113
  };
@@ -2231,7 +2237,6 @@ Q5.renderers.c2d.text = ($, q) => {
2231
2237
  if ($._textBaseline == 'middle') tY -= leading * (lines.length - 1) * 0.5;
2232
2238
  else if ($._textBaseline == 'bottom') tY -= leading * (lines.length - 1);
2233
2239
  } else {
2234
- tX = 0;
2235
2240
  tY = leading;
2236
2241
 
2237
2242
  if (!img) {
@@ -2272,9 +2277,14 @@ Q5.renderers.c2d.text = ($, q) => {
2272
2277
 
2273
2278
  ctx = img.ctx;
2274
2279
 
2280
+ ctx.textAlign = $._textAlign;
2281
+ if ($._textAlign == 'center') tX = img.width / 2;
2282
+ else if ($._textAlign == 'right') tX = img.width;
2283
+ else tX = 0;
2284
+
2275
2285
  ctx.font = $.ctx.font;
2276
- ctx.fillStyle = $._fill;
2277
- ctx.strokeStyle = $._stroke;
2286
+ if ($._doFill && $._fillSet) ctx.fillStyle = $._fill;
2287
+ if ($._doStroke && $._strokeSet) ctx.strokeStyle = $._stroke;
2278
2288
  ctx.lineWidth = $.ctx.lineWidth;
2279
2289
  }
2280
2290
 
@@ -2318,7 +2328,11 @@ Q5.renderers.c2d.text = ($, q) => {
2318
2328
  colorCache = styleCache[hash];
2319
2329
  for (let c in colorCache) {
2320
2330
  let _img = colorCache[c];
2321
- if (_img._texture) _img._texture.destroy();
2331
+ if (_img._texture) {
2332
+ let owner = _img._owner || $;
2333
+ if (owner._texturesToDestroy) owner._texturesToDestroy.push(_img._texture);
2334
+ else _img._texture.destroy();
2335
+ }
2322
2336
  delete colorCache[c];
2323
2337
  }
2324
2338
  }
@@ -2348,6 +2362,79 @@ Q5.renderers.c2d.text = ($, q) => {
2348
2362
  $.image(img, x, y);
2349
2363
  $._imageMode = og;
2350
2364
  };
2365
+
2366
+ $.textToPoints = (str, x = 0, y = 0, sampleRate = 0.1, density = 1) => {
2367
+ let pd = $._pixelDensity;
2368
+ $._pixelDensity = density;
2369
+ let img = $.createTextImage(str);
2370
+ $._pixelDensity = pd;
2371
+
2372
+ img.loadPixels();
2373
+
2374
+ let w = img.canvas.width,
2375
+ h = img.canvas.height;
2376
+
2377
+ let points = [];
2378
+
2379
+ let ta = $._textAlign,
2380
+ bl = $._textBaseline,
2381
+ offsetX = 0,
2382
+ offsetY = 0;
2383
+
2384
+ if (ta == 'center') offsetX = -w / 2;
2385
+ else if (ta == 'right') offsetX = -w;
2386
+
2387
+ if (bl == 'alphabetic') offsetY = -img._leading;
2388
+ else if (bl == 'middle') offsetY = -img._middle;
2389
+ else if (bl == 'bottom') offsetY = -img._bottom;
2390
+ else if (bl == 'top') offsetY = -img._top;
2391
+
2392
+ offsetY *= density;
2393
+
2394
+ let allPoints = [];
2395
+
2396
+ // Z-order curve (Morton code)
2397
+ const part1by1 = (n) => {
2398
+ n &= 0x0000ffff;
2399
+ n = (n | (n << 8)) & 0x00ff00ff;
2400
+ n = (n | (n << 4)) & 0x0f0f0f0f;
2401
+ n = (n | (n << 2)) & 0x33333333;
2402
+ n = (n | (n << 1)) & 0x55555555;
2403
+ return n;
2404
+ };
2405
+
2406
+ let r = Math.max(0.5, sampleRate);
2407
+
2408
+ for (let py = 0; py < h; py++) {
2409
+ for (let px = 0; px < w; px++) {
2410
+ let index = (py * w + px) * 4;
2411
+
2412
+ if ((r == 1 || $.random() < r) && img.pixels[index + 3] > 128) {
2413
+ allPoints.push({
2414
+ x: px,
2415
+ y: py,
2416
+ z: part1by1(px) | (part1by1(py) << 1)
2417
+ });
2418
+ }
2419
+ }
2420
+ }
2421
+
2422
+ let total = allPoints.length;
2423
+ let numPoints = total * sampleRate * (1 / r);
2424
+
2425
+ if (sampleRate < 1) allPoints.sort((a, b) => a.z - b.z);
2426
+
2427
+ let step = total / numPoints;
2428
+ for (let i = 0; i < total; i += step) {
2429
+ let p = allPoints[Math.floor(i)];
2430
+ points.push({
2431
+ x: (p.x + offsetX) / density + x,
2432
+ y: (p.y + offsetY) / density + y
2433
+ });
2434
+ }
2435
+
2436
+ return points;
2437
+ };
2351
2438
  };
2352
2439
 
2353
2440
  Q5.fonts = [];
@@ -3317,8 +3404,8 @@ Q5.modules.dom = ($, q) => {
3317
3404
  return vid;
3318
3405
  };
3319
3406
 
3320
- $.findElement = (selector) => document.querySelector(selector);
3321
- $.findElements = (selector) => document.querySelectorAll(selector);
3407
+ $.findEl = (selector) => document.querySelector(selector);
3408
+ $.findEls = (selector) => document.querySelectorAll(selector);
3322
3409
  };
3323
3410
  Q5.modules.fes = ($) => {
3324
3411
  $._fes = async (e) => {
@@ -3335,7 +3422,7 @@ Q5.modules.fes = ($) => {
3335
3422
  idx = 0;
3336
3423
  sep = '@';
3337
3424
  }
3338
- while (stackLines[idx].indexOf('q5') >= 0) idx++;
3425
+ while (idx > stackLines.length && stackLines[idx].indexOf('q5') >= 0) idx++;
3339
3426
 
3340
3427
  let errFile = stackLines[idx].split(sep).at(-1);
3341
3428
  if (errFile.startsWith('blob:')) errFile = errFile.slice(5);
@@ -3789,6 +3876,7 @@ Q5.modules.math = ($, q) => {
3789
3876
  return {
3790
3877
  setSeed(val) {
3791
3878
  jsr = seed = (val == null ? Math.random() * m : val) >>> 0;
3879
+ if (jsr === 0) jsr = 1;
3792
3880
  },
3793
3881
  getSeed() {
3794
3882
  return seed;
@@ -5309,6 +5397,7 @@ struct Q5 {
5309
5397
  $._pipelineConfigs = [];
5310
5398
  $._pipelines = [];
5311
5399
  $._buffers = [];
5400
+ $._texturesToDestroy = [];
5312
5401
 
5313
5402
  // local variables used for slightly better performance
5314
5403
 
@@ -6355,9 +6444,14 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6355
6444
  ellipseStackIdx = 0;
6356
6445
 
6357
6446
  // destroy buffers
6447
+ let bufs = $._buffers;
6448
+ let texs = $._texturesToDestroy;
6449
+ $._buffers = [];
6450
+ $._texturesToDestroy = [];
6451
+
6358
6452
  Q5.device.queue.onSubmittedWorkDone().then(() => {
6359
- for (let b of $._buffers) b.destroy();
6360
- $._buffers = [];
6453
+ for (let b of bufs) b.destroy();
6454
+ for (let t of texs) t.destroy();
6361
6455
  });
6362
6456
  };
6363
6457
 
@@ -7072,7 +7166,12 @@ fn vertexMain(v: VertexParams) -> FragParams {
7072
7166
  f.position = transformVertex(pos, ellipse.matrixIndex);
7073
7167
  f.outerEdge = dist / (ellipse.size + halfStrokeSize);
7074
7168
  f.fillEdge = dist / ellipse.size;
7075
- f.innerEdge = dist / (ellipse.size - halfStrokeSize);
7169
+ let innerSize = ellipse.size - halfStrokeSize;
7170
+ if (innerSize.x <= 0.0 || innerSize.y <= 0.0) {
7171
+ f.innerEdge = vec2f(2.0, 0.0);
7172
+ } else {
7173
+ f.innerEdge = dist / innerSize;
7174
+ }
7076
7175
  f.strokeWeight = ellipse.strokeWeight;
7077
7176
 
7078
7177
  let fill = colors[i32(ellipse.fillIndex)];
@@ -7676,7 +7775,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7676
7775
  let isVideo;
7677
7776
  if (img._texture == undefined) {
7678
7777
  isVideo = img.tagName == 'VIDEO';
7679
- if (img.width <= 1 || (isVideo && !img.currentTime)) return;
7778
+ if (!isVideo || !img.currentTime) return;
7680
7779
  if (img.flipped) $.scale(-1, 1);
7681
7780
  }
7682
7781
 
@@ -8113,9 +8212,10 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8113
8212
 
8114
8213
  let _textSize = 18,
8115
8214
  _textAlign = 'left',
8215
+ _textStyle = 'normal',
8116
8216
  _textBaseline = 'alphabetic',
8117
8217
  leadingSet = false,
8118
- leading = 22.5,
8218
+ _textLeading = 22.5,
8119
8219
  leadDiff = 4.5,
8120
8220
  leadPercent = 1.25;
8121
8221
 
@@ -8136,8 +8236,8 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8136
8236
  if (size == undefined) return _textSize;
8137
8237
  _textSize = size;
8138
8238
  if (!leadingSet) {
8139
- leading = size * leadPercent;
8140
- leadDiff = leading - size;
8239
+ _textLeading = size * leadPercent;
8240
+ leadDiff = _textLeading - size;
8141
8241
  }
8142
8242
  };
8143
8243
 
@@ -8170,29 +8270,29 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8170
8270
 
8171
8271
  $.textLeading = (lineHeight) => {
8172
8272
  if (!$._font) return $._g.textLeading(lineHeight);
8173
- if (!lineHeight) return leading;
8174
- $._font.lineHeight = leading = lineHeight;
8175
- leadDiff = leading - _textSize;
8176
- leadPercent = leading / _textSize;
8273
+ if (!lineHeight) return _textLeading;
8274
+ $._font.lineHeight = _textLeading = lineHeight;
8275
+ leadDiff = _textLeading - _textSize;
8276
+ leadPercent = _textLeading / _textSize;
8177
8277
  leadingSet = true;
8178
8278
  };
8179
8279
 
8180
8280
  $.textAlign = (horiz, vert) => {
8181
8281
  _textAlign = horiz;
8182
- if (vert) _textBaseline = vert;
8282
+ if (vert) _textBaseline = vert[0] == 'c' ? 'middle' : vert;
8183
8283
  };
8184
8284
 
8185
8285
  $.textStyle = (style) => {
8186
- // stub
8286
+ _textStyle = style;
8187
8287
  };
8188
8288
 
8189
8289
  let charStack = [],
8190
8290
  textStack = [];
8191
8291
 
8192
8292
  // Reusable array for line widths to avoid GC
8193
- let lineWidthsCache = new Array(100);
8293
+ let lineWidths = new Array(100);
8194
8294
 
8195
- // Reusable buffers for text data to avoid creating new arrays
8295
+ // Reusable buffers for text data to avoid GC
8196
8296
  let charDataBuffer = new Float32Array(Q5.MAX_CHARS * 4); // reusable buffer for char data
8197
8297
  let textDataBuffer = new Float32Array(Q5.MAX_TEXTS * 8); // reusable buffer for text metadata
8198
8298
 
@@ -8202,7 +8302,6 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8202
8302
  offsetY = 0,
8203
8303
  line = 0,
8204
8304
  printedCharCount = 0,
8205
- lineWidths = lineWidthsCache, // reuse array
8206
8305
  nextCharCode = text.charCodeAt(0);
8207
8306
 
8208
8307
  for (let i = 0; i < text.length; ++i) {
@@ -8210,7 +8309,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8210
8309
  nextCharCode = i < text.length - 1 ? text.charCodeAt(i + 1) : -1;
8211
8310
  switch (charCode) {
8212
8311
  case 10: // newline
8213
- lineWidths.push(offsetX);
8312
+ lineWidths[line] = offsetX;
8214
8313
  line++;
8215
8314
  maxWidth = Math.max(maxWidth, offsetX);
8216
8315
  offsetX = 0;
@@ -8303,8 +8402,8 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8303
8402
  });
8304
8403
 
8305
8404
  if (tb == 'alphabetic') y += _textSize * yDir;
8306
- else if (tb == 'center') y += _textSize * 0.5 * yDir;
8307
- else if (tb == 'bottom') y += leading * yDir;
8405
+ else if (tb == 'middle') y += _textSize * 0.5 * yDir;
8406
+ else if (tb == 'bottom') y += _textLeading * yDir;
8308
8407
  } else {
8309
8408
  // measure the text to get the line height before setting
8310
8409
  // the x position to properly align the text
@@ -8312,7 +8411,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8312
8411
 
8313
8412
  let offsetY = 0;
8314
8413
  if (tb == 'alphabetic') y += _textSize * yDir;
8315
- else if (tb == 'center') offsetY = measurements.height * 0.5;
8414
+ else if (tb == 'middle') offsetY = measurements.height * 0.5;
8316
8415
  else if (tb == 'bottom') offsetY = measurements.height;
8317
8416
 
8318
8417
  measureText($._font, str, (textX, textY, line, char) => {
@@ -8361,7 +8460,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8361
8460
  $._g.textSize(_textSize);
8362
8461
  return $._g.textAscent(str);
8363
8462
  }
8364
- return leading - leadDiff;
8463
+ return _textLeading - leadDiff;
8365
8464
  };
8366
8465
 
8367
8466
  $.textDescent = (str) => {
@@ -8372,18 +8471,29 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8372
8471
  return leadDiff;
8373
8472
  };
8374
8473
 
8375
- $.createTextImage = (str, w, h) => {
8376
- $._g.textSize(_textSize);
8377
-
8378
- if (doFill && fillSet) {
8474
+ $._applyTextStylesToC2D = () => {
8475
+ if (!doFill) $._g.noFill();
8476
+ else if (fillSet) {
8379
8477
  let fi = fillIdx * 4;
8380
8478
  $._g.fill(colorStack.slice(fi, fi + 4));
8381
8479
  }
8382
- if (doStroke && strokeSet) {
8480
+
8481
+ if (!doStroke) $._g.noStroke();
8482
+ else if (strokeSet) {
8383
8483
  let si = strokeIdx * 4;
8384
8484
  $._g.stroke(colorStack.slice(si, si + 4));
8385
8485
  }
8386
8486
 
8487
+ if (sw != $._g._strokeWeight) $._g.strokeWeight(sw);
8488
+ if (_textSize != $._g._textSize) $._g.textSize(_textSize);
8489
+ if (_textStyle != $._g._textStyle) $._g.textStyle(_textStyle);
8490
+ if (_textLeading != $._g.textLeading()) $._g.textLeading(_textLeading);
8491
+ $._g.textAlign(_textAlign, _textBaseline);
8492
+ };
8493
+
8494
+ $.createTextImage = (str, w, h) => {
8495
+ $._applyTextStylesToC2D();
8496
+
8387
8497
  let g = $._g.createTextImage(str, w, h);
8388
8498
  $._makeDrawable(g);
8389
8499
  return g;
@@ -8401,7 +8511,7 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8401
8511
 
8402
8512
  let bl = _textBaseline;
8403
8513
  if (bl == 'alphabetic') y += img._leading * yDir;
8404
- else if (bl == 'center') y += img._middle * yDir;
8514
+ else if (bl == 'middle') y += img._middle * yDir;
8405
8515
  else if (bl == 'bottom') y += img._bottom * yDir;
8406
8516
  else if (bl == 'top') y += img._top * yDir;
8407
8517
 
@@ -8409,6 +8519,11 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8409
8519
  _imageMode = og;
8410
8520
  };
8411
8521
 
8522
+ $.textToPoints = (str, x, y, sampleRate, density) => {
8523
+ $._applyTextStylesToC2D();
8524
+ return $._g.textToPoints(str, x, y, sampleRate, density);
8525
+ };
8526
+
8412
8527
  /* SHADERS */
8413
8528
 
8414
8529
  let pipelineTypes = ['frame', 'shapes', 'image', 'video', 'text'];