@lightningjs/renderer 3.0.0-beta10 → 3.0.0-beta11

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 (275) hide show
  1. package/LICENSE +202 -202
  2. package/NOTICE +3 -3
  3. package/README.md +133 -133
  4. package/dist/src/core/CoreTextNode.d.ts +3 -0
  5. package/dist/src/core/CoreTextNode.js +45 -10
  6. package/dist/src/core/CoreTextNode.js.map +1 -1
  7. package/dist/src/core/platform.d.ts +10 -0
  8. package/dist/src/core/platform.js +81 -0
  9. package/dist/src/core/platform.js.map +1 -0
  10. package/dist/src/core/renderers/CoreShader.d.ts +9 -0
  11. package/dist/src/core/renderers/CoreShader.js +28 -0
  12. package/dist/src/core/renderers/CoreShader.js.map +1 -0
  13. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.d.ts +33 -0
  14. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js +250 -0
  15. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js.map +1 -0
  16. package/dist/src/core/renderers/canvas/CanvasCoreTexture.d.ts +16 -0
  17. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js +124 -0
  18. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js.map +1 -0
  19. package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.d.ts +13 -0
  20. package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.js +113 -192
  21. package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.js.map +1 -1
  22. package/dist/src/core/renderers/canvas/internal/ColorUtils.d.ts +0 -2
  23. package/dist/src/core/renderers/canvas/internal/ColorUtils.js +0 -14
  24. package/dist/src/core/renderers/canvas/internal/ColorUtils.js.map +1 -1
  25. package/dist/src/core/renderers/canvas/shaders/UnsupportedShader.d.ts +10 -0
  26. package/dist/src/core/renderers/canvas/shaders/UnsupportedShader.js +43 -0
  27. package/dist/src/core/renderers/canvas/shaders/UnsupportedShader.js.map +1 -0
  28. package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.d.ts +12 -0
  29. package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.js +58 -0
  30. package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.js.map +1 -0
  31. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.d.ts +9 -0
  32. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js +38 -0
  33. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js.map +1 -0
  34. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +56 -0
  35. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +239 -0
  36. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -0
  37. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.d.ts +34 -0
  38. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js +114 -0
  39. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js.map +1 -0
  40. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +133 -0
  41. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +616 -0
  42. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -0
  43. package/dist/src/core/renderers/webgl/WebGlCoreShader.d.ts +83 -0
  44. package/dist/src/core/renderers/webgl/WebGlCoreShader.js +233 -0
  45. package/dist/src/core/renderers/webgl/WebGlCoreShader.js.map +1 -0
  46. package/dist/src/core/renderers/webgl/internal/ShaderUtils.js +35 -35
  47. package/dist/src/core/renderers/webgl/shaders/DefaultShader.d.ts +9 -0
  48. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +87 -0
  49. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js.map +1 -0
  50. package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.d.ts +10 -0
  51. package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js +119 -0
  52. package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js.map +1 -0
  53. package/dist/src/core/renderers/webgl/shaders/DynamicShader.d.ts +29 -0
  54. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +413 -0
  55. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js.map +1 -0
  56. package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.d.ts +28 -0
  57. package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js +131 -0
  58. package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js.map +1 -0
  59. package/dist/src/core/renderers/webgl/shaders/SdfShader.d.ts +47 -0
  60. package/dist/src/core/renderers/webgl/shaders/SdfShader.js +160 -0
  61. package/dist/src/core/renderers/webgl/shaders/SdfShader.js.map +1 -0
  62. package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.d.ts +31 -0
  63. package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js +71 -0
  64. package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js.map +1 -0
  65. package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.d.ts +30 -0
  66. package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js +58 -0
  67. package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js.map +1 -0
  68. package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.d.ts +31 -0
  69. package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js +71 -0
  70. package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js.map +1 -0
  71. package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.d.ts +31 -0
  72. package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js +71 -0
  73. package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js.map +1 -0
  74. package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.d.ts +31 -0
  75. package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js +71 -0
  76. package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js.map +1 -0
  77. package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.d.ts +9 -0
  78. package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.js +136 -0
  79. package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.js.map +1 -0
  80. package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.d.ts +36 -0
  81. package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js +85 -0
  82. package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js.map +1 -0
  83. package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.d.ts +45 -0
  84. package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.js +104 -0
  85. package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.js.map +1 -0
  86. package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.d.ts +22 -0
  87. package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.js +45 -0
  88. package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.js.map +1 -0
  89. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.d.ts +58 -0
  90. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js +80 -0
  91. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js.map +1 -0
  92. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.d.ts +35 -0
  93. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +129 -0
  94. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js.map +1 -0
  95. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.d.ts +39 -0
  96. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +116 -0
  97. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js.map +1 -0
  98. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.d.ts +61 -0
  99. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +127 -0
  100. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js.map +1 -0
  101. package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.d.ts +40 -0
  102. package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js +71 -0
  103. package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js.map +1 -0
  104. package/dist/src/core/renderers/webgl/shaders/effects/ShaderEffect.d.ts +115 -0
  105. package/dist/src/core/renderers/webgl/shaders/effects/ShaderEffect.js +61 -0
  106. package/dist/src/core/renderers/webgl/shaders/effects/ShaderEffect.js.map +1 -0
  107. package/dist/src/core/shaders/templates/shaderUtils.d.ts +5 -0
  108. package/dist/src/core/shaders/templates/shaderUtils.js +41 -0
  109. package/dist/src/core/shaders/templates/shaderUtils.js.map +1 -0
  110. package/dist/src/core/shaders/webgl/Border.js +82 -82
  111. package/dist/src/core/shaders/webgl/Default.js +47 -47
  112. package/dist/src/core/shaders/webgl/DefaultBatched.js +61 -61
  113. package/dist/src/core/shaders/webgl/HolePunch.js +32 -32
  114. package/dist/src/core/shaders/webgl/LinearGradient.js +36 -36
  115. package/dist/src/core/shaders/webgl/RadialGradient.js +33 -33
  116. package/dist/src/core/shaders/webgl/Rounded.js +71 -71
  117. package/dist/src/core/shaders/webgl/RoundedWithBorder.js +111 -111
  118. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +130 -130
  119. package/dist/src/core/shaders/webgl/RoundedWithShadow.js +54 -54
  120. package/dist/src/core/shaders/webgl/SdfShader.js +62 -62
  121. package/dist/src/core/shaders/webgl/Shadow.js +83 -83
  122. package/dist/src/core/shaders/webgl/Spinner.d.ts +1 -0
  123. package/dist/src/core/shaders/webgl/Spinner.js +2 -0
  124. package/dist/src/core/shaders/webgl/Spinner.js.map +1 -0
  125. package/dist/src/core/text-rendering/CanvasFontHandler.d.ts +16 -0
  126. package/dist/src/core/text-rendering/CanvasFontHandler.js +29 -0
  127. package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
  128. package/dist/src/core/text-rendering/SdfFontHandler.d.ts +15 -0
  129. package/dist/src/core/text-rendering/SdfFontHandler.js +34 -2
  130. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  131. package/dist/src/core/text-rendering/TextRenderer.d.ts +2 -0
  132. package/dist/src/core/text-rendering/TextTextureRendererUtils.js.map +1 -1
  133. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js +2 -2
  134. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js.map +1 -1
  135. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +0 -5
  136. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  137. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.d.ts +1 -7
  138. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js +2 -50
  139. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js.map +1 -1
  140. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +3 -2
  141. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +83 -42
  142. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  143. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.d.ts +1 -1
  144. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +8 -66
  145. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
  146. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +4 -14
  147. package/dist/src/core/text-rendering/renderers/TextRenderer.js +0 -3
  148. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  149. package/dist/src/core/text-rendering/sdf/PeekableGenerator.d.ts +12 -0
  150. package/dist/src/core/text-rendering/sdf/PeekableGenerator.js +61 -0
  151. package/dist/src/core/text-rendering/sdf/PeekableGenerator.js.map +1 -0
  152. package/dist/src/core/text-rendering/sdf/SimpleFontShaper.d.ts +45 -0
  153. package/dist/src/core/text-rendering/sdf/SimpleFontShaper.js +69 -0
  154. package/dist/src/core/text-rendering/sdf/SimpleFontShaper.js.map +1 -0
  155. package/dist/src/main-api/DynamicShaderController.d.ts +29 -0
  156. package/dist/src/main-api/DynamicShaderController.js +58 -0
  157. package/dist/src/main-api/DynamicShaderController.js.map +1 -0
  158. package/dist/src/main-api/ShaderController.d.ts +31 -0
  159. package/dist/src/main-api/ShaderController.js +37 -0
  160. package/dist/src/main-api/ShaderController.js.map +1 -0
  161. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  162. package/exports/canvas-shaders.ts +28 -28
  163. package/exports/canvas.ts +45 -45
  164. package/exports/index.ts +82 -82
  165. package/exports/inspector.ts +24 -24
  166. package/exports/utils.ts +50 -50
  167. package/exports/webgl-shaders.ts +28 -28
  168. package/exports/webgl.ts +52 -52
  169. package/package.json +2 -1
  170. package/src/common/CommonTypes.ts +146 -146
  171. package/src/common/EventEmitter.ts +77 -77
  172. package/src/common/IAnimationController.ts +92 -92
  173. package/src/common/IEventEmitter.ts +28 -28
  174. package/src/core/CoreNode.test.ts +202 -202
  175. package/src/core/CoreNode.ts +2491 -2491
  176. package/src/core/CoreShaderManager.ts +188 -188
  177. package/src/core/CoreTextNode.ts +483 -443
  178. package/src/core/CoreTextureManager.ts +565 -565
  179. package/src/core/Stage.ts +906 -906
  180. package/src/core/TextureMemoryManager.ts +445 -445
  181. package/src/core/animations/AnimationManager.ts +38 -38
  182. package/src/core/animations/CoreAnimation.ts +291 -291
  183. package/src/core/animations/CoreAnimationController.ts +166 -166
  184. package/src/core/lib/ContextSpy.ts +41 -41
  185. package/src/core/lib/ImageWorker.ts +286 -286
  186. package/src/core/lib/Matrix3d.ts +244 -244
  187. package/src/core/lib/RenderCoords.ts +71 -71
  188. package/src/core/lib/WebGlContextWrapper.ts +1381 -1381
  189. package/src/core/lib/colorCache.ts +20 -20
  190. package/src/core/lib/colorParser.ts +85 -85
  191. package/src/core/lib/textureCompression.ts +152 -152
  192. package/src/core/lib/textureSvg.ts +78 -78
  193. package/src/core/lib/utils.ts +412 -412
  194. package/src/core/lib/validateImageBitmap.ts +87 -87
  195. package/src/core/platforms/Platform.ts +77 -77
  196. package/src/core/platforms/web/WebPlatform.ts +121 -121
  197. package/src/core/renderers/CoreContextTexture.ts +43 -43
  198. package/src/core/renderers/CoreRenderOp.ts +22 -22
  199. package/src/core/renderers/CoreRenderer.ts +110 -110
  200. package/src/core/renderers/CoreShaderNode.ts +175 -175
  201. package/src/core/renderers/CoreShaderProgram.ts +23 -23
  202. package/src/core/renderers/canvas/CanvasRenderer.ts +283 -283
  203. package/src/core/renderers/canvas/CanvasShaderNode.ts +96 -96
  204. package/src/core/renderers/canvas/CanvasTexture.ts +156 -156
  205. package/src/core/renderers/webgl/WebGlCtxRenderTexture.ts +91 -91
  206. package/src/core/renderers/webgl/WebGlCtxSubTexture.ts +50 -50
  207. package/src/core/renderers/webgl/WebGlCtxTexture.ts +310 -310
  208. package/src/core/renderers/webgl/WebGlRenderOp.ts +167 -167
  209. package/src/core/renderers/webgl/WebGlRenderer.ts +747 -747
  210. package/src/core/renderers/webgl/WebGlShaderNode.ts +435 -435
  211. package/src/core/renderers/webgl/WebGlShaderProgram.ts +341 -341
  212. package/src/core/renderers/webgl/internal/BufferCollection.ts +54 -54
  213. package/src/core/renderers/webgl/internal/RendererUtils.ts +155 -155
  214. package/src/core/renderers/webgl/internal/ShaderUtils.ts +281 -281
  215. package/src/core/renderers/webgl/internal/WebGlUtils.ts +35 -35
  216. package/src/core/shaders/canvas/Border.ts +75 -75
  217. package/src/core/shaders/canvas/HolePunch.ts +62 -62
  218. package/src/core/shaders/canvas/LinearGradient.ts +71 -71
  219. package/src/core/shaders/canvas/RadialGradient.ts +99 -99
  220. package/src/core/shaders/canvas/Rounded.ts +55 -55
  221. package/src/core/shaders/canvas/RoundedWithBorder.ts +74 -74
  222. package/src/core/shaders/canvas/RoundedWithBorderAndShadow.ts +90 -90
  223. package/src/core/shaders/canvas/RoundedWithShadow.ts +70 -70
  224. package/src/core/shaders/canvas/Shadow.ts +52 -52
  225. package/src/core/shaders/canvas/utils/render.ts +151 -151
  226. package/src/core/shaders/templates/BorderTemplate.ts +115 -115
  227. package/src/core/shaders/templates/HolePunchTemplate.ts +82 -82
  228. package/src/core/shaders/templates/LinearGradientTemplate.ts +71 -71
  229. package/src/core/shaders/templates/RadialGradientTemplate.ts +81 -81
  230. package/src/core/shaders/templates/RoundedTemplate.ts +98 -98
  231. package/src/core/shaders/templates/RoundedWithBorderAndShadowTemplate.ts +38 -38
  232. package/src/core/shaders/templates/RoundedWithBorderTemplate.ts +35 -35
  233. package/src/core/shaders/templates/RoundedWithShadowTemplate.ts +35 -35
  234. package/src/core/shaders/templates/ShadowTemplate.ts +106 -106
  235. package/src/core/shaders/utils.ts +46 -46
  236. package/src/core/shaders/webgl/Border.ts +116 -116
  237. package/src/core/shaders/webgl/Default.ts +89 -89
  238. package/src/core/shaders/webgl/DefaultBatched.ts +129 -129
  239. package/src/core/shaders/webgl/HolePunch.ts +75 -75
  240. package/src/core/shaders/webgl/LinearGradient.ts +82 -82
  241. package/src/core/shaders/webgl/RadialGradient.ts +85 -85
  242. package/src/core/shaders/webgl/Rounded.ts +117 -117
  243. package/src/core/shaders/webgl/RoundedWithBorder.ts +155 -155
  244. package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +175 -175
  245. package/src/core/shaders/webgl/RoundedWithShadow.ts +98 -98
  246. package/src/core/shaders/webgl/SdfShader.ts +134 -134
  247. package/src/core/shaders/webgl/Shadow.ts +115 -115
  248. package/src/core/text-rendering/CanvasFontHandler.ts +210 -176
  249. package/src/core/text-rendering/CanvasTextRenderer.ts +622 -622
  250. package/src/core/text-rendering/SdfFontHandler.ts +554 -517
  251. package/src/core/text-rendering/SdfTextRenderer.ts +466 -466
  252. package/src/core/text-rendering/TextRenderer.ts +406 -404
  253. package/src/core/text-rendering/Utils.ts +257 -257
  254. package/src/core/text-rendering/canvas/Settings.ts +99 -99
  255. package/src/core/text-rendering/canvas/Utils.test.ts +206 -206
  256. package/src/core/text-rendering/canvas/Utils.ts +178 -178
  257. package/src/core/text-rendering/canvas/calculateRenderInfo.ts +299 -299
  258. package/src/core/text-rendering/canvas/draw.ts +165 -165
  259. package/src/core/text-rendering/sdf/Utils.test.ts +402 -402
  260. package/src/core/text-rendering/sdf/Utils.ts +436 -436
  261. package/src/core/text-rendering/sdf/index.ts +20 -20
  262. package/src/core/textures/ColorTexture.ts +102 -102
  263. package/src/core/textures/ImageTexture.ts +418 -418
  264. package/src/core/textures/NoiseTexture.ts +104 -104
  265. package/src/core/textures/RenderTexture.ts +85 -85
  266. package/src/core/textures/SubTexture.ts +205 -205
  267. package/src/core/textures/Texture.ts +381 -381
  268. package/src/core/utils.ts +227 -227
  269. package/src/env.d.ts +7 -7
  270. package/src/main-api/INode.ts +100 -100
  271. package/src/main-api/Inspector.ts +567 -567
  272. package/src/main-api/Renderer.ts +873 -873
  273. package/src/main-api/utils.ts +45 -45
  274. package/src/utils.ts +267 -267
  275. package/COPYING +0 -1
