@tldraw/tlschema 4.2.2 → 4.2.3

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 (117) hide show
  1. package/dist-cjs/bindings/TLBaseBinding.js.map +2 -2
  2. package/dist-cjs/createTLSchema.js.map +2 -2
  3. package/dist-cjs/index.d.ts +71 -242
  4. package/dist-cjs/index.js +1 -4
  5. package/dist-cjs/index.js.map +2 -2
  6. package/dist-cjs/misc/TLOpacity.js +5 -1
  7. package/dist-cjs/misc/TLOpacity.js.map +2 -2
  8. package/dist-cjs/misc/TLRichText.js +1 -5
  9. package/dist-cjs/misc/TLRichText.js.map +2 -2
  10. package/dist-cjs/records/TLAsset.js.map +1 -1
  11. package/dist-cjs/records/TLBinding.js.map +2 -2
  12. package/dist-cjs/records/TLShape.js.map +2 -2
  13. package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
  14. package/dist-cjs/shapes/TLArrowShape.js +13 -26
  15. package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
  16. package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
  17. package/dist-cjs/shapes/TLDrawShape.js +4 -37
  18. package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
  19. package/dist-cjs/shapes/TLEmbedShape.js +0 -17
  20. package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
  21. package/dist-cjs/shapes/TLGeoShape.js +1 -12
  22. package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
  23. package/dist-cjs/shapes/TLHighlightShape.js +2 -29
  24. package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
  25. package/dist-cjs/shapes/TLNoteShape.js +1 -12
  26. package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
  27. package/dist-cjs/shapes/TLTextShape.js +1 -12
  28. package/dist-cjs/shapes/TLTextShape.js.map +2 -2
  29. package/dist-cjs/store-migrations.js +15 -15
  30. package/dist-cjs/store-migrations.js.map +2 -2
  31. package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
  32. package/dist-esm/createTLSchema.mjs.map +2 -2
  33. package/dist-esm/index.d.mts +71 -242
  34. package/dist-esm/index.mjs +1 -5
  35. package/dist-esm/index.mjs.map +2 -2
  36. package/dist-esm/misc/TLOpacity.mjs +5 -1
  37. package/dist-esm/misc/TLOpacity.mjs.map +2 -2
  38. package/dist-esm/misc/TLRichText.mjs +1 -5
  39. package/dist-esm/misc/TLRichText.mjs.map +2 -2
  40. package/dist-esm/records/TLAsset.mjs.map +1 -1
  41. package/dist-esm/records/TLBinding.mjs.map +2 -2
  42. package/dist-esm/records/TLShape.mjs.map +2 -2
  43. package/dist-esm/shapes/TLArrowShape.mjs +13 -26
  44. package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
  45. package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
  46. package/dist-esm/shapes/TLDrawShape.mjs +4 -37
  47. package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
  48. package/dist-esm/shapes/TLEmbedShape.mjs +0 -17
  49. package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
  50. package/dist-esm/shapes/TLGeoShape.mjs +1 -12
  51. package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
  52. package/dist-esm/shapes/TLHighlightShape.mjs +2 -29
  53. package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
  54. package/dist-esm/shapes/TLNoteShape.mjs +1 -12
  55. package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
  56. package/dist-esm/shapes/TLTextShape.mjs +1 -12
  57. package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
  58. package/dist-esm/store-migrations.mjs +15 -15
  59. package/dist-esm/store-migrations.mjs.map +2 -2
  60. package/package.json +8 -8
  61. package/src/__tests__/migrationTestUtils.ts +3 -9
  62. package/src/assets/TLBookmarkAsset.test.ts +96 -0
  63. package/src/assets/TLImageAsset.test.ts +213 -0
  64. package/src/assets/TLVideoAsset.test.ts +105 -0
  65. package/src/bindings/TLArrowBinding.test.ts +55 -0
  66. package/src/bindings/TLBaseBinding.ts +14 -25
  67. package/src/createTLSchema.ts +2 -8
  68. package/src/index.ts +0 -9
  69. package/src/migrations.test.ts +1 -149
  70. package/src/misc/TLOpacity.ts +5 -1
  71. package/src/misc/TLRichText.ts +1 -6
  72. package/src/misc/id-validator.test.ts +50 -0
  73. package/src/records/TLAsset.test.ts +234 -0
  74. package/src/records/TLAsset.ts +2 -2
  75. package/src/records/TLBinding.test.ts +22 -0
  76. package/src/records/TLBinding.ts +23 -65
  77. package/src/records/TLCamera.test.ts +19 -0
  78. package/src/records/TLDocument.test.ts +35 -0
  79. package/src/records/TLInstance.test.ts +201 -0
  80. package/src/records/TLPage.test.ts +110 -0
  81. package/src/records/TLPageState.test.ts +228 -0
  82. package/src/records/TLPointer.test.ts +63 -0
  83. package/src/records/TLPresence.test.ts +190 -0
  84. package/src/records/TLRecord.test.ts +70 -0
  85. package/src/records/TLShape.test.ts +232 -0
  86. package/src/records/TLShape.ts +5 -100
  87. package/src/shapes/ShapeWithCrop.test.ts +18 -0
  88. package/src/shapes/ShapeWithCrop.ts +2 -2
  89. package/src/shapes/TLArrowShape.test.ts +505 -0
  90. package/src/shapes/TLArrowShape.ts +14 -28
  91. package/src/shapes/TLBaseShape.test.ts +142 -0
  92. package/src/shapes/TLBaseShape.ts +10 -34
  93. package/src/shapes/TLBookmarkShape.test.ts +122 -0
  94. package/src/shapes/TLDrawShape.test.ts +177 -0
  95. package/src/shapes/TLDrawShape.ts +12 -59
  96. package/src/shapes/TLEmbedShape.test.ts +286 -0
  97. package/src/shapes/TLEmbedShape.ts +0 -17
  98. package/src/shapes/TLFrameShape.test.ts +71 -0
  99. package/src/shapes/TLGeoShape.test.ts +247 -0
  100. package/src/shapes/TLGeoShape.ts +1 -14
  101. package/src/shapes/TLGroupShape.test.ts +59 -0
  102. package/src/shapes/TLHighlightShape.test.ts +325 -0
  103. package/src/shapes/TLHighlightShape.ts +0 -37
  104. package/src/shapes/TLImageShape.test.ts +534 -0
  105. package/src/shapes/TLLineShape.test.ts +269 -0
  106. package/src/shapes/TLNoteShape.test.ts +1568 -0
  107. package/src/shapes/TLNoteShape.ts +1 -15
  108. package/src/shapes/TLTextShape.test.ts +407 -0
  109. package/src/shapes/TLTextShape.ts +2 -16
  110. package/src/shapes/TLVideoShape.test.ts +112 -0
  111. package/src/store-migrations.ts +16 -17
  112. package/src/styles/TLColorStyle.test.ts +439 -0
  113. package/dist-cjs/misc/b64Vecs.js +0 -224
  114. package/dist-cjs/misc/b64Vecs.js.map +0 -7
  115. package/dist-esm/misc/b64Vecs.mjs +0 -204
  116. package/dist-esm/misc/b64Vecs.mjs.map +0 -7
  117. package/src/misc/b64Vecs.ts +0 -308
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/misc/b64Vecs.ts"],
4
- "sourcesContent": ["import { VecModel } from './geometry-types'\n\n// Each point = 3 Float16s = 6 bytes = 8 base64 chars\nconst POINT_B64_LENGTH = 8\n\n// O(1) lookup table for base64 decoding (maps char code -> 6-bit value)\nconst BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nconst B64_LOOKUP = new Uint8Array(128)\nfor (let i = 0; i < 64; i++) {\n\tB64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i\n}\n\n// Precomputed powers of 2 for Float16 exponents (exp - 15, so indices 0-30 map to 2^-15 to 2^15)\nconst POW2 = new Float64Array(31)\nfor (let i = 0; i < 31; i++) {\n\tPOW2[i] = Math.pow(2, i - 15)\n}\nconst POW2_SUBNORMAL = Math.pow(2, -14) / 1024 // For subnormal numbers\n\n// Precomputed mantissa values: 1 + frac/1024 for all 1024 possible frac values\n// Avoids division in hot path\nconst MANTISSA = new Float64Array(1024)\nfor (let i = 0; i < 1024; i++) {\n\tMANTISSA[i] = 1 + i / 1024\n}\n\n/**\n * Convert a Uint16Array (containing Float16 bits) to base64.\n * Processes bytes in groups of 3 to produce 4 base64 characters.\n *\n * @internal\n */\nfunction uint16ArrayToBase64(uint16Array: Uint16Array): string {\n\tconst uint8Array = new Uint8Array(\n\t\tuint16Array.buffer,\n\t\tuint16Array.byteOffset,\n\t\tuint16Array.byteLength\n\t)\n\tlet result = ''\n\n\t// Process bytes in groups of 3 -> 4 base64 chars\n\tfor (let i = 0; i < uint8Array.length; i += 3) {\n\t\tconst byte1 = uint8Array[i]\n\t\tconst byte2 = uint8Array[i + 1] // Always exists for our use case (multiple of 6 bytes)\n\t\tconst byte3 = uint8Array[i + 2]\n\n\t\tconst bitmap = (byte1 << 16) | (byte2 << 8) | byte3\n\t\tresult +=\n\t\t\tBASE64_CHARS[(bitmap >> 18) & 63] +\n\t\t\tBASE64_CHARS[(bitmap >> 12) & 63] +\n\t\t\tBASE64_CHARS[(bitmap >> 6) & 63] +\n\t\t\tBASE64_CHARS[bitmap & 63]\n\t}\n\n\treturn result\n}\n\n/**\n * Convert a base64 string to Uint16Array containing Float16 bits.\n * The base64 string must have a length that is a multiple of 4.\n *\n * @param base64 - The base64-encoded string to decode\n * @returns A Uint16Array containing the decoded Float16 bit values\n * @public\n */\nfunction base64ToUint16Array(base64: string): Uint16Array {\n\t// Calculate exact number of bytes (4 base64 chars = 3 bytes)\n\tconst numBytes = Math.floor((base64.length * 3) / 4)\n\tconst bytes = new Uint8Array(numBytes)\n\tlet byteIndex = 0\n\n\t// Process in groups of 4 base64 characters\n\tfor (let i = 0; i < base64.length; i += 4) {\n\t\tconst c0 = B64_LOOKUP[base64.charCodeAt(i)]\n\t\tconst c1 = B64_LOOKUP[base64.charCodeAt(i + 1)]\n\t\tconst c2 = B64_LOOKUP[base64.charCodeAt(i + 2)]\n\t\tconst c3 = B64_LOOKUP[base64.charCodeAt(i + 3)]\n\n\t\tconst bitmap = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3\n\n\t\tbytes[byteIndex++] = (bitmap >> 16) & 255\n\t\tbytes[byteIndex++] = (bitmap >> 8) & 255\n\t\tbytes[byteIndex++] = bitmap & 255\n\t}\n\n\treturn new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)\n}\n\n/**\n * Convert Float16 bits to a number using optimized lookup tables.\n * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.\n *\n * @param bits - The 16-bit Float16 value to decode\n * @returns The decoded number value\n */\nfunction float16BitsToNumber(bits: number): number {\n\tconst sign = bits >> 15\n\tconst exp = (bits >> 10) & 0x1f\n\tconst frac = bits & 0x3ff\n\n\tif (exp === 0) {\n\t\t// Subnormal or zero - rare case\n\t\treturn sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL\n\t}\n\tif (exp === 31) {\n\t\t// Infinity or NaN - very rare\n\t\treturn frac ? NaN : sign ? -Infinity : Infinity\n\t}\n\t// Normal case - two table lookups, one multiply, no division\n\tconst magnitude = POW2[exp] * MANTISSA[frac]\n\treturn sign ? -magnitude : magnitude\n}\n\n/**\n * Convert a number to Float16 bits.\n * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.\n *\n * @param value - The number to encode as Float16\n * @returns The 16-bit Float16 representation of the number\n * @internal\n */\nfunction numberToFloat16Bits(value: number): number {\n\tif (value === 0) return Object.is(value, -0) ? 0x8000 : 0\n\tif (!Number.isFinite(value)) {\n\t\tif (Number.isNaN(value)) return 0x7e00\n\t\treturn value > 0 ? 0x7c00 : 0xfc00\n\t}\n\n\tconst sign = value < 0 ? 1 : 0\n\tvalue = Math.abs(value)\n\n\t// Find exponent and mantissa\n\tconst exp = Math.floor(Math.log2(value))\n\tlet expBiased = exp + 15\n\n\tif (expBiased >= 31) {\n\t\t// Overflow to infinity\n\t\treturn (sign << 15) | 0x7c00\n\t}\n\tif (expBiased <= 0) {\n\t\t// Subnormal or underflow\n\t\tconst frac = Math.round(value * Math.pow(2, 14) * 1024)\n\t\treturn (sign << 15) | (frac & 0x3ff)\n\t}\n\n\t// Normal number\n\tconst mantissa = value / Math.pow(2, exp) - 1\n\tlet frac = Math.round(mantissa * 1024)\n\n\t// Handle rounding overflow: if frac rounds to 1024, increment exponent\n\tif (frac >= 1024) {\n\t\tfrac = 0\n\t\texpBiased++\n\t\tif (expBiased >= 31) {\n\t\t\t// Overflow to infinity\n\t\t\treturn (sign << 15) | 0x7c00\n\t\t}\n\t}\n\n\treturn (sign << 15) | (expBiased << 10) | frac\n}\n\n/**\n * Utilities for encoding and decoding points using base64 and Float16 encoding.\n * Provides functions for converting between VecModel arrays and compact base64 strings,\n * as well as individual point encoding/decoding operations.\n *\n * @public\n */\nexport class b64Vecs {\n\t/**\n\t * Encode a single point (x, y, z) to 8 base64 characters.\n\t * Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.\n\t *\n\t * @param x - The x coordinate\n\t * @param y - The y coordinate\n\t * @param z - The z coordinate\n\t * @returns An 8-character base64 string representing the point\n\t */\n\tstatic encodePoint(x: number, y: number, z: number): string {\n\t\tconst xBits = numberToFloat16Bits(x)\n\t\tconst yBits = numberToFloat16Bits(y)\n\t\tconst zBits = numberToFloat16Bits(z)\n\n\t\t// Convert Float16 bits to 6 bytes (little-endian)\n\t\tconst b0 = xBits & 0xff\n\t\tconst b1 = (xBits >> 8) & 0xff\n\t\tconst b2 = yBits & 0xff\n\t\tconst b3 = (yBits >> 8) & 0xff\n\t\tconst b4 = zBits & 0xff\n\t\tconst b5 = (zBits >> 8) & 0xff\n\n\t\t// Convert 6 bytes to 8 base64 chars\n\t\tconst bitmap1 = (b0 << 16) | (b1 << 8) | b2\n\t\tconst bitmap2 = (b3 << 16) | (b4 << 8) | b5\n\n\t\treturn (\n\t\t\tBASE64_CHARS[(bitmap1 >> 18) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap1 >> 12) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap1 >> 6) & 0x3f] +\n\t\t\tBASE64_CHARS[bitmap1 & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 18) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 12) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 6) & 0x3f] +\n\t\t\tBASE64_CHARS[bitmap2 & 0x3f]\n\t\t)\n\t}\n\n\t/**\n\t * Convert an array of VecModels to a base64 string for compact storage.\n\t * Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is\n\t * undefined, it defaults to 0.5.\n\t *\n\t * @param points - An array of VecModel objects to encode\n\t * @returns A base64-encoded string containing all points\n\t */\n\tstatic encodePoints(points: VecModel[]): string {\n\t\tconst uint16s = new Uint16Array(points.length * 3)\n\t\tfor (let i = 0; i < points.length; i++) {\n\t\t\tconst p = points[i]\n\t\t\tuint16s[i * 3] = numberToFloat16Bits(p.x)\n\t\t\tuint16s[i * 3 + 1] = numberToFloat16Bits(p.y)\n\t\t\tuint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5)\n\t\t}\n\t\treturn uint16ArrayToBase64(uint16s)\n\t}\n\n\t/**\n\t * Convert a base64 string back to an array of VecModels.\n\t * Decodes Float16-encoded coordinates (x, y, z) from the base64 string.\n\t *\n\t * @param base64 - The base64-encoded string containing point data\n\t * @returns An array of VecModel objects decoded from the string\n\t */\n\tstatic decodePoints(base64: string): VecModel[] {\n\t\tconst uint16s = base64ToUint16Array(base64)\n\t\tconst result: VecModel[] = []\n\t\tfor (let i = 0; i < uint16s.length; i += 3) {\n\t\t\tresult.push({\n\t\t\t\tx: float16BitsToNumber(uint16s[i]),\n\t\t\t\ty: float16BitsToNumber(uint16s[i + 1]),\n\t\t\t\tz: float16BitsToNumber(uint16s[i + 2]),\n\t\t\t})\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Decode a single point (8 base64 chars) starting at the given offset.\n\t * Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @param charOffset - The character offset where the point starts (must be a multiple of 8)\n\t * @returns A VecModel object with x, y, and z coordinates\n\t * @internal\n\t */\n\tstatic decodePointAt(b64Points: string, charOffset: number): VecModel {\n\t\t// Decode 8 base64 chars -> 6 bytes -> 3 Float16s using O(1) lookup\n\t\tconst c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)]\n\t\tconst c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)]\n\t\tconst c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)]\n\t\tconst c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)]\n\t\tconst c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)]\n\t\tconst c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)]\n\t\tconst c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)]\n\t\tconst c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)]\n\n\t\t// 4 base64 chars -> 24 bits -> 3 bytes\n\t\tconst bitmap1 = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3\n\t\tconst bitmap2 = (c4 << 18) | (c5 << 12) | (c6 << 6) | c7\n\n\t\t// Extract Float16 bits directly (little-endian byte order)\n\t\t// bitmap1 = [byte0:8][byte1:8][byte2:8], bitmap2 = [byte3:8][byte4:8][byte5:8]\n\t\t// xBits = byte0 | (byte1 << 8), yBits = byte2 | (byte3 << 8), zBits = byte4 | (byte5 << 8)\n\t\tconst xBits = ((bitmap1 >> 16) & 0xff) | (bitmap1 & 0xff00)\n\t\tconst yBits = (bitmap1 & 0xff) | ((bitmap2 >> 8) & 0xff00)\n\t\tconst zBits = ((bitmap2 >> 8) & 0xff) | ((bitmap2 << 8) & 0xff00)\n\n\t\treturn {\n\t\t\tx: float16BitsToNumber(xBits),\n\t\t\ty: float16BitsToNumber(yBits),\n\t\t\tz: float16BitsToNumber(zBits),\n\t\t}\n\t}\n\n\t/**\n\t * Get the first point from a base64-encoded string of points.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @returns The first point as a VecModel, or null if the string is too short\n\t * @public\n\t */\n\tstatic decodeFirstPoint(b64Points: string): VecModel | null {\n\t\tif (b64Points.length < POINT_B64_LENGTH) return null\n\t\treturn b64Vecs.decodePointAt(b64Points, 0)\n\t}\n\n\t/**\n\t * Get the last point from a base64-encoded string of points.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @returns The last point as a VecModel, or null if the string is too short\n\t */\n\tstatic decodeLastPoint(b64Points: string): VecModel | null {\n\t\tif (b64Points.length < POINT_B64_LENGTH) return null\n\t\treturn b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH)\n\t}\n}\n"],
5
- "mappings": "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",
6
- "names": ["frac"]
7
- }
@@ -1,204 +0,0 @@
1
- const POINT_B64_LENGTH = 8;
2
- const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
- const B64_LOOKUP = new Uint8Array(128);
4
- for (let i = 0; i < 64; i++) {
5
- B64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i;
6
- }
7
- const POW2 = new Float64Array(31);
8
- for (let i = 0; i < 31; i++) {
9
- POW2[i] = Math.pow(2, i - 15);
10
- }
11
- const POW2_SUBNORMAL = Math.pow(2, -14) / 1024;
12
- const MANTISSA = new Float64Array(1024);
13
- for (let i = 0; i < 1024; i++) {
14
- MANTISSA[i] = 1 + i / 1024;
15
- }
16
- function uint16ArrayToBase64(uint16Array) {
17
- const uint8Array = new Uint8Array(
18
- uint16Array.buffer,
19
- uint16Array.byteOffset,
20
- uint16Array.byteLength
21
- );
22
- let result = "";
23
- for (let i = 0; i < uint8Array.length; i += 3) {
24
- const byte1 = uint8Array[i];
25
- const byte2 = uint8Array[i + 1];
26
- const byte3 = uint8Array[i + 2];
27
- const bitmap = byte1 << 16 | byte2 << 8 | byte3;
28
- result += BASE64_CHARS[bitmap >> 18 & 63] + BASE64_CHARS[bitmap >> 12 & 63] + BASE64_CHARS[bitmap >> 6 & 63] + BASE64_CHARS[bitmap & 63];
29
- }
30
- return result;
31
- }
32
- function base64ToUint16Array(base64) {
33
- const numBytes = Math.floor(base64.length * 3 / 4);
34
- const bytes = new Uint8Array(numBytes);
35
- let byteIndex = 0;
36
- for (let i = 0; i < base64.length; i += 4) {
37
- const c0 = B64_LOOKUP[base64.charCodeAt(i)];
38
- const c1 = B64_LOOKUP[base64.charCodeAt(i + 1)];
39
- const c2 = B64_LOOKUP[base64.charCodeAt(i + 2)];
40
- const c3 = B64_LOOKUP[base64.charCodeAt(i + 3)];
41
- const bitmap = c0 << 18 | c1 << 12 | c2 << 6 | c3;
42
- bytes[byteIndex++] = bitmap >> 16 & 255;
43
- bytes[byteIndex++] = bitmap >> 8 & 255;
44
- bytes[byteIndex++] = bitmap & 255;
45
- }
46
- return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
47
- }
48
- function float16BitsToNumber(bits) {
49
- const sign = bits >> 15;
50
- const exp = bits >> 10 & 31;
51
- const frac = bits & 1023;
52
- if (exp === 0) {
53
- return sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL;
54
- }
55
- if (exp === 31) {
56
- return frac ? NaN : sign ? -Infinity : Infinity;
57
- }
58
- const magnitude = POW2[exp] * MANTISSA[frac];
59
- return sign ? -magnitude : magnitude;
60
- }
61
- function numberToFloat16Bits(value) {
62
- if (value === 0) return Object.is(value, -0) ? 32768 : 0;
63
- if (!Number.isFinite(value)) {
64
- if (Number.isNaN(value)) return 32256;
65
- return value > 0 ? 31744 : 64512;
66
- }
67
- const sign = value < 0 ? 1 : 0;
68
- value = Math.abs(value);
69
- const exp = Math.floor(Math.log2(value));
70
- let expBiased = exp + 15;
71
- if (expBiased >= 31) {
72
- return sign << 15 | 31744;
73
- }
74
- if (expBiased <= 0) {
75
- const frac2 = Math.round(value * Math.pow(2, 14) * 1024);
76
- return sign << 15 | frac2 & 1023;
77
- }
78
- const mantissa = value / Math.pow(2, exp) - 1;
79
- let frac = Math.round(mantissa * 1024);
80
- if (frac >= 1024) {
81
- frac = 0;
82
- expBiased++;
83
- if (expBiased >= 31) {
84
- return sign << 15 | 31744;
85
- }
86
- }
87
- return sign << 15 | expBiased << 10 | frac;
88
- }
89
- class b64Vecs {
90
- /**
91
- * Encode a single point (x, y, z) to 8 base64 characters.
92
- * Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.
93
- *
94
- * @param x - The x coordinate
95
- * @param y - The y coordinate
96
- * @param z - The z coordinate
97
- * @returns An 8-character base64 string representing the point
98
- */
99
- static encodePoint(x, y, z) {
100
- const xBits = numberToFloat16Bits(x);
101
- const yBits = numberToFloat16Bits(y);
102
- const zBits = numberToFloat16Bits(z);
103
- const b0 = xBits & 255;
104
- const b1 = xBits >> 8 & 255;
105
- const b2 = yBits & 255;
106
- const b3 = yBits >> 8 & 255;
107
- const b4 = zBits & 255;
108
- const b5 = zBits >> 8 & 255;
109
- const bitmap1 = b0 << 16 | b1 << 8 | b2;
110
- const bitmap2 = b3 << 16 | b4 << 8 | b5;
111
- return BASE64_CHARS[bitmap1 >> 18 & 63] + BASE64_CHARS[bitmap1 >> 12 & 63] + BASE64_CHARS[bitmap1 >> 6 & 63] + BASE64_CHARS[bitmap1 & 63] + BASE64_CHARS[bitmap2 >> 18 & 63] + BASE64_CHARS[bitmap2 >> 12 & 63] + BASE64_CHARS[bitmap2 >> 6 & 63] + BASE64_CHARS[bitmap2 & 63];
112
- }
113
- /**
114
- * Convert an array of VecModels to a base64 string for compact storage.
115
- * Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is
116
- * undefined, it defaults to 0.5.
117
- *
118
- * @param points - An array of VecModel objects to encode
119
- * @returns A base64-encoded string containing all points
120
- */
121
- static encodePoints(points) {
122
- const uint16s = new Uint16Array(points.length * 3);
123
- for (let i = 0; i < points.length; i++) {
124
- const p = points[i];
125
- uint16s[i * 3] = numberToFloat16Bits(p.x);
126
- uint16s[i * 3 + 1] = numberToFloat16Bits(p.y);
127
- uint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5);
128
- }
129
- return uint16ArrayToBase64(uint16s);
130
- }
131
- /**
132
- * Convert a base64 string back to an array of VecModels.
133
- * Decodes Float16-encoded coordinates (x, y, z) from the base64 string.
134
- *
135
- * @param base64 - The base64-encoded string containing point data
136
- * @returns An array of VecModel objects decoded from the string
137
- */
138
- static decodePoints(base64) {
139
- const uint16s = base64ToUint16Array(base64);
140
- const result = [];
141
- for (let i = 0; i < uint16s.length; i += 3) {
142
- result.push({
143
- x: float16BitsToNumber(uint16s[i]),
144
- y: float16BitsToNumber(uint16s[i + 1]),
145
- z: float16BitsToNumber(uint16s[i + 2])
146
- });
147
- }
148
- return result;
149
- }
150
- /**
151
- * Decode a single point (8 base64 chars) starting at the given offset.
152
- * Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.
153
- *
154
- * @param b64Points - The base64-encoded string containing point data
155
- * @param charOffset - The character offset where the point starts (must be a multiple of 8)
156
- * @returns A VecModel object with x, y, and z coordinates
157
- * @internal
158
- */
159
- static decodePointAt(b64Points, charOffset) {
160
- const c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)];
161
- const c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)];
162
- const c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)];
163
- const c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)];
164
- const c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)];
165
- const c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)];
166
- const c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)];
167
- const c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)];
168
- const bitmap1 = c0 << 18 | c1 << 12 | c2 << 6 | c3;
169
- const bitmap2 = c4 << 18 | c5 << 12 | c6 << 6 | c7;
170
- const xBits = bitmap1 >> 16 & 255 | bitmap1 & 65280;
171
- const yBits = bitmap1 & 255 | bitmap2 >> 8 & 65280;
172
- const zBits = bitmap2 >> 8 & 255 | bitmap2 << 8 & 65280;
173
- return {
174
- x: float16BitsToNumber(xBits),
175
- y: float16BitsToNumber(yBits),
176
- z: float16BitsToNumber(zBits)
177
- };
178
- }
179
- /**
180
- * Get the first point from a base64-encoded string of points.
181
- *
182
- * @param b64Points - The base64-encoded string containing point data
183
- * @returns The first point as a VecModel, or null if the string is too short
184
- * @public
185
- */
186
- static decodeFirstPoint(b64Points) {
187
- if (b64Points.length < POINT_B64_LENGTH) return null;
188
- return b64Vecs.decodePointAt(b64Points, 0);
189
- }
190
- /**
191
- * Get the last point from a base64-encoded string of points.
192
- *
193
- * @param b64Points - The base64-encoded string containing point data
194
- * @returns The last point as a VecModel, or null if the string is too short
195
- */
196
- static decodeLastPoint(b64Points) {
197
- if (b64Points.length < POINT_B64_LENGTH) return null;
198
- return b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH);
199
- }
200
- }
201
- export {
202
- b64Vecs
203
- };
204
- //# sourceMappingURL=b64Vecs.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/misc/b64Vecs.ts"],
4
- "sourcesContent": ["import { VecModel } from './geometry-types'\n\n// Each point = 3 Float16s = 6 bytes = 8 base64 chars\nconst POINT_B64_LENGTH = 8\n\n// O(1) lookup table for base64 decoding (maps char code -> 6-bit value)\nconst BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nconst B64_LOOKUP = new Uint8Array(128)\nfor (let i = 0; i < 64; i++) {\n\tB64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i\n}\n\n// Precomputed powers of 2 for Float16 exponents (exp - 15, so indices 0-30 map to 2^-15 to 2^15)\nconst POW2 = new Float64Array(31)\nfor (let i = 0; i < 31; i++) {\n\tPOW2[i] = Math.pow(2, i - 15)\n}\nconst POW2_SUBNORMAL = Math.pow(2, -14) / 1024 // For subnormal numbers\n\n// Precomputed mantissa values: 1 + frac/1024 for all 1024 possible frac values\n// Avoids division in hot path\nconst MANTISSA = new Float64Array(1024)\nfor (let i = 0; i < 1024; i++) {\n\tMANTISSA[i] = 1 + i / 1024\n}\n\n/**\n * Convert a Uint16Array (containing Float16 bits) to base64.\n * Processes bytes in groups of 3 to produce 4 base64 characters.\n *\n * @internal\n */\nfunction uint16ArrayToBase64(uint16Array: Uint16Array): string {\n\tconst uint8Array = new Uint8Array(\n\t\tuint16Array.buffer,\n\t\tuint16Array.byteOffset,\n\t\tuint16Array.byteLength\n\t)\n\tlet result = ''\n\n\t// Process bytes in groups of 3 -> 4 base64 chars\n\tfor (let i = 0; i < uint8Array.length; i += 3) {\n\t\tconst byte1 = uint8Array[i]\n\t\tconst byte2 = uint8Array[i + 1] // Always exists for our use case (multiple of 6 bytes)\n\t\tconst byte3 = uint8Array[i + 2]\n\n\t\tconst bitmap = (byte1 << 16) | (byte2 << 8) | byte3\n\t\tresult +=\n\t\t\tBASE64_CHARS[(bitmap >> 18) & 63] +\n\t\t\tBASE64_CHARS[(bitmap >> 12) & 63] +\n\t\t\tBASE64_CHARS[(bitmap >> 6) & 63] +\n\t\t\tBASE64_CHARS[bitmap & 63]\n\t}\n\n\treturn result\n}\n\n/**\n * Convert a base64 string to Uint16Array containing Float16 bits.\n * The base64 string must have a length that is a multiple of 4.\n *\n * @param base64 - The base64-encoded string to decode\n * @returns A Uint16Array containing the decoded Float16 bit values\n * @public\n */\nfunction base64ToUint16Array(base64: string): Uint16Array {\n\t// Calculate exact number of bytes (4 base64 chars = 3 bytes)\n\tconst numBytes = Math.floor((base64.length * 3) / 4)\n\tconst bytes = new Uint8Array(numBytes)\n\tlet byteIndex = 0\n\n\t// Process in groups of 4 base64 characters\n\tfor (let i = 0; i < base64.length; i += 4) {\n\t\tconst c0 = B64_LOOKUP[base64.charCodeAt(i)]\n\t\tconst c1 = B64_LOOKUP[base64.charCodeAt(i + 1)]\n\t\tconst c2 = B64_LOOKUP[base64.charCodeAt(i + 2)]\n\t\tconst c3 = B64_LOOKUP[base64.charCodeAt(i + 3)]\n\n\t\tconst bitmap = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3\n\n\t\tbytes[byteIndex++] = (bitmap >> 16) & 255\n\t\tbytes[byteIndex++] = (bitmap >> 8) & 255\n\t\tbytes[byteIndex++] = bitmap & 255\n\t}\n\n\treturn new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)\n}\n\n/**\n * Convert Float16 bits to a number using optimized lookup tables.\n * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.\n *\n * @param bits - The 16-bit Float16 value to decode\n * @returns The decoded number value\n */\nfunction float16BitsToNumber(bits: number): number {\n\tconst sign = bits >> 15\n\tconst exp = (bits >> 10) & 0x1f\n\tconst frac = bits & 0x3ff\n\n\tif (exp === 0) {\n\t\t// Subnormal or zero - rare case\n\t\treturn sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL\n\t}\n\tif (exp === 31) {\n\t\t// Infinity or NaN - very rare\n\t\treturn frac ? NaN : sign ? -Infinity : Infinity\n\t}\n\t// Normal case - two table lookups, one multiply, no division\n\tconst magnitude = POW2[exp] * MANTISSA[frac]\n\treturn sign ? -magnitude : magnitude\n}\n\n/**\n * Convert a number to Float16 bits.\n * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.\n *\n * @param value - The number to encode as Float16\n * @returns The 16-bit Float16 representation of the number\n * @internal\n */\nfunction numberToFloat16Bits(value: number): number {\n\tif (value === 0) return Object.is(value, -0) ? 0x8000 : 0\n\tif (!Number.isFinite(value)) {\n\t\tif (Number.isNaN(value)) return 0x7e00\n\t\treturn value > 0 ? 0x7c00 : 0xfc00\n\t}\n\n\tconst sign = value < 0 ? 1 : 0\n\tvalue = Math.abs(value)\n\n\t// Find exponent and mantissa\n\tconst exp = Math.floor(Math.log2(value))\n\tlet expBiased = exp + 15\n\n\tif (expBiased >= 31) {\n\t\t// Overflow to infinity\n\t\treturn (sign << 15) | 0x7c00\n\t}\n\tif (expBiased <= 0) {\n\t\t// Subnormal or underflow\n\t\tconst frac = Math.round(value * Math.pow(2, 14) * 1024)\n\t\treturn (sign << 15) | (frac & 0x3ff)\n\t}\n\n\t// Normal number\n\tconst mantissa = value / Math.pow(2, exp) - 1\n\tlet frac = Math.round(mantissa * 1024)\n\n\t// Handle rounding overflow: if frac rounds to 1024, increment exponent\n\tif (frac >= 1024) {\n\t\tfrac = 0\n\t\texpBiased++\n\t\tif (expBiased >= 31) {\n\t\t\t// Overflow to infinity\n\t\t\treturn (sign << 15) | 0x7c00\n\t\t}\n\t}\n\n\treturn (sign << 15) | (expBiased << 10) | frac\n}\n\n/**\n * Utilities for encoding and decoding points using base64 and Float16 encoding.\n * Provides functions for converting between VecModel arrays and compact base64 strings,\n * as well as individual point encoding/decoding operations.\n *\n * @public\n */\nexport class b64Vecs {\n\t/**\n\t * Encode a single point (x, y, z) to 8 base64 characters.\n\t * Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.\n\t *\n\t * @param x - The x coordinate\n\t * @param y - The y coordinate\n\t * @param z - The z coordinate\n\t * @returns An 8-character base64 string representing the point\n\t */\n\tstatic encodePoint(x: number, y: number, z: number): string {\n\t\tconst xBits = numberToFloat16Bits(x)\n\t\tconst yBits = numberToFloat16Bits(y)\n\t\tconst zBits = numberToFloat16Bits(z)\n\n\t\t// Convert Float16 bits to 6 bytes (little-endian)\n\t\tconst b0 = xBits & 0xff\n\t\tconst b1 = (xBits >> 8) & 0xff\n\t\tconst b2 = yBits & 0xff\n\t\tconst b3 = (yBits >> 8) & 0xff\n\t\tconst b4 = zBits & 0xff\n\t\tconst b5 = (zBits >> 8) & 0xff\n\n\t\t// Convert 6 bytes to 8 base64 chars\n\t\tconst bitmap1 = (b0 << 16) | (b1 << 8) | b2\n\t\tconst bitmap2 = (b3 << 16) | (b4 << 8) | b5\n\n\t\treturn (\n\t\t\tBASE64_CHARS[(bitmap1 >> 18) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap1 >> 12) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap1 >> 6) & 0x3f] +\n\t\t\tBASE64_CHARS[bitmap1 & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 18) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 12) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 6) & 0x3f] +\n\t\t\tBASE64_CHARS[bitmap2 & 0x3f]\n\t\t)\n\t}\n\n\t/**\n\t * Convert an array of VecModels to a base64 string for compact storage.\n\t * Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is\n\t * undefined, it defaults to 0.5.\n\t *\n\t * @param points - An array of VecModel objects to encode\n\t * @returns A base64-encoded string containing all points\n\t */\n\tstatic encodePoints(points: VecModel[]): string {\n\t\tconst uint16s = new Uint16Array(points.length * 3)\n\t\tfor (let i = 0; i < points.length; i++) {\n\t\t\tconst p = points[i]\n\t\t\tuint16s[i * 3] = numberToFloat16Bits(p.x)\n\t\t\tuint16s[i * 3 + 1] = numberToFloat16Bits(p.y)\n\t\t\tuint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5)\n\t\t}\n\t\treturn uint16ArrayToBase64(uint16s)\n\t}\n\n\t/**\n\t * Convert a base64 string back to an array of VecModels.\n\t * Decodes Float16-encoded coordinates (x, y, z) from the base64 string.\n\t *\n\t * @param base64 - The base64-encoded string containing point data\n\t * @returns An array of VecModel objects decoded from the string\n\t */\n\tstatic decodePoints(base64: string): VecModel[] {\n\t\tconst uint16s = base64ToUint16Array(base64)\n\t\tconst result: VecModel[] = []\n\t\tfor (let i = 0; i < uint16s.length; i += 3) {\n\t\t\tresult.push({\n\t\t\t\tx: float16BitsToNumber(uint16s[i]),\n\t\t\t\ty: float16BitsToNumber(uint16s[i + 1]),\n\t\t\t\tz: float16BitsToNumber(uint16s[i + 2]),\n\t\t\t})\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Decode a single point (8 base64 chars) starting at the given offset.\n\t * Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @param charOffset - The character offset where the point starts (must be a multiple of 8)\n\t * @returns A VecModel object with x, y, and z coordinates\n\t * @internal\n\t */\n\tstatic decodePointAt(b64Points: string, charOffset: number): VecModel {\n\t\t// Decode 8 base64 chars -> 6 bytes -> 3 Float16s using O(1) lookup\n\t\tconst c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)]\n\t\tconst c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)]\n\t\tconst c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)]\n\t\tconst c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)]\n\t\tconst c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)]\n\t\tconst c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)]\n\t\tconst c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)]\n\t\tconst c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)]\n\n\t\t// 4 base64 chars -> 24 bits -> 3 bytes\n\t\tconst bitmap1 = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3\n\t\tconst bitmap2 = (c4 << 18) | (c5 << 12) | (c6 << 6) | c7\n\n\t\t// Extract Float16 bits directly (little-endian byte order)\n\t\t// bitmap1 = [byte0:8][byte1:8][byte2:8], bitmap2 = [byte3:8][byte4:8][byte5:8]\n\t\t// xBits = byte0 | (byte1 << 8), yBits = byte2 | (byte3 << 8), zBits = byte4 | (byte5 << 8)\n\t\tconst xBits = ((bitmap1 >> 16) & 0xff) | (bitmap1 & 0xff00)\n\t\tconst yBits = (bitmap1 & 0xff) | ((bitmap2 >> 8) & 0xff00)\n\t\tconst zBits = ((bitmap2 >> 8) & 0xff) | ((bitmap2 << 8) & 0xff00)\n\n\t\treturn {\n\t\t\tx: float16BitsToNumber(xBits),\n\t\t\ty: float16BitsToNumber(yBits),\n\t\t\tz: float16BitsToNumber(zBits),\n\t\t}\n\t}\n\n\t/**\n\t * Get the first point from a base64-encoded string of points.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @returns The first point as a VecModel, or null if the string is too short\n\t * @public\n\t */\n\tstatic decodeFirstPoint(b64Points: string): VecModel | null {\n\t\tif (b64Points.length < POINT_B64_LENGTH) return null\n\t\treturn b64Vecs.decodePointAt(b64Points, 0)\n\t}\n\n\t/**\n\t * Get the last point from a base64-encoded string of points.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @returns The last point as a VecModel, or null if the string is too short\n\t */\n\tstatic decodeLastPoint(b64Points: string): VecModel | null {\n\t\tif (b64Points.length < POINT_B64_LENGTH) return null\n\t\treturn b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH)\n\t}\n}\n"],
5
- "mappings": "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",
6
- "names": ["frac"]
7
- }
@@ -1,308 +0,0 @@
1
- import { VecModel } from './geometry-types'
2
-
3
- // Each point = 3 Float16s = 6 bytes = 8 base64 chars
4
- const POINT_B64_LENGTH = 8
5
-
6
- // O(1) lookup table for base64 decoding (maps char code -> 6-bit value)
7
- const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
8
- const B64_LOOKUP = new Uint8Array(128)
9
- for (let i = 0; i < 64; i++) {
10
- B64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i
11
- }
12
-
13
- // Precomputed powers of 2 for Float16 exponents (exp - 15, so indices 0-30 map to 2^-15 to 2^15)
14
- const POW2 = new Float64Array(31)
15
- for (let i = 0; i < 31; i++) {
16
- POW2[i] = Math.pow(2, i - 15)
17
- }
18
- const POW2_SUBNORMAL = Math.pow(2, -14) / 1024 // For subnormal numbers
19
-
20
- // Precomputed mantissa values: 1 + frac/1024 for all 1024 possible frac values
21
- // Avoids division in hot path
22
- const MANTISSA = new Float64Array(1024)
23
- for (let i = 0; i < 1024; i++) {
24
- MANTISSA[i] = 1 + i / 1024
25
- }
26
-
27
- /**
28
- * Convert a Uint16Array (containing Float16 bits) to base64.
29
- * Processes bytes in groups of 3 to produce 4 base64 characters.
30
- *
31
- * @internal
32
- */
33
- function uint16ArrayToBase64(uint16Array: Uint16Array): string {
34
- const uint8Array = new Uint8Array(
35
- uint16Array.buffer,
36
- uint16Array.byteOffset,
37
- uint16Array.byteLength
38
- )
39
- let result = ''
40
-
41
- // Process bytes in groups of 3 -> 4 base64 chars
42
- for (let i = 0; i < uint8Array.length; i += 3) {
43
- const byte1 = uint8Array[i]
44
- const byte2 = uint8Array[i + 1] // Always exists for our use case (multiple of 6 bytes)
45
- const byte3 = uint8Array[i + 2]
46
-
47
- const bitmap = (byte1 << 16) | (byte2 << 8) | byte3
48
- result +=
49
- BASE64_CHARS[(bitmap >> 18) & 63] +
50
- BASE64_CHARS[(bitmap >> 12) & 63] +
51
- BASE64_CHARS[(bitmap >> 6) & 63] +
52
- BASE64_CHARS[bitmap & 63]
53
- }
54
-
55
- return result
56
- }
57
-
58
- /**
59
- * Convert a base64 string to Uint16Array containing Float16 bits.
60
- * The base64 string must have a length that is a multiple of 4.
61
- *
62
- * @param base64 - The base64-encoded string to decode
63
- * @returns A Uint16Array containing the decoded Float16 bit values
64
- * @public
65
- */
66
- function base64ToUint16Array(base64: string): Uint16Array {
67
- // Calculate exact number of bytes (4 base64 chars = 3 bytes)
68
- const numBytes = Math.floor((base64.length * 3) / 4)
69
- const bytes = new Uint8Array(numBytes)
70
- let byteIndex = 0
71
-
72
- // Process in groups of 4 base64 characters
73
- for (let i = 0; i < base64.length; i += 4) {
74
- const c0 = B64_LOOKUP[base64.charCodeAt(i)]
75
- const c1 = B64_LOOKUP[base64.charCodeAt(i + 1)]
76
- const c2 = B64_LOOKUP[base64.charCodeAt(i + 2)]
77
- const c3 = B64_LOOKUP[base64.charCodeAt(i + 3)]
78
-
79
- const bitmap = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3
80
-
81
- bytes[byteIndex++] = (bitmap >> 16) & 255
82
- bytes[byteIndex++] = (bitmap >> 8) & 255
83
- bytes[byteIndex++] = bitmap & 255
84
- }
85
-
86
- return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)
87
- }
88
-
89
- /**
90
- * Convert Float16 bits to a number using optimized lookup tables.
91
- * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.
92
- *
93
- * @param bits - The 16-bit Float16 value to decode
94
- * @returns The decoded number value
95
- */
96
- function float16BitsToNumber(bits: number): number {
97
- const sign = bits >> 15
98
- const exp = (bits >> 10) & 0x1f
99
- const frac = bits & 0x3ff
100
-
101
- if (exp === 0) {
102
- // Subnormal or zero - rare case
103
- return sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL
104
- }
105
- if (exp === 31) {
106
- // Infinity or NaN - very rare
107
- return frac ? NaN : sign ? -Infinity : Infinity
108
- }
109
- // Normal case - two table lookups, one multiply, no division
110
- const magnitude = POW2[exp] * MANTISSA[frac]
111
- return sign ? -magnitude : magnitude
112
- }
113
-
114
- /**
115
- * Convert a number to Float16 bits.
116
- * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.
117
- *
118
- * @param value - The number to encode as Float16
119
- * @returns The 16-bit Float16 representation of the number
120
- * @internal
121
- */
122
- function numberToFloat16Bits(value: number): number {
123
- if (value === 0) return Object.is(value, -0) ? 0x8000 : 0
124
- if (!Number.isFinite(value)) {
125
- if (Number.isNaN(value)) return 0x7e00
126
- return value > 0 ? 0x7c00 : 0xfc00
127
- }
128
-
129
- const sign = value < 0 ? 1 : 0
130
- value = Math.abs(value)
131
-
132
- // Find exponent and mantissa
133
- const exp = Math.floor(Math.log2(value))
134
- let expBiased = exp + 15
135
-
136
- if (expBiased >= 31) {
137
- // Overflow to infinity
138
- return (sign << 15) | 0x7c00
139
- }
140
- if (expBiased <= 0) {
141
- // Subnormal or underflow
142
- const frac = Math.round(value * Math.pow(2, 14) * 1024)
143
- return (sign << 15) | (frac & 0x3ff)
144
- }
145
-
146
- // Normal number
147
- const mantissa = value / Math.pow(2, exp) - 1
148
- let frac = Math.round(mantissa * 1024)
149
-
150
- // Handle rounding overflow: if frac rounds to 1024, increment exponent
151
- if (frac >= 1024) {
152
- frac = 0
153
- expBiased++
154
- if (expBiased >= 31) {
155
- // Overflow to infinity
156
- return (sign << 15) | 0x7c00
157
- }
158
- }
159
-
160
- return (sign << 15) | (expBiased << 10) | frac
161
- }
162
-
163
- /**
164
- * Utilities for encoding and decoding points using base64 and Float16 encoding.
165
- * Provides functions for converting between VecModel arrays and compact base64 strings,
166
- * as well as individual point encoding/decoding operations.
167
- *
168
- * @public
169
- */
170
- export class b64Vecs {
171
- /**
172
- * Encode a single point (x, y, z) to 8 base64 characters.
173
- * Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.
174
- *
175
- * @param x - The x coordinate
176
- * @param y - The y coordinate
177
- * @param z - The z coordinate
178
- * @returns An 8-character base64 string representing the point
179
- */
180
- static encodePoint(x: number, y: number, z: number): string {
181
- const xBits = numberToFloat16Bits(x)
182
- const yBits = numberToFloat16Bits(y)
183
- const zBits = numberToFloat16Bits(z)
184
-
185
- // Convert Float16 bits to 6 bytes (little-endian)
186
- const b0 = xBits & 0xff
187
- const b1 = (xBits >> 8) & 0xff
188
- const b2 = yBits & 0xff
189
- const b3 = (yBits >> 8) & 0xff
190
- const b4 = zBits & 0xff
191
- const b5 = (zBits >> 8) & 0xff
192
-
193
- // Convert 6 bytes to 8 base64 chars
194
- const bitmap1 = (b0 << 16) | (b1 << 8) | b2
195
- const bitmap2 = (b3 << 16) | (b4 << 8) | b5
196
-
197
- return (
198
- BASE64_CHARS[(bitmap1 >> 18) & 0x3f] +
199
- BASE64_CHARS[(bitmap1 >> 12) & 0x3f] +
200
- BASE64_CHARS[(bitmap1 >> 6) & 0x3f] +
201
- BASE64_CHARS[bitmap1 & 0x3f] +
202
- BASE64_CHARS[(bitmap2 >> 18) & 0x3f] +
203
- BASE64_CHARS[(bitmap2 >> 12) & 0x3f] +
204
- BASE64_CHARS[(bitmap2 >> 6) & 0x3f] +
205
- BASE64_CHARS[bitmap2 & 0x3f]
206
- )
207
- }
208
-
209
- /**
210
- * Convert an array of VecModels to a base64 string for compact storage.
211
- * Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is
212
- * undefined, it defaults to 0.5.
213
- *
214
- * @param points - An array of VecModel objects to encode
215
- * @returns A base64-encoded string containing all points
216
- */
217
- static encodePoints(points: VecModel[]): string {
218
- const uint16s = new Uint16Array(points.length * 3)
219
- for (let i = 0; i < points.length; i++) {
220
- const p = points[i]
221
- uint16s[i * 3] = numberToFloat16Bits(p.x)
222
- uint16s[i * 3 + 1] = numberToFloat16Bits(p.y)
223
- uint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5)
224
- }
225
- return uint16ArrayToBase64(uint16s)
226
- }
227
-
228
- /**
229
- * Convert a base64 string back to an array of VecModels.
230
- * Decodes Float16-encoded coordinates (x, y, z) from the base64 string.
231
- *
232
- * @param base64 - The base64-encoded string containing point data
233
- * @returns An array of VecModel objects decoded from the string
234
- */
235
- static decodePoints(base64: string): VecModel[] {
236
- const uint16s = base64ToUint16Array(base64)
237
- const result: VecModel[] = []
238
- for (let i = 0; i < uint16s.length; i += 3) {
239
- result.push({
240
- x: float16BitsToNumber(uint16s[i]),
241
- y: float16BitsToNumber(uint16s[i + 1]),
242
- z: float16BitsToNumber(uint16s[i + 2]),
243
- })
244
- }
245
- return result
246
- }
247
-
248
- /**
249
- * Decode a single point (8 base64 chars) starting at the given offset.
250
- * Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.
251
- *
252
- * @param b64Points - The base64-encoded string containing point data
253
- * @param charOffset - The character offset where the point starts (must be a multiple of 8)
254
- * @returns A VecModel object with x, y, and z coordinates
255
- * @internal
256
- */
257
- static decodePointAt(b64Points: string, charOffset: number): VecModel {
258
- // Decode 8 base64 chars -> 6 bytes -> 3 Float16s using O(1) lookup
259
- const c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)]
260
- const c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)]
261
- const c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)]
262
- const c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)]
263
- const c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)]
264
- const c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)]
265
- const c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)]
266
- const c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)]
267
-
268
- // 4 base64 chars -> 24 bits -> 3 bytes
269
- const bitmap1 = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3
270
- const bitmap2 = (c4 << 18) | (c5 << 12) | (c6 << 6) | c7
271
-
272
- // Extract Float16 bits directly (little-endian byte order)
273
- // bitmap1 = [byte0:8][byte1:8][byte2:8], bitmap2 = [byte3:8][byte4:8][byte5:8]
274
- // xBits = byte0 | (byte1 << 8), yBits = byte2 | (byte3 << 8), zBits = byte4 | (byte5 << 8)
275
- const xBits = ((bitmap1 >> 16) & 0xff) | (bitmap1 & 0xff00)
276
- const yBits = (bitmap1 & 0xff) | ((bitmap2 >> 8) & 0xff00)
277
- const zBits = ((bitmap2 >> 8) & 0xff) | ((bitmap2 << 8) & 0xff00)
278
-
279
- return {
280
- x: float16BitsToNumber(xBits),
281
- y: float16BitsToNumber(yBits),
282
- z: float16BitsToNumber(zBits),
283
- }
284
- }
285
-
286
- /**
287
- * Get the first point from a base64-encoded string of points.
288
- *
289
- * @param b64Points - The base64-encoded string containing point data
290
- * @returns The first point as a VecModel, or null if the string is too short
291
- * @public
292
- */
293
- static decodeFirstPoint(b64Points: string): VecModel | null {
294
- if (b64Points.length < POINT_B64_LENGTH) return null
295
- return b64Vecs.decodePointAt(b64Points, 0)
296
- }
297
-
298
- /**
299
- * Get the last point from a base64-encoded string of points.
300
- *
301
- * @param b64Points - The base64-encoded string containing point data
302
- * @returns The last point as a VecModel, or null if the string is too short
303
- */
304
- static decodeLastPoint(b64Points: string): VecModel | null {
305
- if (b64Points.length < POINT_B64_LENGTH) return null
306
- return b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH)
307
- }
308
- }