From 07e738ffb9c4c19a89b2fb80b40e3f6071e319c0 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 18 Jul 2024 17:27:19 +0800 Subject: [PATCH 1/7] fix(text): fix measure text width error --- packages/core/src/2d/text/TextUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 7209431e9f..731dd8f602 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -424,7 +424,7 @@ export class TextUtils { h: size, offsetX: 0, offsetY: (ascent - descent) * 0.5, - xAdvance: width, + xAdvance: Math.round(textMetrics.width), uvs: [new Vector2(), new Vector2(), new Vector2(), new Vector2()], ascent, descent, From 28d9810a63bc87c8d0ecfdc19fc8f1afa33250ea Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 18 Jul 2024 19:10:11 +0800 Subject: [PATCH 2/7] fix(text): fix measure text width error --- packages/core/src/2d/text/TextRenderer.ts | 2 +- packages/core/src/2d/text/TextUtils.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 78135e2c3c..069054ce51 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -573,7 +573,7 @@ export class TextRenderer extends Renderer { j === firstRow && (minX = Math.min(minX, left)); maxX = Math.max(maxX, right); } - startX += charInfo.xAdvance; + startX += charInfo.xAdvance + charInfo.offsetX; } } startY -= lineHeight; diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 731dd8f602..366d9a8ad6 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -351,11 +351,11 @@ export class TextUtils { // The measure text width of some special invisible characters may be 0, so make sure the width is at least 1. // @todo: Text layout may vary from standard and not support emoji. const textMetrics = context.measureText(measureString); + const { actualBoundingBoxLeft, actualBoundingBoxRight } = context.measureText(measureString); // In some case (ex: " "), actualBoundingBoxRight and actualBoundingBoxLeft will be 0, so use width. - const width = Math.max( - 1, - Math.round(textMetrics.actualBoundingBoxRight - textMetrics.actualBoundingBoxLeft || textMetrics.width) - ); + let width = Math.max(1, Math.round(Math.max(actualBoundingBoxRight - actualBoundingBoxLeft, textMetrics.width))); + // Make sure enough width. + actualBoundingBoxLeft > 0 && (width += actualBoundingBoxRight); let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); let height = baseline * TextUtils._heightMultiplier; baseline = (TextUtils._baselineMultiplier * baseline) | 0; @@ -371,7 +371,11 @@ export class TextUtils { context.clearRect(0, 0, width, height); context.textBaseline = "middle"; context.fillStyle = "#fff"; - context.fillText(measureString, 0, baseline); + if (actualBoundingBoxLeft > 0) { + context.fillText(measureString, actualBoundingBoxLeft, baseline); + } else { + context.fillText(measureString, 0, baseline); + } const colorData = context.getImageData(0, 0, width, height).data; const len = colorData.length; @@ -422,7 +426,7 @@ export class TextUtils { y: 0, w: width, h: size, - offsetX: 0, + offsetX: actualBoundingBoxLeft > 0 ? actualBoundingBoxLeft : 0, offsetY: (ascent - descent) * 0.5, xAdvance: Math.round(textMetrics.width), uvs: [new Vector2(), new Vector2(), new Vector2(), new Vector2()], From 7a277e360e5f42384d4d2dca8e45daa59cc4accb Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 19 Jul 2024 10:24:39 +0800 Subject: [PATCH 3/7] fix(text): opt code --- packages/core/src/2d/text/TextUtils.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 366d9a8ad6..3d5a2c7f7c 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -350,10 +350,9 @@ export class TextUtils { // Safari gets data confusion through getImageData when the canvas width is not an integer. // The measure text width of some special invisible characters may be 0, so make sure the width is at least 1. // @todo: Text layout may vary from standard and not support emoji. - const textMetrics = context.measureText(measureString); - const { actualBoundingBoxLeft, actualBoundingBoxRight } = context.measureText(measureString); + const { actualBoundingBoxLeft, actualBoundingBoxRight, width: actualWidth } = context.measureText(measureString); // In some case (ex: " "), actualBoundingBoxRight and actualBoundingBoxLeft will be 0, so use width. - let width = Math.max(1, Math.round(Math.max(actualBoundingBoxRight - actualBoundingBoxLeft, textMetrics.width))); + let width = Math.max(1, Math.round(Math.max(actualBoundingBoxRight - actualBoundingBoxLeft, actualWidth))); // Make sure enough width. actualBoundingBoxLeft > 0 && (width += actualBoundingBoxRight); let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); @@ -428,7 +427,7 @@ export class TextUtils { h: size, offsetX: actualBoundingBoxLeft > 0 ? actualBoundingBoxLeft : 0, offsetY: (ascent - descent) * 0.5, - xAdvance: Math.round(textMetrics.width), + xAdvance: Math.round(actualWidth), uvs: [new Vector2(), new Vector2(), new Vector2(), new Vector2()], ascent, descent, From f2f7921e0c4db14d416ddb5670cbe204ad6bcf57 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 19 Jul 2024 11:04:15 +0800 Subject: [PATCH 4/7] fix(text): fix test error --- tests/src/core/2d/text/TextRenderer.test.ts | 6 ++-- tests/src/core/2d/text/TextUtils.test.ts | 36 ++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/src/core/2d/text/TextRenderer.test.ts b/tests/src/core/2d/text/TextRenderer.test.ts index 67f4ddb0e5..ddbb1d7607 100644 --- a/tests/src/core/2d/text/TextRenderer.test.ts +++ b/tests/src/core/2d/text/TextRenderer.test.ts @@ -243,7 +243,7 @@ describe("TextRenderer", () => { textRenderer.verticalAlignment = TextVerticalAlignment.Top; textRenderer.horizontalAlignment = TextHorizontalAlignment.Left; BoundingBox.transform( - new BoundingBox(new Vector3(-1.5, 1.27, 0), new Vector3(1.42, 1.5, 0)), + new BoundingBox(new Vector3(-1.5, 1.27, 0), new Vector3(1.39, 1.5, 0)), textRendererEntity.transform.worldMatrix, box ); @@ -283,7 +283,7 @@ describe("TextRenderer", () => { textRendererEntity.transform.setPosition(0, 1, 0); textRendererEntity.transform.setRotation(10, 3, 0); BoundingBox.transform( - new BoundingBox(new Vector3(-1.42, 1.28, 0), new Vector3(1.5, 1.5, 0)), + new BoundingBox(new Vector3(-1.39, 1.28, 0), new Vector3(1.5, 1.5, 0)), textRendererEntity.transform.worldMatrix, box ); @@ -321,7 +321,7 @@ describe("TextRenderer", () => { // Test that bounds is correct, while verticalAlignment is bottom and horizontalAlignment is right. textRenderer.verticalAlignment = TextVerticalAlignment.Bottom; BoundingBox.transform( - new BoundingBox(new Vector3(-1.42, 1.25, 0), new Vector3(1.5, 1.47, 0)), + new BoundingBox(new Vector3(-1.39, 1.25, 0), new Vector3(1.5, 1.47, 0)), textRendererEntity.transform.worldMatrix, box ); diff --git a/tests/src/core/2d/text/TextUtils.test.ts b/tests/src/core/2d/text/TextUtils.test.ts index 752c58c416..39ff6d7cdd 100644 --- a/tests/src/core/2d/text/TextUtils.test.ts +++ b/tests/src/core/2d/text/TextUtils.test.ts @@ -139,7 +139,7 @@ describe("TextUtils", () => { result = TextUtils.measureTextWithWrap(textRendererTruncate); expect(result.width).to.be.equal(24); expect(result.height).to.be.equal(100); - expect(result.lines).to.be.deep.equal(['阳', '光', '明', '媚', ',t', 'h', 'e ', 'w', 'e', 'at', 'h', 'er', 'is ', 'gr', 'e', 'at', 'to', 'd', 'a', 'y。']); + expect(result.lines).to.be.deep.equal(['阳', '光', '明', '媚', ',', 'th', 'e ', 'w', 'e', 'at', 'h', 'er', 'is ', 'gr', 'e', 'at', 'to', 'd', 'a', 'y', '。']); expect(result.lineHeight).to.be.equal(27); textRendererTruncate.text = text4; result = TextUtils.measureTextWithWrap(textRendererTruncate); @@ -172,7 +172,7 @@ describe("TextUtils", () => { result = TextUtils.measureTextWithWrap(textRendererOverflow); expect(result.width).to.be.equal(24); expect(result.height).to.be.equal(621); - expect(result.lines).to.be.deep.equal(['阳', '光', '明', '媚', ',t', 'h', 'e ', 'w', 'e', 'at', 'h', 'e', 'r ', 'is', 'g', 'r', 'e', 'at', 'to', 'd', 'a', 'y', '。']); + expect(result.lines).to.be.deep.equal(['阳', '光', '明', '媚', ',', 'th', 'e ', 'w', 'e', 'at', 'h', 'e', 'r ', 'is', 'g', 'r', 'e', 'at', 'to', 'd', 'a', 'y', '。']); expect(result.lineHeight).to.be.equal(27); textRendererOverflow.text = text4; result = TextUtils.measureTextWithWrap(textRendererOverflow); @@ -205,31 +205,31 @@ describe("TextUtils", () => { textRendererTruncate.text = text1; let result = TextUtils.measureTextWithoutWrap(textRendererTruncate); - expect(result.width).to.be.equal(478); + expect(result.width).to.be.equal(518); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]); - expect(result.lineWidths).to.be.deep.equal([478]); + expect(result.lineWidths).to.be.deep.equal([518]); expect(result.lineHeight).to.be.equal(27); textRendererTruncate.text = text2; result = TextUtils.measureTextWithoutWrap(textRendererTruncate); - expect(result.width).to.be.equal(292); + expect(result.width).to.be.equal(289); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["The weather is great today."]); - expect(result.lineWidths).to.be.deep.equal([292]); + expect(result.lineWidths).to.be.deep.equal([289]); expect(result.lineHeight).to.be.equal(27); textRendererTruncate.text = text3; result = TextUtils.measureTextWithoutWrap(textRendererTruncate); - expect(result.width).to.be.equal(393); + expect(result.width).to.be.equal(418); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["阳光明媚,the weather is great today。"]); - expect(result.lineWidths).to.be.deep.equal([393]); + expect(result.lineWidths).to.be.deep.equal([418]); expect(result.lineHeight).to.be.equal(27); textRendererTruncate.text = text4; result = TextUtils.measureTextWithoutWrap(textRendererTruncate); - expect(result.width).to.be.equal(112); + expect(result.width).to.be.equal(111); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal([" ", " World"]); - expect(result.lineWidths).to.be.deep.equal([63, 112]); + expect(result.lineWidths).to.be.deep.equal([63, 111]); expect(result.lineHeight).to.be.equal(27); // Test that measureTextWithoutWrap works correctly, while set overflow mode to overflow. @@ -257,31 +257,31 @@ describe("TextUtils", () => { textRendererOverflow.text = text1; result = TextUtils.measureTextWithoutWrap(textRendererOverflow); - expect(result.width).to.be.equal(478); + expect(result.width).to.be.equal(518); expect(result.height).to.be.equal(27); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]); - expect(result.lineWidths).to.be.deep.equal([478]); + expect(result.lineWidths).to.be.deep.equal([518]); expect(result.lineHeight).to.be.equal(27); textRendererOverflow.text = text2; result = TextUtils.measureTextWithoutWrap(textRendererOverflow); - expect(result.width).to.be.equal(292); + expect(result.width).to.be.equal(289); expect(result.height).to.be.equal(27); expect(result.lines).to.be.deep.equal(["The weather is great today."]); - expect(result.lineWidths).to.be.deep.equal([292]); + expect(result.lineWidths).to.be.deep.equal([289]); expect(result.lineHeight).to.be.equal(27); textRendererOverflow.text = text3; result = TextUtils.measureTextWithoutWrap(textRendererOverflow); - expect(result.width).to.be.equal(393); + expect(result.width).to.be.equal(418); expect(result.height).to.be.equal(27); expect(result.lines).to.be.deep.equal(["阳光明媚,the weather is great today。"]); - expect(result.lineWidths).to.be.deep.equal([393]); + expect(result.lineWidths).to.be.deep.equal([418]); expect(result.lineHeight).to.be.equal(27); textRendererOverflow.text = text4; result = TextUtils.measureTextWithoutWrap(textRendererOverflow); - expect(result.width).to.be.equal(112); + expect(result.width).to.be.equal(111); expect(result.height).to.be.equal(54); expect(result.lines).to.be.deep.equal([" ", " World"]); - expect(result.lineWidths).to.be.deep.equal([63, 112]); + expect(result.lineWidths).to.be.deep.equal([63, 111]); expect(result.lineHeight).to.be.equal(27); }); From 89e83ea21927f6b236940aa416acd97f7ff276d0 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 8 Aug 2024 14:33:28 +0800 Subject: [PATCH 5/7] fix: opt code --- packages/core/src/2d/text/TextUtils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 1ab20d88d7..b546d2e670 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -351,9 +351,11 @@ export class TextUtils { // @todo: Text layout may vary from standard and not support emoji. const { actualBoundingBoxLeft, actualBoundingBoxRight, width: actualWidth } = context.measureText(measureString); // In some case (ex: " "), actualBoundingBoxRight and actualBoundingBoxLeft will be 0, so use width. - let width = Math.max(1, Math.round(Math.max(actualBoundingBoxRight - actualBoundingBoxLeft, actualWidth))); + const width = Math.max( + 1, + Math.round(Math.max(actualBoundingBoxRight + Math.abs(actualBoundingBoxLeft), actualWidth)) + ); // Make sure enough width. - actualBoundingBoxLeft > 0 && (width += actualBoundingBoxRight); let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); let height = baseline * TextUtils._heightMultiplier; baseline = (TextUtils._baselineMultiplier * baseline) | 0; From 29eceb97830092a26fe3a87d3fedd22a58f9ade2 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 8 Aug 2024 18:32:54 +0800 Subject: [PATCH 6/7] fix: add todo for calculate render width --- packages/core/src/2d/text/TextUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index b546d2e670..085113b933 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -351,6 +351,8 @@ export class TextUtils { // @todo: Text layout may vary from standard and not support emoji. const { actualBoundingBoxLeft, actualBoundingBoxRight, width: actualWidth } = context.measureText(measureString); // In some case (ex: " "), actualBoundingBoxRight and actualBoundingBoxLeft will be 0, so use width. + // TODO: After testing, actualBoundingBoxLeft + actualBoundingBoxRight is the actual rendering width + // but the space rules between characters are unclear. Using actualBoundingBoxRight + Math.abs(actualBoundingBoxLeft) is the closest to the native effect. const width = Math.max( 1, Math.round(Math.max(actualBoundingBoxRight + Math.abs(actualBoundingBoxLeft), actualWidth)) From 7fdf85de666c6b6ad28f8bf301685bc9d25b633d Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 8 Aug 2024 18:33:59 +0800 Subject: [PATCH 7/7] fix: add todo for calculate render width --- packages/core/src/2d/text/TextUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 085113b933..ff9a0f2a89 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -351,7 +351,7 @@ export class TextUtils { // @todo: Text layout may vary from standard and not support emoji. const { actualBoundingBoxLeft, actualBoundingBoxRight, width: actualWidth } = context.measureText(measureString); // In some case (ex: " "), actualBoundingBoxRight and actualBoundingBoxLeft will be 0, so use width. - // TODO: After testing, actualBoundingBoxLeft + actualBoundingBoxRight is the actual rendering width + // TODO: With testing, actualBoundingBoxLeft + actualBoundingBoxRight is the actual rendering width // but the space rules between characters are unclear. Using actualBoundingBoxRight + Math.abs(actualBoundingBoxLeft) is the closest to the native effect. const width = Math.max( 1,