three-text 0.4.11 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +5 -660
  2. package/LICENSE_THIRD_PARTY +15 -49
  3. package/README.md +265 -44
  4. package/dist/index.cjs +3424 -3450
  5. package/dist/index.d.ts +163 -9
  6. package/dist/index.js +3420 -3451
  7. package/dist/index.min.cjs +718 -676
  8. package/dist/index.min.js +721 -679
  9. package/dist/index.umd.js +3561 -3579
  10. package/dist/index.umd.min.js +803 -756
  11. package/dist/p5/index.cjs +2738 -5
  12. package/dist/p5/index.js +2738 -5
  13. package/dist/patterns/index.js +0 -4
  14. package/dist/slug/index.cjs +380 -0
  15. package/dist/slug/index.d.ts +62 -0
  16. package/dist/slug/index.js +374 -0
  17. package/dist/three/index.cjs +50 -35
  18. package/dist/three/index.js +50 -35
  19. package/dist/three/react.cjs +5 -2
  20. package/dist/three/react.d.ts +66 -120
  21. package/dist/three/react.js +6 -3
  22. package/dist/types/core/Text.d.ts +3 -10
  23. package/dist/types/core/cache/sharedCaches.d.ts +2 -1
  24. package/dist/types/core/shaping/DrawCallbacks.d.ts +11 -3
  25. package/dist/types/core/shaping/TextShaper.d.ts +1 -5
  26. package/dist/types/core/types.d.ts +84 -0
  27. package/dist/types/index.d.ts +7 -3
  28. package/dist/types/{core/cache → mesh}/GlyphContourCollector.d.ts +4 -4
  29. package/dist/types/{core/cache → mesh}/GlyphGeometryBuilder.d.ts +5 -5
  30. package/dist/types/mesh/MeshGeometryBuilder.d.ts +18 -0
  31. package/dist/types/{core → mesh}/geometry/BoundaryClusterer.d.ts +1 -1
  32. package/dist/types/{core → mesh}/geometry/Extruder.d.ts +1 -1
  33. package/dist/types/{core → mesh}/geometry/PathOptimizer.d.ts +1 -1
  34. package/dist/types/{core → mesh}/geometry/Polygonizer.d.ts +1 -1
  35. package/dist/types/{core → mesh}/geometry/Tessellator.d.ts +1 -1
  36. package/dist/types/react/utils.d.ts +2 -0
  37. package/dist/types/vector/GlyphOutlineCollector.d.ts +25 -0
  38. package/dist/types/vector/GlyphVectorGeometryBuilder.d.ts +26 -0
  39. package/dist/types/vector/LoopBlinnGeometry.d.ts +68 -0
  40. package/dist/types/vector/index.d.ts +29 -0
  41. package/dist/types/vector/loopBlinnTSL.d.ts +11 -0
  42. package/dist/types/vector/react.d.ts +24 -0
  43. package/dist/types/vector/webgl/index.d.ts +7 -0
  44. package/dist/types/vector/webgpu/index.d.ts +11 -0
  45. package/dist/vector/index.cjs +1458 -0
  46. package/dist/vector/index.d.ts +122 -0
  47. package/dist/vector/index.js +1434 -0
  48. package/dist/vector/react.cjs +153 -0
  49. package/dist/vector/react.d.ts +317 -0
  50. package/dist/vector/react.js +132 -0
  51. package/dist/vector/types/slug-lib/src/SlugPacker.d.ts +17 -0
  52. package/dist/vector/types/slug-lib/src/WebGL2Renderer.d.ts +21 -0
  53. package/dist/vector/types/slug-lib/src/WebGPURenderer.d.ts +16 -0
  54. package/dist/vector/types/slug-lib/src/index.d.ts +15 -0
  55. package/dist/vector/types/slug-lib/src/shaderStrings.d.ts +9 -0
  56. package/dist/vector/types/slug-lib/src/types.d.ts +34 -0
  57. package/dist/vector/types/src/core/types.d.ts +381 -0
  58. package/dist/vector/types/src/hyphenation/HyphenationPatternLoader.d.ts +2 -0
  59. package/dist/vector/types/src/hyphenation/index.d.ts +7 -0
  60. package/dist/vector/types/src/hyphenation/types.d.ts +6 -0
  61. package/dist/vector/types/src/utils/Cache.d.ts +14 -0
  62. package/dist/vector/types/src/utils/vectors.d.ts +75 -0
  63. package/dist/vector/types/src/vector/VectorDataBuilder.d.ts +30 -0
  64. package/dist/vector/types/src/vector/VectorThreeAdapter.d.ts +27 -0
  65. package/dist/vector/types/src/vector/index.d.ts +15 -0
  66. package/dist/vector/webgl/index.cjs +229 -0
  67. package/dist/vector/webgl/index.d.ts +53 -0
  68. package/dist/vector/webgl/index.js +227 -0
  69. package/dist/vector/webgpu/index.cjs +321 -0
  70. package/dist/vector/webgpu/index.d.ts +57 -0
  71. package/dist/vector/webgpu/index.js +319 -0
  72. package/dist/webgl-vector/index.cjs +243 -0
  73. package/dist/webgl-vector/index.d.ts +34 -0
  74. package/dist/webgl-vector/index.js +241 -0
  75. package/dist/webgpu-vector/index.cjs +336 -0
  76. package/dist/webgpu-vector/index.d.ts +38 -0
  77. package/dist/webgpu-vector/index.js +334 -0
  78. package/package.json +49 -4
  79. package/dist/patterns/cs.cjs +0 -14
  80. package/dist/patterns/cs.d.ts +0 -28
  81. package/dist/patterns/cs.js +0 -14
  82. package/dist/patterns/cs.umd.js +0 -14
  83. package/dist/patterns/id.cjs +0 -14
  84. package/dist/patterns/id.d.ts +0 -28
  85. package/dist/patterns/id.js +0 -14
  86. package/dist/patterns/id.umd.js +0 -14
  87. package/dist/patterns/mk.cjs +0 -14
  88. package/dist/patterns/mk.d.ts +0 -28
  89. package/dist/patterns/mk.js +0 -14
  90. package/dist/patterns/mk.umd.js +0 -14
  91. package/dist/patterns/sr-cyrl.cjs +0 -14
  92. package/dist/patterns/sr-cyrl.d.ts +0 -28
  93. package/dist/patterns/sr-cyrl.js +0 -14
  94. package/dist/patterns/sr-cyrl.umd.js +0 -14
