Skip to content

Commit

Permalink
fix: 使用离屏 Canvas 测量文本, 提高宽度计算的准确性 close #3018
Browse files Browse the repository at this point in the history
  • Loading branch information
lijinke666 committed Dec 26, 2024
1 parent 41a2ad0 commit 13d3661
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 35 deletions.
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')!;
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

0 comments on commit 13d3661

Please sign in to comment.