Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix measure text width error #2247

Merged
merged 9 commits into from
Aug 8, 2024
2 changes: 1 addition & 1 deletion packages/core/src/2d/text/TextRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,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;
Expand Down
17 changes: 12 additions & 5 deletions packages/core/src/2d/text/TextUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,15 @@ 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, width: actualWidth } = context.measureText(measureString);
// In some case (ex: " "), actualBoundingBoxRight and actualBoundingBoxLeft will be 0, so use 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,
Math.round(textMetrics.actualBoundingBoxRight - textMetrics.actualBoundingBoxLeft || textMetrics.width)
Math.round(Math.max(actualBoundingBoxRight + Math.abs(actualBoundingBoxLeft), actualWidth))
);
// Make sure enough width.
let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width);
let height = baseline * TextUtils._heightMultiplier;
baseline = (TextUtils._baselineMultiplier * baseline) | 0;
Expand All @@ -370,7 +373,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;
Expand Down Expand Up @@ -421,9 +428,9 @@ export class TextUtils {
y: 0,
w: width,
h: size,
offsetX: 0,
offsetX: actualBoundingBoxLeft > 0 ? actualBoundingBoxLeft : 0,
offsetY: (ascent - descent) * 0.5,
xAdvance: width,
xAdvance: Math.round(actualWidth),
uvs: [new Vector2(), new Vector2(), new Vector2(), new Vector2()],
ascent,
descent,
Expand Down
6 changes: 3 additions & 3 deletions tests/src/core/2d/text/TextRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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
);
Expand Down
36 changes: 18 additions & 18 deletions tests/src/core/2d/text/TextUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
});

Expand Down
Loading