@@ -0,0 +1,374 @@
1
+ var vertGLSL = "#version 300 es\n// ===================================================\n// GLSL 300 es port of the reference Slug vertex shader.\n// Original HLSL by Eric Lengyel, MIT License, Copyright 2017.\n// See: https://jcgt.org/published/0006/02/02/\n// ===================================================\n\nprecision highp float;\nprecision highp int;\n\n// Per-vertex attributes (5 x vec4, matching Slug reference layout)\nlayout(location = 0) in vec4 a_pos; // .xy = object-space position, .zw = outward normal\nlayout(location = 1) in vec4 a_tex; // .xy = em-space sample coords, .z = packed glyph loc, .w = packed band max + flags\nlayout(location = 2) in vec4 a_jac; // inverse Jacobian (2x2): (j00, j01, j10, j11)\nlayout(location = 3) in vec4 a_bnd; // (bandScaleX, bandScaleY, bandOffsetX, bandOffsetY)\nlayout(location = 4) in vec4 a_col; // vertex color RGBA\n\nuniform mat4 slug_matrix; // MVP matrix (rows as vec4s)\nuniform vec2 slug_viewport; // viewport dimensions in pixels\n\nout vec4 v_color;\nout vec2 v_texcoord;\nflat out vec4 v_banding;\nflat out ivec4 v_glyph;\n\nvoid SlugUnpack(vec4 tex, vec4 bnd, out vec4 vbnd, out ivec4 vgly) {\n uvec2 g = floatBitsToUint(tex.zw);\n vgly = ivec4(g.x & 0xFFFFu, g.x >> 16u, g.y & 0xFFFFu, g.y >> 16u);\n vbnd = bnd;\n}\n\nvec2 SlugDilate(vec4 pos, vec4 tex, vec4 jac, vec4 m0, vec4 m1, vec4 m3, vec2 dim, out vec2 vpos) {\n vec2 n = normalize(pos.zw);\n float s = dot(m3.xy, pos.xy) + m3.w;\n float t = dot(m3.xy, n);\n\n float u = (s * dot(m0.xy, n) - t * (dot(m0.xy, pos.xy) + m0.w)) * dim.x;\n float v = (s * dot(m1.xy, n) - t * (dot(m1.xy, pos.xy) + m1.w)) * dim.y;\n\n float s2 = s * s;\n float st = s * t;\n float uv = u * u + v * v;\n vec2 d = pos.zw * (s2 * (st + sqrt(uv)) / (uv - st * st));\n\n vpos = pos.xy + d;\n return vec2(tex.x + dot(d, jac.xy), tex.y + dot(d, jac.zw));\n}\n\nvoid main() {\n vec2 p;\n\n // Dynamic dilation: expand quad by a pixel to prevent edge clipping.\n v_texcoord = SlugDilate(a_pos, a_tex, a_jac,\n slug_matrix[0], slug_matrix[1], slug_matrix[3],\n slug_viewport, p);\n\n // MVP transform on dilated position.\n gl_Position.x = p.x * slug_matrix[0].x + p.y * slug_matrix[0].y + slug_matrix[0].w;\n gl_Position.y = p.x * slug_matrix[1].x + p.y * slug_matrix[1].y + slug_matrix[1].w;\n gl_Position.z = p.x * slug_matrix[2].x + p.y * slug_matrix[2].y + slug_matrix[2].w;\n gl_Position.w = p.x * slug_matrix[3].x + p.y * slug_matrix[3].y + slug_matrix[3].w;\n\n SlugUnpack(a_tex, a_bnd, v_banding, v_glyph);\n v_color = a_col;\n}\n";
2
+
3
+ var fragGLSL = "#version 300 es\n// ===================================================\n// GLSL 300 es port of the reference Slug pixel shader.\n// Original HLSL by Eric Lengyel, MIT License, Copyright 2017.\n// See: https://jcgt.org/published/0006/02/02/\n// ===================================================\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 emsPerPixel = fwidth(renderCoord);\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
4
+
5
+ var vertWGSL = "// ===================================================\n// WGSL port of the reference Slug vertex shader.\n// Original HLSL by Eric Lengyel, MIT License, Copyright 2017.\n// See: https://jcgt.org/published/0006/02/02/\n// ===================================================\n\nstruct Uniforms {\n slug_matrix: mat4x4<f32>,\n slug_viewport: vec2<f32>,\n};\n\n@group(0) @binding(0) var<uniform> u: Uniforms;\n\nstruct VertexInput {\n @location(0) a_pos: vec4<f32>,\n @location(1) a_tex: vec4<f32>,\n @location(2) a_jac: vec4<f32>,\n @location(3) a_bnd: vec4<f32>,\n @location(4) a_col: vec4<f32>,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) color: vec4<f32>,\n @location(1) texcoord: vec2<f32>,\n @location(2) @interpolate(flat) banding: vec4<f32>,\n @location(3) @interpolate(flat) glyph: vec4<i32>,\n};\n\nfn SlugUnpack(tex: vec4<f32>, bnd: vec4<f32>) -> VertexOutput {\n var out: VertexOutput;\n let g = bitcast<vec2<u32>>(tex.zw);\n out.glyph = vec4<i32>(\n i32(g.x & 0xFFFFu),\n i32(g.x >> 16u),\n i32(g.y & 0xFFFFu),\n i32(g.y >> 16u)\n );\n out.banding = bnd;\n return out;\n}\n\nfn SlugDilate(\n pos: vec4<f32>, tex: vec4<f32>, jac: vec4<f32>,\n m0: vec4<f32>, m1: vec4<f32>, m3: vec4<f32>,\n dim: vec2<f32>\n) -> vec4<f32> {\n // Returns vec4(dilated_pos.xy, new_texcoord.xy)\n let n = normalize(pos.zw);\n let s = dot(m3.xy, pos.xy) + m3.w;\n let t = dot(m3.xy, n);\n\n let u_val = (s * dot(m0.xy, n) - t * (dot(m0.xy, pos.xy) + m0.w)) * dim.x;\n let v_val = (s * dot(m1.xy, n) - t * (dot(m1.xy, pos.xy) + m1.w)) * dim.y;\n\n let s2 = s * s;\n let st = s * t;\n let uv = u_val * u_val + v_val * v_val;\n let d = pos.zw * (s2 * (st + sqrt(uv)) / (uv - st * st));\n\n let vpos = pos.xy + d;\n let vtex = vec2<f32>(tex.x + dot(d, jac.xy), tex.y + dot(d, jac.zw));\n return vec4<f32>(vpos, vtex);\n}\n\n@vertex\nfn vs_main(in: VertexInput) -> VertexOutput {\n let dilated = SlugDilate(\n in.a_pos, in.a_tex, in.a_jac,\n u.slug_matrix[0], u.slug_matrix[1], u.slug_matrix[3],\n u.slug_viewport\n );\n\n let p = dilated.xy;\n var out = SlugUnpack(in.a_tex, in.a_bnd);\n\n out.position = vec4<f32>(\n p.x * u.slug_matrix[0].x + p.y * u.slug_matrix[0].y + u.slug_matrix[0].w,\n p.x * u.slug_matrix[1].x + p.y * u.slug_matrix[1].y + u.slug_matrix[1].w,\n p.x * u.slug_matrix[2].x + p.y * u.slug_matrix[2].y + u.slug_matrix[2].w,\n p.x * u.slug_matrix[3].x + p.y * u.slug_matrix[3].y + u.slug_matrix[3].w\n );\n\n out.texcoord = dilated.zw;\n out.color = in.a_col;\n return out;\n}\n";
6
+
7
+ var fragWGSL = "// ===================================================\n// WGSL port of the reference Slug pixel shader.\n// Original HLSL by Eric Lengyel, MIT License, Copyright 2017.\n// See: https://jcgt.org/published/0006/02/02/\n// ===================================================\n\nconst kLogBandTextureWidth: u32 = 12u;\n\n@group(0) @binding(1) var curveTexture: texture_2d<f32>;\n@group(0) @binding(2) var bandTexture: texture_2d<u32>;\n\nstruct FragmentInput {\n @location(0) color: vec4<f32>,\n @location(1) texcoord: vec2<f32>,\n @location(2) @interpolate(flat) banding: vec4<f32>,\n @location(3) @interpolate(flat) glyph: vec4<i32>,\n};\n\nfn CalcRootCode(y1: f32, y2: f32, y3: f32) -> u32 {\n let i1 = bitcast<u32>(y1) >> 31u;\n let i2 = bitcast<u32>(y2) >> 30u;\n let i3 = bitcast<u32>(y3) >> 29u;\n\n var shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nfn SolveHorizPoly(p12: vec4<f32>, p3: vec2<f32>) -> vec2<f32> {\n let a = p12.xy - p12.zw * 2.0 + p3;\n let b = p12.xy - p12.zw;\n let ra = 1.0 / a.y;\n let rb = 0.5 / b.y;\n\n let d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n var t1 = (b.y - d) * ra;\n var t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) {\n t1 = p12.y * rb;\n t2 = t1;\n }\n\n return vec2<f32>(\n (a.x * t1 - b.x * 2.0) * t1 + p12.x,\n (a.x * t2 - b.x * 2.0) * t2 + p12.x\n );\n}\n\nfn SolveVertPoly(p12: vec4<f32>, p3: vec2<f32>) -> vec2<f32> {\n let a = p12.xy - p12.zw * 2.0 + p3;\n let b = p12.xy - p12.zw;\n let ra = 1.0 / a.x;\n let rb = 0.5 / b.x;\n\n let d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n var t1 = (b.x - d) * ra;\n var t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) {\n t1 = p12.x * rb;\n t2 = t1;\n }\n\n return vec2<f32>(\n (a.y * t1 - b.y * 2.0) * t1 + p12.y,\n (a.y * t2 - b.y * 2.0) * t2 + p12.y\n );\n}\n\nfn CalcBandLoc(glyphLoc: vec2<i32>, offset: u32) -> vec2<i32> {\n var bandLoc = vec2<i32>(glyphLoc.x + i32(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> i32(kLogBandTextureWidth);\n bandLoc.x &= (1 << i32(kLogBandTextureWidth)) - 1;\n return bandLoc;\n}\n\nfn CalcCoverage(xcov: f32, ycov: f32, xwgt: f32, ywgt: f32) -> f32 {\n var coverage = max(\n abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0),\n min(abs(xcov), abs(ycov))\n );\n coverage = clamp(coverage, 0.0, 1.0);\n return coverage;\n}\n\nfn SlugRender(renderCoord: vec2<f32>, bandTransform: vec4<f32>, glyphData: vec4<i32>) -> f32 {\n let emsPerPixel = fwidth(renderCoord);\n let pixelsPerEm = 1.0 / emsPerPixel;\n\n var bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n let bandIndex = clamp(\n vec2<i32>(renderCoord * bandTransform.xy + bandTransform.zw),\n vec2<i32>(0, 0),\n bandMax\n );\n let glyphLoc = glyphData.xy;\n\n var xcov = 0.0;\n var xwgt = 0.0;\n\n let hbandData = textureLoad(bandTexture, vec2<i32>(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n let hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (var ci = 0; ci < i32(hbandData.x); ci++) {\n let curveLoc = vec2<i32>(textureLoad(bandTexture, vec2<i32>(hbandLoc.x + ci, hbandLoc.y), 0).xy);\n\n let p12 = textureLoad(curveTexture, curveLoc, 0) - vec4<f32>(renderCoord, renderCoord);\n let p3 = textureLoad(curveTexture, vec2<i32>(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) { break; }\n\n let code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n let r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n var ycov = 0.0;\n var ywgt = 0.0;\n\n let vbandData = textureLoad(bandTexture, vec2<i32>(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n let vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (var ci = 0; ci < i32(vbandData.x); ci++) {\n let curveLoc = vec2<i32>(textureLoad(bandTexture, vec2<i32>(vbandLoc.x + ci, vbandLoc.y), 0).xy);\n let p12 = textureLoad(curveTexture, curveLoc, 0) - vec4<f32>(renderCoord, renderCoord);\n let p3 = textureLoad(curveTexture, vec2<i32>(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) { break; }\n\n let code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n let r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt);\n}\n\n@fragment\nfn fs_main(in: FragmentInput) -> @location(0) vec4<f32> {\n let coverage = SlugRender(in.texcoord, in.banding, in.glyph);\n return in.color * coverage;\n}\n";
8
+
9
+ /**
10
+ * Slug shader source re-exports.
11
+ * The .glsl/.wgsl files are the single source of truth — imported as strings
12
+ * at build time via the glslPlugin in rollup.config.js.
13
+ */
14
+ // @ts-ignore — resolved by rollup glslPlugin
15
+ const vertexShaderGLSL300 = vertGLSL;
16
+ const fragmentShaderGLSL300 = fragGLSL;
17
+ const vertexShaderWGSL = vertWGSL;
18
+ const fragmentShaderWGSL = fragWGSL;
19
+
20
+ /**
21
+ * CPU-side data packer for the Slug algorithm.
22
+ * Faithful to Eric Lengyel's reference layout (MIT License, 2017).
23
+ *
24
+ * Takes generic quadratic Bezier shapes (not text-specific) and produces
25
+ * GPU-ready packed textures + vertex attribute buffers.
26
+ */
27
+ const TEX_WIDTH = 4096;
28
+ const LOG_TEX_WIDTH = 12;
29
+ // Float ↔ Uint32 reinterpretation helpers
30
+ const _f32 = new Float32Array(1);
31
+ const _u32 = new Uint32Array(_f32.buffer);
32
+ function uintAsFloat(u) {
33
+ _u32[0] = u;
34
+ return _f32[0];
35
+ }
36
+ /**
37
+ * Pack an array of shapes into Slug's GPU data layout.
38
+ *
39
+ * Each shape is a closed region defined by quadratic Bezier curves.
40
+ * Returns textures + vertex buffers ready for GPU upload.
41
+ */
42
+ function packSlugData(shapes, options) {
43
+ const bandCount = options?.bandCount ?? 16;
44
+ const evenOdd = options?.evenOdd ?? false;
45
+ // Phase 1: Pack all curves into curveTexture
46
+ const allCurves = [];
47
+ // Estimate max texels needed
48
+ let totalCurves = 0;
49
+ for (const shape of shapes) {
50
+ totalCurves += shape.curves.length;
51
+ }
52
+ const curveTexHeight = Math.ceil((totalCurves * 2) / TEX_WIDTH) + 1;
53
+ const curveData = new Float32Array(TEX_WIDTH * curveTexHeight * 4);
54
+ let curveX = 0;
55
+ let curveY = 0;
56
+ for (const shape of shapes) {
57
+ const entries = [];
58
+ for (const curve of shape.curves) {
59
+ // Don't let a curve span across row boundary (needs 2 consecutive texels)
60
+ if (curveX >= TEX_WIDTH - 1) {
61
+ curveX = 0;
62
+ curveY++;
63
+ }
64
+ const base = (curveY * TEX_WIDTH + curveX) * 4;
65
+ curveData[base + 0] = curve.p1[0];
66
+ curveData[base + 1] = curve.p1[1];
67
+ curveData[base + 2] = curve.p2[0];
68
+ curveData[base + 3] = curve.p2[1];
69
+ const base2 = base + 4;
70
+ curveData[base2 + 0] = curve.p3[0];
71
+ curveData[base2 + 1] = curve.p3[1];
72
+ const minX = Math.min(curve.p1[0], curve.p2[0], curve.p3[0]);
73
+ const minY = Math.min(curve.p1[1], curve.p2[1], curve.p3[1]);
74
+ const maxX = Math.max(curve.p1[0], curve.p2[0], curve.p3[0]);
75
+ const maxY = Math.max(curve.p1[1], curve.p2[1], curve.p3[1]);
76
+ entries.push({
77
+ p1x: curve.p1[0], p1y: curve.p1[1],
78
+ p2x: curve.p2[0], p2y: curve.p2[1],
79
+ p3x: curve.p3[0], p3y: curve.p3[1],
80
+ minX, minY, maxX, maxY,
81
+ curveTexX: curveX,
82
+ curveTexY: curveY
83
+ });
84
+ curveX += 2;
85
+ }
86
+ allCurves.push(entries);
87
+ }
88
+ const actualCurveTexHeight = curveY + 1;
89
+ // Phase 2: Build band data for each shape and pack into bandTexture
90
+ // Layout per shape in bandTexture (relative to glyphLoc):
91
+ // [0 .. hBandMax] : h-band headers
92
+ // [hBandMax+1 .. hBandMax+1+vBandMax] : v-band headers
93
+ // [hBandMax+vBandMax+2 .. ] : curve index lists
94
+ // First pass: compute total band texels needed
95
+ const shapeBandData = [];
96
+ let totalBandTexels = 0;
97
+ for (let si = 0; si < shapes.length; si++) {
98
+ const shape = shapes[si];
99
+ const curves = allCurves[si];
100
+ if (curves.length === 0) {
101
+ shapeBandData.push({
102
+ hBands: [], vBands: [], hLists: [], vLists: [],
103
+ totalTexels: 0, bandMaxX: 0, bandMaxY: 0
104
+ });
105
+ continue;
106
+ }
107
+ const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
108
+ const w = bMaxX - bMinX;
109
+ const h = bMaxY - bMinY;
110
+ const hBandCount = Math.min(bandCount, 255); // max 255 (fits in 8 bits)
111
+ const vBandCount = Math.min(bandCount, 255);
112
+ const bandMaxY = hBandCount - 1;
113
+ const bandMaxX = vBandCount - 1;
114
+ // Build horizontal bands (partition y-axis)
115
+ const hBands = [];
116
+ const hLists = [];
117
+ const bandH = h / hBandCount;
118
+ for (let bi = 0; bi < hBandCount; bi++) {
119
+ const bandMinY = bMinY + bi * bandH;
120
+ const bandMaxYCoord = bandMinY + bandH;
121
+ // Collect curves whose y-range overlaps this band
122
+ const list = [];
123
+ for (const c of curves) {
124
+ if (c.maxY >= bandMinY && c.minY <= bandMaxYCoord) {
125
+ list.push({ curve: c, sortKey: c.maxX });
126
+ }
127
+ }
128
+ // Sort by descending max-x for early exit
129
+ list.sort((a, b) => b.sortKey - a.sortKey);
130
+ const flatList = [];
131
+ for (const item of list) {
132
+ flatList.push(item.curve.curveTexX, item.curve.curveTexY);
133
+ }
134
+ hBands.push({ curveCount: list.length, listOffset: 0 });
135
+ hLists.push(flatList);
136
+ }
137
+ // Build vertical bands (partition x-axis)
138
+ const vBands = [];
139
+ const vLists = [];
140
+ const bandW = w / vBandCount;
141
+ for (let bi = 0; bi < vBandCount; bi++) {
142
+ const bandMinX = bMinX + bi * bandW;
143
+ const bandMaxXCoord = bandMinX + bandW;
144
+ const list = [];
145
+ for (const c of curves) {
146
+ if (c.maxX >= bandMinX && c.minX <= bandMaxXCoord) {
147
+ list.push({ curve: c, sortKey: c.maxY });
148
+ }
149
+ }
150
+ // Sort by descending max-y for early exit
151
+ list.sort((a, b) => b.sortKey - a.sortKey);
152
+ const flatList = [];
153
+ for (const item of list) {
154
+ flatList.push(item.curve.curveTexX, item.curve.curveTexY);
155
+ }
156
+ vBands.push({ curveCount: list.length, listOffset: 0 });
157
+ vLists.push(flatList);
158
+ }
159
+ // Total texels for this shape: band headers + curve lists
160
+ const headerTexels = hBandCount + vBandCount;
161
+ let listTexels = 0;
162
+ for (const l of hLists)
163
+ listTexels += l.length / 2;
164
+ for (const l of vLists)
165
+ listTexels += l.length / 2;
166
+ const total = headerTexels + listTexels;
167
+ shapeBandData.push({
168
+ hBands, vBands, hLists, vLists,
169
+ totalTexels: total, bandMaxX, bandMaxY
170
+ });
171
+ totalBandTexels += total;
172
+ }
173
+ // Allocate bandTexture
174
+ const bandTexHeight = Math.max(1, Math.ceil(totalBandTexels / TEX_WIDTH) + shapes.length);
175
+ const bandData = new Uint32Array(TEX_WIDTH * bandTexHeight * 4);
176
+ // Pack band data per shape
177
+ let bandX = 0;
178
+ let bandY = 0;
179
+ const glyphLocs = [];
180
+ for (let si = 0; si < shapes.length; si++) {
181
+ const sd = shapeBandData[si];
182
+ if (sd.totalTexels === 0) {
183
+ glyphLocs.push({ x: 0, y: 0 });
184
+ continue;
185
+ }
186
+ // Ensure glyph data doesn't start too close to row end
187
+ // (need at least headerTexels contiguous... actually wrapping is handled by CalcBandLoc)
188
+ // But the initial band header reads don't use CalcBandLoc, so glyphLoc.x + bandMax.y + 1 + bandMaxX
189
+ // must be reachable. CalcBandLoc handles wrapping for curve lists.
190
+ // To be safe, start each glyph at the beginning of a row if remaining space is tight.
191
+ const minContiguous = sd.hBands.length + sd.vBands.length;
192
+ if (bandX + minContiguous > TEX_WIDTH) {
193
+ bandX = 0;
194
+ bandY++;
195
+ }
196
+ const glyphLocX = bandX;
197
+ const glyphLocY = bandY;
198
+ glyphLocs.push({ x: glyphLocX, y: glyphLocY });
199
+ // Curve lists start after all headers
200
+ let listStartOffset = sd.hBands.length + sd.vBands.length;
201
+ // Assign list offsets for h-bands
202
+ for (let bi = 0; bi < sd.hBands.length; bi++) {
203
+ sd.hBands[bi].listOffset = listStartOffset;
204
+ listStartOffset += sd.hLists[bi].length / 2;
205
+ }
206
+ // Assign list offsets for v-bands
207
+ for (let bi = 0; bi < sd.vBands.length; bi++) {
208
+ sd.vBands[bi].listOffset = listStartOffset;
209
+ listStartOffset += sd.vLists[bi].length / 2;
210
+ }
211
+ // Write h-band headers
212
+ for (let bi = 0; bi < sd.hBands.length; bi++) {
213
+ const tx = glyphLocX + bi;
214
+ const ty = glyphLocY;
215
+ const idx = (ty * TEX_WIDTH + tx) * 4;
216
+ bandData[idx + 0] = sd.hBands[bi].curveCount;
217
+ bandData[idx + 1] = sd.hBands[bi].listOffset;
218
+ bandData[idx + 2] = 0;
219
+ bandData[idx + 3] = 0;
220
+ }
221
+ // Write v-band headers (after h-bands)
222
+ const vBandStart = glyphLocX + sd.hBands.length;
223
+ for (let bi = 0; bi < sd.vBands.length; bi++) {
224
+ const tx = vBandStart + bi;
225
+ const ty = glyphLocY;
226
+ const idx = (ty * TEX_WIDTH + tx) * 4;
227
+ bandData[idx + 0] = sd.vBands[bi].curveCount;
228
+ bandData[idx + 1] = sd.vBands[bi].listOffset;
229
+ bandData[idx + 2] = 0;
230
+ bandData[idx + 3] = 0;
231
+ }
232
+ // Write curve lists using CalcBandLoc-style wrapping
233
+ const writeBandLoc = (offset) => {
234
+ let bx = glyphLocX + offset;
235
+ let by = glyphLocY;
236
+ by += bx >> LOG_TEX_WIDTH;
237
+ bx &= (1 << LOG_TEX_WIDTH) - 1;
238
+ return { x: bx, y: by };
239
+ };
240
+ // Write h-band curve lists
241
+ for (let bi = 0; bi < sd.hBands.length; bi++) {
242
+ const list = sd.hLists[bi];
243
+ const baseOffset = sd.hBands[bi].listOffset;
244
+ for (let ci = 0; ci < list.length; ci += 2) {
245
+ const loc = writeBandLoc(baseOffset + ci / 2);
246
+ const idx = (loc.y * TEX_WIDTH + loc.x) * 4;
247
+ bandData[idx + 0] = list[ci]; // curveTexX
248
+ bandData[idx + 1] = list[ci + 1]; // curveTexY
249
+ bandData[idx + 2] = 0;
250
+ bandData[idx + 3] = 0;
251
+ }
252
+ }
253
+ // Write v-band curve lists
254
+ for (let bi = 0; bi < sd.vBands.length; bi++) {
255
+ const list = sd.vLists[bi];
256
+ const baseOffset = sd.vBands[bi].listOffset;
257
+ for (let ci = 0; ci < list.length; ci += 2) {
258
+ const loc = writeBandLoc(baseOffset + ci / 2);
259
+ const idx = (loc.y * TEX_WIDTH + loc.x) * 4;
260
+ bandData[idx + 0] = list[ci];
261
+ bandData[idx + 1] = list[ci + 1];
262
+ bandData[idx + 2] = 0;
263
+ bandData[idx + 3] = 0;
264
+ }
265
+ }
266
+ // Advance band cursor past this shape's data
267
+ const totalForShape = listStartOffset;
268
+ const endLoc = writeBandLoc(totalForShape);
269
+ bandX = endLoc.x;
270
+ bandY = endLoc.y;
271
+ }
272
+ const actualBandTexHeight = bandY + 1;
273
+ // Phase 3: Build vertex attributes
274
+ // 5 attribs x 4 floats x 4 vertices per shape = 80 floats per shape
275
+ const FLOATS_PER_VERTEX = 20; // 5 attribs * 4 components
276
+ const VERTS_PER_SHAPE = 4;
277
+ const vertices = new Float32Array(shapes.length * VERTS_PER_SHAPE * FLOATS_PER_VERTEX);
278
+ const indices = new Uint16Array(shapes.length * 6);
279
+ // Corner normals (outward-pointing, un-normalized — SlugDilate normalizes)
280
+ const cornerNormals = [
281
+ [-1, -1], // bottom-left
282
+ [1, -1], // bottom-right
283
+ [1, 1], // top-right
284
+ [-1, 1], // top-left
285
+ ];
286
+ for (let si = 0; si < shapes.length; si++) {
287
+ const shape = shapes[si];
288
+ const sd = shapeBandData[si];
289
+ const glyph = glyphLocs[si];
290
+ const [bMinX, bMinY, bMaxX, bMaxY] = shape.bounds;
291
+ const w = bMaxX - bMinX;
292
+ const h = bMaxY - bMinY;
293
+ // Corner positions in object-space
294
+ const corners = [
295
+ [bMinX, bMinY],
296
+ [bMaxX, bMinY],
297
+ [bMaxX, bMaxY],
298
+ [bMinX, bMaxY],
299
+ ];
300
+ // Em-space sample coords at corners (same as object-space for 1:1 mapping)
301
+ const emCorners = [
302
+ [bMinX, bMinY],
303
+ [bMaxX, bMinY],
304
+ [bMaxX, bMaxY],
305
+ [bMinX, bMaxY],
306
+ ];
307
+ // Pack tex.z: glyph location in band texture
308
+ const texZ = uintAsFloat((glyph.x & 0xFFFF) | ((glyph.y & 0xFFFF) << 16));
309
+ // Pack tex.w: band max + flags
310
+ let texWBits = (sd.bandMaxX & 0xFF) | ((sd.bandMaxY & 0xFF) << 16);
311
+ if (evenOdd)
312
+ texWBits |= 0x10000000; // E flag at bit 28
313
+ const texW = uintAsFloat(texWBits);
314
+ // Band transform: scale and offset to map em-coords to band indices
315
+ const bandScaleX = w > 0 ? sd.vBands.length / w : 0;
316
+ const bandScaleY = h > 0 ? sd.hBands.length / h : 0;
317
+ const bandOffsetX = -bMinX * bandScaleX;
318
+ const bandOffsetY = -bMinY * bandScaleY;
319
+ for (let vi = 0; vi < 4; vi++) {
320
+ const base = (si * 4 + vi) * FLOATS_PER_VERTEX;
321
+ // pos: .xy = position, .zw = normal
322
+ vertices[base + 0] = corners[vi][0];
323
+ vertices[base + 1] = corners[vi][1];
324
+ vertices[base + 2] = cornerNormals[vi][0];
325
+ vertices[base + 3] = cornerNormals[vi][1];
326
+ // tex: .xy = em-space coords, .z = packed glyph loc, .w = packed band max
327
+ vertices[base + 4] = emCorners[vi][0];
328
+ vertices[base + 5] = emCorners[vi][1];
329
+ vertices[base + 6] = texZ;
330
+ vertices[base + 7] = texW;
331
+ // jac: identity Jacobian (em-space = object-space)
332
+ vertices[base + 8] = 1.0;
333
+ vertices[base + 9] = 0.0;
334
+ vertices[base + 10] = 0.0;
335
+ vertices[base + 11] = 1.0;
336
+ // bnd: band scale and offset
337
+ vertices[base + 12] = bandScaleX;
338
+ vertices[base + 13] = bandScaleY;
339
+ vertices[base + 14] = bandOffsetX;
340
+ vertices[base + 15] = bandOffsetY;
341
+ // col: white with full alpha (caller overrides via uniform or attribute)
342
+ vertices[base + 16] = 1.0;
343
+ vertices[base + 17] = 1.0;
344
+ vertices[base + 18] = 1.0;
345
+ vertices[base + 19] = 1.0;
346
+ }
347
+ // Indices: two triangles per quad
348
+ const vBase = si * 4;
349
+ const iBase = si * 6;
350
+ indices[iBase + 0] = vBase + 0;
351
+ indices[iBase + 1] = vBase + 1;
352
+ indices[iBase + 2] = vBase + 2;
353
+ indices[iBase + 3] = vBase + 0;
354
+ indices[iBase + 4] = vBase + 2;
355
+ indices[iBase + 5] = vBase + 3;
356
+ }
357
+ return {
358
+ curveTexture: {
359
+ data: curveData.subarray(0, TEX_WIDTH * actualCurveTexHeight * 4),
360
+ width: TEX_WIDTH,
361
+ height: actualCurveTexHeight
362
+ },
363
+ bandTexture: {
364
+ data: bandData.subarray(0, TEX_WIDTH * actualBandTexHeight * 4),
365
+ width: TEX_WIDTH,
366
+ height: actualBandTexHeight
367
+ },
368
+ vertices,
369
+ indices,
370
+ shapeCount: shapes.length
371
+ };
372
+ }
373
+
374
+ export { fragmentShaderGLSL300, fragmentShaderWGSL, packSlugData, vertexShaderGLSL300, vertexShaderWGSL };
@@ -2,48 +2,63 @@
2
2
 
