@multiplekex/shallot 0.1.12 → 0.2.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.
Files changed (62) hide show
  1. package/package.json +3 -4
  2. package/src/core/builder.ts +71 -32
  3. package/src/core/component.ts +25 -11
  4. package/src/core/index.ts +14 -13
  5. package/src/core/math.ts +135 -0
  6. package/src/core/runtime.ts +0 -1
  7. package/src/core/state.ts +9 -68
  8. package/src/core/xml.ts +381 -265
  9. package/src/editor/format.ts +5 -0
  10. package/src/editor/index.ts +101 -0
  11. package/src/extras/arrows/index.ts +28 -69
  12. package/src/extras/gradient/index.ts +36 -52
  13. package/src/extras/lines/index.ts +51 -122
  14. package/src/extras/orbit/index.ts +40 -15
  15. package/src/extras/text/font.ts +546 -0
  16. package/src/extras/text/index.ts +158 -204
  17. package/src/extras/text/sdf.ts +429 -0
  18. package/src/standard/activity/index.ts +172 -0
  19. package/src/standard/compute/graph.ts +23 -23
  20. package/src/standard/compute/index.ts +76 -61
  21. package/src/standard/defaults.ts +8 -5
  22. package/src/standard/index.ts +1 -0
  23. package/src/standard/input/index.ts +30 -19
  24. package/src/standard/loading/index.ts +18 -13
  25. package/src/standard/render/bvh/blas.ts +752 -0
  26. package/src/standard/render/bvh/radix.ts +476 -0
  27. package/src/standard/render/bvh/structs.ts +167 -0
  28. package/src/standard/render/bvh/tlas.ts +886 -0
  29. package/src/standard/render/bvh/traverse.ts +467 -0
  30. package/src/standard/render/camera.ts +302 -27
  31. package/src/standard/render/data.ts +93 -0
  32. package/src/standard/render/depth.ts +117 -0
  33. package/src/standard/render/forward/index.ts +259 -0
  34. package/src/standard/render/forward/raster.ts +228 -0
  35. package/src/standard/render/index.ts +443 -70
  36. package/src/standard/render/indirect.ts +40 -0
  37. package/src/standard/render/instance.ts +214 -0
  38. package/src/standard/render/intersection.ts +72 -0
  39. package/src/standard/render/light.ts +16 -16
  40. package/src/standard/render/mesh/index.ts +67 -75
  41. package/src/standard/render/mesh/unified.ts +96 -0
  42. package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
  43. package/src/standard/render/pass.ts +10 -4
  44. package/src/standard/render/postprocess.ts +142 -64
  45. package/src/standard/render/ray.ts +61 -0
  46. package/src/standard/render/scene.ts +38 -164
  47. package/src/standard/render/shaders.ts +484 -0
  48. package/src/standard/render/surface/compile.ts +3 -10
  49. package/src/standard/render/surface/index.ts +60 -30
  50. package/src/standard/render/surface/noise.ts +45 -0
  51. package/src/standard/render/surface/structs.ts +60 -19
  52. package/src/standard/render/surface/wgsl.ts +573 -0
  53. package/src/standard/render/triangle.ts +84 -0
  54. package/src/standard/transforms/index.ts +4 -6
  55. package/src/standard/tween/index.ts +10 -1
  56. package/src/standard/tween/sequence.ts +24 -16
  57. package/src/standard/tween/tween.ts +67 -16
  58. package/src/core/types.ts +0 -37
  59. package/src/standard/compute/inspect.ts +0 -201
  60. package/src/standard/compute/pass.ts +0 -23
  61. package/src/standard/compute/timing.ts +0 -139
  62. package/src/standard/render/forward.ts +0 -273
