@pacem/pacem 1.0.0-bessel → 1.0.0-dirac
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/dist/browser/pacem-2d.js +149 -2
- package/dist/browser/pacem-2d.js.map +1 -1
- package/dist/browser/pacem-2d.min.js +1 -1
- package/dist/browser/pacem-3d.js +646 -2
- package/dist/browser/pacem-3d.js.map +1 -1
- package/dist/browser/pacem-3d.min.js +1 -1
- package/dist/browser/pacem-charts.js +21 -1
- package/dist/browser/pacem-charts.js.map +1 -1
- package/dist/browser/pacem-charts.min.js +1 -1
- package/dist/browser/pacem-cms.js +118 -3
- package/dist/browser/pacem-cms.js.map +1 -1
- package/dist/browser/pacem-cms.min.js +1 -1
- package/dist/browser/pacem-core.js +870 -9
- package/dist/browser/pacem-core.js.map +1 -1
- package/dist/browser/pacem-core.min.js +2 -2
- package/dist/browser/pacem-foundation.js +296 -1
- package/dist/browser/pacem-foundation.js.map +1 -1
- package/dist/browser/pacem-foundation.min.js +1 -1
- package/dist/browser/pacem-fx.js +18 -1
- package/dist/browser/pacem-fx.js.map +1 -1
- package/dist/browser/pacem-fx.min.js +1 -1
- package/dist/browser/pacem-logging.js +15 -1
- package/dist/browser/pacem-logging.js.map +1 -1
- package/dist/browser/pacem-logging.min.js +1 -1
- package/dist/browser/pacem-maps.js +97 -1
- package/dist/browser/pacem-maps.js.map +1 -1
- package/dist/browser/pacem-maps.min.js +1 -1
- package/dist/browser/pacem-media.js +14 -1
- package/dist/browser/pacem-media.js.map +1 -1
- package/dist/browser/pacem-media.min.js +1 -1
- package/dist/browser/pacem-networking.js +22 -1
- package/dist/browser/pacem-networking.js.map +1 -1
- package/dist/browser/pacem-networking.min.js +1 -1
- package/dist/browser/pacem-numerical.js +360 -1
- package/dist/browser/pacem-numerical.js.map +1 -1
- package/dist/browser/pacem-numerical.min.js +1 -1
- package/dist/browser/pacem-plus.js +195 -4
- package/dist/browser/pacem-plus.js.map +1 -1
- package/dist/browser/pacem-plus.min.js +1 -1
- package/dist/browser/pacem-scaffolding.js +420 -38
- package/dist/browser/pacem-scaffolding.js.map +1 -1
- package/dist/browser/pacem-scaffolding.min.js +2 -2
- package/dist/browser/pacem-ui.js +143 -19
- package/dist/browser/pacem-ui.js.map +1 -1
- package/dist/browser/pacem-ui.min.js +2 -2
- package/dist/bundle/pacem.min.mjs +147 -147
- package/dist/bundle/pacem.mjs +2905 -1417
- package/dist/bundle/pacem.mjs.map +3 -3
- package/dist/docs/pacem-2d.json +11549 -0
- package/dist/docs/pacem-3d.json +29096 -0
- package/dist/docs/pacem-charts.json +4244 -0
- package/dist/docs/pacem-cms.json +9325 -0
- package/dist/docs/pacem-core.json +40109 -0
- package/dist/docs/pacem-foundation.json +8941 -0
- package/dist/docs/pacem-fx.json +3121 -0
- package/dist/docs/pacem-logging.json +941 -0
- package/dist/docs/pacem-maps.json +17387 -0
- package/dist/docs/pacem-media.json +1203 -0
- package/dist/docs/pacem-networking.json +1798 -0
- package/dist/docs/pacem-numerical.json +13706 -0
- package/dist/docs/pacem-plus.json +8468 -0
- package/dist/docs/pacem-scaffolding.json +34788 -0
- package/dist/docs/pacem-ui.json +18628 -0
- package/dist/typings/index.d.ts +4109 -31
- package/dist/vscode.html-custom.json +1303 -741
- package/package.json +4 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem v1.0.0-
|
|
2
|
+
* @pacem/pacem v1.0.0-dirac (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
@@ -41,7 +41,14 @@
|
|
|
41
41
|
|
|
42
42
|
//namespace Pacem {
|
|
43
43
|
const JSON_DATE_PATTERN = /^\/Date\([\d]+\)\/$/i;
|
|
44
|
+
/** Date parsing and arithmetic utilities. */
|
|
44
45
|
class Dates {
|
|
46
|
+
/**
|
|
47
|
+
* Parses a value into a `Date`. Accepts ISO/parseable date strings, ASP.NET-style
|
|
48
|
+
* `/Date(ticks)/` JSON date strings, numeric timestamps, or a `Date` instance (returned as-is).
|
|
49
|
+
* @param input Value to parse.
|
|
50
|
+
* @returns The resulting `Date`.
|
|
51
|
+
*/
|
|
45
52
|
static parse(input) {
|
|
46
53
|
let d;
|
|
47
54
|
if (typeof input === 'string') {
|
|
@@ -57,9 +64,19 @@
|
|
|
57
64
|
else
|
|
58
65
|
return input;
|
|
59
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Gets whether the given year is a leap year.
|
|
69
|
+
* @param year Year to check.
|
|
70
|
+
*/
|
|
60
71
|
static isLeapYear(year) {
|
|
61
72
|
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
|
|
62
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Gets the number of days in a given month of a given year.
|
|
76
|
+
* @param year Year (used to account for leap Februaries).
|
|
77
|
+
* @param month Zero-based month index (0 = January, ..., 11 = December).
|
|
78
|
+
* @returns Number of days in the month.
|
|
79
|
+
*/
|
|
63
80
|
static daysInMonth(year, month) {
|
|
64
81
|
return [31, (Dates.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
|
|
65
82
|
}
|
|
@@ -77,6 +94,13 @@
|
|
|
77
94
|
static dateOnly(datetime) {
|
|
78
95
|
return new Date(datetime.getFullYear(), datetime.getMonth(), datetime.getDate());
|
|
79
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns a new date obtained by adding a (possibly negative) number of months to `input`,
|
|
99
|
+
* clamping the day-of-month to the target month's length (e.g. Jan 31 + 1 month => Feb 28/29).
|
|
100
|
+
* @param input Starting date.
|
|
101
|
+
* @param value Number of months to add.
|
|
102
|
+
* @returns The resulting date.
|
|
103
|
+
*/
|
|
80
104
|
static addMonths(input, value) {
|
|
81
105
|
let n = input.getDate(), i = new Date(input), month = i.getMonth() + value, years = 0;
|
|
82
106
|
while (month < 0) {
|
|
@@ -89,9 +113,21 @@
|
|
|
89
113
|
i.setDate(Math.min(n, Dates.daysInMonth(i.getFullYear(), i.getMonth())));
|
|
90
114
|
return i;
|
|
91
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns a new date obtained by adding a (possibly negative) number of days to `input`.
|
|
118
|
+
* @param input Starting date.
|
|
119
|
+
* @param value Number of days to add.
|
|
120
|
+
* @returns The resulting date.
|
|
121
|
+
*/
|
|
92
122
|
static addDays(input, value) {
|
|
93
123
|
return new Date(input.valueOf() + value * 86400000);
|
|
94
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Computes the number of days (possibly fractional) between two dates.
|
|
127
|
+
* @param start Start date.
|
|
128
|
+
* @param end End date.
|
|
129
|
+
* @returns Number of days between `start` and `end` (negative if `end` precedes `start`).
|
|
130
|
+
*/
|
|
95
131
|
static daysBetween(start, end) {
|
|
96
132
|
return (end.valueOf() - start.valueOf()) / 86400000;
|
|
97
133
|
}
|
|
@@ -100,10 +136,21 @@
|
|
|
100
136
|
|
|
101
137
|
/// <reference path="dates.ts" />
|
|
102
138
|
// namespace Pacem {
|
|
139
|
+
/** Null/empty value-checking utilities. */
|
|
103
140
|
class NullChecker {
|
|
141
|
+
/**
|
|
142
|
+
* Returns whether the given value is `null` or `undefined`.
|
|
143
|
+
* @param o Value to check.
|
|
144
|
+
*/
|
|
104
145
|
static isNull(o) {
|
|
105
146
|
return o === null || o === void 0;
|
|
106
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Returns whether the given value is "empty": an empty array, an empty string, or a
|
|
150
|
+
* plain object with no own enumerable keys (dates, regexes and file-system handles are never
|
|
151
|
+
* considered empty).
|
|
152
|
+
* @param o Value to check.
|
|
153
|
+
*/
|
|
107
154
|
static isEmpty(o) {
|
|
108
155
|
try {
|
|
109
156
|
return (Array.isArray(o) && o.length === 0)
|
|
@@ -115,6 +162,10 @@
|
|
|
115
162
|
return false;
|
|
116
163
|
}
|
|
117
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Returns whether the given value is `null`, `undefined`, or "empty" (see {@link NullChecker.isEmpty}).
|
|
167
|
+
* @param o Value to check.
|
|
168
|
+
*/
|
|
118
169
|
static isNullOrEmpty(o) {
|
|
119
170
|
return NullChecker.isNull(o) || NullChecker.isEmpty(o);
|
|
120
171
|
}
|
|
@@ -209,9 +260,18 @@
|
|
|
209
260
|
}
|
|
210
261
|
return output;
|
|
211
262
|
}
|
|
263
|
+
/** Deep-merging utility for plain objects. */
|
|
212
264
|
class DeepMerger {
|
|
213
265
|
constructor() {
|
|
214
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Recursively merges `source` into `target`, in place: plain-object properties are merged
|
|
269
|
+
* recursively (into a fresh object, never adopted by reference), arrays are shallow-copied,
|
|
270
|
+
* and any other value overwrites the target's.
|
|
271
|
+
* @param source Object whose properties are merged into `target`.
|
|
272
|
+
* @param target Object to merge into (defaults to, and lazily creates, the global `Pacem` namespace object).
|
|
273
|
+
* @returns The mutated `target`, typed as the union of both inputs.
|
|
274
|
+
*/
|
|
215
275
|
static merge(source, target) {
|
|
216
276
|
target ??= (globalThis['Pacem'] ??= {});
|
|
217
277
|
return deepMerge(target, source);
|
|
@@ -219,6 +279,7 @@
|
|
|
219
279
|
}
|
|
220
280
|
|
|
221
281
|
//namespace Pacem {
|
|
282
|
+
/** Array manipulation utilities. */
|
|
222
283
|
class Arrays {
|
|
223
284
|
static range(min, max, items = 2) {
|
|
224
285
|
let x = min;
|
|
@@ -237,6 +298,12 @@
|
|
|
237
298
|
}
|
|
238
299
|
return output;
|
|
239
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Splits an array into chunks of a given size.
|
|
303
|
+
* @param source Array to split.
|
|
304
|
+
* @param size Maximum size of each chunk (must be a positive integer).
|
|
305
|
+
* @returns Array of chunks; an empty array if `source` is null or empty.
|
|
306
|
+
*/
|
|
240
307
|
static chunkify(source, size) {
|
|
241
308
|
if (NullChecker.isNullOrEmpty(source)) {
|
|
242
309
|
return [];
|
|
@@ -254,7 +321,13 @@
|
|
|
254
321
|
//}
|
|
255
322
|
|
|
256
323
|
//namespace Pacem {
|
|
324
|
+
/** Conversion utilities between `Blob`, base64/data URLs and plain text. */
|
|
257
325
|
class Blobs {
|
|
326
|
+
/**
|
|
327
|
+
* Reads a `Blob` and resolves it as a base64-encoded data URL.
|
|
328
|
+
* @param blob Blob to read.
|
|
329
|
+
* @returns A promise resolving with the data URL string.
|
|
330
|
+
*/
|
|
258
331
|
static blobToDataURL(blob) {
|
|
259
332
|
return new Promise((resolve, _) => {
|
|
260
333
|
const a = new FileReader();
|
|
@@ -270,10 +343,21 @@
|
|
|
270
343
|
}
|
|
271
344
|
return u8arr;
|
|
272
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Converts a base64-encoded string into a `Blob`.
|
|
348
|
+
* @param bstr Base64-encoded string (without the `data:...;base64,` prefix).
|
|
349
|
+
* @param options Blob options (e.g. MIME `type`).
|
|
350
|
+
* @returns The resulting `Blob`.
|
|
351
|
+
*/
|
|
273
352
|
static base64ToBlob(bstr, options) {
|
|
274
353
|
const u8arr = Blobs._b64ToUint8Array(atob(bstr));
|
|
275
354
|
return new Blob([u8arr], options);
|
|
276
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Converts a data URL (`data:<mime>;base64,<data>`) into a `Blob`, inferring the MIME type when present.
|
|
358
|
+
* @param dataurl Data URL to convert.
|
|
359
|
+
* @returns The resulting `Blob`.
|
|
360
|
+
*/
|
|
277
361
|
static dataURLToBlob(dataurl) {
|
|
278
362
|
const coma = dataurl.indexOf(',');
|
|
279
363
|
if (coma === -1) {
|
|
@@ -285,6 +369,12 @@
|
|
|
285
369
|
return Blobs.base64ToBlob(bstr, { type: mime });
|
|
286
370
|
}
|
|
287
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Reads a `Blob` and decodes it as text.
|
|
374
|
+
* @param blob Blob to read.
|
|
375
|
+
* @param encoding Text encoding to use for decoding (defaults to the platform's default, typically UTF-8).
|
|
376
|
+
* @returns A promise resolving with the decoded text.
|
|
377
|
+
*/
|
|
288
378
|
static async blobToText(blob, encoding) {
|
|
289
379
|
const buffer = await blob.arrayBuffer();
|
|
290
380
|
const decoder = new TextDecoder(encoding, { fatal: false });
|
|
@@ -371,6 +461,11 @@
|
|
|
371
461
|
return match ? match[2] : null;
|
|
372
462
|
};
|
|
373
463
|
// https://stackoverflow.com/77095874
|
|
464
|
+
/**
|
|
465
|
+
* Detects the browser name and version from a user-agent string.
|
|
466
|
+
* @param userAgent User-agent string to parse (defaults to `navigator.userAgent`).
|
|
467
|
+
* @returns An object with the detected browser `name` (e.g. `Chrome`, `Firefox`, `Safari`, `Edge`, `IE`, `unknown`, ...) and its `version`.
|
|
468
|
+
*/
|
|
374
469
|
function detect(userAgent = navigator.userAgent) {
|
|
375
470
|
let browser = 'unknown';
|
|
376
471
|
let version = null;
|
|
@@ -569,9 +664,13 @@
|
|
|
569
664
|
return v.toString(16).padStart(2, '0');
|
|
570
665
|
//return Pacem.Strings.leftPad(v.toString(16), 2, '0');
|
|
571
666
|
}
|
|
667
|
+
/** Color parsing, conversion (RGB/HSL/CIE XYZ) and manipulation utilities. */
|
|
572
668
|
class Colors {
|
|
669
|
+
/** Minimum WCAG contrast ratio recommended for large-scale text/graphical objects. */
|
|
573
670
|
static { this.WCAG_MINIMUM_CONTRAST = 3.0; }
|
|
671
|
+
/** Minimum WCAG (AA) contrast ratio recommended for normal-sized text. */
|
|
574
672
|
static { this.WCAG_NORMAL_TEXT_CONTRAST = 4.5; }
|
|
673
|
+
/** Minimum WCAG (AAA) contrast ratio recommended for enhanced accessibility. */
|
|
575
674
|
static { this.WCAG_SAFE_CONTRAST = 7.0; }
|
|
576
675
|
static _clampRGB(rgb) {
|
|
577
676
|
const clmp = (n) => Math.min(1.0, Math.max(0, n));
|
|
@@ -583,6 +682,11 @@
|
|
|
583
682
|
static _normalize(rgb) {
|
|
584
683
|
return [rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0, rgb[3]];
|
|
585
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* Parses a CSS color string (`rgb()`, `rgba()`, `#rrggbb`, `#rgb`, `hsl()` or `hsla()`) into an {@link Rgba} value.
|
|
687
|
+
* @param clr CSS color string.
|
|
688
|
+
* @returns The parsed color, or `undefined` if the string does not match any supported format.
|
|
689
|
+
*/
|
|
586
690
|
static parse(clr) {
|
|
587
691
|
clr = (clr || '').toLowerCase();
|
|
588
692
|
/* rgb(255,255,255) */
|
|
@@ -619,6 +723,11 @@
|
|
|
619
723
|
return this.rgb(hsla);
|
|
620
724
|
}
|
|
621
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Converts an {@link Rgba} color into its {@link Hsla} equivalent.
|
|
728
|
+
* @param rgb Color to convert.
|
|
729
|
+
* @returns The converted color.
|
|
730
|
+
*/
|
|
622
731
|
static hsl(rgb) {
|
|
623
732
|
const l = luminance(rgb), s = saturation(rgb, l), h = hueCircle(rgb);
|
|
624
733
|
return { h, s, l, a: rgb.a };
|
|
@@ -767,6 +876,11 @@
|
|
|
767
876
|
|
|
768
877
|
// 1.7763568394002505e-15 -20.4551920341394 40 40
|
|
769
878
|
const FLOAT_PATTERN = /[-+]?([\d]+(\.[\d]+(e-[\d]+)?)?|[\d]*(\.[\d]+(e-[\d]+)?))/g;
|
|
879
|
+
/**
|
|
880
|
+
* Extracts all the floating point numbers found in a string (e.g. `"10px 20px"` => `[10, 20]`).
|
|
881
|
+
* @param input String to parse.
|
|
882
|
+
* @returns Array of the numbers found, in order of appearance.
|
|
883
|
+
*/
|
|
770
884
|
function parseAsNumericalArray(input) {
|
|
771
885
|
const arr = [];
|
|
772
886
|
let reg = input.match(FLOAT_PATTERN);
|
|
@@ -775,7 +889,12 @@
|
|
|
775
889
|
}
|
|
776
890
|
return arr;
|
|
777
891
|
}
|
|
892
|
+
/** Angle-related utilities. */
|
|
778
893
|
class Angle {
|
|
894
|
+
/**
|
|
895
|
+
* Type-guards a value as a valid {@link Angle} object.
|
|
896
|
+
* @param obj Value to check.
|
|
897
|
+
*/
|
|
779
898
|
static isAngle(obj) {
|
|
780
899
|
return typeof obj === 'object'
|
|
781
900
|
&& 'vertex' in obj && Point.isPoint(obj['vertex'])
|
|
@@ -809,7 +928,14 @@
|
|
|
809
928
|
return -Math.atan2(cross, dot);
|
|
810
929
|
}
|
|
811
930
|
}
|
|
931
|
+
/** Size-related utilities. */
|
|
812
932
|
class Size {
|
|
933
|
+
/**
|
|
934
|
+
* Parses a string containing exactly two numbers into a {@link Size}.
|
|
935
|
+
* @param sz String to parse (e.g. `"100 50"`).
|
|
936
|
+
* @returns The parsed size.
|
|
937
|
+
* @throws If `sz` does not contain exactly two numbers.
|
|
938
|
+
*/
|
|
813
939
|
static parse(sz) {
|
|
814
940
|
let arr = parseAsNumericalArray(sz);
|
|
815
941
|
if (arr && arr.length === 2) {
|
|
@@ -817,13 +943,24 @@
|
|
|
817
943
|
}
|
|
818
944
|
throw new Error(`Cannot parse "${sz}" as a valid Size.`);
|
|
819
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Type-guards a value as a valid {@link Size} object.
|
|
948
|
+
* @param obj Value to check.
|
|
949
|
+
*/
|
|
820
950
|
static isSize(obj) {
|
|
821
951
|
return typeof obj === 'object'
|
|
822
952
|
&& 'width' in obj && typeof obj['width'] === 'number'
|
|
823
953
|
&& 'height' in obj && typeof obj['height'] === 'number';
|
|
824
954
|
}
|
|
825
955
|
}
|
|
956
|
+
/** Point-related utilities. */
|
|
826
957
|
class Point {
|
|
958
|
+
/**
|
|
959
|
+
* Parses a string containing exactly two numbers into a {@link Point}.
|
|
960
|
+
* @param pt String to parse (e.g. `"10 20"`).
|
|
961
|
+
* @returns The parsed point.
|
|
962
|
+
* @throws If `pt` does not contain exactly two numbers.
|
|
963
|
+
*/
|
|
827
964
|
static parse(pt) {
|
|
828
965
|
let arr = parseAsNumericalArray(pt);
|
|
829
966
|
if (arr && arr.length === 2) {
|
|
@@ -831,6 +968,10 @@
|
|
|
831
968
|
}
|
|
832
969
|
throw new Error(`Cannot parse "${pt}" as a valid Point.`);
|
|
833
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* Type-guards a value as a valid {@link Point} object.
|
|
973
|
+
* @param obj Value to check.
|
|
974
|
+
*/
|
|
834
975
|
static isPoint(obj) {
|
|
835
976
|
return typeof obj === 'object'
|
|
836
977
|
&& 'x' in obj && typeof obj['x'] === 'number'
|
|
@@ -877,22 +1018,42 @@
|
|
|
877
1018
|
//}
|
|
878
1019
|
|
|
879
1020
|
/// <reference path="geom.ts" />
|
|
1021
|
+
/** 2D affine matrix ([a b; c d] linear part, [e f] translation) manipulation utilities. */
|
|
880
1022
|
class Matrix2D {
|
|
1023
|
+
/**
|
|
1024
|
+
* Returns whether the given matrix is the identity matrix.
|
|
1025
|
+
* @param m Matrix to check.
|
|
1026
|
+
*/
|
|
881
1027
|
static isIdentity(m) {
|
|
882
1028
|
return m.a === 1 && m.d === 1 && m.b === 0 && m.f === 0 && m.c === 0 && m.e === 0;
|
|
883
1029
|
}
|
|
1030
|
+
/** Gets a fresh identity matrix. @readonly */
|
|
884
1031
|
static get identity() {
|
|
885
1032
|
return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
886
1033
|
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Returns a shallow copy of the given matrix.
|
|
1036
|
+
* @param m Matrix to copy.
|
|
1037
|
+
*/
|
|
887
1038
|
static copy(m) {
|
|
888
1039
|
return { a: m.a, b: m.b, c: m.c, d: m.d, e: m.e, f: m.f };
|
|
889
1040
|
}
|
|
890
1041
|
static scale(m, sx, sy = sx) {
|
|
891
1042
|
return { a: m.a * sx, b: m.b, c: m.c, d: m.d * sy, e: m.e, f: m.f };
|
|
892
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Returns a copy of `m` translated by the given vector.
|
|
1046
|
+
* @param m Matrix to translate.
|
|
1047
|
+
* @param t Translation vector.
|
|
1048
|
+
*/
|
|
893
1049
|
static translate(m, t) {
|
|
894
1050
|
return { a: m.a, b: m.b, c: m.c, d: m.d, e: m.e + t.x, f: m.f + t.y };
|
|
895
1051
|
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Returns a copy of `m` rotated (about the origin) by the given angle.
|
|
1054
|
+
* @param m Matrix to rotate.
|
|
1055
|
+
* @param rad Rotation angle, in radians.
|
|
1056
|
+
*/
|
|
896
1057
|
static rotate(m, rad) {
|
|
897
1058
|
const cos = Math.cos(rad), sin = Math.sin(rad);
|
|
898
1059
|
return Matrix2D.multiply(m, { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 });
|
|
@@ -920,9 +1081,18 @@
|
|
|
920
1081
|
f: m1.e * m2.b + m1.f * m2.d + m2.f
|
|
921
1082
|
};
|
|
922
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Computes the determinant of the matrix's linear part.
|
|
1086
|
+
* @param m Matrix to compute the determinant of.
|
|
1087
|
+
*/
|
|
923
1088
|
static det(m) {
|
|
924
1089
|
return m.a * m.d - m.c * m.b;
|
|
925
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Computes the inverse of the given matrix.
|
|
1093
|
+
* @param m Matrix to invert.
|
|
1094
|
+
* @returns The inverse matrix, or `null` if `m` is singular (determinant is zero).
|
|
1095
|
+
*/
|
|
926
1096
|
static invert(m) {
|
|
927
1097
|
const det = this.det(m);
|
|
928
1098
|
if (det === 0) {
|
|
@@ -946,6 +1116,12 @@
|
|
|
946
1116
|
f: m.e * m.b - m.a * m.f
|
|
947
1117
|
}, invdet);
|
|
948
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Decomposes a matrix into its scale, rotation and translation components.
|
|
1121
|
+
* @param m Matrix to decompose.
|
|
1122
|
+
* @param transformOrigin Origin about which the rotation/scale are considered to be applied (defaults to `{x:0, y:0}`).
|
|
1123
|
+
* @returns The decomposed `scaleX`, `scaleY`, `rotation` (radians) and `translation`.
|
|
1124
|
+
*/
|
|
949
1125
|
static toComponents(m, transformOrigin = { x: 0, y: 0 }) {
|
|
950
1126
|
const scaleX = Math.sqrt(m.a * m.a + m.c * m.c);
|
|
951
1127
|
const scaleY = Math.sqrt(m.b * m.b + m.d * m.d);
|
|
@@ -1009,7 +1185,14 @@
|
|
|
1009
1185
|
}
|
|
1010
1186
|
// }
|
|
1011
1187
|
|
|
1188
|
+
/** Rectangle-related utilities (parsing, transform computation, intersection, bounding box). */
|
|
1012
1189
|
class Rect {
|
|
1190
|
+
/**
|
|
1191
|
+
* Parses a string containing exactly four numbers into a {@link Rect}.
|
|
1192
|
+
* @param rect String to parse (e.g. `"0 0 100 50"`, in `x y width height` order).
|
|
1193
|
+
* @returns The parsed rect.
|
|
1194
|
+
* @throws If `rect` does not contain exactly four numbers.
|
|
1195
|
+
*/
|
|
1013
1196
|
static parse(rect) {
|
|
1014
1197
|
let arr = parseAsNumericalArray(rect);
|
|
1015
1198
|
if (arr && arr.length === 4) {
|
|
@@ -1017,6 +1200,10 @@
|
|
|
1017
1200
|
}
|
|
1018
1201
|
throw new Error(`Cannot parse "${rect}" as a valid Rect.`);
|
|
1019
1202
|
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Type-guards a value as a valid {@link Rect} object.
|
|
1205
|
+
* @param obj Value to check.
|
|
1206
|
+
*/
|
|
1020
1207
|
static isRect(obj) {
|
|
1021
1208
|
return Point.isPoint(obj) && Size.isSize(obj);
|
|
1022
1209
|
}
|
|
@@ -1139,6 +1326,10 @@
|
|
|
1139
1326
|
// namespace Pacem {
|
|
1140
1327
|
/** Grants undo/redo functionalities for given state. */
|
|
1141
1328
|
class HistoryService {
|
|
1329
|
+
/**
|
|
1330
|
+
* @param state Initial state.
|
|
1331
|
+
* @param maxlength Maximum number of stored states (undo queue + redo stack); unbounded when omitted.
|
|
1332
|
+
*/
|
|
1142
1333
|
constructor(state, maxlength) {
|
|
1143
1334
|
this.#current = state;
|
|
1144
1335
|
this.#maxlength = maxlength;
|
|
@@ -1156,9 +1347,11 @@
|
|
|
1156
1347
|
get current() {
|
|
1157
1348
|
return this.#current;
|
|
1158
1349
|
}
|
|
1350
|
+
/** Gets whether there is a previous state to revert to. @readonly */
|
|
1159
1351
|
get canUndo() {
|
|
1160
1352
|
return this.#backwards.length > 0;
|
|
1161
1353
|
}
|
|
1354
|
+
/** Gets whether there is a subsequent state to reapply. @readonly */
|
|
1162
1355
|
get canRedo() {
|
|
1163
1356
|
return this.#forwards.length > 0;
|
|
1164
1357
|
}
|
|
@@ -1167,6 +1360,7 @@
|
|
|
1167
1360
|
set maxlength(v) {
|
|
1168
1361
|
this.#maxlength = v;
|
|
1169
1362
|
}
|
|
1363
|
+
/** Reverts to the previous state, if any (see {@link canUndo}), moving the current state onto the redo stack. */
|
|
1170
1364
|
undo() {
|
|
1171
1365
|
if (this.canUndo) {
|
|
1172
1366
|
const back = this.#backwards, fore = this.#forwards, current = this.#current;
|
|
@@ -1174,6 +1368,7 @@
|
|
|
1174
1368
|
this.#current = back.pop();
|
|
1175
1369
|
}
|
|
1176
1370
|
}
|
|
1371
|
+
/** Reapplies the next state, if any (see {@link canRedo}), moving the current state onto the undo queue. */
|
|
1177
1372
|
redo() {
|
|
1178
1373
|
if (this.canRedo) {
|
|
1179
1374
|
const back = this.#backwards, fore = this.#forwards, current = this.#current;
|
|
@@ -1181,6 +1376,11 @@
|
|
|
1181
1376
|
this.#current = fore.shift();
|
|
1182
1377
|
}
|
|
1183
1378
|
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Pushes a new current state, clearing the redo stack and enqueuing the previous current state for undo.
|
|
1381
|
+
* Trims the undo queue to `maxlength`, if set.
|
|
1382
|
+
* @param state New current state.
|
|
1383
|
+
*/
|
|
1184
1384
|
push(state) {
|
|
1185
1385
|
this.#forwards.splice(0);
|
|
1186
1386
|
this.#backwards.push(this.#current);
|
|
@@ -1190,6 +1390,7 @@
|
|
|
1190
1390
|
queue.splice(0, queue.length - max);
|
|
1191
1391
|
}
|
|
1192
1392
|
}
|
|
1393
|
+
/** Clears both the undo queue and the redo stack, leaving the current state untouched. */
|
|
1193
1394
|
reset() {
|
|
1194
1395
|
this.#forwards.splice(0);
|
|
1195
1396
|
this.#backwards.splice(0);
|
|
@@ -1205,7 +1406,13 @@
|
|
|
1205
1406
|
quality: 1,
|
|
1206
1407
|
type: 'image/jpeg'
|
|
1207
1408
|
};
|
|
1409
|
+
/** Image loading and resizing utilities, based on `OffscreenCanvas`. */
|
|
1208
1410
|
class Imaging {
|
|
1411
|
+
/**
|
|
1412
|
+
* Fetches an image from a URL as a `Blob`.
|
|
1413
|
+
* @param url URL of the image to load.
|
|
1414
|
+
* @returns A promise resolving with the loaded `Blob`, or `null` if the fetch response was not OK.
|
|
1415
|
+
*/
|
|
1209
1416
|
static async loadImage(url) {
|
|
1210
1417
|
const response = await fetch(url);
|
|
1211
1418
|
if (!response.ok) {
|
|
@@ -1248,16 +1455,30 @@
|
|
|
1248
1455
|
// }
|
|
1249
1456
|
|
|
1250
1457
|
// namespace Pacem {
|
|
1458
|
+
/** Unique key/id generation utilities. */
|
|
1251
1459
|
class Keys {
|
|
1252
1460
|
static #seed;
|
|
1461
|
+
/**
|
|
1462
|
+
* Generates a process-wide monotonically increasing numeric id, seeded from the current timestamp.
|
|
1463
|
+
* @returns A new unique numeric id.
|
|
1464
|
+
*/
|
|
1253
1465
|
static uniqueId() {
|
|
1254
1466
|
let seed = Keys.#seed || Date.now();
|
|
1255
1467
|
return Keys.#seed = ++seed;
|
|
1256
1468
|
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Generates a process-wide unique id, base-62 encoded.
|
|
1471
|
+
* @returns A new unique string code.
|
|
1472
|
+
*/
|
|
1257
1473
|
static uniqueCode() {
|
|
1258
1474
|
const newid = Keys.uniqueId();
|
|
1259
1475
|
return newid.toBase62();
|
|
1260
1476
|
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Generates a random base-62 string of at least the given length.
|
|
1479
|
+
* @param length Minimum length of the generated string.
|
|
1480
|
+
* @returns A random string.
|
|
1481
|
+
*/
|
|
1261
1482
|
static randomString(length) {
|
|
1262
1483
|
let retval = '';
|
|
1263
1484
|
do {
|
|
@@ -1269,13 +1490,32 @@
|
|
|
1269
1490
|
// }
|
|
1270
1491
|
|
|
1271
1492
|
// namespace Pacem {
|
|
1493
|
+
/** Numeric utilities. */
|
|
1272
1494
|
class Numbers {
|
|
1495
|
+
/**
|
|
1496
|
+
* Rounds a number to the nearest multiple of `step`.
|
|
1497
|
+
* @param x Value to round.
|
|
1498
|
+
* @param step Rounding step/increment (defaults to 1).
|
|
1499
|
+
* @returns The rounded value.
|
|
1500
|
+
*/
|
|
1273
1501
|
static round(x, step = 1.0) {
|
|
1274
1502
|
return Math.round(x / step) * step;
|
|
1275
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Computes the logarithm of `x` in an arbitrary `base`.
|
|
1506
|
+
* @param x Value to compute the logarithm of.
|
|
1507
|
+
* @param base Logarithm base.
|
|
1508
|
+
*/
|
|
1276
1509
|
static log(x, base) {
|
|
1277
1510
|
return Math.log10(x) / Math.log10(base);
|
|
1278
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Converts a number (or numeric string) from one base to another.
|
|
1514
|
+
* @param v Value to convert, expressed in base `from`.
|
|
1515
|
+
* @param from Source base (2-36).
|
|
1516
|
+
* @param to Target base (2-36).
|
|
1517
|
+
* @returns The value re-expressed in base `to`, as a string; `"NaN"` if `v` is not a valid number in base `from`.
|
|
1518
|
+
*/
|
|
1279
1519
|
static rebase(v, from, to) {
|
|
1280
1520
|
const vStr = v.toString();
|
|
1281
1521
|
const firstPass = parseInt(vStr, from);
|
|
@@ -1428,6 +1668,7 @@
|
|
|
1428
1668
|
return diffInternal(aTokens, bTokens, dp, '\n');
|
|
1429
1669
|
}
|
|
1430
1670
|
//#endregion
|
|
1671
|
+
/** String manipulation utilities. */
|
|
1431
1672
|
class Strings {
|
|
1432
1673
|
/** @deprecated Use String.prototype.padStart() instead. */
|
|
1433
1674
|
static leftPad(v, targetLength, padChar) {
|
|
@@ -1437,6 +1678,14 @@
|
|
|
1437
1678
|
// retval = padChar + retval;
|
|
1438
1679
|
//return retval;
|
|
1439
1680
|
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Formats a template string, replacing `{0}`, `{1}`, ... placeholders with the corresponding arguments.
|
|
1683
|
+
* Doubled braces (`{{`/`}}`) escape a placeholder.
|
|
1684
|
+
* @param template Template string containing `{n}` placeholders.
|
|
1685
|
+
* @param args Values to substitute into the placeholders, by index.
|
|
1686
|
+
* @returns The formatted string.
|
|
1687
|
+
* @throws If the template references an argument index that was not supplied.
|
|
1688
|
+
*/
|
|
1440
1689
|
static format(template, ...args) {
|
|
1441
1690
|
const array = Array.from(args);
|
|
1442
1691
|
let retval = template;
|
|
@@ -1452,6 +1701,13 @@
|
|
|
1452
1701
|
}
|
|
1453
1702
|
return retval;
|
|
1454
1703
|
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Computes a diff between two strings, using the Longest Common Subsequence algorithm.
|
|
1706
|
+
* @param a First (original) string.
|
|
1707
|
+
* @param b Second (modified) string.
|
|
1708
|
+
* @param mode Diffing granularity: `'char'` (default), `'word'` or `'line'`.
|
|
1709
|
+
* @returns Array of {@link DiffToken}s describing unchanged, removed and added chunks.
|
|
1710
|
+
*/
|
|
1455
1711
|
static diff(a, b, mode = 'char') {
|
|
1456
1712
|
switch (mode) {
|
|
1457
1713
|
case 'line':
|
|
@@ -1462,6 +1718,12 @@
|
|
|
1462
1718
|
return diffChars(a, b);
|
|
1463
1719
|
}
|
|
1464
1720
|
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Computes the cryptographic hash of a string using the SubtleCrypto API.
|
|
1723
|
+
* @param input String to hash.
|
|
1724
|
+
* @param algo Hash algorithm to use (defaults to `SHA-256`).
|
|
1725
|
+
* @returns A promise resolving with the hash, hex-encoded.
|
|
1726
|
+
*/
|
|
1465
1727
|
static async hash(input, algo = 'SHA-256') {
|
|
1466
1728
|
const hashBuffer = await crypto.subtle.digest(algo, new TextEncoder().encode(input));
|
|
1467
1729
|
return Array.from(new Uint8Array(hashBuffer))
|
|
@@ -1494,10 +1756,21 @@
|
|
|
1494
1756
|
return tokens;
|
|
1495
1757
|
}
|
|
1496
1758
|
}
|
|
1759
|
+
/** Generic tokenize-then-render text parser: lexes input against a grammar, then renders the resulting token tree. */
|
|
1497
1760
|
let Parser$1 = class Parser {
|
|
1761
|
+
/**
|
|
1762
|
+
* @param _lexer Lexer implementation to use (defaults to a built-in greedy, rule-ordered lexer).
|
|
1763
|
+
*/
|
|
1498
1764
|
constructor(_lexer = new LexerClass()) {
|
|
1499
1765
|
this._lexer = _lexer;
|
|
1500
1766
|
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Tokenizes `input` against `grammar` and renders the resulting token tree.
|
|
1769
|
+
* @param input String to parse.
|
|
1770
|
+
* @param grammar Ordered list of rules describing the grammar.
|
|
1771
|
+
* @param renderer Renderer used to turn the token tree into the output string.
|
|
1772
|
+
* @returns The rendered output.
|
|
1773
|
+
*/
|
|
1501
1774
|
parse(input, grammar, renderer) {
|
|
1502
1775
|
const tokens = this._lexer.tokenize(input, grammar);
|
|
1503
1776
|
return renderer.render(tokens);
|
|
@@ -1620,6 +1893,12 @@
|
|
|
1620
1893
|
MARKDOWN_RULES[MARKDOWN_RULES["Text"] = 254] = "Text";
|
|
1621
1894
|
MARKDOWN_RULES[MARKDOWN_RULES["Space"] = 255] = "Space";
|
|
1622
1895
|
})(MARKDOWN_RULES || (MARKDOWN_RULES = {}));
|
|
1896
|
+
/**
|
|
1897
|
+
* Builds the full Markdown grammar (block + inline rules, comment/reference handling first),
|
|
1898
|
+
* optionally merging in caller-supplied {@link ExtraRule}s.
|
|
1899
|
+
* @param extra Additional block/inline/code rules to plug into the built-in grammar.
|
|
1900
|
+
* @returns Ordered list of {@link Compile.Rule}s ready to be fed to a {@link Compile.Lexer}.
|
|
1901
|
+
*/
|
|
1623
1902
|
function getGrammar(extra = []) {
|
|
1624
1903
|
const inert = {
|
|
1625
1904
|
// comment
|
|
@@ -1970,6 +2249,7 @@
|
|
|
1970
2249
|
return input?.replace(TRIM_INSIDE, '\n').trim();
|
|
1971
2250
|
}
|
|
1972
2251
|
// #endregion
|
|
2252
|
+
/** Renders a Markdown token tree, as produced by the grammar returned by {@link getGrammar}, into HTML markup. */
|
|
1973
2253
|
class HtmlRenderer {
|
|
1974
2254
|
constructor(_extraItemRender = (token, __) => token?.text || '') {
|
|
1975
2255
|
this._extraItemRender = _extraItemRender;
|
|
@@ -2061,6 +2341,7 @@
|
|
|
2061
2341
|
return trim(this._render(tree));
|
|
2062
2342
|
}
|
|
2063
2343
|
}
|
|
2344
|
+
/** Renders a Markdown token tree as a JSON array of tokens, mainly useful for debugging/inspection. */
|
|
2064
2345
|
class TokenRenderer {
|
|
2065
2346
|
_render(token) {
|
|
2066
2347
|
return NullChecker.isNullOrEmpty(token) ? '' : JSON.stringify(token);
|
|
@@ -2072,7 +2353,15 @@
|
|
|
2072
2353
|
function isRenderer(obj) {
|
|
2073
2354
|
return obj && typeof obj === 'object' && typeof obj.render === 'function';
|
|
2074
2355
|
}
|
|
2356
|
+
/** Markdown parser: tokenizes Markdown source and renders it to HTML (or another format via a custom {@link Compile.Renderer}). */
|
|
2075
2357
|
class Parser extends Parser$1 {
|
|
2358
|
+
/**
|
|
2359
|
+
* Parses Markdown `input` and renders it to an HTML string.
|
|
2360
|
+
* @param input Markdown source.
|
|
2361
|
+
* @param extraGrammar Additional grammar rules to extend the built-in Markdown syntax.
|
|
2362
|
+
* @param extraItemRenderer Fallback renderer invoked for token types not natively handled (e.g. tokens produced by `extraGrammar`); defaults to returning the token's `text`.
|
|
2363
|
+
* @returns The rendered HTML string.
|
|
2364
|
+
*/
|
|
2076
2365
|
toHtml(input, extraGrammar, extraItemRenderer) {
|
|
2077
2366
|
const renderer = new HtmlRenderer(extraItemRenderer);
|
|
2078
2367
|
if (NullChecker.isNullOrEmpty(extraGrammar)) {
|
|
@@ -2080,6 +2369,12 @@
|
|
|
2080
2369
|
}
|
|
2081
2370
|
return this.parse(input, getGrammar(extraGrammar), renderer);
|
|
2082
2371
|
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Parses Markdown `input` and returns its token tree as a plain (JSON-serializable) array, useful for inspection/debugging.
|
|
2374
|
+
* @param input Markdown source.
|
|
2375
|
+
* @param extraGrammar Additional grammar rules to extend the built-in Markdown syntax.
|
|
2376
|
+
* @returns The parsed token tree.
|
|
2377
|
+
*/
|
|
2083
2378
|
tokenize(input, extraGrammar) {
|
|
2084
2379
|
const json = this.parse(input, getGrammar(extraGrammar), new TokenRenderer());
|
|
2085
2380
|
return JSON.parse(json);
|