@@ -1,622 +1,622 @@
1
- /*
2
- * If not stated otherwise in this file or this component's LICENSE file the
3
- * following copyright and licenses apply:
4
- *
5
- * Copyright 2023 Comcast Cable Communications Management, LLC.
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the License);
8
- * you may not use this file except in compliance with the License.
9
- * You may obtain a copy of the License at
10
- *
11
- * http://www.apache.org/licenses/LICENSE-2.0
12
- *
13
- * Unless required by applicable law or agreed to in writing, software
14
- * distributed under the License is distributed on an "AS IS" BASIS,
15
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- * See the License for the specific language governing permissions and
17
- * limitations under the License.
18
- */
19
-
20
- import { assertTruthy } from '../../utils.js';
21
- import type { Stage } from '../Stage.js';
22
- import type {
23
- TextLayout,
24
- NormalizedFontMetrics,
25
- TextBaseline,
26
- } from './TextRenderer.js';
27
- import * as CanvasFontHandler from './CanvasFontHandler.js';
28
- import { type LineType } from './canvas/calculateRenderInfo.js';
29
- import { calcHeight, measureText, wrapText, wrapWord } from './canvas/Utils.js';
30
- import { normalizeCanvasColor } from '../lib/colorCache.js';
31
- import type { CoreTextNodeProps } from '../CoreTextNode.js';
32
- import { isZeroWidthSpace } from './Utils.js';
33
-
34
- const MAX_TEXTURE_DIMENSION = 4096;
35
-
36
- const type = 'canvas' as const;
37
-
38
- let canvas: HTMLCanvasElement | OffscreenCanvas | null = null;
39
- let context:
40
- | CanvasRenderingContext2D
41
- | OffscreenCanvasRenderingContext2D
42
- | null = null;
43
-
44
- // Separate canvas and context for text measurements
45
- let measureCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
46
- let measureContext:
47
- | CanvasRenderingContext2D
48
- | OffscreenCanvasRenderingContext2D
49
- | null = null;
50
-
51
- // Cache for text layout calculations
52
- const layoutCache = new Map<
53
- string,
54
- {
55
- lines: string[];
56
- lineWidths: number[];
57
- maxLineWidth: number;
58
- remainingText: string;
59
- moreTextLines: boolean;
60
- }
61
- >();
62
-
63
- // Initialize the Text Renderer
64
- const init = (stage: Stage): void => {
65
- // Drawing canvas and context
66
- canvas = stage.platform.createCanvas() as HTMLCanvasElement | OffscreenCanvas;
67
- context = canvas.getContext('2d', { willReadFrequently: true }) as
68
- | CanvasRenderingContext2D
69
- | OffscreenCanvasRenderingContext2D;
70
-
71
- // Separate measuring canvas and context
72
- measureCanvas = stage.platform.createCanvas() as
73
- | HTMLCanvasElement
74
- | OffscreenCanvas;
75
- measureContext = measureCanvas.getContext('2d') as
76
- | CanvasRenderingContext2D
77
- | OffscreenCanvasRenderingContext2D;
78
-
79
- // Set up a minimal size for the measuring canvas since we only use it for measurements
80
- measureCanvas.width = 1;
81
- measureCanvas.height = 1;
82
-
83
- CanvasFontHandler.init(context);
84
- };
85
-
86
- /**
87
- * Canvas text renderer
88
- *
89
- * @param stage - Stage instance for font resolution
90
- * @param props - Text rendering properties
91
- * @returns Object containing ImageData and dimensions
92
- */
93
- const renderText = (
94
- stage: Stage,
95
- props: CoreTextNodeProps,
96
- ): {
97
- imageData: ImageData | null;
98
- width: number;
99
- height: number;
100
- layout?: TextLayout;
101
- } => {
102
- assertTruthy(canvas, 'Canvas is not initialized');
103
- assertTruthy(context, 'Canvas context is not available');
104
-
105
- // Extract already normalized properties
106
- const {
107
- text,
108
- fontFamily,
109
- fontStyle,
110
- fontSize,
111
- textAlign,
112
- lineHeight: propLineHeight,
113
- maxLines,
114
- textBaseline,
115
- verticalAlign,
116
- overflowSuffix,
117
- maxWidth,
118
- maxHeight,
119
- offsetY,
120
- letterSpacing,
121
- } = props;
122
-
123
- // Performance optimization constants
124
- const precision = 1;
125
- const paddingLeft = 0;
126
- const paddingRight = 0;
127
- const textIndent = 0;
128
- const textRenderIssueMargin = 0;
129
- const textColor = 0xffffffff;
130
-
131
- // Determine word wrap behavior
132
- const wordWrap = maxWidth > 0;
133
- const textOverflow = overflowSuffix ? 'ellipsis' : null;
134
-
135
- // Calculate scaled values
136
- const scaledFontSize = fontSize * precision;
137
- const scaledOffsetY = offsetY * precision;
138
- const scaledLetterSpacing = letterSpacing * precision;
139
- // Get font metrics and calculate line height
140
- context.font = `${fontStyle} ${scaledFontSize}px ${fontFamily}`;
141
- context.textBaseline = textBaseline;
142
-
143
- const metrics = CanvasFontHandler.getFontMetrics(fontFamily, scaledFontSize);
144
- const lineHeight =
145
- propLineHeight === 0
146
- ? scaledFontSize *
147
- (metrics.ascender - metrics.descender + metrics.lineGap) *
148
- precision
149
- : propLineHeight;
150
-
151
- // Calculate max lines constraint
152
- const containedMaxLines =
153
- maxHeight !== null ? Math.floor(maxHeight / lineHeight) : 0;
154
- const computedMaxLines = calculateMaxLines(containedMaxLines, maxLines);
155
-
156
- // Calculate initial width and inner width
157
- let width = maxWidth || 2048 / precision;
158
- let innerWidth = width - paddingLeft;
159
- if (innerWidth < 10) {
160
- width += 10 - innerWidth;
161
- innerWidth = 10;
162
- }
163
- const finalWordWrapWidth = maxWidth === 0 ? innerWidth : maxWidth;
164
-
165
- // Calculate text layout using cached helper function
166
- const layout = calculateTextLayout(
167
- text,
168
- fontFamily,
169
- scaledFontSize,
170
- fontStyle,
171
- wordWrap,
172
- finalWordWrapWidth,
173
- scaledLetterSpacing,
174
- textIndent,
175
- computedMaxLines,
176
- overflowSuffix,
177
- textOverflow,
178
- );
179
-
180
- // Calculate final dimensions
181
- const dimensions = calculateTextDimensions(
182
- layout,
183
- paddingLeft,
184
- paddingRight,
185
- textBaseline,
186
- scaledFontSize,
187
- lineHeight,
188
- scaledOffsetY,
189
- maxWidth,
190
- maxHeight,
191
- wordWrap,
192
- textAlign,
193
- );
194
-
195
- // Set up canvas dimensions
196
- canvas.width = Math.min(
197
- Math.ceil(dimensions.width + textRenderIssueMargin),
198
- MAX_TEXTURE_DIMENSION,
199
- );
200
- canvas.height = Math.min(Math.ceil(dimensions.height), MAX_TEXTURE_DIMENSION);
201
-
202
- // Reset font context after canvas resize
203
- context.font = `${fontStyle} ${scaledFontSize}px ${fontFamily}`;
204
- context.textBaseline = textBaseline;
205
-
206
- // Performance optimization for large fonts
207
- if (scaledFontSize >= 128) {
208
- context.globalAlpha = 0.01;
209
- context.fillRect(0, 0, 0.01, 0.01);
210
- context.globalAlpha = 1.0;
211
- }
212
-
213
- // Calculate drawing positions
214
- const drawLines = calculateDrawPositions(
215
- layout.lines,
216
- layout.lineWidths,
217
- textAlign,
218
- verticalAlign,
219
- innerWidth,
220
- paddingLeft,
221
- textIndent,
222
- lineHeight,
223
- metrics,
224
- scaledFontSize,
225
- );
226
-
227
- // Render text to canvas
228
- renderTextToCanvas(
229
- context,
230
- drawLines,
231
- scaledLetterSpacing,
232
- textColor,
233
- fontStyle,
234
- scaledFontSize,
235
- fontFamily,
236
- );
237
-
238
- width = dimensions.width;
239
- const height = lineHeight * layout.lines.length;
240
- // Extract image data
241
- let imageData: ImageData | null = null;
242
- if (canvas.width > 0 && canvas.height > 0) {
243
- imageData = context.getImageData(0, 0, width, height);
244
- }
245
-
246
- return {
247
- imageData,
248
- width,
249
- height,
250
- };
251
- };
252
-
253
- /**
254
- * Calculate the effective max lines constraint
255
- */
256
- function calculateMaxLines(
257
- containedMaxLines: number,
258
- maxLines: number,
259
- ): number {
260
- if (containedMaxLines > 0 && maxLines > 0) {
261
- return containedMaxLines < maxLines ? containedMaxLines : maxLines;
262
- } else {
263
- return containedMaxLines > maxLines ? containedMaxLines : maxLines;
264
- }
265
- }
266
-
267
- /**
268
- * Generate a cache key for text layout calculations
269
- */
270
- function generateLayoutCacheKey(
271
- text: string,
272
- fontFamily: string,
273
- fontSize: number,
274
- fontStyle: string,
275
- wordWrap: boolean,
276
- wordWrapWidth: number,
277
- letterSpacing: number,
278
- maxLines: number,
279
- overflowSuffix: string,
280
- ): string {
281
- return `${text}-${fontFamily}-${fontSize}-${fontStyle}-${wordWrap}-${wordWrapWidth}-${letterSpacing}-${maxLines}-${overflowSuffix}`;
282
- }
283
-
284
- /**
285
- * Calculate text dimensions and wrapping
286
- */
287
- function calculateTextLayout(
288
- text: string,
289
- fontFamily: string,
290
- fontSize: number,
291
- fontStyle: string,
292
- wordWrap: boolean,
293
- wordWrapWidth: number,
294
- letterSpacing: number,
295
- textIndent: number,
296
- maxLines: number,
297
- overflowSuffix: string,
298
- textOverflow: string | null,
299
- ): {
300
- lines: string[];
301
- lineWidths: number[];
302
- maxLineWidth: number;
303
- remainingText: string;
304
- moreTextLines: boolean;
305
- } {
306
- assertTruthy(measureContext, 'Measure context is not available');
307
-
308
- // Check cache first
309
- const cacheKey = generateLayoutCacheKey(
310
- text,
311
- fontFamily,
312
- fontSize,
313
- fontStyle,
314
- wordWrap,
315
- wordWrapWidth,
316
- letterSpacing,
317
- maxLines,
318
- overflowSuffix,
319
- );
320
-
321
- const cached = layoutCache.get(cacheKey);
322
- if (cached) {
323
- return cached;
324
- }
325
-
326
- // Set font context for measurements on the dedicated measuring context
327
- measureContext.font = `${fontStyle} ${fontSize}px ${fontFamily}`;
328
-
329
- // Handle text overflow for non-wrapped text
330
- let processedText = text;
331
- if (textOverflow !== null && wordWrap === false) {
332
- let suffix: string;
333
- if (textOverflow === 'clip') {
334
- suffix = '';
335
- } else if (textOverflow === 'ellipsis') {
336
- suffix = overflowSuffix;
337
- } else {
338
- suffix = textOverflow;
339
- }
340
- processedText = wrapWord(
341
- measureContext,
342
- text,
343
- wordWrapWidth - textIndent,
344
- suffix,
345
- letterSpacing,
346
- );
347
- }
348
-
349
- // Word wrap
350
- let linesInfo: { n: number[]; l: string[] };
351
- if (wordWrap === true) {
352
- linesInfo = wrapText(
353
- measureContext,
354
- processedText,
355
- wordWrapWidth,
356
- letterSpacing,
357
- textIndent,
358
- );
359
- } else {
360
- linesInfo = { l: processedText.split(/(?:\r\n|\r|\n)/), n: [] };
361
- const n = linesInfo.l.length;
362
- for (let i = 0; i < n - 1; i++) {
363
- linesInfo.n.push(i);
364
- }
365
- }
366
- let lines: string[] = linesInfo.l;
367
-
368
- let remainingText = '';
369
- let moreTextLines = false;
370
-
371
- // Handle max lines constraint
372
- if (maxLines > 0 && lines.length > maxLines) {
373
- const usedLines = lines.slice(0, maxLines);
374
- let otherLines: string[] = [];
375
- if (overflowSuffix.length > 0) {
376
- const w = measureText(measureContext, overflowSuffix, letterSpacing);
377
- const al = wrapText(
378
- measureContext,
379
- usedLines[usedLines.length - 1] || '',
380
- wordWrapWidth - w,
381
- letterSpacing,
382
- textIndent,
383
- );
384
- usedLines[usedLines.length - 1] = `${al.l[0] || ''}${overflowSuffix}`;
385
- otherLines = [al.l.length > 1 ? al.l[1] || '' : ''];
386
- } else {
387
- otherLines = [''];
388
- }
389
-
390
- // Re-assemble the remaining text
391
- let i: number;
392
- const n = lines.length;
393
- let j = 0;
394
- const m = linesInfo.n.length;
395
- for (i = maxLines; i < n; i++) {
396
- otherLines[j] += `${otherLines[j] ? ' ' : ''}${lines[i] ?? ''}`;
397
- if (i + 1 < m && linesInfo.n[i + 1] !== undefined) {
398
- j++;
399
- }
400
- }
401
- remainingText = otherLines.join('\n');
402
- moreTextLines = true;
403
- lines = usedLines;
404
- }
405
-
406
- // Calculate line widths using the dedicated measuring context
407
- let maxLineWidth = 0;
408
- const lineWidths: number[] = [];
409
- for (let i = 0; i < lines.length; i++) {
410
- const lineWidth =
411
- measureText(measureContext, lines[i] || '', letterSpacing) +
412
- (i === 0 ? textIndent : 0);
413
- lineWidths.push(lineWidth);
414
- maxLineWidth = Math.max(maxLineWidth, lineWidth);
415
- }
416
-
417
- const result = {
418
- lines,
419
- lineWidths,
420
- maxLineWidth,
421
- remainingText,
422
- moreTextLines,
423
- };
424
-
425
- // Cache the result
426
- layoutCache.set(cacheKey, result);
427
-
428
- return result;
429
- }
430
-
431
- /**
432
- * Calculate text dimensions based on layout
433
- */
434
- function calculateTextDimensions(
435
- layout: {
436
- lines: string[];
437
- lineWidths: number[];
438
- maxLineWidth: number;
439
- },
440
- paddingLeft: number,
441
- paddingRight: number,
442
- textBaseline: TextBaseline,
443
- fontSize: number,
444
- lineHeight: number,
445
- offsetY: number,
446
- initialWidth: number,
447
- initialHeight: number,
448
- wordWrap: boolean,
449
- textAlign: string,
450
- ): { width: number; height: number } {
451
- let width = initialWidth;
452
- let height = initialHeight;
453
-
454
- // Calculate width
455
- if (initialWidth === 0) {
456
- width = layout.maxLineWidth + paddingLeft + paddingRight;
457
- }
458
-
459
- // Adjust width for single-line left-aligned wrapped text
460
- if (
461
- wordWrap === true &&
462
- width > layout.maxLineWidth &&
463
- textAlign === 'left' &&
464
- layout.lines.length === 1
465
- ) {
466
- width = layout.maxLineWidth + paddingLeft + paddingRight;
467
- }
468
-
469
- // Calculate height if not provided
470
- if (height === 0) {
471
- height = calcHeight(
472
- textBaseline,
473
- fontSize,
474
- lineHeight,
475
- layout.lines.length,
476
- offsetY,
477
- );
478
- }
479
-
480
- return { width, height };
481
- }
482
-
483
- /**
484
- * Calculate drawing positions for text lines
485
- */
486
- function calculateDrawPositions(
487
- lines: string[],
488
- lineWidths: number[],
489
- textAlign: string,
490
- verticalAlign: string,
491
- innerWidth: number,
492
- paddingLeft: number,
493
- textIndent: number,
494
- lineHeight: number,
495
- metrics: NormalizedFontMetrics,
496
- fontSize: number,
497
- ): LineType[] {
498
- const drawLines: LineType[] = [];
499
- const ascenderPx = metrics.ascender * fontSize;
500
- const bareLineHeightPx = (metrics.ascender - metrics.descender) * fontSize;
501
-
502
- for (let i = 0, n = lines.length; i < n; i++) {
503
- let linePositionX = i === 0 ? textIndent : 0;
504
- let linePositionY = i * lineHeight + ascenderPx;
505
-
506
- // Vertical alignment
507
- if (verticalAlign == 'middle') {
508
- linePositionY += (lineHeight - bareLineHeightPx) / 2;
509
- } else if (verticalAlign == 'bottom') {
510
- linePositionY += lineHeight - bareLineHeightPx;
511
- }
512
-
513
- // Horizontal alignment
514
- const lineWidth = lineWidths[i];
515
- if (lineWidth !== undefined) {
516
- if (textAlign === 'right') {
517
- linePositionX += innerWidth - lineWidth;
518
- } else if (textAlign === 'center') {
519
- linePositionX += (innerWidth - lineWidth) / 2;
520
- }
521
- }
522
-
523
- linePositionX += paddingLeft;
524
-
525
- const lineText = lines[i];
526
- if (lineText !== undefined) {
527
- drawLines.push({
528
- text: lineText,
529
- x: linePositionX,
530
- y: linePositionY,
531
- w: lineWidth || 0,
532
- });
533
- }
534
- }
535
-
536
- return drawLines;
537
- }
538
-
539
- /**
540
- * Render text lines to canvas
541
- */
542
- function renderTextToCanvas(
543
- context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
544
- drawLines: LineType[],
545
- letterSpacing: number,
546
- textColor: number,
547
- fontStyle: string,
548
- fontSize: number,
549
- fontFamily: string,
550
- ): void {
551
- assertTruthy(measureContext, 'Measure context is not available');
552
-
553
- context.fillStyle = normalizeCanvasColor(textColor);
554
-
555
- // Sync font settings to measure context if we need to use it for letter spacing
556
- if (letterSpacing > 0) {
557
- measureContext.font = `${fontStyle} ${fontSize}px ${fontFamily}`;
558
- }
559
-
560
- for (let i = 0, n = drawLines.length; i < n; i++) {
561
- const drawLine = drawLines[i];
562
- if (drawLine) {
563
- if (letterSpacing === 0) {
564
- context.fillText(drawLine.text, drawLine.x, drawLine.y);
565
- } else {
566
- const textSplit = drawLine.text.split('');
567
- let x = drawLine.x;
568
- for (let j = 0, k = textSplit.length; j < k; j++) {
569
- const char = textSplit[j];
570
- if (char) {
571
- // Skip zero-width spaces for rendering but keep them in the text flow
572
- if (isZeroWidthSpace(char)) {
573
- continue;
574
- }
575
- context.fillText(char, x, drawLine.y);
576
- // Use the dedicated measuring context for letter spacing calculations
577
- x += measureText(measureContext, char, letterSpacing);
578
- }
579
- }
580
- }
581
- }
582
- }
583
- }
584
-
585
- /**
586
- * Clear layout cache for memory management
587
- */
588
- const clearLayoutCache = (): void => {
589
- layoutCache.clear();
590
- };
591
-
592
- /**
593
- * Add quads for rendering (Canvas doesn't use quads)
594
- */
595
- const addQuads = (): Float32Array | null => {
596
- // Canvas renderer doesn't use quad-based rendering
597
- // Return null for interface compatibility
598
- return null;
599
- };
600
-
601
- /**
602
- * Render quads for Canvas renderer (Canvas doesn't use quad-based rendering)
603
- */
604
- const renderQuads = (): void => {
605
- // Canvas renderer doesn't use quad-based rendering
606
- // This method is for interface compatibility only
607
- };
608
-
609
- /**
610
- * Canvas Text Renderer - implements TextRenderer interface
611
- */
612
- const CanvasTextRenderer = {
613
- type,
614
- font: CanvasFontHandler,
615
- renderText,
616
- addQuads,
617
- renderQuads,
618
- init,
619
- clearLayoutCache,
620
- };
621
-
622
- export default CanvasTextRenderer;
1
+ /*
2
+ * If not stated otherwise in this file or this component's LICENSE file the
3
+ * following copyright and licenses apply:
4
+ *
5
+ * Copyright 2023 Comcast Cable Communications Management, LLC.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the License);
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ import { assertTruthy } from '../../utils.js';
21
+ import type { Stage } from '../Stage.js';
22
+ import type {
23
+ TextLayout,
24
+ NormalizedFontMetrics,
25
+ TextBaseline,
26
+ } from './TextRenderer.js';
27
+ import * as CanvasFontHandler from './CanvasFontHandler.js';
28
+ import { type LineType } from './canvas/calculateRenderInfo.js';
29
+ import { calcHeight, measureText, wrapText, wrapWord } from './canvas/Utils.js';
30
+ import { normalizeCanvasColor } from '../lib/colorCache.js';
31
+ import type { CoreTextNodeProps } from '../CoreTextNode.js';
32
+ import { isZeroWidthSpace } from './Utils.js';
33
+
34
+ const MAX_TEXTURE_DIMENSION = 4096;
35
+
36
+ const type = 'canvas' as const;
37
+
38
+ let canvas: HTMLCanvasElement | OffscreenCanvas | null = null;
39
+ let context:
40
+ | CanvasRenderingContext2D
41
+ | OffscreenCanvasRenderingContext2D
42
+ | null = null;
43
+
44
+ // Separate canvas and context for text measurements
45
+ let measureCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
46
+ let measureContext:
47
+ | CanvasRenderingContext2D
48
+ | OffscreenCanvasRenderingContext2D
49
+ | null = null;
50
+
51
+ // Cache for text layout calculations
52
+ const layoutCache = new Map<
53
+ string,
54
+ {
55
+ lines: string[];
56
+ lineWidths: number[];
57
+ maxLineWidth: number;
58
+ remainingText: string;
59
+ moreTextLines: boolean;
60
+ }
61
+ >();
62
+
63
+ // Initialize the Text Renderer
64
+ const init = (stage: Stage): void => {
65
+ // Drawing canvas and context
66
+ canvas = stage.platform.createCanvas() as HTMLCanvasElement | OffscreenCanvas;
67
+ context = canvas.getContext('2d', { willReadFrequently: true }) as
68
+ | CanvasRenderingContext2D
69
+ | OffscreenCanvasRenderingContext2D;
70
+
71
+ // Separate measuring canvas and context
72
+ measureCanvas = stage.platform.createCanvas() as
73
+ | HTMLCanvasElement
74
+ | OffscreenCanvas;
75
+ measureContext = measureCanvas.getContext('2d') as
76
+ | CanvasRenderingContext2D
77
+ | OffscreenCanvasRenderingContext2D;
78
+
79
+ // Set up a minimal size for the measuring canvas since we only use it for measurements
80
+ measureCanvas.width = 1;
81
+ measureCanvas.height = 1;
82
+
83
+ CanvasFontHandler.init(context);
84
+ };
85
+
86
+ /**
87
+ * Canvas text renderer
88
+ *
89
+ * @param stage - Stage instance for font resolution
90
+ * @param props - Text rendering properties
91
+ * @returns Object containing ImageData and dimensions
92
+ */
93
+ const renderText = (
94
+ stage: Stage,
95
+ props: CoreTextNodeProps,
96
+ ): {
97
+ imageData: ImageData | null;
98
+ width: number;
99
+ height: number;
100
+ layout?: TextLayout;
101
+ } => {
102
+ assertTruthy(canvas, 'Canvas is not initialized');
103
+ assertTruthy(context, 'Canvas context is not available');
104
+
105
+ // Extract already normalized properties
106
+ const {
107
+ text,
108
+ fontFamily,
109
+ fontStyle,
110
+ fontSize,
111
+ textAlign,
112
+ lineHeight: propLineHeight,
113
+ maxLines,
114
+ textBaseline,
115
+ verticalAlign,
116
+ overflowSuffix,
117
+ maxWidth,
118
+ maxHeight,
119
+ offsetY,
120
+ letterSpacing,
121
+ } = props;
122
+
123
+ // Performance optimization constants
124
+ const precision = 1;
125
+ const paddingLeft = 0;
126
+ const paddingRight = 0;
127
+ const textIndent = 0;
128
+ const textRenderIssueMargin = 0;
129
+ const textColor = 0xffffffff;
130
+
131
+ // Determine word wrap behavior
132
+ const wordWrap = maxWidth > 0;
133
+ const textOverflow = overflowSuffix ? 'ellipsis' : null;
134
+
135
+ // Calculate scaled values
136
+ const scaledFontSize = fontSize * precision;
137
+ const scaledOffsetY = offsetY * precision;
138
+ const scaledLetterSpacing = letterSpacing * precision;
139
+ // Get font metrics and calculate line height
140
+ context.font = `${fontStyle} ${scaledFontSize}px ${fontFamily}`;
141
+ context.textBaseline = textBaseline;
142
+
143
+ const metrics = CanvasFontHandler.getFontMetrics(fontFamily, scaledFontSize);
144
+ const lineHeight =
145
+ propLineHeight === 0
146
+ ? scaledFontSize *
147
+ (metrics.ascender - metrics.descender + metrics.lineGap) *
148
+ precision
149
+ : propLineHeight;
150
+
151
+ // Calculate max lines constraint
152
+ const containedMaxLines =
153
+ maxHeight !== null ? Math.floor(maxHeight / lineHeight) : 0;
154
+ const computedMaxLines = calculateMaxLines(containedMaxLines, maxLines);
155
+
156
+ // Calculate initial width and inner width
157
+ let width = maxWidth || 2048 / precision;
158
+ let innerWidth = width - paddingLeft;
159
+ if (innerWidth < 10) {
160
+ width += 10 - innerWidth;
161
+ innerWidth = 10;
162
+ }
163
+ const finalWordWrapWidth = maxWidth === 0 ? innerWidth : maxWidth;
164
+
165
+ // Calculate text layout using cached helper function
166
+ const layout = calculateTextLayout(
167
+ text,
168
+ fontFamily,
169
+ scaledFontSize,
170
+ fontStyle,
171
+ wordWrap,
172
+ finalWordWrapWidth,
173
+ scaledLetterSpacing,
174
+ textIndent,
175
+ computedMaxLines,
176
+ overflowSuffix,
177
+ textOverflow,
178
+ );
179
+
180
+ // Calculate final dimensions
181
+ const dimensions = calculateTextDimensions(
182
+ layout,
183
+ paddingLeft,
184
+ paddingRight,
185
+ textBaseline,
186
+ scaledFontSize,
187
+ lineHeight,
188
+ scaledOffsetY,
189
+ maxWidth,
190
+ maxHeight,
191
+ wordWrap,
192
+ textAlign,
193
+ );
194
+
195
+ // Set up canvas dimensions
196
+ canvas.width = Math.min(
197
+ Math.ceil(dimensions.width + textRenderIssueMargin),
198
+ MAX_TEXTURE_DIMENSION,
199
+ );
200
+ canvas.height = Math.min(Math.ceil(dimensions.height), MAX_TEXTURE_DIMENSION);
201
+
202
+ // Reset font context after canvas resize
203
+ context.font = `${fontStyle} ${scaledFontSize}px ${fontFamily}`;
204
+ context.textBaseline = textBaseline;
205
+
206
+ // Performance optimization for large fonts
207
+ if (scaledFontSize >= 128) {
208
+ context.globalAlpha = 0.01;
209
+ context.fillRect(0, 0, 0.01, 0.01);
210
+ context.globalAlpha = 1.0;
211
+ }
212
+
213
+ // Calculate drawing positions
214
+ const drawLines = calculateDrawPositions(
215
+ layout.lines,
216
+ layout.lineWidths,
217
+ textAlign,
218
+ verticalAlign,
219
+ innerWidth,
220
+ paddingLeft,
221
+ textIndent,
222
+ lineHeight,
223
+ metrics,
224
+ scaledFontSize,
225
+ );
226
+
227
+ // Render text to canvas
228
+ renderTextToCanvas(
229
+ context,
230
+ drawLines,
231
+ scaledLetterSpacing,
232
+ textColor,
233
+ fontStyle,
234
+ scaledFontSize,
235
+ fontFamily,
236
+ );
237
+
238
+ width = dimensions.width;
239
+ const height = lineHeight * layout.lines.length;
240
+ // Extract image data
241
+ let imageData: ImageData | null = null;
242
+ if (canvas.width > 0 && canvas.height > 0) {
243
+ imageData = context.getImageData(0, 0, width, height);
244
+ }
245
+
246
+ return {
247
+ imageData,
248
+ width,
249
+ height,
250
+ };
251
+ };
252
+
253
+ /**
254
+ * Calculate the effective max lines constraint
255
+ */
256
+ function calculateMaxLines(
257
+ containedMaxLines: number,
258
+ maxLines: number,
259
+ ): number {
260
+ if (containedMaxLines > 0 && maxLines > 0) {
261
+ return containedMaxLines < maxLines ? containedMaxLines : maxLines;
262
+ } else {
263
+ return containedMaxLines > maxLines ? containedMaxLines : maxLines;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Generate a cache key for text layout calculations
269
+ */
270
+ function generateLayoutCacheKey(
271
+ text: string,
272
+ fontFamily: string,
273
+ fontSize: number,
274
+ fontStyle: string,
275
+ wordWrap: boolean,
276
+ wordWrapWidth: number,
277
+ letterSpacing: number,
278
+ maxLines: number,
279
+ overflowSuffix: string,
280
+ ): string {
281
+ return `${text}-${fontFamily}-${fontSize}-${fontStyle}-${wordWrap}-${wordWrapWidth}-${letterSpacing}-${maxLines}-${overflowSuffix}`;
282
+ }
283
+
284
+ /**
285
+ * Calculate text dimensions and wrapping
286
+ */
287
+ function calculateTextLayout(
288
+ text: string,
289
+ fontFamily: string,
290
+ fontSize: number,
291
+ fontStyle: string,
292
+ wordWrap: boolean,
293
+ wordWrapWidth: number,
294
+ letterSpacing: number,
295
+ textIndent: number,
296
+ maxLines: number,
297
+ overflowSuffix: string,
298
+ textOverflow: string | null,
299
+ ): {
300
+ lines: string[];
301
+ lineWidths: number[];
302
+ maxLineWidth: number;
303
+ remainingText: string;
304
+ moreTextLines: boolean;
305
+ } {
306
+ assertTruthy(measureContext, 'Measure context is not available');
307
+
308
+ // Check cache first
309
+ const cacheKey = generateLayoutCacheKey(
310
+ text,
311
+ fontFamily,
312
+ fontSize,
313
+ fontStyle,
314
+ wordWrap,
315
+ wordWrapWidth,
316
+ letterSpacing,
317
+ maxLines,
318
+ overflowSuffix,
319
+ );
320
+
321
+ const cached = layoutCache.get(cacheKey);
322
+ if (cached) {
323
+ return cached;
324
+ }
325
+
326
+ // Set font context for measurements on the dedicated measuring context
327
+ measureContext.font = `${fontStyle} ${fontSize}px ${fontFamily}`;
328
+
329
+ // Handle text overflow for non-wrapped text
330
+ let processedText = text;
331
+ if (textOverflow !== null && wordWrap === false) {
332
+ let suffix: string;
333
+ if (textOverflow === 'clip') {
334
+ suffix = '';
335
+ } else if (textOverflow === 'ellipsis') {
336
+ suffix = overflowSuffix;
337
+ } else {
338
+ suffix = textOverflow;
339
+ }
340
+ processedText = wrapWord(
341
+ measureContext,
342
+ text,
343
+ wordWrapWidth - textIndent,
344
+ suffix,
345
+ letterSpacing,
346
+ );
347
+ }
348
+
349
+ // Word wrap
350
+ let linesInfo: { n: number[]; l: string[] };
351
+ if (wordWrap === true) {
352
+ linesInfo = wrapText(
353
+ measureContext,
354
+ processedText,
355
+ wordWrapWidth,
356
+ letterSpacing,
357
+ textIndent,
358
+ );
359
+ } else {
360
+ linesInfo = { l: processedText.split(/(?:\r\n|\r|\n)/), n: [] };
361
+ const n = linesInfo.l.length;
362
+ for (let i = 0; i < n - 1; i++) {
363
+ linesInfo.n.push(i);
364
+ }
365
+ }
366
+ let lines: string[] = linesInfo.l;
367
+
368
+ let remainingText = '';
369
+ let moreTextLines = false;
370
+
371
+ // Handle max lines constraint
372
+ if (maxLines > 0 && lines.length > maxLines) {
373
+ const usedLines = lines.slice(0, maxLines);
374
+ let otherLines: string[] = [];
375
+ if (overflowSuffix.length > 0) {
376
+ const w = measureText(measureContext, overflowSuffix, letterSpacing);
377
+ const al = wrapText(
378
+ measureContext,
379
+ usedLines[usedLines.length - 1] || '',
380
+ wordWrapWidth - w,
381
+ letterSpacing,
382
+ textIndent,
383
+ );
384
+ usedLines[usedLines.length - 1] = `${al.l[0] || ''}${overflowSuffix}`;
385
+ otherLines = [al.l.length > 1 ? al.l[1] || '' : ''];
386
+ } else {
387
+ otherLines = [''];
388
+ }
389
+
390
+ // Re-assemble the remaining text
391
+ let i: number;
392
+ const n = lines.length;
393
+ let j = 0;
394
+ const m = linesInfo.n.length;
395
+ for (i = maxLines; i < n; i++) {
396
+ otherLines[j] += `${otherLines[j] ? ' ' : ''}${lines[i] ?? ''}`;
397
+ if (i + 1 < m && linesInfo.n[i + 1] !== undefined) {
398
+ j++;
399
+ }
400
+ }
401
+ remainingText = otherLines.join('\n');
402
+ moreTextLines = true;
403
+ lines = usedLines;
404
+ }
405
+
406
+ // Calculate line widths using the dedicated measuring context
407
+ let maxLineWidth = 0;
408
+ const lineWidths: number[] = [];
409
+ for (let i = 0; i < lines.length; i++) {
410
+ const lineWidth =
411
+ measureText(measureContext, lines[i] || '', letterSpacing) +
412
+ (i === 0 ? textIndent : 0);
413
+ lineWidths.push(lineWidth);
414
+ maxLineWidth = Math.max(maxLineWidth, lineWidth);
415
+ }
416
+
417
+ const result = {
418
+ lines,
419
+ lineWidths,
420
+ maxLineWidth,
421
+ remainingText,
422
+ moreTextLines,
423
+ };
424
+
425
+ // Cache the result
426
+ layoutCache.set(cacheKey, result);
427
+
428
+ return result;
429
+ }
430
+
431
+ /**
432
+ * Calculate text dimensions based on layout
433
+ */
434
+ function calculateTextDimensions(
435
+ layout: {
436
+ lines: string[];
437
+ lineWidths: number[];
438
+ maxLineWidth: number;
439
+ },
440
+ paddingLeft: number,
441
+ paddingRight: number,
442
+ textBaseline: TextBaseline,
443
+ fontSize: number,
444
+ lineHeight: number,
445
+ offsetY: number,
446
+ initialWidth: number,
447
+ initialHeight: number,
448
+ wordWrap: boolean,
449
+ textAlign: string,
450
+ ): { width: number; height: number } {
451
+ let width = initialWidth;
452
+ let height = initialHeight;
453
+
454
+ // Calculate width
455
+ if (initialWidth === 0) {
456
+ width = layout.maxLineWidth + paddingLeft + paddingRight;
457
+ }
458
+
459
+ // Adjust width for single-line left-aligned wrapped text
460
+ if (
461
+ wordWrap === true &&
462
+ width > layout.maxLineWidth &&
463
+ textAlign === 'left' &&
464
+ layout.lines.length === 1
465
+ ) {
466
+ width = layout.maxLineWidth + paddingLeft + paddingRight;
467
+ }
468
+
469
+ // Calculate height if not provided
470
+ if (height === 0) {
471
+ height = calcHeight(
472
+ textBaseline,
473
+ fontSize,
474
+ lineHeight,
475
+ layout.lines.length,
476
+ offsetY,
477
+ );
478
+ }
479
+
480
+ return { width, height };
481
+ }
482
+
483
+ /**
484
+ * Calculate drawing positions for text lines
485
+ */
486
+ function calculateDrawPositions(
487
+ lines: string[],
488
+ lineWidths: number[],
489
+ textAlign: string,
490
+ verticalAlign: string,
491
+ innerWidth: number,
492
+ paddingLeft: number,
493
+ textIndent: number,
494
+ lineHeight: number,
495
+ metrics: NormalizedFontMetrics,
496
+ fontSize: number,
497
+ ): LineType[] {
498
+ const drawLines: LineType[] = [];
499
+ const ascenderPx = metrics.ascender * fontSize;
500
+ const bareLineHeightPx = (metrics.ascender - metrics.descender) * fontSize;
501
+
502
+ for (let i = 0, n = lines.length; i < n; i++) {
503
+ let linePositionX = i === 0 ? textIndent : 0;
504
+ let linePositionY = i * lineHeight + ascenderPx;
505
+
506
+ // Vertical alignment
507
+ if (verticalAlign == 'middle') {
508
+ linePositionY += (lineHeight - bareLineHeightPx) / 2;
509
+ } else if (verticalAlign == 'bottom') {
510
+ linePositionY += lineHeight - bareLineHeightPx;
511
+ }
512
+
513
+ // Horizontal alignment
514
+ const lineWidth = lineWidths[i];
515
+ if (lineWidth !== undefined) {
516
+ if (textAlign === 'right') {
517
+ linePositionX += innerWidth - lineWidth;
518
+ } else if (textAlign === 'center') {
519
+ linePositionX += (innerWidth - lineWidth) / 2;
520
+ }
521
+ }
522
+
523
+ linePositionX += paddingLeft;
524
+
525
+ const lineText = lines[i];
526
+ if (lineText !== undefined) {
527
+ drawLines.push({
528
+ text: lineText,
529
+ x: linePositionX,
530
+ y: linePositionY,
531
+ w: lineWidth || 0,
532
+ });
533
+ }
534
+ }
535
+
536
+ return drawLines;
537
+ }
538
+
539
+ /**
540
+ * Render text lines to canvas
541
+ */
542
+ function renderTextToCanvas(
543
+ context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
544
+ drawLines: LineType[],
545
+ letterSpacing: number,
546
+ textColor: number,
547
+ fontStyle: string,
548
+ fontSize: number,
549
+ fontFamily: string,
550
+ ): void {
551
+ assertTruthy(measureContext, 'Measure context is not available');
552
+
553
+ context.fillStyle = normalizeCanvasColor(textColor);
554
+
555
+ // Sync font settings to measure context if we need to use it for letter spacing
556
+ if (letterSpacing > 0) {
557
+ measureContext.font = `${fontStyle} ${fontSize}px ${fontFamily}`;
558
+ }
559
+
560
+ for (let i = 0, n = drawLines.length; i < n; i++) {
561
+ const drawLine = drawLines[i];
562
+ if (drawLine) {
563
+ if (letterSpacing === 0) {
564
+ context.fillText(drawLine.text, drawLine.x, drawLine.y);
565
+ } else {
566
+ const textSplit = drawLine.text.split('');
567
+ let x = drawLine.x;
568
+ for (let j = 0, k = textSplit.length; j < k; j++) {
569
+ const char = textSplit[j];
570
+ if (char) {
571
+ // Skip zero-width spaces for rendering but keep them in the text flow
572
+ if (isZeroWidthSpace(char)) {
573
+ continue;
574
+ }
575
+ context.fillText(char, x, drawLine.y);
576
+ // Use the dedicated measuring context for letter spacing calculations
577
+ x += measureText(measureContext, char, letterSpacing);
578
+ }
579
+ }
580
+ }
581
+ }
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Clear layout cache for memory management
587
+ */
588
+ const clearLayoutCache = (): void => {
589
+ layoutCache.clear();
590
+ };
591
+
592
+ /**
593
+ * Add quads for rendering (Canvas doesn't use quads)
594
+ */
595
+ const addQuads = (): Float32Array | null => {
596
+ // Canvas renderer doesn't use quad-based rendering
597
+ // Return null for interface compatibility
598
+ return null;
599
+ };
600
+
601
+ /**
602
+ * Render quads for Canvas renderer (Canvas doesn't use quad-based rendering)
603
+ */
604
+ const renderQuads = (): void => {
605
+ // Canvas renderer doesn't use quad-based rendering
606
+ // This method is for interface compatibility only
607
+ };
608
+
609
+ /**
610
+ * Canvas Text Renderer - implements TextRenderer interface
611
+ */
612
+ const CanvasTextRenderer = {
613
+ type,
614
+ font: CanvasFontHandler,
615
+ renderText,
616
+ addQuads,
617
+ renderQuads,
618
+ init,
619
+ clearLayoutCache,
620
+ };
621
+
622
+ export default CanvasTextRenderer;