@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.
- package/LICENSE +202 -202
- package/NOTICE +3 -3
- package/README.md +133 -133
- package/dist/src/core/CoreTextNode.d.ts +3 -0
- package/dist/src/core/CoreTextNode.js +45 -10
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/platform.d.ts +10 -0
- package/dist/src/core/platform.js +81 -0
- package/dist/src/core/platform.js.map +1 -0
- package/dist/src/core/renderers/CoreShader.d.ts +9 -0
- package/dist/src/core/renderers/CoreShader.js +28 -0
- package/dist/src/core/renderers/CoreShader.js.map +1 -0
- package/dist/src/core/renderers/canvas/CanvasCoreRenderer.d.ts +33 -0
- package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js +250 -0
- package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js.map +1 -0
- package/dist/src/core/renderers/canvas/CanvasCoreTexture.d.ts +16 -0
- package/dist/src/core/renderers/canvas/CanvasCoreTexture.js +124 -0
- package/dist/src/core/renderers/canvas/CanvasCoreTexture.js.map +1 -0
- package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.d.ts +13 -0
- package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.js +113 -192
- package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.js.map +1 -1
- package/dist/src/core/renderers/canvas/internal/ColorUtils.d.ts +0 -2
- package/dist/src/core/renderers/canvas/internal/ColorUtils.js +0 -14
- package/dist/src/core/renderers/canvas/internal/ColorUtils.js.map +1 -1
- package/dist/src/core/renderers/canvas/shaders/UnsupportedShader.d.ts +10 -0
- package/dist/src/core/renderers/canvas/shaders/UnsupportedShader.js +43 -0
- package/dist/src/core/renderers/canvas/shaders/UnsupportedShader.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.d.ts +12 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.js +58 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.d.ts +9 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js +38 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +56 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +239 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.d.ts +34 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js +114 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +133 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +616 -0
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShader.d.ts +83 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShader.js +233 -0
- package/dist/src/core/renderers/webgl/WebGlCoreShader.js.map +1 -0
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.js +35 -35
- package/dist/src/core/renderers/webgl/shaders/DefaultShader.d.ts +9 -0
- package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +87 -0
- package/dist/src/core/renderers/webgl/shaders/DefaultShader.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.d.ts +10 -0
- package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js +119 -0
- package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/DynamicShader.d.ts +29 -0
- package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +413 -0
- package/dist/src/core/renderers/webgl/shaders/DynamicShader.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.d.ts +28 -0
- package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js +131 -0
- package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/SdfShader.d.ts +47 -0
- package/dist/src/core/renderers/webgl/shaders/SdfShader.js +160 -0
- package/dist/src/core/renderers/webgl/shaders/SdfShader.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.d.ts +31 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js +71 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.d.ts +30 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js +58 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.d.ts +31 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js +71 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.d.ts +31 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js +71 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.d.ts +31 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js +71 -0
- package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.d.ts +9 -0
- package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.js +136 -0
- package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.d.ts +36 -0
- package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js +85 -0
- package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.d.ts +45 -0
- package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.js +104 -0
- package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.d.ts +22 -0
- package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.js +45 -0
- package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.d.ts +58 -0
- package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js +80 -0
- package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.d.ts +35 -0
- package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +129 -0
- package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.d.ts +39 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +116 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.d.ts +61 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +127 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.d.ts +40 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js +71 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js.map +1 -0
- package/dist/src/core/renderers/webgl/shaders/effects/ShaderEffect.d.ts +115 -0
- package/dist/src/core/renderers/webgl/shaders/effects/ShaderEffect.js +61 -0
- package/dist/src/core/renderers/webgl/shaders/effects/ShaderEffect.js.map +1 -0
- package/dist/src/core/shaders/templates/shaderUtils.d.ts +5 -0
- package/dist/src/core/shaders/templates/shaderUtils.js +41 -0
- package/dist/src/core/shaders/templates/shaderUtils.js.map +1 -0
- package/dist/src/core/shaders/webgl/Border.js +82 -82
- 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 +111 -111
- package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +130 -130
- 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/shaders/webgl/Spinner.d.ts +1 -0
- package/dist/src/core/shaders/webgl/Spinner.js +2 -0
- package/dist/src/core/shaders/webgl/Spinner.js.map +1 -0
- package/dist/src/core/text-rendering/CanvasFontHandler.d.ts +16 -0
- package/dist/src/core/text-rendering/CanvasFontHandler.js +29 -0
- package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
- package/dist/src/core/text-rendering/SdfFontHandler.d.ts +15 -0
- package/dist/src/core/text-rendering/SdfFontHandler.js +34 -2
- package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
- package/dist/src/core/text-rendering/TextRenderer.d.ts +2 -0
- package/dist/src/core/text-rendering/TextTextureRendererUtils.js.map +1 -1
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js +2 -2
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +0 -5
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.d.ts +1 -7
- package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js +2 -50
- package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +3 -2
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +83 -42
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.d.ts +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +8 -66
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +4 -14
- package/dist/src/core/text-rendering/renderers/TextRenderer.js +0 -3
- package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/sdf/PeekableGenerator.d.ts +12 -0
- package/dist/src/core/text-rendering/sdf/PeekableGenerator.js +61 -0
- package/dist/src/core/text-rendering/sdf/PeekableGenerator.js.map +1 -0
- package/dist/src/core/text-rendering/sdf/SimpleFontShaper.d.ts +45 -0
- package/dist/src/core/text-rendering/sdf/SimpleFontShaper.js +69 -0
- package/dist/src/core/text-rendering/sdf/SimpleFontShaper.js.map +1 -0
- package/dist/src/main-api/DynamicShaderController.d.ts +29 -0
- package/dist/src/main-api/DynamicShaderController.js +58 -0
- package/dist/src/main-api/DynamicShaderController.js.map +1 -0
- package/dist/src/main-api/ShaderController.d.ts +31 -0
- package/dist/src/main-api/ShaderController.js +37 -0
- package/dist/src/main-api/ShaderController.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 +82 -82
- package/exports/inspector.ts +24 -24
- package/exports/utils.ts +50 -50
- package/exports/webgl-shaders.ts +28 -28
- package/exports/webgl.ts +52 -52
- package/package.json +2 -1
- 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 +202 -202
- package/src/core/CoreNode.ts +2491 -2491
- package/src/core/CoreShaderManager.ts +188 -188
- package/src/core/CoreTextNode.ts +483 -443
- package/src/core/CoreTextureManager.ts +565 -565
- package/src/core/Stage.ts +906 -906
- package/src/core/TextureMemoryManager.ts +445 -445
- package/src/core/animations/AnimationManager.ts +38 -38
- package/src/core/animations/CoreAnimation.ts +291 -291
- package/src/core/animations/CoreAnimationController.ts +166 -166
- package/src/core/lib/ContextSpy.ts +41 -41
- package/src/core/lib/ImageWorker.ts +286 -286
- package/src/core/lib/Matrix3d.ts +244 -244
- package/src/core/lib/RenderCoords.ts +71 -71
- package/src/core/lib/WebGlContextWrapper.ts +1381 -1381
- package/src/core/lib/colorCache.ts +20 -20
- package/src/core/lib/colorParser.ts +85 -85
- package/src/core/lib/textureCompression.ts +152 -152
- package/src/core/lib/textureSvg.ts +78 -78
- package/src/core/lib/utils.ts +412 -412
- package/src/core/lib/validateImageBitmap.ts +87 -87
- package/src/core/platforms/Platform.ts +77 -77
- package/src/core/platforms/web/WebPlatform.ts +121 -121
- package/src/core/renderers/CoreContextTexture.ts +43 -43
- package/src/core/renderers/CoreRenderOp.ts +22 -22
- package/src/core/renderers/CoreRenderer.ts +110 -110
- package/src/core/renderers/CoreShaderNode.ts +175 -175
- package/src/core/renderers/CoreShaderProgram.ts +23 -23
- package/src/core/renderers/canvas/CanvasRenderer.ts +283 -283
- package/src/core/renderers/canvas/CanvasShaderNode.ts +96 -96
- package/src/core/renderers/canvas/CanvasTexture.ts +156 -156
- package/src/core/renderers/webgl/WebGlCtxRenderTexture.ts +91 -91
- package/src/core/renderers/webgl/WebGlCtxSubTexture.ts +50 -50
- package/src/core/renderers/webgl/WebGlCtxTexture.ts +310 -310
- package/src/core/renderers/webgl/WebGlRenderOp.ts +167 -167
- package/src/core/renderers/webgl/WebGlRenderer.ts +747 -747
- package/src/core/renderers/webgl/WebGlShaderNode.ts +435 -435
- package/src/core/renderers/webgl/WebGlShaderProgram.ts +341 -341
- 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 +75 -75
- package/src/core/shaders/canvas/HolePunch.ts +62 -62
- package/src/core/shaders/canvas/LinearGradient.ts +71 -71
- package/src/core/shaders/canvas/RadialGradient.ts +99 -99
- package/src/core/shaders/canvas/Rounded.ts +55 -55
- package/src/core/shaders/canvas/RoundedWithBorder.ts +74 -74
- package/src/core/shaders/canvas/RoundedWithBorderAndShadow.ts +90 -90
- package/src/core/shaders/canvas/RoundedWithShadow.ts +70 -70
- 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/utils.ts +46 -46
- package/src/core/shaders/webgl/Border.ts +116 -116
- 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 +75 -75
- package/src/core/shaders/webgl/LinearGradient.ts +82 -82
- package/src/core/shaders/webgl/RadialGradient.ts +85 -85
- package/src/core/shaders/webgl/Rounded.ts +117 -117
- package/src/core/shaders/webgl/RoundedWithBorder.ts +155 -155
- package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +175 -175
- 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/CanvasFontHandler.ts +210 -176
- package/src/core/text-rendering/CanvasTextRenderer.ts +622 -622
- package/src/core/text-rendering/SdfFontHandler.ts +554 -517
- package/src/core/text-rendering/SdfTextRenderer.ts +466 -466
- package/src/core/text-rendering/TextRenderer.ts +406 -404
- package/src/core/text-rendering/Utils.ts +257 -257
- package/src/core/text-rendering/canvas/Settings.ts +99 -99
- package/src/core/text-rendering/canvas/Utils.test.ts +206 -206
- package/src/core/text-rendering/canvas/Utils.ts +178 -178
- package/src/core/text-rendering/canvas/calculateRenderInfo.ts +299 -299
- package/src/core/text-rendering/canvas/draw.ts +165 -165
- package/src/core/text-rendering/sdf/Utils.test.ts +402 -402
- package/src/core/text-rendering/sdf/Utils.ts +436 -436
- package/src/core/text-rendering/sdf/index.ts +20 -20
- package/src/core/textures/ColorTexture.ts +102 -102
- package/src/core/textures/ImageTexture.ts +418 -418
- 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 +381 -381
- 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 +567 -567
- package/src/main-api/Renderer.ts +873 -873
- package/src/main-api/utils.ts +45 -45
- package/src/utils.ts +267 -267
- 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;
|