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: 使用离屏 Canvas 测量文本, 提高宽度计算的准确性 close #3018 #3053

Open
wants to merge 2 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 183 additions & 10 deletions packages/s2-core/__tests__/spreadsheet/compare-layout-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const s2Options: S2Options = {

describe('Compare Layout Tests', () => {
const expectTextOverflowing = (s2: SpreadSheet) => {
[...s2.facet.getColCells(), ...s2.facet.getDataCells()].forEach((cell) => {
s2.facet.getCells().forEach((cell) => {
expect(cell.getTextShape().isOverflowing()).toBeFalsy();
});
};
Expand Down Expand Up @@ -55,9 +55,9 @@ describe('Compare Layout Tests', () => {

const colLeafNodes = s2.facet.getColLeafNodes();

expect(Math.floor(colLeafNodes[0].width)).toBeCloseTo(133);
expect(Math.floor(colLeafNodes[0].width)).toBeCloseTo(122);
expect(Math.floor(colLeafNodes[1].width)).toEqual(
options.showDefaultHeaderActionIcon ? 71 : 66,
options.showDefaultHeaderActionIcon ? 66 : 63,
);
expectTextOverflowing(s2);
},
Expand All @@ -84,15 +84,15 @@ describe('Compare Layout Tests', () => {
});
await s2.render();

const expectWidth = options.showDefaultHeaderActionIcon ? 71 : 66;
const expectWidth = options.showDefaultHeaderActionIcon ? 66 : 63;
const isLargeFontSize = options.fontSize === 20;
const colLeafNodes = s2.facet.getColLeafNodes();

expect(Math.floor(colLeafNodes[0].width)).toBeCloseTo(
isLargeFontSize ? 209 : 133,
isLargeFontSize ? 191 : 122,
);
expect(Math.floor(colLeafNodes[1].width)).toEqual(
isLargeFontSize ? 97 : expectWidth,
isLargeFontSize ? 92 : expectWidth,
);
expectTextOverflowing(s2);
});
Expand Down Expand Up @@ -123,7 +123,7 @@ describe('Compare Layout Tests', () => {

const colLeafNodes = s2.facet.getColLeafNodes();
const { dataCellWidthList, colLeafNodeWidthList } = mapWidthList(s2);
const expectWidth = 207;
const expectWidth = 183;

expect(Math.floor(colLeafNodes[0].width)).toBeCloseTo(expectWidth);
expect(
Expand Down Expand Up @@ -171,13 +171,186 @@ describe('Compare Layout Tests', () => {

expect(dataCellWidthList).toEqual(
options.showDefaultHeaderActionIcon
? [227, 227, 227, 227, 115, 115, 115, 115, 93, 93, 93, 93]
: [227, 227, 227, 227, 115, 115, 115, 115, 71, 71, 71, 71],
? [209, 209, 209, 209, 110, 110, 110, 110, 85, 85, 85, 85]
: [209, 209, 209, 209, 110, 110, 110, 110, 69, 69, 69, 69],
);
expect(colLeafNodeWidthList).toEqual(
options.showDefaultHeaderActionIcon ? [227, 115, 93] : [227, 115, 71],
options.showDefaultHeaderActionIcon ? [209, 110, 85] : [209, 110, 69],
);
expectTextOverflowing(s2);
},
);

test.each([{ showIcon: true }, { showIcon: false }])(
'should get max row width for pivot sheet and format name by %o',
async (options) => {
const s2 = new PivotSheet(
getContainer(),
{
...mockDataConfig,
meta: [
{ field: 'province', name: '省份666' },
{ field: 'city', name: '城市1234567' },
],
},
{
...s2Options,
headerActionIcons: options.showIcon
? [
{
icons: ['SortDown'],
belongsCell: 'cornerCell',
},
]
: [],
},
);

await s2.render();

const rowNodes = s2.facet.getRowNodes();

expect(Math.floor(rowNodes[0].width)).toBeCloseTo(
options.showIcon ? 80 : 62,
);
expect(Math.floor(rowNodes[1].width)).toEqual(
options.showIcon ? 106 : 88,
);
expectTextOverflowing(s2);
},
);

test('should not render overflowing text for table sheet and a difference type text', async () => {
const s2 = new TableSheet(getContainer(), mockDataConfig, s2Options);

s2.setDataCfg({
fields: {
columns: [
'date',
'zh',
'percentage',
'number',
'url-number',
'url-en',
'url-zh',
],
},
meta: [
{
field: 'date',
formatter: () => '2021-09-08',
},
{
field: 'zh',
formatter: () => '中文文本测试中文文本',
},
{
field: 'percentage',
formatter: () => '100.23433333%',
},
{
field: 'number',
formatter: () => '111111111111',
},
{
field: 'url-number',
formatter: () => `https://wwww.test.cn?test=${'1'.repeat(10)}`,
},
{
field: 'url-en',
formatter: () => `https://wwww.test.cn?test=${'t'.repeat(10)}`,
},
{
field: 'url-zh',
formatter: () => `https://wwww.test.cn?test=${'测'.repeat(10)}`,
},
],
});

await s2.render();

expectTextOverflowing(s2);
});

test('should get max col width for pivot sheet conditions', async () => {
const s2 = new PivotSheet(getContainer(), mockDataConfig, {
...s2Options,
conditions: {
icon: [
{
field: 'price',
position: 'left',
mapping(fieldValue: number) {
if (!fieldValue) {
return null;
}

return fieldValue > 0
? {
fill: 'red',
icon: 'CellUp',
}
: {
fill: 'green',
icon: 'CellDown',
};
},
},
],
},
});

s2.setDataCfg({
meta: [
{
field: 'price',
formatter: (value) => (value === '111' ? '35333.7%' : value),
},
],
});

await s2.render();

const { dataCellWidthList, colLeafNodeWidthList } = mapWidthList(s2);

expect(dataCellWidthList).toEqual([
140, 140, 140, 140, 81, 81, 81, 81, 92, 92, 92, 92,
]);
expect(colLeafNodeWidthList).toEqual([140, 81, 92]);
expectTextOverflowing(s2);
});

test.each([
{ text: '中文文本测试中文文本测试', width: 145 },
{ text: '中文文本测试中文文本123', width: 142 },
{ text: '中文文本测试中文文本word', width: 150 },
{ text: '11111111111111111', width: 104 },
{ text: '参数:', width: 37 },
{ text: 'word', width: 30 },
{ text: 'word123', width: 50 },
{ text: 'word123...', width: 60 },
{ text: '100.234%', width: 56 },
{ text: '2024-12-24', width: 63 },
{ text: '纸张123456', width: 66 },
{ text: `https://wwww.test.cn?test=${'1'.repeat(10)}`, width: 217 },
])('should get correctly text width for %o', async ({ text, width }) => {
const s2 = new PivotSheet(getContainer(), mockDataConfig, {
...s2Options,
});

await s2.render();

const result = s2.facet.measureTextWidth(text, {
fontFamily: 'Roboto, PingFangSC, Microsoft YaHei, Arial, sans-serif',
fontSize: 12,
fontWeight: 700,
fill: '#000000',
opacity: 1,
textAlign: 'left',
textBaseline: 'middle',
linkTextFill: '#2C60D4',
});

expect(result).toEqual(width);
});
});
2 changes: 1 addition & 1 deletion packages/s2-core/__tests__/unit/utils/canvas-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getOffscreenCanvas, removeOffscreenCanvas } from '@/utils';
import { sleep } from './../../util/helpers';

describe('Canvas Utils Tests', () => {
const ID = 's2-offscreen-canvas';
const ID = 'antv-s2-offscreen-canvas';

test('should get offscreen canvas', () => {
const canvas = getOffscreenCanvas();
Expand Down
2 changes: 1 addition & 1 deletion packages/s2-core/scripts/test-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ora from 'ora';
inquirer.registerPrompt('autocomplete', autoCompletePrompt);

function run(path) {
const command = `cross-env DEBUG_MODE=1 npx jest ${path} --passWithNoTests --detectOpenHandles`;
const command = `cross-env DEBUG_MODE=1 npx jest ${path} --passWithNoTests`;
const jestSpinner = ora(`[测试运行中]: ${command}`).start();

try {
Expand Down
3 changes: 2 additions & 1 deletion packages/s2-core/src/cell/base-cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,14 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
const maxTextWidth = Math.max(this.getMaxTextWidth(), 0) + EXTRA_PIXEL;
const textStyle = this.getTextStyle();
const maxLines = this.getResizedTextMaxLines() || textStyle?.maxLines;
const text = this.getFieldValue()!;

// 在坐标计算 (getTextPosition) 之前, 预渲染一次, 提前生成 textShape, 获得文字宽度, 用于计算 icon 绘制坐标
this.renderTextShape({
...textStyle,
x: 0,
y: 0,
text: this.getFieldValue()!,
text,
wordWrapWidth: maxTextWidth,
maxLines,
});
Expand Down
14 changes: 1 addition & 13 deletions packages/s2-core/src/facet/base-facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2454,22 +2454,10 @@ export abstract class BaseFacet {
* @tip 和 this.spreadsheet.measureTextWidth() 的区别在于:
* 1. 额外添加一像素余量,防止 maxLabel 有多个同样长度情况下,一些 label 不能展示完全, 出现省略号
* 2. 测量时, 文本宽度取整, 避免子像素的不一致性
* 3. TODO: 由于 G 测量文本是一个一个字符进行计算, 在数字/英文等场景会有较大误差, 这里为了防止紧凑模式出现省略号, 暂时保持一样的策略
*/
protected measureTextWidth(
text: SimpleData,
font: unknown,
roughly = true,
): number {
protected measureTextWidth(text: SimpleData, font: unknown): number {
const EXTRA_PIXEL = 1;

if (roughly) {
return (
Math.ceil(this.spreadsheet.measureTextWidthRoughly(text, font)) +
EXTRA_PIXEL
);
}

return (
Math.ceil(this.spreadsheet.measureTextWidth(text, font)) + EXTRA_PIXEL
);
Expand Down
2 changes: 1 addition & 1 deletion packages/s2-core/src/facet/pivot-facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ export class PivotFacet extends FrozenFacet {
* 额外增加 1,当内容和容器宽度恰好相等时会出现换行
*/
const maxLabelWidth =
this.measureTextWidth(treeHeaderLabel, cornerCellTextStyle, false) +
this.measureTextWidth(treeHeaderLabel, cornerCellTextStyle) +
cornerIconStyle.size * 2 +
cornerIconStyle.margin?.left +
cornerIconStyle.margin?.right +
Expand Down
2 changes: 1 addition & 1 deletion packages/s2-core/src/facet/table-facet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class TableFacet extends FrozenFacet {
const iconX = viewportWidth / 2 - icon.width / 2;
const iconY = height / 2 + maxY - icon.height / 2 + icon.margin.top;
const text = empty?.description ?? i18n('暂无数据');
const descWidth = this.measureTextWidth(text, description, false);
const descWidth = this.measureTextWidth(text, description);
const descX = viewportWidth / 2 - descWidth / 2;
const descY = iconY + icon.height + icon.margin.bottom;

Expand Down
4 changes: 2 additions & 2 deletions packages/s2-core/src/sheet-type/spread-sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { RootInteraction } from '../interaction/root';
import { getTheme } from '../theme';
import { HdAdapter } from '../ui/hd-adapter';
import { BaseTooltip } from '../ui/tooltip';
import { removeOffscreenCanvas } from '../utils/canvas';
import { getOffscreenCanvas, removeOffscreenCanvas } from '../utils/canvas';
import { clearValueRangeState } from '../utils/condition/state-controller';
import { hideColumnsByThunkGroup } from '../utils/hide-columns';
import { isMobile } from '../utils/is-mobile';
Expand Down Expand Up @@ -784,7 +784,7 @@ export abstract class SpreadSheet extends EE {
return null;
}

const ctx = this.getCanvasElement()?.getContext('2d')!;
const ctx = getOffscreenCanvas()?.getContext('2d')!;
lijinke666 marked this conversation as resolved.
Show resolved Hide resolved
const { fontSize, fontFamily, fontWeight, fontStyle, fontVariant } =
font as CSSStyleDeclaration;

Expand Down
4 changes: 3 additions & 1 deletion packages/s2-core/src/utils/canvas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const OFFSCREEN_CANVAS_DOM_ID = 's2-offscreen-canvas';
import { S2_PREFIX_CLS } from '../common/constant/classnames';

const OFFSCREEN_CANVAS_DOM_ID = `${S2_PREFIX_CLS}-offscreen-canvas`;

/**
* 获取工具 canvas
Expand Down
5 changes: 2 additions & 3 deletions packages/s2-react/playground/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const pivotSheetDataCfgForCompactMode = customMerge<S2DataConfig>(
{
province: '浙江省',
city: '杭州市',
sub_type: '纸张',
sub_type: '纸张123456',
type: '办公用品',
number: 2,
},
Expand All @@ -112,7 +112,7 @@ export const pivotSheetDataCfgForCompactMode = customMerge<S2DataConfig>(
city: '舟山市',
sub_type: '笔',
type: '办公用品',
number: 2,
number: '20000.334%',
},
{
province: '浙江省',
Expand Down Expand Up @@ -412,7 +412,6 @@ export const s2Options: SheetComponentOptions = {
// ],
// ],
tooltip: S2TooltipOptions,
style: {},
};

export const sliderOptions: SliderSingleProps = {
Expand Down
2 changes: 1 addition & 1 deletion packages/s2-react/scripts/test-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ora from 'ora';
inquirer.registerPrompt('autocomplete', autoCompletePrompt);

function run(path) {
const command = `cross-env DEBUG_MODE=1 npx jest ${path} --passWithNoTests --detectOpenHandles`;
const command = `cross-env DEBUG_MODE=1 npx jest ${path} --passWithNoTests`;
const jestSpinner = ora(`[测试运行中]: ${command}`).start();

try {
Expand Down
Loading