@@ -0,0 +1,546 @@
1
+ export interface Font {
2
+ unitsPerEm: number;
3
+ ascender: number;
4
+ descender: number;
5
+ lineGap: number;
6
+ glyphPath(char: string): string | null;
7
+ glyphBounds(char: string): [number, number, number, number] | null;
8
+ advance(char: string): number;
9
+ kerning(left: string, right: string): number;
10
+ }
11
+
12
+ interface TableEntry {
13
+ offset: number;
14
+ length: number;
15
+ }
16
+
17
+ interface Reader {
18
+ data: DataView;
19
+ offset: number;
20
+ }
21
+
22
+ function u8(r: Reader): number {
23
+ return r.data.getUint8(r.offset++);
24
+ }
25
+
26
+ function i16(r: Reader): number {
27
+ const v = r.data.getInt16(r.offset);
28
+ r.offset += 2;
29
+ return v;
30
+ }
31
+
32
+ function u16(r: Reader): number {
33
+ const v = r.data.getUint16(r.offset);
34
+ r.offset += 2;
35
+ return v;
36
+ }
37
+
38
+ function u32(r: Reader): number {
39
+ const v = r.data.getUint32(r.offset);
40
+ r.offset += 4;
41
+ return v;
42
+ }
43
+
44
+ function tag(r: Reader): string {
45
+ return String.fromCharCode(u8(r), u8(r), u8(r), u8(r));
46
+ }
47
+
48
+ function seek(r: Reader, offset: number): void {
49
+ r.offset = offset;
50
+ }
51
+
52
+ function parseTables(r: Reader): Map<string, TableEntry> {
53
+ const tables = new Map<string, TableEntry>();
54
+ seek(r, 0);
55
+
56
+ const sfntVersion = tag(r);
57
+ if (sfntVersion !== "\x00\x01\x00\x00" && sfntVersion !== "OTTO" && sfntVersion !== "true") {
58
+ throw new Error("Not a valid TTF/OTF font");
59
+ }
60
+
61
+ const numTables = u16(r);
62
+ u16(r);
63
+ u16(r);
64
+ u16(r);
65
+
66
+ for (let i = 0; i < numTables; i++) {
67
+ const name = tag(r);
68
+ u32(r);
69
+ const offset = u32(r);
70
+ const length = u32(r);
71
+ tables.set(name, { offset, length });
72
+ }
73
+
74
+ return tables;
75
+ }
76
+
77
+ function parseHead(r: Reader, table: TableEntry): { unitsPerEm: number; indexToLocFormat: number } {
78
+ seek(r, table.offset + 18);
79
+ const unitsPerEm = u16(r);
80
+ seek(r, table.offset + 50);
81
+ const indexToLocFormat = i16(r);
82
+ return { unitsPerEm, indexToLocFormat };
83
+ }
84
+
85
+ function parseHhea(r: Reader, table: TableEntry): { ascender: number; descender: number; lineGap: number; numHMetrics: number } {
86
+ seek(r, table.offset + 4);
87
+ const ascender = i16(r);
88
+ const descender = i16(r);
89
+ const lineGap = i16(r);
90
+ seek(r, table.offset + 34);
91
+ const numHMetrics = u16(r);
92
+ return { ascender, descender, lineGap, numHMetrics };
93
+ }
94
+
95
+ function parseHmtx(r: Reader, table: TableEntry, numHMetrics: number, numGlyphs: number): { advances: Uint16Array } {
96
+ const advances = new Uint16Array(numGlyphs);
97
+ seek(r, table.offset);
98
+
99
+ let lastAdvance = 0;
100
+ for (let i = 0; i < numHMetrics; i++) {
101
+ lastAdvance = u16(r);
102
+ advances[i] = lastAdvance;
103
+ i16(r);
104
+ }
105
+ for (let i = numHMetrics; i < numGlyphs; i++) {
106
+ advances[i] = lastAdvance;
107
+ }
108
+
109
+ return { advances };
110
+ }
111
+
112
+ function parseMaxp(r: Reader, table: TableEntry): number {
113
+ seek(r, table.offset + 4);
114
+ return u16(r);
115
+ }
116
+
117
+ function parseLoca(r: Reader, table: TableEntry, numGlyphs: number, indexToLocFormat: number): Uint32Array {
118
+ const offsets = new Uint32Array(numGlyphs + 1);
119
+ seek(r, table.offset);
120
+
121
+ if (indexToLocFormat === 0) {
122
+ for (let i = 0; i <= numGlyphs; i++) {
123
+ offsets[i] = u16(r) * 2;
124
+ }
125
+ } else {
126
+ for (let i = 0; i <= numGlyphs; i++) {
127
+ offsets[i] = u32(r);
128
+ }
129
+ }
130
+
131
+ return offsets;
132
+ }
133
+
134
+ function parseCmap(r: Reader, table: TableEntry): Map<number, number> {
135
+ const charToGlyph = new Map<number, number>();
136
+ seek(r, table.offset);
137
+
138
+ u16(r);
139
+ const numSubtables = u16(r);
140
+
141
+ let format4Offset = -1;
142
+ let format12Offset = -1;
143
+
144
+ for (let i = 0; i < numSubtables; i++) {
145
+ const platformId = u16(r);
146
+ const encodingId = u16(r);
147
+ const offset = u32(r);
148
+
149
+ if (platformId === 3 && encodingId === 1) format4Offset = table.offset + offset;
150
+ if (platformId === 3 && encodingId === 10) format12Offset = table.offset + offset;
151
+ if (platformId === 0 && encodingId === 3) format4Offset = table.offset + offset;
152
+ if (platformId === 0 && encodingId === 4) format12Offset = table.offset + offset;
153
+ }
154
+
155
+ if (format12Offset !== -1) {
156
+ seek(r, format12Offset);
157
+ const format = u16(r);
158
+ if (format === 12) {
159
+ u16(r);
160
+ u32(r);
161
+ u32(r);
162
+ const numGroups = u32(r);
163
+ for (let i = 0; i < numGroups; i++) {
164
+ const startCode = u32(r);
165
+ const endCode = u32(r);
166
+ const startGlyph = u32(r);
167
+ for (let c = startCode; c <= endCode; c++) {
168
+ charToGlyph.set(c, startGlyph + (c - startCode));
169
+ }
170
+ }
171
+ return charToGlyph;
172
+ }
173
+ }
174
+
175
+ if (format4Offset !== -1) {
176
+ seek(r, format4Offset);
177
+ const format = u16(r);
178
+ if (format === 4) {
179
+ u16(r);
180
+ u16(r);
181
+ const segCount = u16(r) / 2;
182
+ u16(r);
183
+ u16(r);
184
+ u16(r);
185
+
186
+ const endCodes: number[] = [];
187
+ for (let i = 0; i < segCount; i++) endCodes.push(u16(r));
188
+ u16(r);
189
+
190
+ const startCodes: number[] = [];
191
+ for (let i = 0; i < segCount; i++) startCodes.push(u16(r));
192
+
193
+ const idDeltas: number[] = [];
194
+ for (let i = 0; i < segCount; i++) idDeltas.push(i16(r));
195
+
196
+ const idRangeOffsetPos = r.offset;
197
+ const idRangeOffsets: number[] = [];
198
+ for (let i = 0; i < segCount; i++) idRangeOffsets.push(u16(r));
199
+
200
+ for (let i = 0; i < segCount; i++) {
201
+ const start = startCodes[i];
202
+ const end = endCodes[i];
203
+ const delta = idDeltas[i];
204
+ const rangeOffset = idRangeOffsets[i];
205
+
206
+ if (end === 0xffff) continue;
207
+
208
+ for (let c = start; c <= end; c++) {
209
+ let glyphId: number;
210
+ if (rangeOffset === 0) {
211
+ glyphId = (c + delta) & 0xffff;
212
+ } else {
213
+ const glyphIdOffset = idRangeOffsetPos + i * 2 + rangeOffset + (c - start) * 2;
214
+ seek(r, glyphIdOffset);
215
+ glyphId = u16(r);
216
+ if (glyphId !== 0) {
217
+ glyphId = (glyphId + delta) & 0xffff;
218
+ }
219
+ }
220
+ if (glyphId !== 0) {
221
+ charToGlyph.set(c, glyphId);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ return charToGlyph;
229
+ }
230
+
231
+ function parseKern(r: Reader, table: TableEntry): Map<number, number> {
232
+ const kerning = new Map<number, number>();
233
+ seek(r, table.offset);
234
+
235
+ const version = u16(r);
236
+ if (version === 0) {
237
+ const numSubtables = u16(r);
238
+ for (let t = 0; t < numSubtables; t++) {
239
+ u16(r);
240
+ u16(r);
241
+ const coverage = u16(r);
242
+ const format = coverage >> 8;
243
+
244
+ if (format === 0) {
245
+ const numPairs = u16(r);
246
+ u16(r);
247
+ u16(r);
248
+ u16(r);
249
+
250
+ for (let i = 0; i < numPairs; i++) {
251
+ const left = u16(r);
252
+ const right = u16(r);
253
+ const value = i16(r);
254
+ kerning.set((left << 16) | right, value);
255
+ }
256
+ }
257
+ }
258
+ } else if (version === 1) {
259
+ u16(r);
260
+ const numSubtables = u32(r);
261
+ for (let t = 0; t < numSubtables; t++) {
262
+ const subtableLength = u32(r);
263
+ const coverage = u16(r);
264
+ const format = coverage & 0xff;
265
+
266
+ if (format === 0) {
267
+ const numPairs = u16(r);
268
+ u16(r);
269
+ u16(r);
270
+ u16(r);
271
+
272
+ for (let i = 0; i < numPairs; i++) {
273
+ const left = u16(r);
274
+ const right = u16(r);
275
+ const value = i16(r);
276
+ kerning.set((left << 16) | right, value);
277
+ }
278
+ } else {
279
+ seek(r, r.offset + subtableLength - 8);
280
+ }
281
+ }
282
+ }
283
+
284
+ return kerning;
285
+ }
286
+
287
+ const ON_CURVE = 1;
288
+ const X_SHORT = 2;
289
+ const Y_SHORT = 4;
290
+ const REPEAT = 8;
291
+ const X_SAME = 16;
292
+ const Y_SAME = 32;
293
+
294
+ function parseGlyph(r: Reader, glyfOffset: number, loca: Uint32Array, glyphId: number): { path: string; bounds: [number, number, number, number] } | null {
295
+ const start = loca[glyphId];
296
+ const end = loca[glyphId + 1];
297
+ if (start === end) return null;
298
+
299
+ seek(r, glyfOffset + start);
300
+ const numContours = i16(r);
301
+ const xMin = i16(r);
302
+ const yMin = i16(r);
303
+ const xMax = i16(r);
304
+ const yMax = i16(r);
305
+
306
+ if (numContours < 0) {
307
+ return parseCompositeGlyph(r, glyfOffset, loca);
308
+ }
309
+
310
+ const endPts: number[] = [];
311
+ for (let i = 0; i < numContours; i++) {
312
+ endPts.push(u16(r));
313
+ }
314
+
315
+ const numPoints = endPts.length > 0 ? endPts[endPts.length - 1] + 1 : 0;
316
+ const instructionLength = u16(r);
317
+ seek(r, r.offset + instructionLength);
318
+
319
+ const flags: number[] = [];
320
+ while (flags.length < numPoints) {
321
+ const flag = u8(r);
322
+ flags.push(flag);
323
+ if (flag & REPEAT) {
324
+ const repeat = u8(r);
325
+ for (let i = 0; i < repeat; i++) flags.push(flag);
326
+ }
327
+ }
328
+
329
+ const xs: number[] = [];
330
+ let x = 0;
331
+ for (let i = 0; i < numPoints; i++) {
332
+ const flag = flags[i];
333
+ if (flag & X_SHORT) {
334
+ const dx = u8(r);
335
+ x += flag & X_SAME ? dx : -dx;
336
+ } else if (!(flag & X_SAME)) {
337
+ x += i16(r);
338
+ }
339
+ xs.push(x);
340
+ }
341
+
342
+ const ys: number[] = [];
343
+ let y = 0;
344
+ for (let i = 0; i < numPoints; i++) {
345
+ const flag = flags[i];
346
+ if (flag & Y_SHORT) {
347
+ const dy = u8(r);
348
+ y += flag & Y_SAME ? dy : -dy;
349
+ } else if (!(flag & Y_SAME)) {
350
+ y += i16(r);
351
+ }
352
+ ys.push(y);
353
+ }
354
+
355
+ let path = "";
356
+ let contourStart = 0;
357
+
358
+ for (let c = 0; c < numContours; c++) {
359
+ const contourEnd = endPts[c];
360
+ const points: { x: number; y: number; on: boolean }[] = [];
361
+
362
+ for (let i = contourStart; i <= contourEnd; i++) {
363
+ points.push({ x: xs[i], y: ys[i], on: !!(flags[i] & ON_CURVE) });
364
+ }
365
+
366
+ if (points.length === 0) {
367
+ contourStart = contourEnd + 1;
368
+ continue;
369
+ }
370
+
371
+ let firstOn = 0;
372
+ while (firstOn < points.length && !points[firstOn].on) firstOn++;
373
+
374
+ if (firstOn === points.length) {
375
+ const mid = { x: (points[0].x + points[1].x) / 2, y: (points[0].y + points[1].y) / 2, on: true };
376
+ points.unshift(mid);
377
+ firstOn = 0;
378
+ }
379
+
380
+ const reordered = [...points.slice(firstOn), ...points.slice(0, firstOn)];
381
+ path += `M${reordered[0].x},${reordered[0].y}`;
382
+
383
+ let i = 1;
384
+ while (i < reordered.length) {
385
+ const p = reordered[i];
386
+ if (p.on) {
387
+ path += `L${p.x},${p.y}`;
388
+ i++;
389
+ } else {
390
+ const next = reordered[(i + 1) % reordered.length];
391
+ if (next.on) {
392
+ path += `Q${p.x},${p.y},${next.x},${next.y}`;
393
+ i += 2;
394
+ } else {
395
+ const midX = (p.x + next.x) / 2;
396
+ const midY = (p.y + next.y) / 2;
397
+ path += `Q${p.x},${p.y},${midX},${midY}`;
398
+ i++;
399
+ }
400
+ }
401
+ }
402
+
403
+ if (!reordered[reordered.length - 1].on) {
404
+ const last = reordered[reordered.length - 1];
405
+ path += `Q${last.x},${last.y},${reordered[0].x},${reordered[0].y}`;
406
+ }
407
+
408
+ path += "Z";
409
+ contourStart = contourEnd + 1;
410
+ }
411
+
412
+ return { path, bounds: [xMin, yMin, xMax, yMax] };
413
+ }
414
+
415
+ function parseCompositeGlyph(r: Reader, glyfOffset: number, loca: Uint32Array): { path: string; bounds: [number, number, number, number] } | null {
416
+ let path = "";
417
+ let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
418
+ let hasMore = true;
419
+
420
+ while (hasMore) {
421
+ const flags = u16(r);
422
+ const glyphIndex = u16(r);
423
+
424
+ let dx = 0, dy = 0;
425
+ let a = 1, b = 0, c = 0, d = 1;
426
+
427
+ if (flags & 1) {
428
+ dx = i16(r);
429
+ dy = i16(r);
430
+ } else {
431
+ dx = (r.data.getInt8(r.offset++) + r.data.getInt8(r.offset++)) / 2;
432
+ dy = 0;
433
+ }
434
+
435
+ if (flags & 8) {
436
+ a = d = i16(r) / 16384;
437
+ } else if (flags & 64) {
438
+ a = i16(r) / 16384;
439
+ d = i16(r) / 16384;
440
+ } else if (flags & 128) {
441
+ a = i16(r) / 16384;
442
+ b = i16(r) / 16384;
443
+ c = i16(r) / 16384;
444
+ d = i16(r) / 16384;
445
+ }
446
+
447
+ const savedOffset = r.offset;
448
+ const component = parseGlyph(r, glyfOffset, loca, glyphIndex);
449
+ r.offset = savedOffset;
450
+
451
+ if (component) {
452
+ const transformed = transformPath(component.path, a, b, c, d, dx, dy);
453
+ path += transformed;
454
+ xMin = Math.min(xMin, component.bounds[0] * a + component.bounds[1] * b + dx);
455
+ yMin = Math.min(yMin, component.bounds[0] * c + component.bounds[1] * d + dy);
456
+ xMax = Math.max(xMax, component.bounds[2] * a + component.bounds[3] * b + dx);
457
+ yMax = Math.max(yMax, component.bounds[2] * c + component.bounds[3] * d + dy);
458
+ }
459
+
460
+ hasMore = !!(flags & 32);
461
+ }
462
+
463
+ if (path === "") return null;
464
+ return { path, bounds: [xMin, yMin, xMax, yMax] };
465
+ }
466
+
467
+ function transformPath(path: string, a: number, b: number, c: number, d: number, dx: number, dy: number): string {
468
+ return path.replace(/(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/g, (_, x, y) => {
469
+ const nx = parseFloat(x) * a + parseFloat(y) * b + dx;
470
+ const ny = parseFloat(x) * c + parseFloat(y) * d + dy;
471
+ return `${nx},${ny}`;
472
+ });
473
+ }
474
+
475
+ export function parseFont(buffer: ArrayBuffer): Font {
476
+ const r: Reader = { data: new DataView(buffer), offset: 0 };
477
+ const tables = parseTables(r);
478
+
479
+ const headTable = tables.get("head");
480
+ const hheaTable = tables.get("hhea");
481
+ const hmtxTable = tables.get("hmtx");
482
+ const maxpTable = tables.get("maxp");
483
+ const cmapTable = tables.get("cmap");
484
+ const locaTable = tables.get("loca");
485
+ const glyfTable = tables.get("glyf");
486
+ const kernTable = tables.get("kern");
487
+
488
+ if (!headTable || !hheaTable || !hmtxTable || !maxpTable || !cmapTable || !locaTable || !glyfTable) {
489
+ throw new Error("Missing required font tables");
490
+ }
491
+
492
+ const head = parseHead(r, headTable);
493
+ const hhea = parseHhea(r, hheaTable);
494
+ const numGlyphs = parseMaxp(r, maxpTable);
495
+ const hmtx = parseHmtx(r, hmtxTable, hhea.numHMetrics, numGlyphs);
496
+ const loca = parseLoca(r, locaTable, numGlyphs, head.indexToLocFormat);
497
+ const cmap = parseCmap(r, cmapTable);
498
+ const kern = kernTable ? parseKern(r, kernTable) : new Map<number, number>();
499
+
500
+ const glyphCache = new Map<number, { path: string; bounds: [number, number, number, number] } | null>();
501
+ const glyfOffset = glyfTable.offset;
502
+
503
+ function getGlyphId(char: string): number {
504
+ return cmap.get(char.codePointAt(0) ?? 0) ?? 0;
505
+ }
506
+
507
+ function getGlyph(glyphId: number): { path: string; bounds: [number, number, number, number] } | null {
508
+ if (glyphCache.has(glyphId)) return glyphCache.get(glyphId)!;
509
+ const glyph = parseGlyph(r, glyfOffset, loca, glyphId);
510
+ glyphCache.set(glyphId, glyph);
511
+ return glyph;
512
+ }
513
+
514
+ return {
515
+ unitsPerEm: head.unitsPerEm,
516
+ ascender: hhea.ascender,
517
+ descender: hhea.descender,
518
+ lineGap: hhea.lineGap,
519
+
520
+ glyphPath(char: string): string | null {
521
+ const glyph = getGlyph(getGlyphId(char));
522
+ return glyph?.path ?? null;
523
+ },
524
+
525
+ glyphBounds(char: string): [number, number, number, number] | null {
526
+ const glyph = getGlyph(getGlyphId(char));
527
+ return glyph?.bounds ?? null;
528
+ },
529
+
530
+ advance(char: string): number {
531
+ return hmtx.advances[getGlyphId(char)] ?? 0;
532
+ },
533
+
534
+ kerning(left: string, right: string): number {
535
+ const l = getGlyphId(left);
536
+ const r = getGlyphId(right);
537
+ return kern.get((l << 16) | r) ?? 0;
538
+ },
539
+ };
540
+ }
541
+
542
+ export async function loadFont(url: string): Promise<Font> {
543
+ const response = await fetch(url);
544
+ if (!response.ok) throw new Error(`Failed to load font: ${response.statusText}`);
545
+ return parseFont(await response.arrayBuffer());
546
+ }