@lightningjs/renderer 3.0.0-beta4 → 3.0.0-beta5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -202
- package/NOTICE +3 -3
- package/README.md +147 -147
- package/dist/exports/core-api.d.ts +74 -0
- package/dist/exports/core-api.js +96 -0
- package/dist/exports/core-api.js.map +1 -0
- package/dist/exports/main-api.d.ts +30 -0
- package/dist/exports/main-api.js +45 -0
- package/dist/exports/main-api.js.map +1 -0
- package/dist/src/core/CoreExtension.d.ts +12 -0
- package/dist/src/core/CoreExtension.js +29 -0
- package/dist/src/core/CoreExtension.js.map +1 -0
- package/dist/src/core/CoreNode.js +1 -1
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreStuff.js +138 -0
- package/dist/src/core/CoreStuff.js.map +1 -0
- package/dist/src/core/CoreTextNode.js +1 -0
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/CoreTexturizer.d.ts +14 -0
- package/dist/src/core/CoreTexturizer.js +47 -0
- package/dist/src/core/CoreTexturizer.js.map +1 -0
- package/dist/src/core/LngNode.d.ts +736 -0
- package/dist/src/core/LngNode.js +1174 -0
- package/dist/src/core/LngNode.js.map +1 -0
- package/dist/src/core/Matrix2DContext.d.ts +15 -0
- package/dist/src/core/Matrix2DContext.js +45 -0
- package/dist/src/core/Matrix2DContext.js.map +1 -0
- package/dist/src/core/ShaderNode.d.ts +10 -0
- package/dist/src/core/ShaderNode.js +30 -0
- package/dist/src/core/ShaderNode.js.map +1 -0
- package/dist/src/core/TextNode.d.ts +103 -0
- package/dist/src/core/TextNode.js +331 -0
- package/dist/src/core/TextNode.js.map +1 -0
- package/dist/src/core/lib/Coords.d.ts +14 -0
- package/dist/src/core/lib/Coords.js +55 -0
- package/dist/src/core/lib/Coords.js.map +1 -0
- package/dist/src/core/lib/glm/common.d.ts +162 -0
- package/dist/src/core/lib/glm/common.js +81 -0
- package/dist/src/core/lib/glm/common.js.map +1 -0
- package/dist/src/core/lib/glm/index.d.ts +11 -0
- package/dist/src/core/lib/glm/index.js +30 -0
- package/dist/src/core/lib/glm/index.js.map +1 -0
- package/dist/src/core/lib/glm/mat2.d.ts +219 -0
- package/dist/src/core/lib/glm/mat2.js +396 -0
- package/dist/src/core/lib/glm/mat2.js.map +1 -0
- package/dist/src/core/lib/glm/mat2d.d.ts +237 -0
- package/dist/src/core/lib/glm/mat2d.js +442 -0
- package/dist/src/core/lib/glm/mat2d.js.map +1 -0
- package/dist/src/core/lib/glm/mat3.d.ts +283 -0
- package/dist/src/core/lib/glm/mat3.js +680 -0
- package/dist/src/core/lib/glm/mat3.js.map +1 -0
- package/dist/src/core/lib/glm/mat4.d.ts +550 -0
- package/dist/src/core/lib/glm/mat4.js +1802 -0
- package/dist/src/core/lib/glm/mat4.js.map +1 -0
- package/dist/src/core/lib/glm/quat.d.ts +363 -0
- package/dist/src/core/lib/glm/quat.js +693 -0
- package/dist/src/core/lib/glm/quat.js.map +1 -0
- package/dist/src/core/lib/glm/quat2.d.ts +356 -0
- package/dist/src/core/lib/glm/quat2.js +754 -0
- package/dist/src/core/lib/glm/quat2.js.map +1 -0
- package/dist/src/core/lib/glm/vec2.d.ts +365 -0
- package/dist/src/core/lib/glm/vec2.js +569 -0
- package/dist/src/core/lib/glm/vec2.js.map +1 -0
- package/dist/src/core/lib/glm/vec3.d.ts +406 -0
- package/dist/src/core/lib/glm/vec3.js +720 -0
- package/dist/src/core/lib/glm/vec3.js.map +1 -0
- package/dist/src/core/lib/glm/vec4.d.ts +330 -0
- package/dist/src/core/lib/glm/vec4.js +608 -0
- package/dist/src/core/lib/glm/vec4.js.map +1 -0
- package/dist/src/core/renderers/CoreShaderManager.d.ts +19 -0
- package/dist/src/core/renderers/CoreShaderManager.js +33 -0
- package/dist/src/core/renderers/CoreShaderManager.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShaderManager.d.ts +27 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShaderManager.js +82 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShaderManager.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShaderProgram.d.ts +11 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShaderProgram.js +34 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShaderProgram.js.map +1 -0
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.js +35 -35
- package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +45 -45
- package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js +61 -61
- package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +93 -93
- package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js +63 -63
- package/dist/src/core/renderers/webgl/shaders/SdfShader.js +62 -62
- package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js +15 -15
- package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js +6 -6
- package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js +15 -15
- package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js +15 -15
- package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js +15 -15
- package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js +42 -42
- package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.js +44 -44
- package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.js +3 -3
- package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js +22 -22
- package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +28 -28
- package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +10 -10
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +37 -37
- package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js +19 -19
- package/dist/src/core/scene/Scene.d.ts +59 -0
- package/dist/src/core/scene/Scene.js +106 -0
- package/dist/src/core/scene/Scene.js.map +1 -0
- package/dist/src/core/shaders/webgl/Border.js +59 -59
- package/dist/src/core/shaders/webgl/Default.js +47 -47
- package/dist/src/core/shaders/webgl/DefaultBatched.js +61 -61
- package/dist/src/core/shaders/webgl/HolePunch.js +32 -32
- package/dist/src/core/shaders/webgl/LinearGradient.js +36 -36
- package/dist/src/core/shaders/webgl/RadialGradient.js +33 -33
- package/dist/src/core/shaders/webgl/Rounded.js +71 -71
- package/dist/src/core/shaders/webgl/RoundedWithBorder.js +66 -66
- package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +79 -79
- package/dist/src/core/shaders/webgl/RoundedWithShadow.js +54 -54
- package/dist/src/core/shaders/webgl/SdfShader.js +62 -62
- package/dist/src/core/shaders/webgl/Shadow.js +83 -83
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/makeRenderWindow.d.ts +20 -0
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/makeRenderWindow.js +55 -0
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/makeRenderWindow.js.map +1 -0
- package/dist/src/main-api/ICoreDriver.d.ts +27 -0
- package/dist/src/main-api/ICoreDriver.js +20 -0
- package/dist/src/main-api/ICoreDriver.js.map +1 -0
- package/dist/src/main-api/IRenderDriver.d.ts +20 -0
- package/dist/src/main-api/IRenderDriver.js +20 -0
- package/dist/src/main-api/IRenderDriver.js.map +1 -0
- package/dist/src/main-api/IShaderController.d.ts +14 -0
- package/dist/src/main-api/IShaderController.js +30 -0
- package/dist/src/main-api/IShaderController.js.map +1 -0
- package/dist/src/main-api/IShaderNode.d.ts +17 -0
- package/dist/src/main-api/IShaderNode.js +19 -0
- package/dist/src/main-api/IShaderNode.js.map +1 -0
- package/dist/src/main-api/RendererMain.d.ts +375 -0
- package/dist/src/main-api/RendererMain.js +365 -0
- package/dist/src/main-api/RendererMain.js.map +1 -0
- package/dist/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.d.ts +9 -0
- package/dist/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.js +38 -0
- package/dist/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.js.map +1 -0
- package/dist/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.d.ts +56 -0
- package/dist/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.js +101 -0
- package/dist/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.js.map +1 -0
- package/dist/src/main-api/texture-usage-trackers/TextureUsageTracker.d.ts +32 -0
- package/dist/src/main-api/texture-usage-trackers/TextureUsageTracker.js +28 -0
- package/dist/src/main-api/texture-usage-trackers/TextureUsageTracker.js.map +1 -0
- package/dist/src/render-drivers/main/MainCoreDriver.d.ts +24 -0
- package/dist/src/render-drivers/main/MainCoreDriver.js +118 -0
- package/dist/src/render-drivers/main/MainCoreDriver.js.map +1 -0
- package/dist/src/render-drivers/main/MainOnlyNode.d.ts +99 -0
- package/dist/src/render-drivers/main/MainOnlyNode.js +396 -0
- package/dist/src/render-drivers/main/MainOnlyNode.js.map +1 -0
- package/dist/src/render-drivers/main/MainOnlyShaderController.d.ts +6 -0
- package/dist/src/render-drivers/main/MainOnlyShaderController.js +15 -0
- package/dist/src/render-drivers/main/MainOnlyShaderController.js.map +1 -0
- package/dist/src/render-drivers/main/MainOnlyShaderNode.d.ts +7 -0
- package/dist/src/render-drivers/main/MainOnlyShaderNode.js +34 -0
- package/dist/src/render-drivers/main/MainOnlyShaderNode.js.map +1 -0
- package/dist/src/render-drivers/main/MainOnlyTextNode.d.ts +47 -0
- package/dist/src/render-drivers/main/MainOnlyTextNode.js +205 -0
- package/dist/src/render-drivers/main/MainOnlyTextNode.js.map +1 -0
- package/dist/src/render-drivers/main/MainRenderDriver.d.ts +17 -0
- package/dist/src/render-drivers/main/MainRenderDriver.js +88 -0
- package/dist/src/render-drivers/main/MainRenderDriver.js.map +1 -0
- package/dist/src/render-drivers/threadx/NodeStruct.d.ts +90 -0
- package/dist/src/render-drivers/threadx/NodeStruct.js +281 -0
- package/dist/src/render-drivers/threadx/NodeStruct.js.map +1 -0
- package/dist/src/render-drivers/threadx/SharedNode.d.ts +39 -0
- package/dist/src/render-drivers/threadx/SharedNode.js +60 -0
- package/dist/src/render-drivers/threadx/SharedNode.js.map +1 -0
- package/dist/src/render-drivers/threadx/TextNodeStruct.d.ts +44 -0
- package/dist/src/render-drivers/threadx/TextNodeStruct.js +201 -0
- package/dist/src/render-drivers/threadx/TextNodeStruct.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXCoreDriver.d.ts +28 -0
- package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js +234 -0
- package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.d.ts +20 -0
- package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.js +84 -0
- package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXMainNode.d.ts +44 -0
- package/dist/src/render-drivers/threadx/ThreadXMainNode.js +154 -0
- package/dist/src/render-drivers/threadx/ThreadXMainNode.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXMainShaderController.d.ts +6 -0
- package/dist/src/render-drivers/threadx/ThreadXMainShaderController.js +16 -0
- package/dist/src/render-drivers/threadx/ThreadXMainShaderController.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXMainShaderNode.d.ts +7 -0
- package/dist/src/render-drivers/threadx/ThreadXMainShaderNode.js +15 -0
- package/dist/src/render-drivers/threadx/ThreadXMainShaderNode.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXMainTextNode.d.ts +28 -0
- package/dist/src/render-drivers/threadx/ThreadXMainTextNode.js +55 -0
- package/dist/src/render-drivers/threadx/ThreadXMainTextNode.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXRenderDriver.d.ts +21 -0
- package/dist/src/render-drivers/threadx/ThreadXRenderDriver.js +198 -0
- package/dist/src/render-drivers/threadx/ThreadXRenderDriver.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXRendererMessage.d.ts +70 -0
- package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js +32 -0
- package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js.map +1 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.d.ts +19 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js +177 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js.map +1 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererTextNode.d.ts +27 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererTextNode.js +108 -0
- package/dist/src/render-drivers/threadx/worker/ThreadXRendererTextNode.js.map +1 -0
- package/dist/src/render-drivers/threadx/worker/renderer.d.ts +1 -0
- package/dist/src/render-drivers/threadx/worker/renderer.js +145 -0
- package/dist/src/render-drivers/threadx/worker/renderer.js.map +1 -0
- package/dist/src/render-drivers/utils.d.ts +12 -0
- package/dist/src/render-drivers/utils.js +69 -0
- package/dist/src/render-drivers/utils.js.map +1 -0
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/exports/canvas-shaders.ts +28 -28
- package/exports/canvas.ts +45 -45
- package/exports/index.ts +90 -90
- package/exports/inspector.ts +24 -24
- package/exports/utils.ts +44 -44
- package/exports/webgl-shaders.ts +28 -28
- package/exports/webgl.ts +50 -50
- package/package.json +2 -1
- package/scripts/please-use-pnpm.js +13 -13
- package/src/common/CommonTypes.ts +146 -146
- package/src/common/EventEmitter.ts +77 -77
- package/src/common/IAnimationController.ts +92 -92
- package/src/common/IEventEmitter.ts +28 -28
- package/src/core/CoreNode.test.ts +203 -203
- package/src/core/CoreNode.ts +2494 -2494
- package/src/core/CoreShaderManager.ts +188 -188
- package/src/core/CoreTextNode.ts +449 -448
- package/src/core/CoreTextureManager.ts +601 -601
- package/src/core/Stage.ts +754 -754
- package/src/core/TextureMemoryManager.ts +395 -395
- package/src/core/animations/AnimationManager.ts +38 -38
- package/src/core/animations/CoreAnimation.ts +284 -284
- package/src/core/animations/CoreAnimationController.ts +157 -157
- package/src/core/lib/ContextSpy.ts +41 -41
- package/src/core/lib/ImageWorker.ts +280 -280
- package/src/core/lib/Matrix3d.ts +244 -244
- package/src/core/lib/RenderCoords.ts +71 -71
- package/src/core/lib/WebGlContextWrapper.ts +1374 -1374
- package/src/core/lib/textureCompression.ts +152 -152
- package/src/core/lib/textureSvg.ts +78 -78
- package/src/core/lib/utils.ts +386 -386
- package/src/core/lib/validateImageBitmap.ts +87 -87
- package/src/core/platform.ts +64 -64
- package/src/core/platforms/Platform.ts +77 -77
- package/src/core/platforms/web/WebPlatform.ts +84 -84
- package/src/core/renderers/CoreContextTexture.ts +43 -43
- package/src/core/renderers/CoreRenderOp.ts +22 -22
- package/src/core/renderers/CoreRenderer.ts +109 -109
- package/src/core/renderers/CoreShaderNode.ts +165 -165
- package/src/core/renderers/CoreShaderProgram.ts +23 -23
- package/src/core/renderers/canvas/CanvasRenderer.ts +298 -298
- package/src/core/renderers/canvas/CanvasShaderNode.ts +99 -99
- package/src/core/renderers/canvas/CanvasTexture.ts +156 -156
- package/src/core/renderers/canvas/internal/C2DShaderUtils.ts +220 -220
- package/src/core/renderers/canvas/internal/ColorUtils.ts +85 -85
- package/src/core/renderers/webgl/WebGlCtxRenderTexture.ts +86 -86
- package/src/core/renderers/webgl/WebGlCtxSubTexture.ts +50 -50
- package/src/core/renderers/webgl/WebGlCtxTexture.ts +301 -301
- package/src/core/renderers/webgl/WebGlRenderOp.ts +161 -161
- package/src/core/renderers/webgl/WebGlRenderer.ts +750 -750
- package/src/core/renderers/webgl/WebGlShaderNode.ts +437 -437
- package/src/core/renderers/webgl/WebGlShaderProgram.ts +318 -318
- package/src/core/renderers/webgl/internal/BufferCollection.ts +54 -54
- package/src/core/renderers/webgl/internal/RendererUtils.ts +155 -155
- package/src/core/renderers/webgl/internal/ShaderUtils.ts +281 -281
- package/src/core/renderers/webgl/internal/WebGlUtils.ts +35 -35
- package/src/core/shaders/canvas/Border.ts +78 -78
- package/src/core/shaders/canvas/HolePunch.ts +62 -62
- package/src/core/shaders/canvas/LinearGradient.ts +69 -69
- package/src/core/shaders/canvas/RadialGradient.ts +113 -113
- package/src/core/shaders/canvas/Rounded.ts +55 -55
- package/src/core/shaders/canvas/RoundedWithBorder.ts +68 -68
- package/src/core/shaders/canvas/RoundedWithBorderAndShadow.ts +88 -88
- package/src/core/shaders/canvas/RoundedWithShadow.ts +69 -69
- package/src/core/shaders/canvas/Shadow.ts +52 -52
- package/src/core/shaders/canvas/utils/render.ts +151 -151
- package/src/core/shaders/templates/BorderTemplate.ts +115 -115
- package/src/core/shaders/templates/HolePunchTemplate.ts +82 -82
- package/src/core/shaders/templates/LinearGradientTemplate.ts +71 -71
- package/src/core/shaders/templates/RadialGradientTemplate.ts +81 -81
- package/src/core/shaders/templates/RoundedTemplate.ts +98 -98
- package/src/core/shaders/templates/RoundedWithBorderAndShadowTemplate.ts +38 -38
- package/src/core/shaders/templates/RoundedWithBorderTemplate.ts +35 -35
- package/src/core/shaders/templates/RoundedWithShadowTemplate.ts +35 -35
- package/src/core/shaders/templates/ShadowTemplate.ts +106 -106
- package/src/core/shaders/templates/shaderUtils.ts +47 -47
- package/src/core/shaders/webgl/Border.ts +96 -96
- package/src/core/shaders/webgl/Default.ts +89 -89
- package/src/core/shaders/webgl/DefaultBatched.ts +129 -129
- package/src/core/shaders/webgl/HolePunch.ts +78 -78
- package/src/core/shaders/webgl/LinearGradient.ts +81 -81
- package/src/core/shaders/webgl/RadialGradient.ts +84 -84
- package/src/core/shaders/webgl/Rounded.ts +117 -117
- package/src/core/shaders/webgl/RoundedWithBorder.ts +114 -114
- package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +133 -133
- package/src/core/shaders/webgl/RoundedWithShadow.ts +98 -98
- package/src/core/shaders/webgl/SdfShader.ts +134 -134
- package/src/core/shaders/webgl/Shadow.ts +115 -115
- package/src/core/text-rendering/TextRenderingUtils.ts +36 -36
- package/src/core/text-rendering/TextTextureRendererUtils.ts +263 -263
- package/src/core/text-rendering/TrFontManager.ts +183 -183
- package/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts +176 -176
- package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/FontShaper.ts +139 -139
- package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.test.ts +173 -173
- package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.ts +171 -171
- package/src/core/text-rendering/font-face-types/TrFontFace.ts +187 -187
- package/src/core/text-rendering/font-face-types/WebTrFontFace.ts +94 -94
- package/src/core/text-rendering/font-face-types/utils.ts +39 -39
- package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +514 -514
- package/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +863 -863
- package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +846 -846
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/PeekableGenerator.test.ts +48 -48
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/PeekableGenerator.ts +66 -66
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/SpecialCodepoints.ts +52 -52
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/constants.ts +32 -32
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.ts +117 -117
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getUnicodeCodepoints.test.ts +133 -133
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getUnicodeCodepoints.ts +38 -38
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +497 -497
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/measureText.test.ts +49 -49
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/measureText.ts +52 -52
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.test.ts +205 -205
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.ts +93 -93
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/util.ts +40 -40
- package/src/core/text-rendering/renderers/TextRenderer.ts +567 -567
- package/src/core/textures/ColorTexture.ts +102 -102
- package/src/core/textures/ImageTexture.ts +410 -410
- package/src/core/textures/NoiseTexture.ts +104 -104
- package/src/core/textures/RenderTexture.ts +85 -85
- package/src/core/textures/SubTexture.ts +205 -205
- package/src/core/textures/Texture.ts +358 -358
- package/src/core/utils.ts +227 -227
- package/src/env.d.ts +7 -7
- package/src/main-api/INode.ts +100 -100
- package/src/main-api/Inspector.ts +522 -522
- package/src/main-api/Renderer.ts +675 -675
- package/src/main-api/utils.ts +45 -45
- package/src/utils.ts +267 -267
- package/COPYING +0 -1
- package/dist/src/core/temp.js +0 -77
- package/dist/src/core/temp.js.map +0 -1
- /package/dist/src/core/{temp.d.ts → CoreStuff.d.ts} +0 -0
|
@@ -1,863 +1,863 @@
|
|
|
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
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
21
|
-
|
|
22
|
-
import { assertTruthy } from '../../../utils.js';
|
|
23
|
-
import { getRgbaString, type RGBA } from '../../lib/utils.js';
|
|
24
|
-
import { calcDefaultLineHeight } from '../TextRenderingUtils.js';
|
|
25
|
-
import {
|
|
26
|
-
getWebFontMetrics,
|
|
27
|
-
isZeroWidthSpace,
|
|
28
|
-
} from '../TextTextureRendererUtils.js';
|
|
29
|
-
import type { NormalizedFontMetrics } from '../font-face-types/TrFontFace.js';
|
|
30
|
-
import type { WebTrFontFace } from '../font-face-types/WebTrFontFace.js';
|
|
31
|
-
|
|
32
|
-
const MAX_TEXTURE_DIMENSION = 2048;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Text Overflow Values
|
|
36
|
-
*/
|
|
37
|
-
export type TextOverflow =
|
|
38
|
-
| 'ellipsis'
|
|
39
|
-
| 'clip'
|
|
40
|
-
| (string & Record<never, never>);
|
|
41
|
-
|
|
42
|
-
/***
|
|
43
|
-
* Text Horizontal Align Values
|
|
44
|
-
*/
|
|
45
|
-
export type TextAlign = 'left' | 'center' | 'right';
|
|
46
|
-
|
|
47
|
-
/***
|
|
48
|
-
* Text Baseline Values
|
|
49
|
-
*/
|
|
50
|
-
export type TextBaseline =
|
|
51
|
-
| 'alphabetic'
|
|
52
|
-
| 'top'
|
|
53
|
-
| 'hanging'
|
|
54
|
-
| 'middle'
|
|
55
|
-
| 'ideographic'
|
|
56
|
-
| 'bottom';
|
|
57
|
-
|
|
58
|
-
/***
|
|
59
|
-
* Text Vertical Align Values
|
|
60
|
-
*/
|
|
61
|
-
export type TextVerticalAlign = 'top' | 'middle' | 'bottom';
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Text Texture Settings
|
|
65
|
-
*/
|
|
66
|
-
export interface Settings {
|
|
67
|
-
w: number;
|
|
68
|
-
h: number;
|
|
69
|
-
text: string;
|
|
70
|
-
fontStyle: string;
|
|
71
|
-
fontSize: number;
|
|
72
|
-
fontBaselineRatio: number;
|
|
73
|
-
fontFamily: string | null;
|
|
74
|
-
trFontFace: WebTrFontFace | null;
|
|
75
|
-
wordWrap: boolean;
|
|
76
|
-
wordWrapWidth: number;
|
|
77
|
-
wordBreak: 'normal' | 'break-all' | 'break-word';
|
|
78
|
-
textOverflow: TextOverflow | null;
|
|
79
|
-
lineHeight: number | null;
|
|
80
|
-
textBaseline: TextBaseline;
|
|
81
|
-
textAlign: TextAlign;
|
|
82
|
-
verticalAlign: TextVerticalAlign;
|
|
83
|
-
offsetY: number | null;
|
|
84
|
-
maxLines: number;
|
|
85
|
-
maxHeight: number | null;
|
|
86
|
-
overflowSuffix: string;
|
|
87
|
-
precision: number;
|
|
88
|
-
textColor: RGBA;
|
|
89
|
-
paddingLeft: number;
|
|
90
|
-
paddingRight: number;
|
|
91
|
-
shadow: boolean;
|
|
92
|
-
shadowColor: RGBA;
|
|
93
|
-
shadowOffsetX: number;
|
|
94
|
-
shadowOffsetY: number;
|
|
95
|
-
shadowBlur: number;
|
|
96
|
-
highlight: boolean;
|
|
97
|
-
highlightHeight: number;
|
|
98
|
-
highlightColor: RGBA;
|
|
99
|
-
highlightOffset: number;
|
|
100
|
-
highlightPaddingLeft: number;
|
|
101
|
-
highlightPaddingRight: number;
|
|
102
|
-
letterSpacing: number;
|
|
103
|
-
textIndent: number;
|
|
104
|
-
cutSx: number;
|
|
105
|
-
cutSy: number;
|
|
106
|
-
cutEx: number;
|
|
107
|
-
cutEy: number;
|
|
108
|
-
advancedRenderer: boolean;
|
|
109
|
-
|
|
110
|
-
// Normally stage options
|
|
111
|
-
textRenderIssueMargin: number;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface RenderInfo {
|
|
115
|
-
w: number;
|
|
116
|
-
h: number;
|
|
117
|
-
lines: string[];
|
|
118
|
-
precision: number;
|
|
119
|
-
remainingText: string;
|
|
120
|
-
moreTextLines: boolean;
|
|
121
|
-
width: number;
|
|
122
|
-
innerWidth: number;
|
|
123
|
-
height: number;
|
|
124
|
-
fontSize: number;
|
|
125
|
-
cutSx: number;
|
|
126
|
-
cutSy: number;
|
|
127
|
-
cutEx: number;
|
|
128
|
-
cutEy: number;
|
|
129
|
-
lineHeight: number;
|
|
130
|
-
defLineHeight: number;
|
|
131
|
-
lineWidths: number[];
|
|
132
|
-
offsetY: number;
|
|
133
|
-
paddingLeft: number;
|
|
134
|
-
paddingRight: number;
|
|
135
|
-
letterSpacing: number;
|
|
136
|
-
textIndent: number;
|
|
137
|
-
metrics: NormalizedFontMetrics;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export interface LineType {
|
|
141
|
-
text: string;
|
|
142
|
-
x: number;
|
|
143
|
-
y: number;
|
|
144
|
-
w: number;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Calculate height for the canvas
|
|
149
|
-
*
|
|
150
|
-
* @param textBaseline
|
|
151
|
-
* @param fontSize
|
|
152
|
-
* @param lineHeight
|
|
153
|
-
* @param numLines
|
|
154
|
-
* @param offsetY
|
|
155
|
-
* @returns
|
|
156
|
-
*/
|
|
157
|
-
function calcHeight(
|
|
158
|
-
textBaseline: TextBaseline,
|
|
159
|
-
fontSize: number,
|
|
160
|
-
lineHeight: number,
|
|
161
|
-
numLines: number,
|
|
162
|
-
offsetY: number | null,
|
|
163
|
-
) {
|
|
164
|
-
const baselineOffset = textBaseline !== 'bottom' ? 0.5 * fontSize : 0;
|
|
165
|
-
return (
|
|
166
|
-
lineHeight * (numLines - 1) +
|
|
167
|
-
baselineOffset +
|
|
168
|
-
Math.max(lineHeight, fontSize) +
|
|
169
|
-
(offsetY || 0)
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export class LightningTextTextureRenderer {
|
|
174
|
-
private _canvas: OffscreenCanvas | HTMLCanvasElement;
|
|
175
|
-
private _context:
|
|
176
|
-
| OffscreenCanvasRenderingContext2D
|
|
177
|
-
| CanvasRenderingContext2D;
|
|
178
|
-
private _settings: Settings;
|
|
179
|
-
|
|
180
|
-
constructor(
|
|
181
|
-
canvas: OffscreenCanvas | HTMLCanvasElement,
|
|
182
|
-
context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,
|
|
183
|
-
) {
|
|
184
|
-
this._canvas = canvas;
|
|
185
|
-
this._context = context;
|
|
186
|
-
this._settings = this.mergeDefaults({});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
set settings(v: Partial<Settings>) {
|
|
190
|
-
this._settings = this.mergeDefaults(v);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
get settings(): Settings {
|
|
194
|
-
return this._settings;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
getPrecision() {
|
|
198
|
-
return this._settings.precision;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
setFontProperties() {
|
|
202
|
-
this._context.font = this._getFontSetting();
|
|
203
|
-
this._context.textBaseline = this._settings.textBaseline;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
_getFontSetting() {
|
|
207
|
-
const ff = [this._settings.fontFamily];
|
|
208
|
-
|
|
209
|
-
const ffs: string[] = [];
|
|
210
|
-
for (let i = 0, n = ff.length; i < n; i++) {
|
|
211
|
-
if (ff[i] === 'serif' || ff[i] === 'sans-serif') {
|
|
212
|
-
ffs.push(ff[i]!);
|
|
213
|
-
} else {
|
|
214
|
-
ffs.push(`"${ff[i]!}"`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return `${this._settings.fontStyle} ${
|
|
219
|
-
this._settings.fontSize * this.getPrecision()
|
|
220
|
-
}px ${ffs.join(',')}`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
_load() {
|
|
224
|
-
if (true && document.fonts) {
|
|
225
|
-
const fontSetting = this._getFontSetting();
|
|
226
|
-
try {
|
|
227
|
-
if (!document.fonts.check(fontSetting, this._settings.text)) {
|
|
228
|
-
// Use a promise that waits for loading.
|
|
229
|
-
return document.fonts
|
|
230
|
-
.load(fontSetting, this._settings.text)
|
|
231
|
-
.catch((err) => {
|
|
232
|
-
// Just load the fallback font.
|
|
233
|
-
console.warn('[Lightning] Font load error', err, fontSetting);
|
|
234
|
-
})
|
|
235
|
-
.then(() => {
|
|
236
|
-
if (!document.fonts.check(fontSetting, this._settings.text)) {
|
|
237
|
-
console.warn('[Lightning] Font not found', fontSetting);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
} catch (e) {
|
|
242
|
-
console.warn("[Lightning] Can't check font loading for " + fontSetting);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
calculateRenderInfo(): RenderInfo {
|
|
248
|
-
const renderInfo: Partial<RenderInfo> = {};
|
|
249
|
-
|
|
250
|
-
const precision = this.getPrecision();
|
|
251
|
-
|
|
252
|
-
const paddingLeft = this._settings.paddingLeft * precision;
|
|
253
|
-
const paddingRight = this._settings.paddingRight * precision;
|
|
254
|
-
const fontSize = this._settings.fontSize * precision;
|
|
255
|
-
let offsetY =
|
|
256
|
-
this._settings.offsetY === null
|
|
257
|
-
? null
|
|
258
|
-
: this._settings.offsetY * precision;
|
|
259
|
-
const w = this._settings.w * precision;
|
|
260
|
-
const h = this._settings.h * precision;
|
|
261
|
-
let wordWrapWidth = this._settings.wordWrapWidth * precision;
|
|
262
|
-
const cutSx = this._settings.cutSx * precision;
|
|
263
|
-
const cutEx = this._settings.cutEx * precision;
|
|
264
|
-
const cutSy = this._settings.cutSy * precision;
|
|
265
|
-
const cutEy = this._settings.cutEy * precision;
|
|
266
|
-
const letterSpacing = (this._settings.letterSpacing || 0) * precision;
|
|
267
|
-
const textIndent = this._settings.textIndent * precision;
|
|
268
|
-
const trFontFace = this._settings.trFontFace;
|
|
269
|
-
|
|
270
|
-
// Set font properties.
|
|
271
|
-
this.setFontProperties();
|
|
272
|
-
|
|
273
|
-
assertTruthy(trFontFace);
|
|
274
|
-
const metrics = getWebFontMetrics(this._context, trFontFace, fontSize);
|
|
275
|
-
const defLineHeight = calcDefaultLineHeight(metrics, fontSize) * precision;
|
|
276
|
-
const lineHeight =
|
|
277
|
-
this._settings.lineHeight !== null
|
|
278
|
-
? this._settings.lineHeight * precision
|
|
279
|
-
: defLineHeight;
|
|
280
|
-
|
|
281
|
-
const maxHeight = this._settings.maxHeight;
|
|
282
|
-
const containedMaxLines =
|
|
283
|
-
maxHeight !== null && lineHeight > 0
|
|
284
|
-
? Math.floor(maxHeight / lineHeight)
|
|
285
|
-
: 0;
|
|
286
|
-
|
|
287
|
-
const setMaxLines = this._settings.maxLines;
|
|
288
|
-
const calcMaxLines =
|
|
289
|
-
containedMaxLines > 0 && setMaxLines > 0
|
|
290
|
-
? Math.min(containedMaxLines, setMaxLines)
|
|
291
|
-
: Math.max(containedMaxLines, setMaxLines);
|
|
292
|
-
|
|
293
|
-
// Total width.
|
|
294
|
-
let width = w || 2048 / this.getPrecision();
|
|
295
|
-
|
|
296
|
-
// Inner width.
|
|
297
|
-
let innerWidth = width - paddingLeft;
|
|
298
|
-
if (innerWidth < 10) {
|
|
299
|
-
width += 10 - innerWidth;
|
|
300
|
-
innerWidth = 10;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (!wordWrapWidth) {
|
|
304
|
-
wordWrapWidth = innerWidth;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Text overflow
|
|
308
|
-
// TODO Probably never used
|
|
309
|
-
if (this._settings.textOverflow && !this._settings.wordWrap) {
|
|
310
|
-
let suffix;
|
|
311
|
-
switch (this._settings.textOverflow) {
|
|
312
|
-
case 'clip':
|
|
313
|
-
suffix = '';
|
|
314
|
-
break;
|
|
315
|
-
case 'ellipsis':
|
|
316
|
-
suffix = this._settings.overflowSuffix;
|
|
317
|
-
break;
|
|
318
|
-
default:
|
|
319
|
-
suffix = this._settings.textOverflow;
|
|
320
|
-
}
|
|
321
|
-
this._settings.text = this.wrapWord(
|
|
322
|
-
this._settings.text,
|
|
323
|
-
wordWrapWidth - textIndent,
|
|
324
|
-
suffix,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// word wrap
|
|
329
|
-
// preserve original text
|
|
330
|
-
let linesInfo: { n: number[]; l: string[] };
|
|
331
|
-
if (this._settings.wordWrap) {
|
|
332
|
-
linesInfo = this.wrapText(
|
|
333
|
-
this._settings.text,
|
|
334
|
-
wordWrapWidth,
|
|
335
|
-
letterSpacing,
|
|
336
|
-
textIndent,
|
|
337
|
-
);
|
|
338
|
-
} else {
|
|
339
|
-
linesInfo = { l: this._settings.text.split(/(?:\r\n|\r|\n)/), n: [] };
|
|
340
|
-
const n = linesInfo.l.length;
|
|
341
|
-
for (let i = 0; i < n - 1; i++) {
|
|
342
|
-
linesInfo.n.push(i);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
let lines = linesInfo.l;
|
|
346
|
-
|
|
347
|
-
if (calcMaxLines && lines.length > calcMaxLines) {
|
|
348
|
-
const usedLines = lines.slice(0, calcMaxLines);
|
|
349
|
-
|
|
350
|
-
let otherLines: string[] | null = null;
|
|
351
|
-
if (this._settings.overflowSuffix) {
|
|
352
|
-
// Wrap again with max lines suffix enabled.
|
|
353
|
-
const w = this._settings.overflowSuffix
|
|
354
|
-
? this.measureText(this._settings.overflowSuffix)
|
|
355
|
-
: 0;
|
|
356
|
-
const al = this.wrapText(
|
|
357
|
-
usedLines[usedLines.length - 1]!,
|
|
358
|
-
wordWrapWidth - w,
|
|
359
|
-
letterSpacing,
|
|
360
|
-
textIndent,
|
|
361
|
-
);
|
|
362
|
-
usedLines[usedLines.length - 1] = `${al.l[0]!}${
|
|
363
|
-
this._settings.overflowSuffix
|
|
364
|
-
}`;
|
|
365
|
-
otherLines = [al.l.length > 1 ? al.l[1]! : ''];
|
|
366
|
-
} else {
|
|
367
|
-
otherLines = [''];
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Re-assemble the remaining text.
|
|
371
|
-
let i;
|
|
372
|
-
const n = lines.length;
|
|
373
|
-
let j = 0;
|
|
374
|
-
const m = linesInfo.n.length;
|
|
375
|
-
for (i = calcMaxLines; i < n; i++) {
|
|
376
|
-
otherLines[j] += `${otherLines[j] ? ' ' : ''}${lines[i]!}`;
|
|
377
|
-
if (i + 1 < m && linesInfo.n[i + 1]) {
|
|
378
|
-
j++;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
renderInfo.remainingText = otherLines.join('\n');
|
|
383
|
-
|
|
384
|
-
renderInfo.moreTextLines = true;
|
|
385
|
-
|
|
386
|
-
lines = usedLines;
|
|
387
|
-
} else {
|
|
388
|
-
renderInfo.moreTextLines = false;
|
|
389
|
-
renderInfo.remainingText = '';
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// calculate text width
|
|
393
|
-
let maxLineWidth = 0;
|
|
394
|
-
const lineWidths: number[] = [];
|
|
395
|
-
for (let i = 0; i < lines.length; i++) {
|
|
396
|
-
const lineWidth =
|
|
397
|
-
this.measureText(lines[i]!, letterSpacing) + (i === 0 ? textIndent : 0);
|
|
398
|
-
lineWidths.push(lineWidth);
|
|
399
|
-
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
renderInfo.lineWidths = lineWidths;
|
|
403
|
-
|
|
404
|
-
if (!w) {
|
|
405
|
-
// Auto-set width to max text length.
|
|
406
|
-
width = maxLineWidth + paddingLeft + paddingRight;
|
|
407
|
-
innerWidth = maxLineWidth;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// If word wrap is enabled the width needs to be the width of the text.
|
|
411
|
-
if (
|
|
412
|
-
this._settings.wordWrap &&
|
|
413
|
-
w > maxLineWidth &&
|
|
414
|
-
this._settings.textAlign === 'left' &&
|
|
415
|
-
lines.length === 1
|
|
416
|
-
) {
|
|
417
|
-
width = maxLineWidth + paddingLeft + paddingRight;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
let height;
|
|
421
|
-
if (h) {
|
|
422
|
-
height = h;
|
|
423
|
-
} else {
|
|
424
|
-
height = calcHeight(
|
|
425
|
-
this._settings.textBaseline,
|
|
426
|
-
fontSize,
|
|
427
|
-
lineHeight,
|
|
428
|
-
lines.length,
|
|
429
|
-
offsetY,
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (offsetY === null) {
|
|
434
|
-
offsetY = fontSize;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
renderInfo.w = width;
|
|
438
|
-
renderInfo.h = height;
|
|
439
|
-
renderInfo.lines = lines;
|
|
440
|
-
renderInfo.precision = precision;
|
|
441
|
-
|
|
442
|
-
if (!width) {
|
|
443
|
-
// To prevent canvas errors.
|
|
444
|
-
width = 1;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (!height) {
|
|
448
|
-
// To prevent canvas errors.
|
|
449
|
-
height = 1;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (cutSx || cutEx) {
|
|
453
|
-
width = Math.min(width, cutEx - cutSx);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (cutSy || cutEy) {
|
|
457
|
-
height = Math.min(height, cutEy - cutSy);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
renderInfo.width = width;
|
|
461
|
-
renderInfo.innerWidth = innerWidth;
|
|
462
|
-
renderInfo.height = height;
|
|
463
|
-
renderInfo.fontSize = fontSize;
|
|
464
|
-
renderInfo.cutSx = cutSx;
|
|
465
|
-
renderInfo.cutSy = cutSy;
|
|
466
|
-
renderInfo.cutEx = cutEx;
|
|
467
|
-
renderInfo.cutEy = cutEy;
|
|
468
|
-
renderInfo.lineHeight = lineHeight;
|
|
469
|
-
renderInfo.defLineHeight = defLineHeight;
|
|
470
|
-
renderInfo.lineWidths = lineWidths;
|
|
471
|
-
renderInfo.offsetY = offsetY;
|
|
472
|
-
renderInfo.paddingLeft = paddingLeft;
|
|
473
|
-
renderInfo.paddingRight = paddingRight;
|
|
474
|
-
renderInfo.letterSpacing = letterSpacing;
|
|
475
|
-
renderInfo.textIndent = textIndent;
|
|
476
|
-
renderInfo.metrics = metrics;
|
|
477
|
-
|
|
478
|
-
return renderInfo as RenderInfo;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
draw(
|
|
482
|
-
renderInfo: RenderInfo,
|
|
483
|
-
linesOverride?: { lines: string[]; lineWidths: number[] },
|
|
484
|
-
) {
|
|
485
|
-
const precision = this.getPrecision();
|
|
486
|
-
|
|
487
|
-
// Allow lines to be overriden for partial rendering.
|
|
488
|
-
const lines = linesOverride?.lines || renderInfo.lines;
|
|
489
|
-
const lineWidths = linesOverride?.lineWidths || renderInfo.lineWidths;
|
|
490
|
-
const height = linesOverride
|
|
491
|
-
? calcHeight(
|
|
492
|
-
this._settings.textBaseline,
|
|
493
|
-
renderInfo.fontSize,
|
|
494
|
-
renderInfo.lineHeight,
|
|
495
|
-
linesOverride.lines.length,
|
|
496
|
-
this._settings.offsetY === null
|
|
497
|
-
? null
|
|
498
|
-
: this._settings.offsetY * precision,
|
|
499
|
-
)
|
|
500
|
-
: renderInfo.height;
|
|
501
|
-
|
|
502
|
-
// Add extra margin to prevent issue with clipped text when scaling.
|
|
503
|
-
this._canvas.width = Math.min(
|
|
504
|
-
Math.ceil(renderInfo.width + this._settings.textRenderIssueMargin),
|
|
505
|
-
MAX_TEXTURE_DIMENSION,
|
|
506
|
-
);
|
|
507
|
-
this._canvas.height = Math.min(Math.ceil(height), MAX_TEXTURE_DIMENSION);
|
|
508
|
-
|
|
509
|
-
// Canvas context has been reset.
|
|
510
|
-
this.setFontProperties();
|
|
511
|
-
|
|
512
|
-
if (renderInfo.fontSize >= 128) {
|
|
513
|
-
// WpeWebKit bug: must force compositing because cairo-traps-compositor will not work with text first.
|
|
514
|
-
this._context.globalAlpha = 0.01;
|
|
515
|
-
this._context.fillRect(0, 0, 0.01, 0.01);
|
|
516
|
-
this._context.globalAlpha = 1.0;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (renderInfo.cutSx || renderInfo.cutSy) {
|
|
520
|
-
this._context.translate(-renderInfo.cutSx, -renderInfo.cutSy);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
let linePositionX;
|
|
524
|
-
let linePositionY;
|
|
525
|
-
|
|
526
|
-
const drawLines: LineType[] = [];
|
|
527
|
-
|
|
528
|
-
const { metrics } = renderInfo;
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Ascender (in pixels)
|
|
532
|
-
*/
|
|
533
|
-
const ascenderPx = metrics
|
|
534
|
-
? metrics.ascender * renderInfo.fontSize
|
|
535
|
-
: renderInfo.fontSize;
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Bare line height is the distance between the ascender and descender of the font.
|
|
539
|
-
* without the line gap metric.
|
|
540
|
-
*/
|
|
541
|
-
const bareLineHeightPx =
|
|
542
|
-
(metrics.ascender - metrics.descender) * renderInfo.fontSize;
|
|
543
|
-
|
|
544
|
-
// Draw lines line by line.
|
|
545
|
-
for (let i = 0, n = lines.length; i < n; i++) {
|
|
546
|
-
linePositionX = i === 0 ? renderInfo.textIndent : 0;
|
|
547
|
-
|
|
548
|
-
// By default, text is aligned to top
|
|
549
|
-
linePositionY = i * renderInfo.lineHeight + ascenderPx;
|
|
550
|
-
|
|
551
|
-
if (this._settings.verticalAlign == 'middle') {
|
|
552
|
-
linePositionY += (renderInfo.lineHeight - bareLineHeightPx) / 2;
|
|
553
|
-
} else if (this._settings.verticalAlign == 'bottom') {
|
|
554
|
-
linePositionY += renderInfo.lineHeight - bareLineHeightPx;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
if (this._settings.textAlign === 'right') {
|
|
558
|
-
linePositionX += renderInfo.innerWidth - lineWidths[i]!;
|
|
559
|
-
} else if (this._settings.textAlign === 'center') {
|
|
560
|
-
linePositionX += (renderInfo.innerWidth - lineWidths[i]!) / 2;
|
|
561
|
-
}
|
|
562
|
-
linePositionX += renderInfo.paddingLeft;
|
|
563
|
-
|
|
564
|
-
drawLines.push({
|
|
565
|
-
text: lines[i]!,
|
|
566
|
-
x: linePositionX,
|
|
567
|
-
y: linePositionY,
|
|
568
|
-
w: lineWidths[i]!,
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Highlight.
|
|
573
|
-
if (this._settings.highlight) {
|
|
574
|
-
const color = this._settings.highlightColor;
|
|
575
|
-
|
|
576
|
-
const hlHeight =
|
|
577
|
-
this._settings.highlightHeight * precision || renderInfo.fontSize * 1.5;
|
|
578
|
-
const offset = this._settings.highlightOffset * precision;
|
|
579
|
-
const hlPaddingLeft =
|
|
580
|
-
this._settings.highlightPaddingLeft !== null
|
|
581
|
-
? this._settings.highlightPaddingLeft * precision
|
|
582
|
-
: renderInfo.paddingLeft;
|
|
583
|
-
const hlPaddingRight =
|
|
584
|
-
this._settings.highlightPaddingRight !== null
|
|
585
|
-
? this._settings.highlightPaddingRight * precision
|
|
586
|
-
: renderInfo.paddingRight;
|
|
587
|
-
|
|
588
|
-
this._context.fillStyle = getRgbaString(color);
|
|
589
|
-
for (let i = 0; i < drawLines.length; i++) {
|
|
590
|
-
const drawLine = drawLines[i]!;
|
|
591
|
-
this._context.fillRect(
|
|
592
|
-
drawLine.x - hlPaddingLeft,
|
|
593
|
-
drawLine.y - renderInfo.offsetY + offset,
|
|
594
|
-
drawLine.w + hlPaddingRight + hlPaddingLeft,
|
|
595
|
-
hlHeight,
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Text shadow.
|
|
601
|
-
let prevShadowSettings: null | [string, number, number, number] = null;
|
|
602
|
-
if (this._settings.shadow) {
|
|
603
|
-
prevShadowSettings = [
|
|
604
|
-
this._context.shadowColor,
|
|
605
|
-
this._context.shadowOffsetX,
|
|
606
|
-
this._context.shadowOffsetY,
|
|
607
|
-
this._context.shadowBlur,
|
|
608
|
-
];
|
|
609
|
-
|
|
610
|
-
this._context.shadowColor = getRgbaString(this._settings.shadowColor);
|
|
611
|
-
this._context.shadowOffsetX = this._settings.shadowOffsetX * precision;
|
|
612
|
-
this._context.shadowOffsetY = this._settings.shadowOffsetY * precision;
|
|
613
|
-
this._context.shadowBlur = this._settings.shadowBlur * precision;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
this._context.fillStyle = getRgbaString(this._settings.textColor);
|
|
617
|
-
for (let i = 0, n = drawLines.length; i < n; i++) {
|
|
618
|
-
const drawLine = drawLines[i]!;
|
|
619
|
-
|
|
620
|
-
if (renderInfo.letterSpacing === 0) {
|
|
621
|
-
this._context.fillText(drawLine.text, drawLine.x, drawLine.y);
|
|
622
|
-
} else {
|
|
623
|
-
const textSplit = drawLine.text.split('');
|
|
624
|
-
let x = drawLine.x;
|
|
625
|
-
for (let i = 0, j = textSplit.length; i < j; i++) {
|
|
626
|
-
this._context.fillText(textSplit[i]!, x, drawLine.y);
|
|
627
|
-
x += this.measureText(textSplit[i]!, renderInfo.letterSpacing);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (prevShadowSettings) {
|
|
633
|
-
this._context.shadowColor = prevShadowSettings[0];
|
|
634
|
-
this._context.shadowOffsetX = prevShadowSettings[1];
|
|
635
|
-
this._context.shadowOffsetY = prevShadowSettings[2];
|
|
636
|
-
this._context.shadowBlur = prevShadowSettings[3];
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (renderInfo.cutSx || renderInfo.cutSy) {
|
|
640
|
-
this._context.translate(renderInfo.cutSx, renderInfo.cutSy);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
wrapWord(word: string, wordWrapWidth: number, suffix: string) {
|
|
645
|
-
const suffixWidth = this._context.measureText(suffix).width;
|
|
646
|
-
const wordLen = word.length;
|
|
647
|
-
const wordWidth = this._context.measureText(word).width;
|
|
648
|
-
|
|
649
|
-
/* If word fits wrapWidth, do nothing */
|
|
650
|
-
if (wordWidth <= wordWrapWidth) {
|
|
651
|
-
return word;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/* Make initial guess for text cuttoff */
|
|
655
|
-
let cutoffIndex = Math.floor((wordWrapWidth * wordLen) / wordWidth);
|
|
656
|
-
let truncWordWidth =
|
|
657
|
-
this._context.measureText(word.substring(0, cutoffIndex)).width +
|
|
658
|
-
suffixWidth;
|
|
659
|
-
|
|
660
|
-
/* In case guess was overestimated, shrink it letter by letter. */
|
|
661
|
-
if (truncWordWidth > wordWrapWidth) {
|
|
662
|
-
while (cutoffIndex > 0) {
|
|
663
|
-
truncWordWidth =
|
|
664
|
-
this._context.measureText(word.substring(0, cutoffIndex)).width +
|
|
665
|
-
suffixWidth;
|
|
666
|
-
if (truncWordWidth > wordWrapWidth) {
|
|
667
|
-
cutoffIndex -= 1;
|
|
668
|
-
} else {
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/* In case guess was underestimated, extend it letter by letter. */
|
|
674
|
-
} else {
|
|
675
|
-
while (cutoffIndex < wordLen) {
|
|
676
|
-
truncWordWidth =
|
|
677
|
-
this._context.measureText(word.substring(0, cutoffIndex)).width +
|
|
678
|
-
suffixWidth;
|
|
679
|
-
if (truncWordWidth < wordWrapWidth) {
|
|
680
|
-
cutoffIndex += 1;
|
|
681
|
-
} else {
|
|
682
|
-
// Finally, when bound is crossed, retract last letter.
|
|
683
|
-
cutoffIndex -= 1;
|
|
684
|
-
break;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/* If wrapWidth is too short to even contain suffix alone, return empty string */
|
|
690
|
-
return (
|
|
691
|
-
word.substring(0, cutoffIndex) +
|
|
692
|
-
(wordWrapWidth >= suffixWidth ? suffix : '')
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Applies newlines to a string to have it optimally fit into the horizontal
|
|
698
|
-
* bounds set by the Text object's wordWrapWidth property.
|
|
699
|
-
*/
|
|
700
|
-
wrapText(
|
|
701
|
-
text: string,
|
|
702
|
-
wordWrapWidth: number,
|
|
703
|
-
letterSpacing: number,
|
|
704
|
-
indent = 0,
|
|
705
|
-
) {
|
|
706
|
-
const spaceRegex = / |\u200B/g; // ZWSP and spaces
|
|
707
|
-
const lines = text.split(/\r?\n/g);
|
|
708
|
-
let allLines: string[] = [];
|
|
709
|
-
const realNewlines: number[] = [];
|
|
710
|
-
|
|
711
|
-
for (let i = 0; i < lines.length; i++) {
|
|
712
|
-
const resultLines: string[] = [];
|
|
713
|
-
let result = '';
|
|
714
|
-
let spaceLeft = wordWrapWidth - indent;
|
|
715
|
-
|
|
716
|
-
// Split the line into words, considering ZWSP
|
|
717
|
-
const words = lines[i]!.split(spaceRegex);
|
|
718
|
-
const spaces = lines[i]!.match(spaceRegex) || [];
|
|
719
|
-
|
|
720
|
-
for (let j = 0; j < words.length; j++) {
|
|
721
|
-
const space = spaces[j - 1] || '';
|
|
722
|
-
const word = words[j]!;
|
|
723
|
-
const wordWidth = this.measureText(word, letterSpacing);
|
|
724
|
-
const wordWidthWithSpace = isZeroWidthSpace(space)
|
|
725
|
-
? wordWidth
|
|
726
|
-
: wordWidth + this.measureText(space, letterSpacing);
|
|
727
|
-
|
|
728
|
-
if (
|
|
729
|
-
this._settings.wordBreak === 'break-all' &&
|
|
730
|
-
wordWidthWithSpace > spaceLeft
|
|
731
|
-
) {
|
|
732
|
-
const letters = word.split('');
|
|
733
|
-
for (let k = 0; k < letters.length; k++) {
|
|
734
|
-
const letter = letters[k]!;
|
|
735
|
-
const letterWidthWithSpace =
|
|
736
|
-
k > 0
|
|
737
|
-
? this.measureText(letter, letterSpacing)
|
|
738
|
-
: this.measureText(space + letter, letterSpacing);
|
|
739
|
-
|
|
740
|
-
if (letterWidthWithSpace > spaceLeft) {
|
|
741
|
-
resultLines.push(result);
|
|
742
|
-
result = letter;
|
|
743
|
-
spaceLeft = wordWrapWidth - letterWidthWithSpace;
|
|
744
|
-
} else {
|
|
745
|
-
spaceLeft -= letterWidthWithSpace;
|
|
746
|
-
result += k > 0 ? letter : space + letter;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
} else if (
|
|
750
|
-
this._settings.wordBreak === 'break-word' &&
|
|
751
|
-
wordWidthWithSpace > spaceLeft
|
|
752
|
-
) {
|
|
753
|
-
if (wordWidth < wordWrapWidth) {
|
|
754
|
-
resultLines.push(result);
|
|
755
|
-
result = word;
|
|
756
|
-
spaceLeft = wordWrapWidth - wordWidth - (j === 0 ? indent : 0);
|
|
757
|
-
} else {
|
|
758
|
-
if (result.length > 0) resultLines.push(result);
|
|
759
|
-
result = '';
|
|
760
|
-
spaceLeft = wordWrapWidth - indent;
|
|
761
|
-
const letters = word.split('');
|
|
762
|
-
for (let k = 0; k < letters.length; k++) {
|
|
763
|
-
const letter = letters[k]!;
|
|
764
|
-
const letterWidth = this.measureText(letter, letterSpacing);
|
|
765
|
-
if (letterWidth > spaceLeft) {
|
|
766
|
-
resultLines.push(result);
|
|
767
|
-
result = '';
|
|
768
|
-
spaceLeft = wordWrapWidth - indent;
|
|
769
|
-
} else {
|
|
770
|
-
result += letter;
|
|
771
|
-
spaceLeft -= letterWidth;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
} else if (j === 0 || wordWidthWithSpace > spaceLeft) {
|
|
776
|
-
if (j > 0) {
|
|
777
|
-
resultLines.push(result);
|
|
778
|
-
result = '';
|
|
779
|
-
}
|
|
780
|
-
result += word;
|
|
781
|
-
spaceLeft = wordWrapWidth - wordWidth - (j === 0 ? indent : 0);
|
|
782
|
-
} else {
|
|
783
|
-
spaceLeft -= wordWidthWithSpace;
|
|
784
|
-
result += space + word;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
resultLines.push(result);
|
|
789
|
-
result = '';
|
|
790
|
-
allLines = allLines.concat(resultLines);
|
|
791
|
-
|
|
792
|
-
if (i < lines.length - 1) {
|
|
793
|
-
realNewlines.push(allLines.length);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return { l: allLines, n: realNewlines };
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
measureText(word: string, space = 0) {
|
|
801
|
-
if (!space) {
|
|
802
|
-
return this._context.measureText(word).width;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// Split word into characters, but skip ZWSP in the width calculation
|
|
806
|
-
return word.split('').reduce((acc, char) => {
|
|
807
|
-
// Check if the character is a zero-width space and skip it
|
|
808
|
-
if (isZeroWidthSpace(char)) {
|
|
809
|
-
return acc;
|
|
810
|
-
}
|
|
811
|
-
return acc + this._context.measureText(char).width + space;
|
|
812
|
-
}, 0);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
mergeDefaults(settings: Partial<Settings>): Settings {
|
|
816
|
-
return {
|
|
817
|
-
text: '',
|
|
818
|
-
w: 0,
|
|
819
|
-
h: 0,
|
|
820
|
-
fontStyle: 'normal',
|
|
821
|
-
fontSize: 40,
|
|
822
|
-
fontFamily: null,
|
|
823
|
-
trFontFace: null,
|
|
824
|
-
wordWrap: true,
|
|
825
|
-
wordWrapWidth: 0,
|
|
826
|
-
wordBreak: 'normal',
|
|
827
|
-
textOverflow: '',
|
|
828
|
-
lineHeight: null,
|
|
829
|
-
textBaseline: 'alphabetic',
|
|
830
|
-
textAlign: 'left',
|
|
831
|
-
verticalAlign: 'top',
|
|
832
|
-
offsetY: null,
|
|
833
|
-
maxLines: 0,
|
|
834
|
-
maxHeight: null,
|
|
835
|
-
overflowSuffix: '...',
|
|
836
|
-
textColor: [1.0, 1.0, 1.0, 1.0],
|
|
837
|
-
paddingLeft: 0,
|
|
838
|
-
paddingRight: 0,
|
|
839
|
-
shadow: false,
|
|
840
|
-
shadowColor: [0.0, 0.0, 0.0, 1.0],
|
|
841
|
-
shadowOffsetX: 0,
|
|
842
|
-
shadowOffsetY: 0,
|
|
843
|
-
shadowBlur: 5,
|
|
844
|
-
highlight: false,
|
|
845
|
-
highlightHeight: 0,
|
|
846
|
-
highlightColor: [0.0, 0.0, 0.0, 1.0],
|
|
847
|
-
highlightOffset: 0,
|
|
848
|
-
highlightPaddingLeft: 0,
|
|
849
|
-
highlightPaddingRight: 0,
|
|
850
|
-
letterSpacing: 0,
|
|
851
|
-
textIndent: 0,
|
|
852
|
-
cutSx: 0,
|
|
853
|
-
cutEx: 0,
|
|
854
|
-
cutSy: 0,
|
|
855
|
-
cutEy: 0,
|
|
856
|
-
advancedRenderer: false,
|
|
857
|
-
fontBaselineRatio: 0,
|
|
858
|
-
precision: 1,
|
|
859
|
-
textRenderIssueMargin: 0,
|
|
860
|
-
...settings,
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
}
|
|
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
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
21
|
+
|
|
22
|
+
import { assertTruthy } from '../../../utils.js';
|
|
23
|
+
import { getRgbaString, type RGBA } from '../../lib/utils.js';
|
|
24
|
+
import { calcDefaultLineHeight } from '../TextRenderingUtils.js';
|
|
25
|
+
import {
|
|
26
|
+
getWebFontMetrics,
|
|
27
|
+
isZeroWidthSpace,
|
|
28
|
+
} from '../TextTextureRendererUtils.js';
|
|
29
|
+
import type { NormalizedFontMetrics } from '../font-face-types/TrFontFace.js';
|
|
30
|
+
import type { WebTrFontFace } from '../font-face-types/WebTrFontFace.js';
|
|
31
|
+
|
|
32
|
+
const MAX_TEXTURE_DIMENSION = 2048;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Text Overflow Values
|
|
36
|
+
*/
|
|
37
|
+
export type TextOverflow =
|
|
38
|
+
| 'ellipsis'
|
|
39
|
+
| 'clip'
|
|
40
|
+
| (string & Record<never, never>);
|
|
41
|
+
|
|
42
|
+
/***
|
|
43
|
+
* Text Horizontal Align Values
|
|
44
|
+
*/
|
|
45
|
+
export type TextAlign = 'left' | 'center' | 'right';
|
|
46
|
+
|
|
47
|
+
/***
|
|
48
|
+
* Text Baseline Values
|
|
49
|
+
*/
|
|
50
|
+
export type TextBaseline =
|
|
51
|
+
| 'alphabetic'
|
|
52
|
+
| 'top'
|
|
53
|
+
| 'hanging'
|
|
54
|
+
| 'middle'
|
|
55
|
+
| 'ideographic'
|
|
56
|
+
| 'bottom';
|
|
57
|
+
|
|
58
|
+
/***
|
|
59
|
+
* Text Vertical Align Values
|
|
60
|
+
*/
|
|
61
|
+
export type TextVerticalAlign = 'top' | 'middle' | 'bottom';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Text Texture Settings
|
|
65
|
+
*/
|
|
66
|
+
export interface Settings {
|
|
67
|
+
w: number;
|
|
68
|
+
h: number;
|
|
69
|
+
text: string;
|
|
70
|
+
fontStyle: string;
|
|
71
|
+
fontSize: number;
|
|
72
|
+
fontBaselineRatio: number;
|
|
73
|
+
fontFamily: string | null;
|
|
74
|
+
trFontFace: WebTrFontFace | null;
|
|
75
|
+
wordWrap: boolean;
|
|
76
|
+
wordWrapWidth: number;
|
|
77
|
+
wordBreak: 'normal' | 'break-all' | 'break-word';
|
|
78
|
+
textOverflow: TextOverflow | null;
|
|
79
|
+
lineHeight: number | null;
|
|
80
|
+
textBaseline: TextBaseline;
|
|
81
|
+
textAlign: TextAlign;
|
|
82
|
+
verticalAlign: TextVerticalAlign;
|
|
83
|
+
offsetY: number | null;
|
|
84
|
+
maxLines: number;
|
|
85
|
+
maxHeight: number | null;
|
|
86
|
+
overflowSuffix: string;
|
|
87
|
+
precision: number;
|
|
88
|
+
textColor: RGBA;
|
|
89
|
+
paddingLeft: number;
|
|
90
|
+
paddingRight: number;
|
|
91
|
+
shadow: boolean;
|
|
92
|
+
shadowColor: RGBA;
|
|
93
|
+
shadowOffsetX: number;
|
|
94
|
+
shadowOffsetY: number;
|
|
95
|
+
shadowBlur: number;
|
|
96
|
+
highlight: boolean;
|
|
97
|
+
highlightHeight: number;
|
|
98
|
+
highlightColor: RGBA;
|
|
99
|
+
highlightOffset: number;
|
|
100
|
+
highlightPaddingLeft: number;
|
|
101
|
+
highlightPaddingRight: number;
|
|
102
|
+
letterSpacing: number;
|
|
103
|
+
textIndent: number;
|
|
104
|
+
cutSx: number;
|
|
105
|
+
cutSy: number;
|
|
106
|
+
cutEx: number;
|
|
107
|
+
cutEy: number;
|
|
108
|
+
advancedRenderer: boolean;
|
|
109
|
+
|
|
110
|
+
// Normally stage options
|
|
111
|
+
textRenderIssueMargin: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface RenderInfo {
|
|
115
|
+
w: number;
|
|
116
|
+
h: number;
|
|
117
|
+
lines: string[];
|
|
118
|
+
precision: number;
|
|
119
|
+
remainingText: string;
|
|
120
|
+
moreTextLines: boolean;
|
|
121
|
+
width: number;
|
|
122
|
+
innerWidth: number;
|
|
123
|
+
height: number;
|
|
124
|
+
fontSize: number;
|
|
125
|
+
cutSx: number;
|
|
126
|
+
cutSy: number;
|
|
127
|
+
cutEx: number;
|
|
128
|
+
cutEy: number;
|
|
129
|
+
lineHeight: number;
|
|
130
|
+
defLineHeight: number;
|
|
131
|
+
lineWidths: number[];
|
|
132
|
+
offsetY: number;
|
|
133
|
+
paddingLeft: number;
|
|
134
|
+
paddingRight: number;
|
|
135
|
+
letterSpacing: number;
|
|
136
|
+
textIndent: number;
|
|
137
|
+
metrics: NormalizedFontMetrics;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface LineType {
|
|
141
|
+
text: string;
|
|
142
|
+
x: number;
|
|
143
|
+
y: number;
|
|
144
|
+
w: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Calculate height for the canvas
|
|
149
|
+
*
|
|
150
|
+
* @param textBaseline
|
|
151
|
+
* @param fontSize
|
|
152
|
+
* @param lineHeight
|
|
153
|
+
* @param numLines
|
|
154
|
+
* @param offsetY
|
|
155
|
+
* @returns
|
|
156
|
+
*/
|
|
157
|
+
function calcHeight(
|
|
158
|
+
textBaseline: TextBaseline,
|
|
159
|
+
fontSize: number,
|
|
160
|
+
lineHeight: number,
|
|
161
|
+
numLines: number,
|
|
162
|
+
offsetY: number | null,
|
|
163
|
+
) {
|
|
164
|
+
const baselineOffset = textBaseline !== 'bottom' ? 0.5 * fontSize : 0;
|
|
165
|
+
return (
|
|
166
|
+
lineHeight * (numLines - 1) +
|
|
167
|
+
baselineOffset +
|
|
168
|
+
Math.max(lineHeight, fontSize) +
|
|
169
|
+
(offsetY || 0)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export class LightningTextTextureRenderer {
|
|
174
|
+
private _canvas: OffscreenCanvas | HTMLCanvasElement;
|
|
175
|
+
private _context:
|
|
176
|
+
| OffscreenCanvasRenderingContext2D
|
|
177
|
+
| CanvasRenderingContext2D;
|
|
178
|
+
private _settings: Settings;
|
|
179
|
+
|
|
180
|
+
constructor(
|
|
181
|
+
canvas: OffscreenCanvas | HTMLCanvasElement,
|
|
182
|
+
context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,
|
|
183
|
+
) {
|
|
184
|
+
this._canvas = canvas;
|
|
185
|
+
this._context = context;
|
|
186
|
+
this._settings = this.mergeDefaults({});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
set settings(v: Partial<Settings>) {
|
|
190
|
+
this._settings = this.mergeDefaults(v);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get settings(): Settings {
|
|
194
|
+
return this._settings;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getPrecision() {
|
|
198
|
+
return this._settings.precision;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
setFontProperties() {
|
|
202
|
+
this._context.font = this._getFontSetting();
|
|
203
|
+
this._context.textBaseline = this._settings.textBaseline;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_getFontSetting() {
|
|
207
|
+
const ff = [this._settings.fontFamily];
|
|
208
|
+
|
|
209
|
+
const ffs: string[] = [];
|
|
210
|
+
for (let i = 0, n = ff.length; i < n; i++) {
|
|
211
|
+
if (ff[i] === 'serif' || ff[i] === 'sans-serif') {
|
|
212
|
+
ffs.push(ff[i]!);
|
|
213
|
+
} else {
|
|
214
|
+
ffs.push(`"${ff[i]!}"`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return `${this._settings.fontStyle} ${
|
|
219
|
+
this._settings.fontSize * this.getPrecision()
|
|
220
|
+
}px ${ffs.join(',')}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_load() {
|
|
224
|
+
if (true && document.fonts) {
|
|
225
|
+
const fontSetting = this._getFontSetting();
|
|
226
|
+
try {
|
|
227
|
+
if (!document.fonts.check(fontSetting, this._settings.text)) {
|
|
228
|
+
// Use a promise that waits for loading.
|
|
229
|
+
return document.fonts
|
|
230
|
+
.load(fontSetting, this._settings.text)
|
|
231
|
+
.catch((err) => {
|
|
232
|
+
// Just load the fallback font.
|
|
233
|
+
console.warn('[Lightning] Font load error', err, fontSetting);
|
|
234
|
+
})
|
|
235
|
+
.then(() => {
|
|
236
|
+
if (!document.fonts.check(fontSetting, this._settings.text)) {
|
|
237
|
+
console.warn('[Lightning] Font not found', fontSetting);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
} catch (e) {
|
|
242
|
+
console.warn("[Lightning] Can't check font loading for " + fontSetting);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
calculateRenderInfo(): RenderInfo {
|
|
248
|
+
const renderInfo: Partial<RenderInfo> = {};
|
|
249
|
+
|
|
250
|
+
const precision = this.getPrecision();
|
|
251
|
+
|
|
252
|
+
const paddingLeft = this._settings.paddingLeft * precision;
|
|
253
|
+
const paddingRight = this._settings.paddingRight * precision;
|
|
254
|
+
const fontSize = this._settings.fontSize * precision;
|
|
255
|
+
let offsetY =
|
|
256
|
+
this._settings.offsetY === null
|
|
257
|
+
? null
|
|
258
|
+
: this._settings.offsetY * precision;
|
|
259
|
+
const w = this._settings.w * precision;
|
|
260
|
+
const h = this._settings.h * precision;
|
|
261
|
+
let wordWrapWidth = this._settings.wordWrapWidth * precision;
|
|
262
|
+
const cutSx = this._settings.cutSx * precision;
|
|
263
|
+
const cutEx = this._settings.cutEx * precision;
|
|
264
|
+
const cutSy = this._settings.cutSy * precision;
|
|
265
|
+
const cutEy = this._settings.cutEy * precision;
|
|
266
|
+
const letterSpacing = (this._settings.letterSpacing || 0) * precision;
|
|
267
|
+
const textIndent = this._settings.textIndent * precision;
|
|
268
|
+
const trFontFace = this._settings.trFontFace;
|
|
269
|
+
|
|
270
|
+
// Set font properties.
|
|
271
|
+
this.setFontProperties();
|
|
272
|
+
|
|
273
|
+
assertTruthy(trFontFace);
|
|
274
|
+
const metrics = getWebFontMetrics(this._context, trFontFace, fontSize);
|
|
275
|
+
const defLineHeight = calcDefaultLineHeight(metrics, fontSize) * precision;
|
|
276
|
+
const lineHeight =
|
|
277
|
+
this._settings.lineHeight !== null
|
|
278
|
+
? this._settings.lineHeight * precision
|
|
279
|
+
: defLineHeight;
|
|
280
|
+
|
|
281
|
+
const maxHeight = this._settings.maxHeight;
|
|
282
|
+
const containedMaxLines =
|
|
283
|
+
maxHeight !== null && lineHeight > 0
|
|
284
|
+
? Math.floor(maxHeight / lineHeight)
|
|
285
|
+
: 0;
|
|
286
|
+
|
|
287
|
+
const setMaxLines = this._settings.maxLines;
|
|
288
|
+
const calcMaxLines =
|
|
289
|
+
containedMaxLines > 0 && setMaxLines > 0
|
|
290
|
+
? Math.min(containedMaxLines, setMaxLines)
|
|
291
|
+
: Math.max(containedMaxLines, setMaxLines);
|
|
292
|
+
|
|
293
|
+
// Total width.
|
|
294
|
+
let width = w || 2048 / this.getPrecision();
|
|
295
|
+
|
|
296
|
+
// Inner width.
|
|
297
|
+
let innerWidth = width - paddingLeft;
|
|
298
|
+
if (innerWidth < 10) {
|
|
299
|
+
width += 10 - innerWidth;
|
|
300
|
+
innerWidth = 10;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!wordWrapWidth) {
|
|
304
|
+
wordWrapWidth = innerWidth;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Text overflow
|
|
308
|
+
// TODO Probably never used
|
|
309
|
+
if (this._settings.textOverflow && !this._settings.wordWrap) {
|
|
310
|
+
let suffix;
|
|
311
|
+
switch (this._settings.textOverflow) {
|
|
312
|
+
case 'clip':
|
|
313
|
+
suffix = '';
|
|
314
|
+
break;
|
|
315
|
+
case 'ellipsis':
|
|
316
|
+
suffix = this._settings.overflowSuffix;
|
|
317
|
+
break;
|
|
318
|
+
default:
|
|
319
|
+
suffix = this._settings.textOverflow;
|
|
320
|
+
}
|
|
321
|
+
this._settings.text = this.wrapWord(
|
|
322
|
+
this._settings.text,
|
|
323
|
+
wordWrapWidth - textIndent,
|
|
324
|
+
suffix,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// word wrap
|
|
329
|
+
// preserve original text
|
|
330
|
+
let linesInfo: { n: number[]; l: string[] };
|
|
331
|
+
if (this._settings.wordWrap) {
|
|
332
|
+
linesInfo = this.wrapText(
|
|
333
|
+
this._settings.text,
|
|
334
|
+
wordWrapWidth,
|
|
335
|
+
letterSpacing,
|
|
336
|
+
textIndent,
|
|
337
|
+
);
|
|
338
|
+
} else {
|
|
339
|
+
linesInfo = { l: this._settings.text.split(/(?:\r\n|\r|\n)/), n: [] };
|
|
340
|
+
const n = linesInfo.l.length;
|
|
341
|
+
for (let i = 0; i < n - 1; i++) {
|
|
342
|
+
linesInfo.n.push(i);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
let lines = linesInfo.l;
|
|
346
|
+
|
|
347
|
+
if (calcMaxLines && lines.length > calcMaxLines) {
|
|
348
|
+
const usedLines = lines.slice(0, calcMaxLines);
|
|
349
|
+
|
|
350
|
+
let otherLines: string[] | null = null;
|
|
351
|
+
if (this._settings.overflowSuffix) {
|
|
352
|
+
// Wrap again with max lines suffix enabled.
|
|
353
|
+
const w = this._settings.overflowSuffix
|
|
354
|
+
? this.measureText(this._settings.overflowSuffix)
|
|
355
|
+
: 0;
|
|
356
|
+
const al = this.wrapText(
|
|
357
|
+
usedLines[usedLines.length - 1]!,
|
|
358
|
+
wordWrapWidth - w,
|
|
359
|
+
letterSpacing,
|
|
360
|
+
textIndent,
|
|
361
|
+
);
|
|
362
|
+
usedLines[usedLines.length - 1] = `${al.l[0]!}${
|
|
363
|
+
this._settings.overflowSuffix
|
|
364
|
+
}`;
|
|
365
|
+
otherLines = [al.l.length > 1 ? al.l[1]! : ''];
|
|
366
|
+
} else {
|
|
367
|
+
otherLines = [''];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Re-assemble the remaining text.
|
|
371
|
+
let i;
|
|
372
|
+
const n = lines.length;
|
|
373
|
+
let j = 0;
|
|
374
|
+
const m = linesInfo.n.length;
|
|
375
|
+
for (i = calcMaxLines; i < n; i++) {
|
|
376
|
+
otherLines[j] += `${otherLines[j] ? ' ' : ''}${lines[i]!}`;
|
|
377
|
+
if (i + 1 < m && linesInfo.n[i + 1]) {
|
|
378
|
+
j++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
renderInfo.remainingText = otherLines.join('\n');
|
|
383
|
+
|
|
384
|
+
renderInfo.moreTextLines = true;
|
|
385
|
+
|
|
386
|
+
lines = usedLines;
|
|
387
|
+
} else {
|
|
388
|
+
renderInfo.moreTextLines = false;
|
|
389
|
+
renderInfo.remainingText = '';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// calculate text width
|
|
393
|
+
let maxLineWidth = 0;
|
|
394
|
+
const lineWidths: number[] = [];
|
|
395
|
+
for (let i = 0; i < lines.length; i++) {
|
|
396
|
+
const lineWidth =
|
|
397
|
+
this.measureText(lines[i]!, letterSpacing) + (i === 0 ? textIndent : 0);
|
|
398
|
+
lineWidths.push(lineWidth);
|
|
399
|
+
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
renderInfo.lineWidths = lineWidths;
|
|
403
|
+
|
|
404
|
+
if (!w) {
|
|
405
|
+
// Auto-set width to max text length.
|
|
406
|
+
width = maxLineWidth + paddingLeft + paddingRight;
|
|
407
|
+
innerWidth = maxLineWidth;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// If word wrap is enabled the width needs to be the width of the text.
|
|
411
|
+
if (
|
|
412
|
+
this._settings.wordWrap &&
|
|
413
|
+
w > maxLineWidth &&
|
|
414
|
+
this._settings.textAlign === 'left' &&
|
|
415
|
+
lines.length === 1
|
|
416
|
+
) {
|
|
417
|
+
width = maxLineWidth + paddingLeft + paddingRight;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let height;
|
|
421
|
+
if (h) {
|
|
422
|
+
height = h;
|
|
423
|
+
} else {
|
|
424
|
+
height = calcHeight(
|
|
425
|
+
this._settings.textBaseline,
|
|
426
|
+
fontSize,
|
|
427
|
+
lineHeight,
|
|
428
|
+
lines.length,
|
|
429
|
+
offsetY,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (offsetY === null) {
|
|
434
|
+
offsetY = fontSize;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
renderInfo.w = width;
|
|
438
|
+
renderInfo.h = height;
|
|
439
|
+
renderInfo.lines = lines;
|
|
440
|
+
renderInfo.precision = precision;
|
|
441
|
+
|
|
442
|
+
if (!width) {
|
|
443
|
+
// To prevent canvas errors.
|
|
444
|
+
width = 1;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (!height) {
|
|
448
|
+
// To prevent canvas errors.
|
|
449
|
+
height = 1;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (cutSx || cutEx) {
|
|
453
|
+
width = Math.min(width, cutEx - cutSx);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (cutSy || cutEy) {
|
|
457
|
+
height = Math.min(height, cutEy - cutSy);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
renderInfo.width = width;
|
|
461
|
+
renderInfo.innerWidth = innerWidth;
|
|
462
|
+
renderInfo.height = height;
|
|
463
|
+
renderInfo.fontSize = fontSize;
|
|
464
|
+
renderInfo.cutSx = cutSx;
|
|
465
|
+
renderInfo.cutSy = cutSy;
|
|
466
|
+
renderInfo.cutEx = cutEx;
|
|
467
|
+
renderInfo.cutEy = cutEy;
|
|
468
|
+
renderInfo.lineHeight = lineHeight;
|
|
469
|
+
renderInfo.defLineHeight = defLineHeight;
|
|
470
|
+
renderInfo.lineWidths = lineWidths;
|
|
471
|
+
renderInfo.offsetY = offsetY;
|
|
472
|
+
renderInfo.paddingLeft = paddingLeft;
|
|
473
|
+
renderInfo.paddingRight = paddingRight;
|
|
474
|
+
renderInfo.letterSpacing = letterSpacing;
|
|
475
|
+
renderInfo.textIndent = textIndent;
|
|
476
|
+
renderInfo.metrics = metrics;
|
|
477
|
+
|
|
478
|
+
return renderInfo as RenderInfo;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
draw(
|
|
482
|
+
renderInfo: RenderInfo,
|
|
483
|
+
linesOverride?: { lines: string[]; lineWidths: number[] },
|
|
484
|
+
) {
|
|
485
|
+
const precision = this.getPrecision();
|
|
486
|
+
|
|
487
|
+
// Allow lines to be overriden for partial rendering.
|
|
488
|
+
const lines = linesOverride?.lines || renderInfo.lines;
|
|
489
|
+
const lineWidths = linesOverride?.lineWidths || renderInfo.lineWidths;
|
|
490
|
+
const height = linesOverride
|
|
491
|
+
? calcHeight(
|
|
492
|
+
this._settings.textBaseline,
|
|
493
|
+
renderInfo.fontSize,
|
|
494
|
+
renderInfo.lineHeight,
|
|
495
|
+
linesOverride.lines.length,
|
|
496
|
+
this._settings.offsetY === null
|
|
497
|
+
? null
|
|
498
|
+
: this._settings.offsetY * precision,
|
|
499
|
+
)
|
|
500
|
+
: renderInfo.height;
|
|
501
|
+
|
|
502
|
+
// Add extra margin to prevent issue with clipped text when scaling.
|
|
503
|
+
this._canvas.width = Math.min(
|
|
504
|
+
Math.ceil(renderInfo.width + this._settings.textRenderIssueMargin),
|
|
505
|
+
MAX_TEXTURE_DIMENSION,
|
|
506
|
+
);
|
|
507
|
+
this._canvas.height = Math.min(Math.ceil(height), MAX_TEXTURE_DIMENSION);
|
|
508
|
+
|
|
509
|
+
// Canvas context has been reset.
|
|
510
|
+
this.setFontProperties();
|
|
511
|
+
|
|
512
|
+
if (renderInfo.fontSize >= 128) {
|
|
513
|
+
// WpeWebKit bug: must force compositing because cairo-traps-compositor will not work with text first.
|
|
514
|
+
this._context.globalAlpha = 0.01;
|
|
515
|
+
this._context.fillRect(0, 0, 0.01, 0.01);
|
|
516
|
+
this._context.globalAlpha = 1.0;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (renderInfo.cutSx || renderInfo.cutSy) {
|
|
520
|
+
this._context.translate(-renderInfo.cutSx, -renderInfo.cutSy);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
let linePositionX;
|
|
524
|
+
let linePositionY;
|
|
525
|
+
|
|
526
|
+
const drawLines: LineType[] = [];
|
|
527
|
+
|
|
528
|
+
const { metrics } = renderInfo;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Ascender (in pixels)
|
|
532
|
+
*/
|
|
533
|
+
const ascenderPx = metrics
|
|
534
|
+
? metrics.ascender * renderInfo.fontSize
|
|
535
|
+
: renderInfo.fontSize;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Bare line height is the distance between the ascender and descender of the font.
|
|
539
|
+
* without the line gap metric.
|
|
540
|
+
*/
|
|
541
|
+
const bareLineHeightPx =
|
|
542
|
+
(metrics.ascender - metrics.descender) * renderInfo.fontSize;
|
|
543
|
+
|
|
544
|
+
// Draw lines line by line.
|
|
545
|
+
for (let i = 0, n = lines.length; i < n; i++) {
|
|
546
|
+
linePositionX = i === 0 ? renderInfo.textIndent : 0;
|
|
547
|
+
|
|
548
|
+
// By default, text is aligned to top
|
|
549
|
+
linePositionY = i * renderInfo.lineHeight + ascenderPx;
|
|
550
|
+
|
|
551
|
+
if (this._settings.verticalAlign == 'middle') {
|
|
552
|
+
linePositionY += (renderInfo.lineHeight - bareLineHeightPx) / 2;
|
|
553
|
+
} else if (this._settings.verticalAlign == 'bottom') {
|
|
554
|
+
linePositionY += renderInfo.lineHeight - bareLineHeightPx;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (this._settings.textAlign === 'right') {
|
|
558
|
+
linePositionX += renderInfo.innerWidth - lineWidths[i]!;
|
|
559
|
+
} else if (this._settings.textAlign === 'center') {
|
|
560
|
+
linePositionX += (renderInfo.innerWidth - lineWidths[i]!) / 2;
|
|
561
|
+
}
|
|
562
|
+
linePositionX += renderInfo.paddingLeft;
|
|
563
|
+
|
|
564
|
+
drawLines.push({
|
|
565
|
+
text: lines[i]!,
|
|
566
|
+
x: linePositionX,
|
|
567
|
+
y: linePositionY,
|
|
568
|
+
w: lineWidths[i]!,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Highlight.
|
|
573
|
+
if (this._settings.highlight) {
|
|
574
|
+
const color = this._settings.highlightColor;
|
|
575
|
+
|
|
576
|
+
const hlHeight =
|
|
577
|
+
this._settings.highlightHeight * precision || renderInfo.fontSize * 1.5;
|
|
578
|
+
const offset = this._settings.highlightOffset * precision;
|
|
579
|
+
const hlPaddingLeft =
|
|
580
|
+
this._settings.highlightPaddingLeft !== null
|
|
581
|
+
? this._settings.highlightPaddingLeft * precision
|
|
582
|
+
: renderInfo.paddingLeft;
|
|
583
|
+
const hlPaddingRight =
|
|
584
|
+
this._settings.highlightPaddingRight !== null
|
|
585
|
+
? this._settings.highlightPaddingRight * precision
|
|
586
|
+
: renderInfo.paddingRight;
|
|
587
|
+
|
|
588
|
+
this._context.fillStyle = getRgbaString(color);
|
|
589
|
+
for (let i = 0; i < drawLines.length; i++) {
|
|
590
|
+
const drawLine = drawLines[i]!;
|
|
591
|
+
this._context.fillRect(
|
|
592
|
+
drawLine.x - hlPaddingLeft,
|
|
593
|
+
drawLine.y - renderInfo.offsetY + offset,
|
|
594
|
+
drawLine.w + hlPaddingRight + hlPaddingLeft,
|
|
595
|
+
hlHeight,
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Text shadow.
|
|
601
|
+
let prevShadowSettings: null | [string, number, number, number] = null;
|
|
602
|
+
if (this._settings.shadow) {
|
|
603
|
+
prevShadowSettings = [
|
|
604
|
+
this._context.shadowColor,
|
|
605
|
+
this._context.shadowOffsetX,
|
|
606
|
+
this._context.shadowOffsetY,
|
|
607
|
+
this._context.shadowBlur,
|
|
608
|
+
];
|
|
609
|
+
|
|
610
|
+
this._context.shadowColor = getRgbaString(this._settings.shadowColor);
|
|
611
|
+
this._context.shadowOffsetX = this._settings.shadowOffsetX * precision;
|
|
612
|
+
this._context.shadowOffsetY = this._settings.shadowOffsetY * precision;
|
|
613
|
+
this._context.shadowBlur = this._settings.shadowBlur * precision;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
this._context.fillStyle = getRgbaString(this._settings.textColor);
|
|
617
|
+
for (let i = 0, n = drawLines.length; i < n; i++) {
|
|
618
|
+
const drawLine = drawLines[i]!;
|
|
619
|
+
|
|
620
|
+
if (renderInfo.letterSpacing === 0) {
|
|
621
|
+
this._context.fillText(drawLine.text, drawLine.x, drawLine.y);
|
|
622
|
+
} else {
|
|
623
|
+
const textSplit = drawLine.text.split('');
|
|
624
|
+
let x = drawLine.x;
|
|
625
|
+
for (let i = 0, j = textSplit.length; i < j; i++) {
|
|
626
|
+
this._context.fillText(textSplit[i]!, x, drawLine.y);
|
|
627
|
+
x += this.measureText(textSplit[i]!, renderInfo.letterSpacing);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (prevShadowSettings) {
|
|
633
|
+
this._context.shadowColor = prevShadowSettings[0];
|
|
634
|
+
this._context.shadowOffsetX = prevShadowSettings[1];
|
|
635
|
+
this._context.shadowOffsetY = prevShadowSettings[2];
|
|
636
|
+
this._context.shadowBlur = prevShadowSettings[3];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (renderInfo.cutSx || renderInfo.cutSy) {
|
|
640
|
+
this._context.translate(renderInfo.cutSx, renderInfo.cutSy);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
wrapWord(word: string, wordWrapWidth: number, suffix: string) {
|
|
645
|
+
const suffixWidth = this._context.measureText(suffix).width;
|
|
646
|
+
const wordLen = word.length;
|
|
647
|
+
const wordWidth = this._context.measureText(word).width;
|
|
648
|
+
|
|
649
|
+
/* If word fits wrapWidth, do nothing */
|
|
650
|
+
if (wordWidth <= wordWrapWidth) {
|
|
651
|
+
return word;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/* Make initial guess for text cuttoff */
|
|
655
|
+
let cutoffIndex = Math.floor((wordWrapWidth * wordLen) / wordWidth);
|
|
656
|
+
let truncWordWidth =
|
|
657
|
+
this._context.measureText(word.substring(0, cutoffIndex)).width +
|
|
658
|
+
suffixWidth;
|
|
659
|
+
|
|
660
|
+
/* In case guess was overestimated, shrink it letter by letter. */
|
|
661
|
+
if (truncWordWidth > wordWrapWidth) {
|
|
662
|
+
while (cutoffIndex > 0) {
|
|
663
|
+
truncWordWidth =
|
|
664
|
+
this._context.measureText(word.substring(0, cutoffIndex)).width +
|
|
665
|
+
suffixWidth;
|
|
666
|
+
if (truncWordWidth > wordWrapWidth) {
|
|
667
|
+
cutoffIndex -= 1;
|
|
668
|
+
} else {
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/* In case guess was underestimated, extend it letter by letter. */
|
|
674
|
+
} else {
|
|
675
|
+
while (cutoffIndex < wordLen) {
|
|
676
|
+
truncWordWidth =
|
|
677
|
+
this._context.measureText(word.substring(0, cutoffIndex)).width +
|
|
678
|
+
suffixWidth;
|
|
679
|
+
if (truncWordWidth < wordWrapWidth) {
|
|
680
|
+
cutoffIndex += 1;
|
|
681
|
+
} else {
|
|
682
|
+
// Finally, when bound is crossed, retract last letter.
|
|
683
|
+
cutoffIndex -= 1;
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* If wrapWidth is too short to even contain suffix alone, return empty string */
|
|
690
|
+
return (
|
|
691
|
+
word.substring(0, cutoffIndex) +
|
|
692
|
+
(wordWrapWidth >= suffixWidth ? suffix : '')
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Applies newlines to a string to have it optimally fit into the horizontal
|
|
698
|
+
* bounds set by the Text object's wordWrapWidth property.
|
|
699
|
+
*/
|
|
700
|
+
wrapText(
|
|
701
|
+
text: string,
|
|
702
|
+
wordWrapWidth: number,
|
|
703
|
+
letterSpacing: number,
|
|
704
|
+
indent = 0,
|
|
705
|
+
) {
|
|
706
|
+
const spaceRegex = / |\u200B/g; // ZWSP and spaces
|
|
707
|
+
const lines = text.split(/\r?\n/g);
|
|
708
|
+
let allLines: string[] = [];
|
|
709
|
+
const realNewlines: number[] = [];
|
|
710
|
+
|
|
711
|
+
for (let i = 0; i < lines.length; i++) {
|
|
712
|
+
const resultLines: string[] = [];
|
|
713
|
+
let result = '';
|
|
714
|
+
let spaceLeft = wordWrapWidth - indent;
|
|
715
|
+
|
|
716
|
+
// Split the line into words, considering ZWSP
|
|
717
|
+
const words = lines[i]!.split(spaceRegex);
|
|
718
|
+
const spaces = lines[i]!.match(spaceRegex) || [];
|
|
719
|
+
|
|
720
|
+
for (let j = 0; j < words.length; j++) {
|
|
721
|
+
const space = spaces[j - 1] || '';
|
|
722
|
+
const word = words[j]!;
|
|
723
|
+
const wordWidth = this.measureText(word, letterSpacing);
|
|
724
|
+
const wordWidthWithSpace = isZeroWidthSpace(space)
|
|
725
|
+
? wordWidth
|
|
726
|
+
: wordWidth + this.measureText(space, letterSpacing);
|
|
727
|
+
|
|
728
|
+
if (
|
|
729
|
+
this._settings.wordBreak === 'break-all' &&
|
|
730
|
+
wordWidthWithSpace > spaceLeft
|
|
731
|
+
) {
|
|
732
|
+
const letters = word.split('');
|
|
733
|
+
for (let k = 0; k < letters.length; k++) {
|
|
734
|
+
const letter = letters[k]!;
|
|
735
|
+
const letterWidthWithSpace =
|
|
736
|
+
k > 0
|
|
737
|
+
? this.measureText(letter, letterSpacing)
|
|
738
|
+
: this.measureText(space + letter, letterSpacing);
|
|
739
|
+
|
|
740
|
+
if (letterWidthWithSpace > spaceLeft) {
|
|
741
|
+
resultLines.push(result);
|
|
742
|
+
result = letter;
|
|
743
|
+
spaceLeft = wordWrapWidth - letterWidthWithSpace;
|
|
744
|
+
} else {
|
|
745
|
+
spaceLeft -= letterWidthWithSpace;
|
|
746
|
+
result += k > 0 ? letter : space + letter;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
} else if (
|
|
750
|
+
this._settings.wordBreak === 'break-word' &&
|
|
751
|
+
wordWidthWithSpace > spaceLeft
|
|
752
|
+
) {
|
|
753
|
+
if (wordWidth < wordWrapWidth) {
|
|
754
|
+
resultLines.push(result);
|
|
755
|
+
result = word;
|
|
756
|
+
spaceLeft = wordWrapWidth - wordWidth - (j === 0 ? indent : 0);
|
|
757
|
+
} else {
|
|
758
|
+
if (result.length > 0) resultLines.push(result);
|
|
759
|
+
result = '';
|
|
760
|
+
spaceLeft = wordWrapWidth - indent;
|
|
761
|
+
const letters = word.split('');
|
|
762
|
+
for (let k = 0; k < letters.length; k++) {
|
|
763
|
+
const letter = letters[k]!;
|
|
764
|
+
const letterWidth = this.measureText(letter, letterSpacing);
|
|
765
|
+
if (letterWidth > spaceLeft) {
|
|
766
|
+
resultLines.push(result);
|
|
767
|
+
result = '';
|
|
768
|
+
spaceLeft = wordWrapWidth - indent;
|
|
769
|
+
} else {
|
|
770
|
+
result += letter;
|
|
771
|
+
spaceLeft -= letterWidth;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
} else if (j === 0 || wordWidthWithSpace > spaceLeft) {
|
|
776
|
+
if (j > 0) {
|
|
777
|
+
resultLines.push(result);
|
|
778
|
+
result = '';
|
|
779
|
+
}
|
|
780
|
+
result += word;
|
|
781
|
+
spaceLeft = wordWrapWidth - wordWidth - (j === 0 ? indent : 0);
|
|
782
|
+
} else {
|
|
783
|
+
spaceLeft -= wordWidthWithSpace;
|
|
784
|
+
result += space + word;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
resultLines.push(result);
|
|
789
|
+
result = '';
|
|
790
|
+
allLines = allLines.concat(resultLines);
|
|
791
|
+
|
|
792
|
+
if (i < lines.length - 1) {
|
|
793
|
+
realNewlines.push(allLines.length);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return { l: allLines, n: realNewlines };
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
measureText(word: string, space = 0) {
|
|
801
|
+
if (!space) {
|
|
802
|
+
return this._context.measureText(word).width;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Split word into characters, but skip ZWSP in the width calculation
|
|
806
|
+
return word.split('').reduce((acc, char) => {
|
|
807
|
+
// Check if the character is a zero-width space and skip it
|
|
808
|
+
if (isZeroWidthSpace(char)) {
|
|
809
|
+
return acc;
|
|
810
|
+
}
|
|
811
|
+
return acc + this._context.measureText(char).width + space;
|
|
812
|
+
}, 0);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
mergeDefaults(settings: Partial<Settings>): Settings {
|
|
816
|
+
return {
|
|
817
|
+
text: '',
|
|
818
|
+
w: 0,
|
|
819
|
+
h: 0,
|
|
820
|
+
fontStyle: 'normal',
|
|
821
|
+
fontSize: 40,
|
|
822
|
+
fontFamily: null,
|
|
823
|
+
trFontFace: null,
|
|
824
|
+
wordWrap: true,
|
|
825
|
+
wordWrapWidth: 0,
|
|
826
|
+
wordBreak: 'normal',
|
|
827
|
+
textOverflow: '',
|
|
828
|
+
lineHeight: null,
|
|
829
|
+
textBaseline: 'alphabetic',
|
|
830
|
+
textAlign: 'left',
|
|
831
|
+
verticalAlign: 'top',
|
|
832
|
+
offsetY: null,
|
|
833
|
+
maxLines: 0,
|
|
834
|
+
maxHeight: null,
|
|
835
|
+
overflowSuffix: '...',
|
|
836
|
+
textColor: [1.0, 1.0, 1.0, 1.0],
|
|
837
|
+
paddingLeft: 0,
|
|
838
|
+
paddingRight: 0,
|
|
839
|
+
shadow: false,
|
|
840
|
+
shadowColor: [0.0, 0.0, 0.0, 1.0],
|
|
841
|
+
shadowOffsetX: 0,
|
|
842
|
+
shadowOffsetY: 0,
|
|
843
|
+
shadowBlur: 5,
|
|
844
|
+
highlight: false,
|
|
845
|
+
highlightHeight: 0,
|
|
846
|
+
highlightColor: [0.0, 0.0, 0.0, 1.0],
|
|
847
|
+
highlightOffset: 0,
|
|
848
|
+
highlightPaddingLeft: 0,
|
|
849
|
+
highlightPaddingRight: 0,
|
|
850
|
+
letterSpacing: 0,
|
|
851
|
+
textIndent: 0,
|
|
852
|
+
cutSx: 0,
|
|
853
|
+
cutEx: 0,
|
|
854
|
+
cutSy: 0,
|
|
855
|
+
cutEy: 0,
|
|
856
|
+
advancedRenderer: false,
|
|
857
|
+
fontBaselineRatio: 0,
|
|
858
|
+
precision: 1,
|
|
859
|
+
textRenderIssueMargin: 0,
|
|
860
|
+
...settings,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}
|