3
3
  var three = require('three');
4
4
  var Text$1 = require('../index.cjs');
5
+ var MeshGeometryBuilder = require('../index.cjs');
5
6
 
6
- // Three.js adapter - wraps core text processing and returns BufferGeometry
7
- // This is a thin convenience layer for Three.js users
8
- function convertToThree(result) {
9
- // Create BufferGeometry from raw arrays
7
+ function buildThreeResult(layoutHandle, meshPipeline, options) {
8
+ const meshResult = meshPipeline.build(layoutHandle, options);
10
9
  const geometry = new three.BufferGeometry();
11
- geometry.setAttribute('position', new three.Float32BufferAttribute(result.vertices, 3));
12
- geometry.setAttribute('normal', new three.Float32BufferAttribute(result.normals, 3));
13
- geometry.setIndex(new three.Uint32BufferAttribute(result.indices, 1));
14
- // Add optional color attribute (only if provided)
15
- if (result.colors) {
16
- geometry.setAttribute('color', new three.Float32BufferAttribute(result.colors, 3));
10
+ geometry.setAttribute('position', new three.Float32BufferAttribute(meshResult.vertices, 3));
11
+ geometry.setAttribute('normal', new three.Float32BufferAttribute(meshResult.normals, 3));
12
+ geometry.setIndex(new three.Uint32BufferAttribute(meshResult.indices, 1));
13
+ if (meshResult.colors) {
14
+ geometry.setAttribute('color', new three.Float32BufferAttribute(meshResult.colors, 3));
17
15
  }
18
- if (result.glyphAttributes) {
19
- geometry.setAttribute('glyphCenter', new three.Float32BufferAttribute(result.glyphAttributes.glyphCenter, 3));
20
- geometry.setAttribute('glyphIndex', new three.Float32BufferAttribute(result.glyphAttributes.glyphIndex, 1));
21
- geometry.setAttribute('glyphLineIndex', new three.Float32BufferAttribute(result.glyphAttributes.glyphLineIndex, 1));
22
- geometry.setAttribute('glyphProgress', new three.Float32BufferAttribute(result.glyphAttributes.glyphProgress, 1));
23
- geometry.setAttribute('glyphBaselineY', new three.Float32BufferAttribute(result.glyphAttributes.glyphBaselineY, 1));
16
+ if (meshResult.glyphAttributes) {
17
+ geometry.setAttribute('glyphCenter', new three.Float32BufferAttribute(meshResult.glyphAttributes.glyphCenter, 3));
18
+ geometry.setAttribute('glyphIndex', new three.Float32BufferAttribute(meshResult.glyphAttributes.glyphIndex, 1));
19
+ geometry.setAttribute('glyphLineIndex', new three.Float32BufferAttribute(meshResult.glyphAttributes.glyphLineIndex, 1));
20
+ geometry.setAttribute('glyphProgress', new three.Float32BufferAttribute(meshResult.glyphAttributes.glyphProgress, 1));
21
+ geometry.setAttribute('glyphBaselineY', new three.Float32BufferAttribute(meshResult.glyphAttributes.glyphBaselineY, 1));
24
22
  }
25
23
  geometry.computeBoundingBox();
26
- // Return Three.js specific interface with utility methods
24
+ const update = async (newOptions) => {
25
+ const mergedOptions = { ...options };
26
+ for (const key in newOptions) {
27
+ const value = newOptions[key];
28
+ if (value !== undefined) {
29
+ mergedOptions[key] = value;
30
+ }
31
+ }
32
+ if (newOptions.font !== undefined ||
33
+ newOptions.fontVariations !== undefined ||
34
+ newOptions.fontFeatures !== undefined) {
35
+ const newLayout = await layoutHandle.update(mergedOptions);
36
+ meshPipeline.setFont(newLayout.loadedFont, newLayout.fontId);
37
+ meshPipeline.reset();
38
+ layoutHandle = newLayout;
39
+ options = mergedOptions;
40
+ return buildThreeResult(layoutHandle, meshPipeline, options);
41
+ }
42
+ const newLayout = await layoutHandle.update(mergedOptions);
43
+ layoutHandle = newLayout;
44
+ options = mergedOptions;
45
+ return buildThreeResult(layoutHandle, meshPipeline, options);
46
+ };
27
47
  return {
28
48
  geometry,
29
- glyphs: result.glyphs,
30
- planeBounds: result.planeBounds,
31
- stats: result.stats,
32
- query: result.query,
33
- coloredRanges: result.coloredRanges,
34
- // Pass through utility methods from core
35
- getLoadedFont: result.getLoadedFont,
36
- getCacheSize: result.getCacheSize,
37
- clearCache: result.clearCache,
38
- measureTextWidth: result.measureTextWidth,
39
- update: async (newOptions) => {
40
- const newCoreResult = await result.update(newOptions);
41
- return convertToThree(newCoreResult);
42
- }
49
+ glyphs: meshResult.glyphs,
50
+ planeBounds: meshResult.planeBounds,
51
+ stats: meshResult.stats,
52
+ query: meshResult.query,
53
+ coloredRanges: meshResult.coloredRanges,
54
+ getLoadedFont: () => layoutHandle.getLoadedFont(),
55
+ getCacheSize: () => meshPipeline.getCacheSize(),
56
+ clearCache: () => meshPipeline.clearCache(),
57
+ measureTextWidth: (text, letterSpacing) => layoutHandle.measureTextWidth(text, letterSpacing),
58
+ update
43
59
  };
44
60
  }
45
61
  class Text {
46
- // Delegate static methods to core
47
62
  static { this.setHarfBuzzPath = Text$1.Text.setHarfBuzzPath; }
48
63
  static { this.setHarfBuzzBuffer = Text$1.Text.setHarfBuzzBuffer; }
49
64
  static { this.init = Text$1.Text.init; }
@@ -51,10 +66,10 @@ class Text {
51
66
  static { this.preloadPatterns = Text$1.Text.preloadPatterns; }
52
67
  static { this.setMaxFontCacheMemoryMB = Text$1.Text.setMaxFontCacheMemoryMB; }
53
68
  static { this.enableWoff2 = Text$1.Text.enableWoff2; }
54
- // Main API - wraps core result in BufferGeometry
55
69
  static async create(options) {
56
- const coreResult = await Text$1.Text.create(options);
57
- return convertToThree(coreResult);
70
+ const layoutHandle = await Text$1.Text.create(options);
71
+ const meshPipeline = new MeshGeometryBuilder.MeshGeometryBuilder(layoutHandle.loadedFont, layoutHandle.fontId);
72
+ return buildThreeResult(layoutHandle, meshPipeline, options);
58
73
  }
59
74
  }
60
75
 
@@ -1,47 +1,62 @@
1
1
  import { BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute } from 'three';
2
2
  import { Text as Text$1 } from '../index.js';
3
+ import { MeshGeometryBuilder } from '../index.js';
3
4
 
4
- // Three.js adapter - wraps core text processing and returns BufferGeometry
5
- // This is a thin convenience layer for Three.js users
6
- function convertToThree(result) {
7
- // Create BufferGeometry from raw arrays
5
+ function buildThreeResult(layoutHandle, meshPipeline, options) {
6
+ const meshResult = meshPipeline.build(layoutHandle, options);
8
7
  const geometry = new BufferGeometry();
9
- geometry.setAttribute('position', new Float32BufferAttribute(result.vertices, 3));
10
- geometry.setAttribute('normal', new Float32BufferAttribute(result.normals, 3));
11
- geometry.setIndex(new Uint32BufferAttribute(result.indices, 1));
12
- // Add optional color attribute (only if provided)
13
- if (result.colors) {
14
- geometry.setAttribute('color', new Float32BufferAttribute(result.colors, 3));
8
+ geometry.setAttribute('position', new Float32BufferAttribute(meshResult.vertices, 3));
9
+ geometry.setAttribute('normal', new Float32BufferAttribute(meshResult.normals, 3));
10
+ geometry.setIndex(new Uint32BufferAttribute(meshResult.indices, 1));
11
+ if (meshResult.colors) {
12
+ geometry.setAttribute('color', new Float32BufferAttribute(meshResult.colors, 3));
15
13
  }
16
- if (result.glyphAttributes) {
17
- geometry.setAttribute('glyphCenter', new Float32BufferAttribute(result.glyphAttributes.glyphCenter, 3));
18
- geometry.setAttribute('glyphIndex', new Float32BufferAttribute(result.glyphAttributes.glyphIndex, 1));
19
- geometry.setAttribute('glyphLineIndex', new Float32BufferAttribute(result.glyphAttributes.glyphLineIndex, 1));
20
- geometry.setAttribute('glyphProgress', new Float32BufferAttribute(result.glyphAttributes.glyphProgress, 1));
21
- geometry.setAttribute('glyphBaselineY', new Float32BufferAttribute(result.glyphAttributes.glyphBaselineY, 1));
14
+ if (meshResult.glyphAttributes) {
15
+ geometry.setAttribute('glyphCenter', new Float32BufferAttribute(meshResult.glyphAttributes.glyphCenter, 3));
16
+ geometry.setAttribute('glyphIndex', new Float32BufferAttribute(meshResult.glyphAttributes.glyphIndex, 1));
17
+ geometry.setAttribute('glyphLineIndex', new Float32BufferAttribute(meshResult.glyphAttributes.glyphLineIndex, 1));
18
+ geometry.setAttribute('glyphProgress', new Float32BufferAttribute(meshResult.glyphAttributes.glyphProgress, 1));
19
+ geometry.setAttribute('glyphBaselineY', new Float32BufferAttribute(meshResult.glyphAttributes.glyphBaselineY, 1));
22
20
  }
23
21
  geometry.computeBoundingBox();
24
- // Return Three.js specific interface with utility methods
22
+ const update = async (newOptions) => {
23
+ const mergedOptions = { ...options };
24
+ for (const key in newOptions) {
25
+ const value = newOptions[key];
26
+ if (value !== undefined) {
27
+ mergedOptions[key] = value;
28
+ }
29
+ }
30
+ if (newOptions.font !== undefined ||
31
+ newOptions.fontVariations !== undefined ||
32
+ newOptions.fontFeatures !== undefined) {
33
+ const newLayout = await layoutHandle.update(mergedOptions);
34
+ meshPipeline.setFont(newLayout.loadedFont, newLayout.fontId);
35
+ meshPipeline.reset();
36
+ layoutHandle = newLayout;
37
+ options = mergedOptions;
38
+ return buildThreeResult(layoutHandle, meshPipeline, options);
39
+ }
40
+ const newLayout = await layoutHandle.update(mergedOptions);
41
+ layoutHandle = newLayout;
42
+ options = mergedOptions;
43
+ return buildThreeResult(layoutHandle, meshPipeline, options);
44
+ };
25
45
  return {
26
46
  geometry,
27
- glyphs: result.glyphs,
28
- planeBounds: result.planeBounds,
29
- stats: result.stats,
30
- query: result.query,
31
- coloredRanges: result.coloredRanges,
32
- // Pass through utility methods from core
33
- getLoadedFont: result.getLoadedFont,
34
- getCacheSize: result.getCacheSize,
35
- clearCache: result.clearCache,
36
- measureTextWidth: result.measureTextWidth,
37
- update: async (newOptions) => {
38
- const newCoreResult = await result.update(newOptions);
39
- return convertToThree(newCoreResult);
40
- }
47
+ glyphs: meshResult.glyphs,
48
+ planeBounds: meshResult.planeBounds,
49
+ stats: meshResult.stats,
50
+ query: meshResult.query,
51
+ coloredRanges: meshResult.coloredRanges,
52
+ getLoadedFont: () => layoutHandle.getLoadedFont(),
53
+ getCacheSize: () => meshPipeline.getCacheSize(),
54
+ clearCache: () => meshPipeline.clearCache(),
55
+ measureTextWidth: (text, letterSpacing) => layoutHandle.measureTextWidth(text, letterSpacing),
56
+ update
41
57
  };
42
58
  }
43
59
  class Text {
44
- // Delegate static methods to core
45
60
  static { this.setHarfBuzzPath = Text$1.setHarfBuzzPath; }
46
61
  static { this.setHarfBuzzBuffer = Text$1.setHarfBuzzBuffer; }
47
62
  static { this.init = Text$1.init; }
@@ -49,10 +64,10 @@ class Text {
49
64
  static { this.preloadPatterns = Text$1.preloadPatterns; }
50
65
  static { this.setMaxFontCacheMemoryMB = Text$1.setMaxFontCacheMemoryMB; }
51
66
  static { this.enableWoff2 = Text$1.enableWoff2; }
52
- // Main API - wraps core result in BufferGeometry
53
67
  static async create(options) {
54
- const coreResult = await Text$1.create(options);
55
- return convertToThree(coreResult);
68
+ const layoutHandle = await Text$1.create(options);
69
+ const meshPipeline = new MeshGeometryBuilder(layoutHandle.loadedFont, layoutHandle.fontId);
70
+ return buildThreeResult(layoutHandle, meshPipeline, options);
56
71
  }
57
72
  }
58
73
 
@@ -31,7 +31,6 @@ function deepEqual(a, b) {
31
31
  return false;
32
32
  if (typeof a !== 'object' || typeof b !== 'object')
33
33
  return false;
34
- // Arrays (common in options like color, byCharRange, etc.)
35
34
  if (Array.isArray(a) || Array.isArray(b)) {
36
35
  if (!Array.isArray(a) || !Array.isArray(b))
37
36
  return false;
@@ -50,11 +49,14 @@ function deepEqual(a, b) {
50
49
  for (const key of keysA) {
51
50
  if (!Object.prototype.hasOwnProperty.call(b, key))
52
51
  return false;
53
- if (!deepEqual(a[key], b[key]))
52
+ if (!deepEqual(a[key], b[key])) {
54
53
  return false;
54
+ }
55
55
  }
56
56
  return true;
57
57
  }
58
+ // Stabilizes an object reference across renders so long as
59
+ // its contents haven't changed (by deep comparison)
58
60
  function useDeepCompareMemo(value) {
59
61
  const ref = react.useRef(value);
60
62
  if (!deepEqual(value, ref.current)) {
@@ -62,6 +64,7 @@ function useDeepCompareMemo(value) {
62
64
  }
63
65
  return ref.current;
64
66
  }
67
+
65
68
  const Text$1 = react.forwardRef(function Text(props, ref) {
66
69
  const { children, font, material, position = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1], onLoad, onError, vertexColors = true, ...restOptions } = props;
67
70
  const [geometry, setGeometry] = react.useState(null);