q5 2.4.5 → 2.5.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/src/q5-2d-text.js CHANGED
@@ -1,9 +1,22 @@
1
1
  Q5.renderers.q2d.text = ($, q) => {
2
- $._textFont = 'sans-serif';
2
+ $._textAlign = 'left';
3
+ $._textBaseline = 'alphabetic';
3
4
  $._textSize = 12;
4
- $._textLeading = 15;
5
- $._textLeadDiff = 3;
6
- $._textStyle = 'normal';
5
+
6
+ let font = 'sans-serif',
7
+ leadingSet = false,
8
+ leading = 15,
9
+ leadDiff = 3,
10
+ emphasis = 'normal',
11
+ fontMod = false,
12
+ styleHash = 0,
13
+ styleHashes = [],
14
+ useCache = false,
15
+ genTextImage = false,
16
+ cacheSize = 0,
17
+ cacheMax = 12000;
18
+
19
+ let cache = ($._textCache = {});
7
20
 
8
21
  $.loadFont = (url, cb) => {
9
22
  q._preloadCount++;
@@ -16,156 +29,160 @@ Q5.renderers.q2d.text = ($, q) => {
16
29
  });
17
30
  return name;
18
31
  };
19
- $.textFont = (x) => ($._textFont = x);
32
+
33
+ $.textFont = (x) => {
34
+ font = x;
35
+ fontMod = true;
36
+ styleHash = -1;
37
+ };
20
38
  $.textSize = (x) => {
21
39
  if (x === undefined) return $._textSize;
22
40
  if ($._da) x *= $._da;
23
41
  $._textSize = x;
24
- if (!$._leadingSet) {
25
- $._textLeading = x * 1.25;
26
- $._textLeadDiff = $._textLeading - x;
42
+ fontMod = true;
43
+ styleHash = -1;
44
+ if (!leadingSet) {
45
+ leading = x * 1.25;
46
+ leadDiff = leading - x;
27
47
  }
28
48
  };
49
+ $.textStyle = (x) => {
50
+ emphasis = x;
51
+ fontMod = true;
52
+ styleHash = -1;
53
+ };
29
54
  $.textLeading = (x) => {
30
- if (x === undefined) return $._textLeading;
55
+ if (x === undefined) return leading;
31
56
  if ($._da) x *= $._da;
32
- $._textLeading = x;
33
- $._textLeadDiff = x - $._textSize;
34
- $._leadingSet = true;
57
+ leading = x;
58
+ leadDiff = x - $._textSize;
59
+ leadingSet = true;
60
+ styleHash = -1;
35
61
  };
36
- $.textStyle = (x) => ($._textStyle = x);
37
62
  $.textAlign = (horiz, vert) => {
38
- $.ctx.textAlign = horiz;
63
+ $.ctx.textAlign = $._textAlign = horiz;
39
64
  if (vert) {
40
- $.ctx.textBaseline = vert == $.CENTER ? 'middle' : vert;
65
+ $.ctx.textBaseline = $._textBaseline = vert == $.CENTER ? 'middle' : vert;
41
66
  }
42
67
  };
43
- $.textWidth = (str) => {
44
- $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
45
- return $.ctx.measureText(str).width;
46
- };
47
- $.textAscent = (str) => {
48
- $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
49
- return $.ctx.measureText(str).actualBoundingBoxAscent;
50
- };
51
- $.textDescent = (str) => {
52
- $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
53
- return $.ctx.measureText(str).actualBoundingBoxDescent;
54
- };
68
+
69
+ $.textWidth = (str) => $.ctx.measureText(str).width;
70
+ $.textAscent = (str) => $.ctx.measureText(str).actualBoundingBoxAscent;
71
+ $.textDescent = (str) => $.ctx.measureText(str).actualBoundingBoxDescent;
72
+
55
73
  $.textFill = $.fill;
56
74
  $.textStroke = $.stroke;
57
75
 
58
- $._textCache = !!Q5.Image;
59
- $._TimedCache = class extends Map {
60
- constructor() {
61
- super();
62
- this.maxSize = 50000;
63
- }
64
- set(k, v) {
65
- v.lastAccessed = Date.now();
66
- super.set(k, v);
67
- if (this.size > this.maxSize) this.gc();
68
- }
69
- get(k) {
70
- const v = super.get(k);
71
- if (v) v.lastAccessed = Date.now();
72
- return v;
73
- }
74
- gc() {
75
- let t = Infinity;
76
- let oldest;
77
- let i = 0;
78
- for (const [k, v] of this.entries()) {
79
- if (v.lastAccessed < t) {
80
- t = v.lastAccessed;
81
- oldest = i;
82
- }
83
- i++;
84
- }
85
- i = oldest;
86
- for (const k of this.keys()) {
87
- if (i == 0) {
88
- oldest = k;
89
- break;
90
- }
91
- i--;
92
- }
93
- this.delete(oldest);
76
+ let updateStyleHash = () => {
77
+ let styleString = font + $._textSize + emphasis + leading;
78
+
79
+ let hash = 5381;
80
+ for (let i = 0; i < styleString.length; i++) {
81
+ hash = (hash * 33) ^ styleString.charCodeAt(i);
94
82
  }
83
+ styleHash = hash >>> 0;
95
84
  };
96
- $._tic = new $._TimedCache();
97
- $.textCache = (b, maxSize) => {
98
- if (maxSize) $._tic.maxSize = maxSize;
99
- if (b !== undefined) $._textCache = b;
100
- return $._textCache;
101
- };
102
- $._genTextImageKey = (str, w, h) => {
103
- return (
104
- str.slice(0, 200) +
105
- $._textStyle +
106
- $._textSize +
107
- $._textFont +
108
- ($._doFill ? $.ctx.fillStyle : '') +
109
- '_' +
110
- ($._doStroke && $._strokeSet ? $.ctx.lineWidth + $.ctx.strokeStyle + '_' : '') +
111
- (w || '') +
112
- (h ? 'x' + h : '')
113
- );
85
+
86
+ $.textCache = (enable, maxSize) => {
87
+ if (maxSize) cacheMax = maxSize;
88
+ if (enable !== undefined) useCache = enable;
89
+ return useCache;
114
90
  };
115
91
  $.createTextImage = (str, w, h) => {
116
- let k = $._genTextImageKey(str, w, h);
117
- if ($._tic.get(k)) return $._tic.get(k);
118
-
119
- let og = $._textCache;
120
- $._textCache = true;
121
- $._genTextImage = true;
122
- $.text(str, 0, 0, w, h);
123
- $._genTextImage = false;
124
- $._textCache = og;
125
- return $._tic.get(k);
92
+ genTextImage = true;
93
+ img = $.text(str, 0, 0, w, h);
94
+ genTextImage = false;
95
+ return img;
126
96
  };
97
+
98
+ let lines = [];
127
99
  $.text = (str, x, y, w, h) => {
128
100
  if (str === undefined || (!$._doFill && !$._doStroke)) return;
129
101
  str = str.toString();
130
- let lines = str.split('\n');
131
102
  if ($._da) {
132
103
  x *= $._da;
133
104
  y *= $._da;
134
105
  }
135
106
  let ctx = $.ctx;
136
- ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
107
+ let img, tX, tY;
137
108
 
138
- let useCache, img, cacheKey, tX, tY, ascent, descent;
109
+ if (fontMod) {
110
+ ctx.font = `${emphasis} ${$._textSize}px ${font}`;
111
+ fontMod = false;
112
+ }
139
113
 
140
- if (!(useCache = $._genTextImage) && $._textCache) {
141
- let transform = $.ctx.getTransform();
142
- useCache = transform.b != 0 || transform.c != 0;
114
+ if (useCache || genTextImage) {
115
+ if (styleHash == -1) updateStyleHash();
116
+
117
+ img = cache[str];
118
+ if (img) img = img[styleHash];
119
+
120
+ if (img) {
121
+ if (img._fill == $._fill && img._stroke == $._stroke && img._strokeWeight == $._strokeWeight) {
122
+ if (genTextImage) return img;
123
+ return $.textImage(img, x, y);
124
+ } else img.clear();
125
+ }
143
126
  }
144
127
 
145
- if (!useCache) {
128
+ if (str.indexOf('\n') == -1) lines[0] = str;
129
+ else lines = str.split('\n');
130
+
131
+ if (str.length > w) {
132
+ let wrapped = [];
133
+ for (let line of lines) {
134
+ let i = 0;
135
+
136
+ while (i < line.length) {
137
+ let max = i + w;
138
+ if (max >= line.length) {
139
+ wrapped.push(line.slice(i));
140
+ break;
141
+ }
142
+ let end = line.lastIndexOf(' ', max);
143
+ if (end === -1 || end < i) end = max;
144
+ wrapped.push(line.slice(i, end));
145
+ i = end + 1;
146
+ }
147
+ }
148
+ lines = wrapped;
149
+ }
150
+
151
+ if (!useCache && !genTextImage) {
146
152
  tX = x;
147
153
  tY = y;
148
154
  } else {
149
- cacheKey = $._genTextImageKey(str, w, h);
150
- img = $._tic.get(cacheKey);
151
- if (img && !$._genTextImage) return $.textImage(img, x, y);
152
-
153
155
  tX = 0;
154
- tY = $._textLeading * lines.length;
155
- let measure = ctx.measureText(' ');
156
- ascent = measure.fontBoundingBoxAscent;
157
- descent = measure.fontBoundingBoxDescent;
158
- h ??= tY + descent;
156
+ tY = leading * lines.length;
157
+
158
+ if (!img) {
159
+ let measure = ctx.measureText(' ');
160
+ let ascent = measure.fontBoundingBoxAscent;
161
+ let descent = measure.fontBoundingBoxDescent;
162
+ h ??= tY + descent;
159
163
 
160
- img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(h), {
161
- pixelDensity: $._pixelDensity
162
- });
164
+ img = $.createImage.call($, Math.ceil(ctx.measureText(str).width), Math.ceil(h), {
165
+ pixelDensity: $._pixelDensity
166
+ });
167
+
168
+ img._ascent = ascent;
169
+ img._descent = descent;
170
+ img._top = descent + leadDiff;
171
+ img._middle = img._top + ascent * 0.5;
172
+ img._bottom = img._top + ascent;
173
+ img._leading = leading;
174
+ }
175
+
176
+ img._fill = $._fill;
177
+ img._stroke = $._stroke;
178
+ img._strokeWeight = $._strokeWeight;
179
+ img.modified = true;
163
180
 
164
181
  ctx = img.ctx;
165
182
 
166
183
  ctx.font = $.ctx.font;
167
- ctx.fillStyle = $.ctx.fillStyle;
168
- ctx.strokeStyle = $.ctx.strokeStyle;
184
+ ctx.fillStyle = $._fill;
185
+ ctx.strokeStyle = $._stroke;
169
186
  ctx.lineWidth = $.ctx.lineWidth;
170
187
  }
171
188
 
@@ -175,31 +192,49 @@ Q5.renderers.q2d.text = ($, q) => {
175
192
  ctx.fillStyle = 'black';
176
193
  }
177
194
 
178
- for (let i = 0; i < lines.length; i++) {
179
- if ($._doStroke && $._strokeSet) ctx.strokeText(lines[i], tX, tY);
180
- if ($._doFill) ctx.fillText(lines[i], tX, tY);
181
- tY += $._textLeading;
195
+ for (let line of lines) {
196
+ if ($._doStroke && $._strokeSet) ctx.strokeText(line, tX, tY);
197
+ if ($._doFill) ctx.fillText(line, tX, tY);
198
+ tY += leading;
182
199
  if (tY > h) break;
183
200
  }
201
+ lines.length = 0;
184
202
 
185
203
  if (!$._fillSet) ctx.fillStyle = ogFill;
186
204
 
187
- if (useCache) {
188
- img._ascent = ascent;
189
- img._descent = descent;
190
- $._tic.set(cacheKey, img);
191
- if (!$._genTextImage) $.textImage(img, x, y);
205
+ if (useCache || genTextImage) {
206
+ styleHashes.push(styleHash);
207
+ (cache[str] ??= {})[styleHash] = img;
208
+
209
+ cacheSize++;
210
+ if (cacheSize > cacheMax) {
211
+ let half = Math.ceil(cacheSize / 2);
212
+ let hashes = styleHashes.splice(0, half);
213
+ for (let s in cache) {
214
+ s = cache[s];
215
+ for (let h of hashes) delete s[h];
216
+ }
217
+ cacheSize -= half;
218
+ }
219
+
220
+ if (genTextImage) return img;
221
+ $.textImage(img, x, y);
192
222
  }
193
223
  };
194
224
  $.textImage = (img, x, y) => {
195
225
  let og = $._imageMode;
196
226
  $._imageMode = 'corner';
197
- if ($.ctx.textAlign == 'center') x -= img.width * 0.5;
198
- else if ($.ctx.textAlign == 'right') x -= img.width;
199
- if ($.ctx.textBaseline == 'alphabetic') y -= $._textLeading;
200
- if ($.ctx.textBaseline == 'middle') y -= img._descent + img._ascent * 0.5 + $._textLeadDiff;
201
- else if ($.ctx.textBaseline == 'bottom') y -= img._ascent + img._descent + $._textLeadDiff;
202
- else if ($.ctx.textBaseline == 'top') y -= img._descent + $._textLeadDiff;
227
+
228
+ let ta = $._textAlign;
229
+ if (ta == 'center') x -= img.canvas.hw;
230
+ else if (ta == 'right') x -= img.width;
231
+
232
+ let bl = $._textBaseline;
233
+ if (bl == 'alphabetic') y -= img._leading;
234
+ else if (bl == 'middle') y -= img._middle;
235
+ else if (bl == 'bottom') y -= img._bottom;
236
+ else if (bl == 'top') y -= img._top;
237
+
203
238
  $.image(img, x, y);
204
239
  $._imageMode = og;
205
240
  };
package/src/q5-canvas.js CHANGED
@@ -201,7 +201,7 @@ Q5.modules.canvas = ($, q) => {
201
201
 
202
202
  function parentResized() {
203
203
  if ($.frameCount > 1) {
204
- $._shouldResize = true;
204
+ $._didResize = true;
205
205
  $._adjustDisplay();
206
206
  }
207
207
  }
@@ -261,14 +261,9 @@ Q5.modules.canvas = ($, q) => {
261
261
  '_imageMode',
262
262
  '_rectMode',
263
263
  '_ellipseMode',
264
- '_textFont',
265
- '_textLeading',
266
- '_leadingSet',
267
264
  '_textSize',
268
265
  '_textAlign',
269
- '_textBaseline',
270
- '_textStyle',
271
- '_textWrap'
266
+ '_textBaseline'
272
267
  ];
273
268
  $._styles = [];
274
269
 
@@ -281,6 +276,15 @@ Q5.modules.canvas = ($, q) => {
281
276
  let styles = $._styles.pop();
282
277
  for (let s of $._styleNames) $[s] = styles[s];
283
278
  };
279
+
280
+ if (window && $._scope != 'graphics') {
281
+ window.addEventListener('resize', () => {
282
+ $._didResize = true;
283
+ q.windowWidth = window.innerWidth;
284
+ q.windowHeight = window.innerHeight;
285
+ q.deviceOrientation = window.screen?.orientation?.type;
286
+ });
287
+ }
284
288
  };
285
289
 
286
290
  Q5.canvasOptions = {
package/src/q5-core.js CHANGED
@@ -70,9 +70,9 @@ function Q5(scope, parent, renderer) {
70
70
  let ts = timestamp || performance.now();
71
71
  $._lastFrameTime ??= ts - $._targetFrameDuration;
72
72
 
73
- if ($._shouldResize) {
73
+ if ($._didResize) {
74
74
  $.windowResized();
75
- $._shouldResize = false;
75
+ $._didResize = false;
76
76
  }
77
77
 
78
78
  if ($._loop) looper = raf($._draw);
package/src/q5-util.js CHANGED
@@ -5,10 +5,11 @@ Q5.modules.util = ($, q) => {
5
5
  fetch(path)
6
6
  .then((r) => {
7
7
  if (type == 'json') return r.json();
8
- if (type == 'text') return r.text();
8
+ return r.text();
9
9
  })
10
10
  .then((r) => {
11
11
  q._preloadCount--;
12
+ if (type == 'csv') r = $.CSV.parse(r);
12
13
  Object.assign(ret, r);
13
14
  if (cb) cb(r);
14
15
  });
@@ -17,6 +18,21 @@ Q5.modules.util = ($, q) => {
17
18
 
18
19
  $.loadStrings = (path, cb) => $._loadFile(path, cb, 'text');
19
20
  $.loadJSON = (path, cb) => $._loadFile(path, cb, 'json');
21
+ $.loadCSV = (path, cb) => $._loadFile(path, cb, 'csv');
22
+
23
+ $.CSV = {};
24
+ $.CSV.parse = (csv, sep = ',', lineSep = '\n') => {
25
+ let a = [],
26
+ lns = csv.split(lineSep),
27
+ headers = lns[0].split(sep);
28
+ for (let i = 1; i < lns.length; i++) {
29
+ let o = {},
30
+ ln = lns[i].split(sep);
31
+ headers.forEach((h, i) => (o[h] = JSON.parse(ln[i])));
32
+ a.push(o);
33
+ }
34
+ return a;
35
+ };
20
36
 
21
37
  if (typeof localStorage == 'object') {
22
38
  $.storeItem = localStorage.setItem;