fix: merged cell bg not fully ender (#3671)

Co-authored-by: GitHub Actions <actions@github.com>
This commit is contained in:
lumix
2024-10-11 16:18:01 +08:00
committed by GitHub
parent 0938e54cf9
commit 5bbbec339a
20 changed files with 325 additions and 150 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run: pnpm build:e2e
- name: Run Playwright Tests
run: pnpm exec playwright test
run: pnpm exec playwright test --output=playwright-assets
- name: 🚀 Deploy to Vercel
uses: amondnet/vercel-action@v25
+1
View File
@@ -108,6 +108,7 @@ storybook-static
# playwright
/test-results/
/playwright-report/
/playwright-assets/
/blob-report/
/playwright/.cache/
playwright-report.json
+3 -2
View File
@@ -3,8 +3,9 @@
export interface IE2EControllerAPI {
loadAndRelease(id: number, loadTimeout?: number, disposeTimeout?: number): Promise<void>;
loadDefaultSheet(loadTimeout?: number): Promise<void>;
loadDefaultDoc(loadTimeout?: number,): Promise<void>;
loadDemoSheet(): Promise<void>;
loadDefaultDoc(loadTimeout?: number): Promise<void>;
loadDemoSheet(loadTimeout?: number): Promise<void>;
loadMergeCellSheet(loadTimeout?: number): Promise<void>;
disposeUniver(): Promise<void>;
disposeCurrSheetUnit(disposeTimeout?: number): Promise<void>;
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { expect, test } from '@playwright/test';
import { chromium, expect, test } from '@playwright/test';
import { generateSnapshotName } from '../const';
test('diff default sheet content', async ({ page }) => {
@@ -26,6 +26,7 @@ test('diff default sheet content', async ({ page }) => {
await expect(page).toHaveScreenshot(generateSnapshotName('default-sheet'), { maxDiffPixels: 5 });
});
const isCI = !!process.env.CI;
test('diff demo sheet content', async ({ page }) => {
let errored = false;
@@ -39,8 +40,95 @@ test('diff demo sheet content', async ({ page }) => {
await page.waitForTimeout(2000);
await page.evaluate(() => window.E2EControllerAPI.loadDemoSheet());
await page.waitForTimeout(5000);
await page.waitForTimeout(2000);
await expect(page).toHaveScreenshot(generateSnapshotName('demo-sheet'), { maxDiffPixels: 5 });
await page.waitForTimeout(2000);
expect(errored).toBeFalsy();
});
/**
* Aim for merged cells rendering.
*/
test('diff merged cells rendering', async () => {
const browser = await chromium.launch({
headless: !!isCI, // Set to false to see the browser window
});
const context = await browser.newContext({
viewport: { width: 1280, height: 1280 },
deviceScaleFactor: 2, // Set your desired DPR
});
const page = await context.newPage();
await page.goto('http://localhost:3000/sheets/');
await page.waitForTimeout(2000);
await page.evaluate(() => window.E2EControllerAPI.loadMergeCellSheet());
await page.waitForTimeout(5000);
await expect(page).toHaveScreenshot(generateSnapshotName('mergedCellsRendering'), { maxDiffPixels: 5 });
await page.waitForTimeout(2000);
await browser.close();
});
test('diff merged cells rendering after scrolling', async () => {
const browser = await chromium.launch({
headless: !!isCI, // Set to false to see the browser window in local
});
const context = await browser.newContext({
viewport: { width: 1280, height: 1280 },
deviceScaleFactor: 2, // Set your desired DPR
});
const page = await context.newPage();
await page.goto('http://localhost:3000/sheets/');
await page.waitForTimeout(2000);
await page.evaluate(() => window.E2EControllerAPI.loadMergeCellSheet());
await page.waitForTimeout(2000);
await page.evaluate(async () => {
const dispatchWheelEvent = (deltaX: number, deltaY: number, element: HTMLElement, interval: number = 30, lastFor: number = 1000) => {
// const canvasElements = document.querySelectorAll('canvas.univer-render-canvas') as unknown as HTMLElement[];
// const filteredCanvasElements = Array.from(canvasElements).filter((canvas) => canvas.offsetHeight > 500);
const dispatchSimulateWheelEvent = (element) => {
const event = new WheelEvent('wheel', {
bubbles: true,
cancelable: true,
deltaY,
deltaX,
clientX: 580,
clientY: 580,
});
element.dispatchEvent(event);
};
// mock wheel event.
let intervalID;
const continuousWheelSimulation = (element, interval) => {
intervalID = setInterval(function () {
dispatchSimulateWheelEvent(element);
}, interval);
};
// start mock wheel event.
continuousWheelSimulation(element, interval);
return new Promise((resolve) => {
setTimeout(() => {
clearInterval(intervalID);
resolve(1);
}, lastFor);
});
};
const canvasElements = document.querySelectorAll('canvas.univer-render-canvas') as unknown as HTMLElement[];
const filteredCanvasElements = Array.from(canvasElements).filter((canvas) => canvas.offsetHeight > 500);
const element = filteredCanvasElements[0];
await dispatchWheelEvent(0, 100, element);
await dispatchWheelEvent(0, -100, element);
});
await page.waitForTimeout(2000);
await expect(page).toHaveScreenshot(generateSnapshotName('mergedCellsRenderingScrolling'), { maxDiffPixels: 5 });
await page.waitForTimeout(2000);
await browser.close();
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

@@ -28,7 +28,8 @@ const AWAIT_DISPOSING_TIMEOUT = 5000;
export interface IE2EControllerAPI {
loadAndRelease(id: number, loadTimeout?: number, disposeTimeout?: number): Promise<void>;
loadDefaultSheet(loadTimeout?: number): Promise<void>;
loadDemoSheet(): Promise<void>;
loadDemoSheet(loadTimeout?: number): Promise<void>;
loadMergeCellSheet(loadTimeout?: number): Promise<void>;
loadDefaultDoc(loadTimeout?: number,): Promise<void>;
disposeUniver(): Promise<void>;
disposeCurrSheetUnit(disposeTimeout?: number): Promise<void>;
@@ -64,6 +65,7 @@ export class E2EController extends Disposable {
loadAndRelease: (id, loadTimeout, disposeTimeout) => this._loadAndRelease(id, loadTimeout, disposeTimeout),
loadDefaultSheet: (loadTimeout) => this._loadDefaultSheet(loadTimeout),
loadDemoSheet: () => this._loadDemoSheet(),
loadMergeCellSheet: () => this._loadMergeCellSheet(2000),
disposeCurrSheetUnit: (disposeTimeout?: number) => this._diposeDefaultSheetUnit(disposeTimeout),
loadDefaultDoc: (loadTimeout) => this._loadDefaultDoc(loadTimeout),
disposeUniver: () => this._disposeUniver(),
@@ -92,6 +94,16 @@ export class E2EController extends Disposable {
await awaitTime(AWAIT_LOADING_TIMEOUT);
}
/**
* sheet-003 in default data
*/
private async _loadMergeCellSheet(loadingTimeout: number = AWAIT_LOADING_TIMEOUT): Promise<void> {
const data = { ...DEFAULT_WORKBOOK_DATA_DEMO };
data.sheetOrder = ['sheet-0003'];
this._univerInstanceService.createUnit(UniverInstanceType.UNIVER_SHEET, data);
await awaitTime(loadingTimeout);
}
private async _loadDefaultDoc(loadingTimeout: number = AWAIT_LOADING_TIMEOUT): Promise<void> {
this._univerInstanceService.createUnit(UniverInstanceType.UNIVER_DOC, getDefaultDocData());
await awaitTime(loadingTimeout);
+10
View File
@@ -163,6 +163,16 @@ export class SpanModel extends Disposable {
return null;
}
/**
* Return index of merge data if (row,col) is in merge range. -1 means not in merge range.
* @param row
* @param column
* @returns {number} index of merge range.
*/
public getMergeDataIndex(row: number, column: number) {
return this._getMergeDataIndex(row, column);
}
public isRowContainsMergedCell(row: number) {
if (this._hasAll) {
return true;
+3 -3
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*/
import { BehaviorSubject, Subject } from 'rxjs';
import type { Observable } from 'rxjs';
import type { Nullable } from '../shared';
import type { IRangeType, IWorkbookData, IWorksheetData } from './typedef';
import { BehaviorSubject, Subject } from 'rxjs';
import { UnitModel, UniverInstanceType } from '../common/unit';
import { ILogService } from '../services/log/log.service';
import { Tools } from '../shared';
@@ -24,8 +26,6 @@ import { BooleanNumber } from '../types/enum';
import { getEmptySnapshot } from './empty-snapshot';
import { Styles } from './styles';
import { Worksheet } from './worksheet';
import type { Nullable } from '../shared';
import type { IRangeType, IWorkbookData, IWorksheetData } from './typedef';
export function getWorksheetUID(workbook: Workbook, worksheet: Worksheet): string {
return `${workbook.getUnitId()}|${worksheet.getSheetId()}`;
+4 -4
View File
@@ -14,6 +14,9 @@
* limitations under the License.
*/
import type { IObjectMatrixPrimitiveType, Nullable } from '../shared';
import type { Styles } from './styles';
import type { ICellData, ICellDataForSheetInterceptor, IFreeze, IRange, ISelectionCell, IWorksheetData } from './typedef';
import { BuildTextUtils } from '../docs';
import { ObjectMatrix, Tools } from '../shared';
import { createRowColIter } from '../shared/row-col-iter';
@@ -24,9 +27,6 @@ import { RowManager } from './row-manager';
import { mergeWorksheetSnapshotWithDefault } from './sheet-snapshot-utils';
import { SpanModel } from './span-model';
import { SheetViewModel } from './view-model';
import type { IObjectMatrixPrimitiveType, Nullable } from '../shared';
import type { Styles } from './styles';
import type { ICellData, ICellDataForSheetInterceptor, IFreeze, IRange, ISelectionCell, IWorksheetData } from './typedef';
/**
* The model of a Worksheet.
@@ -249,7 +249,7 @@ export class Worksheet {
}
/**
* Get the merged cell info list which has intersection with the given range.
* Get the merged cell info list which has intersection with the given range.
* @param {number} startRow The start row index of the range
* @param {number} startColumn The start column index of the range
* @param {number} endRow The end row index of the range
@@ -14,13 +14,13 @@
* limitations under the License.
*/
import { Registry } from '@univerjs/core';
import type { IDocumentRenderConfig, IRange, IScale, Nullable } from '@univerjs/core';
import { getScale } from '../basics/tools';
import type { BaseObject } from '../base-object';
import type { Vector2 } from '../basics/vector2';
import type { IBoundRectNoAngle, Vector2 } from '../basics/vector2';
import type { UniverRenderingContext } from '../context';
import { Registry } from '@univerjs/core';
import { getScale } from '../basics/tools';
export interface IExtensionConfig {
originTranslate?: Vector2; // docs
@@ -35,6 +35,7 @@ export interface IDrawInfo {
viewRanges: IRange[];
viewportKey: string;
checkOutOfViewBound?: boolean;
viewBound?: IBoundRectNoAngle;
}
export class ComponentExtension<T, U, V> {
uKey: string = '';
@@ -43,6 +43,7 @@ interface IRenderBGContext {
scaleY: number;
viewRanges: IRange[];
diffRanges: IRange[];
cellInfo: ISelectionCellWithMergeInfo;
}
export class Background extends SheetExtension {
@@ -56,6 +57,7 @@ export class Background extends SheetExtension {
return (this.parent as Spreadsheet)?.isPrinting ? this.PRINTING_Z_INDEX : this.Z_INDEX;
}
// eslint-disable-next-line max-lines-per-function
override draw(
ctx: UniverRenderingContext,
_parentScale: IScale,
@@ -86,6 +88,16 @@ export class Background extends SheetExtension {
diffRanges,
spreadsheetSkeleton,
} as IRenderBGContext;
const mergeRanges: IRange[] = [];
// Currently, viewRanges has only one range.
viewRanges.forEach((range) => {
// For merge cell.
// The background extension is not as strict as the font extension; the font extension must never be redrawn. Therefore, it is not necessary to be that complex.
const intersectMergeRangesInViewRanges = spreadsheetSkeleton.worksheet.getMergedCellRange(range.startRow, range.startColumn, range.endRow, range.endColumn);
mergeRanges.push(...intersectMergeRangesInViewRanges);
});
const renderBGCore = (rgb: string) => {
const bgColorMatrix = bgMatrixCacheByColor[rgb];
ctx.fillStyle = rgb || getColor([255, 255, 255])!;
@@ -93,11 +105,19 @@ export class Background extends SheetExtension {
renderBGContext.backgroundPaths = backgroundPaths;
ctx.beginPath();
// bgColorMatrix.forValue(renderBGByCell);
// Currently, viewRanges has only one range.
viewRanges.forEach((range) => {
Range.foreach(range, (row, col) => {
const index = spreadsheetSkeleton.worksheet.getSpanModel().getMergeDataIndex(row, col);
if (index !== -1) {
return;
}
const cellInfo = spreadsheetSkeleton.getCellByIndexWithNoHeader(row, col);
if (!cellInfo) return;
const bgConfig = bgColorMatrix.getValue(row, col);
if (bgConfig) {
renderBGContext.cellInfo = cellInfo;
this.renderBGByCell(renderBGContext, row, col);
}
});
@@ -106,45 +126,46 @@ export class Background extends SheetExtension {
ctx.closePath();
};
Object.keys(bgMatrixCacheByColor).forEach(renderBGCore);
const renderBGForMergedCells = (rgb: string) => {
const bgColorMatrix = bgMatrixCacheByColor[rgb];
ctx.fillStyle = rgb || getColor([255, 255, 255])!;
const backgroundPaths = new Path2D();
renderBGContext.backgroundPaths = backgroundPaths;
ctx.beginPath();
mergeRanges.forEach((range) => {
// bgConfig is requried to be checked in each color loop.
const bgConfig = bgColorMatrix.getValue(range.startRow, range.startColumn);
if (bgConfig) {
const cellInfo = spreadsheetSkeleton.getCellByIndexWithNoHeader(range.startRow, range.startColumn);
if (!cellInfo) return;
renderBGContext.cellInfo = cellInfo;
this.renderBGByCell(renderBGContext, range.startRow, range.startColumn);
}
});
ctx.fill(backgroundPaths);
ctx.closePath();
};
Object.keys(bgMatrixCacheByColor).forEach((rgb) => {
renderBGCore(rgb);
renderBGForMergedCells(rgb);
});
ctx.restore();
}
renderBGByCell(bgContext: IRenderBGContext, row: number, col: number) {
const { spreadsheetSkeleton, backgroundPositions, backgroundPaths, scaleX, scaleY, viewRanges, diffRanges } = bgContext;
// if (!checkOutOfViewBound && !inViewRanges(viewRanges, row, col)) {
// return true;
// }
const cellInfo = backgroundPositions?.getValue(row, col);
if (cellInfo == null) {
return true;
}
const { spreadsheetSkeleton, backgroundPaths, scaleX, scaleY, viewRanges, diffRanges, cellInfo } = bgContext;
let { startY, endY, startX, endX } = cellInfo;
const { isMerged, isMergedMainCell, mergeInfo } = cellInfo;
const renderRange = diffRanges && diffRanges.length > 0 ? diffRanges : viewRanges;
// isMerged isMergedMainCell are mutually exclusive. isMerged true then isMergedMainCell false.
if (isMerged) {
startY = mergeInfo.startY;
endY = mergeInfo.endY;
startX = mergeInfo.startX;
endX = mergeInfo.endX;
}
// isMergedMainCell has draw all other merged cells, no need draw again.
// For merged cells, and the current cell is the top-left cell in the merged region.
if (isMergedMainCell) {
startY = mergeInfo.startY;
endY = mergeInfo.endY;
startX = mergeInfo.startX;
endX = mergeInfo.endX;
}
// in merge range , but not top-left cell.
// if (isMerged) return true;
// const combineWithMergeRanges = mergeTo;
//expandRangeIfIntersects([...mergeTo], [mergeInfo]);
startY = mergeInfo.startY;
endY = mergeInfo.endY;
startX = mergeInfo.startX;
endX = mergeInfo.endX;
// If curr cell is not in the viewrange (viewport + merged cells), exit early.
if ((!isMerged && !isMergedMainCell) && !inViewRanges(renderRange!, row, col)) {
@@ -15,14 +15,14 @@
*/
import type { IScale } from '@univerjs/core';
import { numberToABC } from '@univerjs/core';
import type { UniverRenderingContext } from '../../../context';
import type { IAColumnCfg, IAColumnCfgObj, IColumnStyleCfg } from '../interfaces';
import type { SpreadsheetSkeleton } from '../sheet-skeleton';
import { numberToABC } from '@univerjs/core';
import { DEFAULT_FONTFACE_PLANE, FIX_ONE_PIXEL_BLUR_OFFSET, MIDDLE_CELL_POS_MAGIC_NUMBER } from '../../../basics/const';
import { getColor } from '../../../basics/tools';
import type { UniverRenderingContext } from '../../../context';
import { SheetColumnHeaderExtensionRegistry } from '../../extension';
import type { SpreadsheetSkeleton } from '../sheet-skeleton';
import type { IAColumnCfg, IAColumnCfgObj, IColumnStyleCfg } from '../interfaces';
import { SheetExtension } from './sheet-extension';
const UNIQUE_KEY = 'DefaultColumnHeaderLayoutExtension';
@@ -17,7 +17,7 @@
/* eslint-disable max-lines-per-function */
/* eslint-disable complexity */
import type { ICellDataForSheetInterceptor, IRange, IScale, Nullable, ObjectMatrix } from '@univerjs/core';
import type { ICellDataForSheetInterceptor, IRange, IScale, ISelectionCellWithMergeInfo, Nullable, ObjectMatrix } from '@univerjs/core';
import type { UniverRenderingContext } from '../../../context';
import type { Documents } from '../../docs/document';
import type { IDrawInfo } from '../../extension';
@@ -51,6 +51,7 @@ interface IRenderFontContext {
endY: number;
startX: number;
endX: number;
cellInfo: ISelectionCellWithMergeInfo;
}
export class Font extends SheetExtension {
@@ -100,22 +101,125 @@ export class Font extends SheetExtension {
spreadsheetSkeleton,
} as IRenderFontContext;
ctx.save();
// old way, it lags, because it has too many loops, fontMatrix holds all sheet font data.
// fontMatrix.forValue((row: number, col: number, fontsConfig: IFontCacheItem) => {
// this.renderFontByCellMatrix(renderFontContext, row, col, fontsConfig);
// });
const uniqueMergeRanges: IRange[] = [];
const mergeRangeIDSet = new Set();
// Currently, viewRanges has only one range.
viewRanges.forEach((range) => {
range.startColumn -= EXPAND_SIZE_FOR_RENDER_OVERFLOW;
range.endColumn += EXPAND_SIZE_FOR_RENDER_OVERFLOW;
range = clampRange(range);
// collect unique merge ranges intersect with view range.
// The ranges in mergeRanges must be unique. Otherwise, the font will rereder, text redrawing causes jagged edges or artifacts.
const intersectMergeRangesWithViewRanges = spreadsheetSkeleton.worksheet.getMergedCellRange(range.startRow, range.startColumn, range.endRow, range.endColumn);
intersectMergeRangesWithViewRanges.forEach((mergeRange) => {
const mergeRangeIndex = spreadsheetSkeleton.worksheet.getSpanModel().getMergeDataIndex(mergeRange.startRow, mergeRange.startColumn);
if (!mergeRangeIDSet.has(mergeRangeIndex)) {
mergeRangeIDSet.add(mergeRangeIndex);
uniqueMergeRanges.push(mergeRange);
}
});
Range.foreach(range, (row, col) => {
const index = spreadsheetSkeleton.worksheet.getSpanModel().getMergeDataIndex(row, col);
// put all merged cells to another pass to render. -1 means not merged.
if (index !== -1) {
return;
}
const cellInfo = spreadsheetSkeleton.getCellByIndexWithNoHeader(row, col);
if (!cellInfo) return;
renderFontContext.cellInfo = cellInfo;
this.renderFontEachCell(renderFontContext, row, col, fontMatrix);
});
});
uniqueMergeRanges.forEach((range) => {
const cellInfo = spreadsheetSkeleton.getCellByIndexWithNoHeader(range.startRow, range.startColumn);
renderFontContext.cellInfo = cellInfo;
this.renderFontEachCell(renderFontContext, range.startRow, range.startColumn, fontMatrix);
});
ctx.restore();
}
clipTextOverflow(renderFontContext: IRenderFontContext, row: number, col: number, fontMatrix: ObjectMatrix<IFontCacheItem>) {
renderFontEachCell(renderFontContext: IRenderFontContext, row: number, col: number, fontMatrix: ObjectMatrix<IFontCacheItem>) {
const { ctx, viewRanges, diffRanges, spreadsheetSkeleton, cellInfo } = renderFontContext;
//#region merged cell
let { startY, endY, startX, endX } = cellInfo;
const { isMerged, isMergedMainCell, mergeInfo } = cellInfo;
// merged, but not primary cell, then skip. DO NOT RENDER AGAIN, or that would cause font blurry.
if (isMerged && !isMergedMainCell) {
return true;
}
// merged and primary cell
if (isMergedMainCell) {
startY = mergeInfo.startY;
endY = mergeInfo.endY;
startX = mergeInfo.startX;
endX = mergeInfo.endX;
}
//#endregion
const fontsConfig = fontMatrix.getValue(row, col);
if (!fontsConfig) return true;
//#region overflow
// If the cell is overflowing, but the overflowRectangle has not been set,
// then overflowRectangle is set to undefined.
const overflowRange = spreadsheetSkeleton.overflowCache.getValue(row, col);
// If it's neither an overflow nor within the current range,
// then we can exit early
const renderRange = diffRanges && diffRanges.length > 0 ? diffRanges : viewRanges;
const notInMergeRange = !isMergedMainCell && !isMerged;
if (!overflowRange && notInMergeRange) {
if (!inViewRanges(renderRange, row, col)) {
return true;
}
}
//#endregion
const visibleRow = spreadsheetSkeleton.worksheet.getRowVisible(row);
const visibleCol = spreadsheetSkeleton.worksheet.getColVisible(col);
if (!visibleRow || !visibleCol) return true;
const cellData = spreadsheetSkeleton.worksheet.getCell(row, col) as ICellDataForSheetInterceptor || {};
if (cellData.fontRenderExtension?.isSkip) {
return true;
}
ctx.save();
ctx.beginPath();
//#region text overflow
renderFontContext.overflowRectangle = overflowRange;
renderFontContext.cellData = cellData;
renderFontContext.startX = startX;
renderFontContext.startY = startY;
renderFontContext.endX = endX;
renderFontContext.endY = endY;
this._clipTextOverflow(renderFontContext, row, col, fontMatrix);
//#endregion
ctx.translate(startX + FIX_ONE_PIXEL_BLUR_OFFSET, startY + FIX_ONE_PIXEL_BLUR_OFFSET);
this._renderDocuments(ctx, fontsConfig, startX, startY, endX, endY, row, col, spreadsheetSkeleton.overflowCache);
ctx.closePath();
ctx.restore();
};
private _clipTextOverflow(renderFontContext: IRenderFontContext, row: number, col: number, fontMatrix: ObjectMatrix<IFontCacheItem>) {
const { ctx, scale, overflowRectangle, rowHeightAccumulation, columnWidthAccumulation, cellData } = renderFontContext;
let { startX, endX, startY, endY } = renderFontContext;
@@ -203,77 +307,6 @@ export class Font extends SheetExtension {
}
}
renderFontEachCell(renderFontContext: IRenderFontContext, row: number, col: number, fontMatrix: ObjectMatrix<IFontCacheItem>) {
const { ctx, viewRanges, diffRanges, spreadsheetSkeleton } = renderFontContext;
//#region merged cell
// const calcHeader = false;
const cellInfo = spreadsheetSkeleton.getCellByIndexWithNoHeader(row, col);
let { startY, endY, startX, endX } = cellInfo;
const { isMerged, isMergedMainCell, mergeInfo } = cellInfo;
// merged, but not primary cell, then skip. DO NOT RENDER AGAIN, or that would cause font blurry.
if (isMerged && !isMergedMainCell) {
return true;
}
// merged and primary cell
if (isMergedMainCell) {
startY = mergeInfo.startY;
endY = mergeInfo.endY;
startX = mergeInfo.startX;
endX = mergeInfo.endX;
}
//#endregion
const fontsConfig = fontMatrix.getValue(row, col);
if (!fontsConfig) return true;
//#region overflow
// If the cell is overflowing, but the overflowRectangle has not been set,
// then overflowRectangle is set to undefined.
const overflowRange = spreadsheetSkeleton.overflowCache.getValue(row, col);
// If it's neither an overflow nor within the current range,
// then we can exit early
const renderRange = diffRanges && diffRanges.length > 0 ? diffRanges : viewRanges;
const notInMergeRange = !isMergedMainCell && !isMerged;
if (!overflowRange && notInMergeRange) {
if (!inViewRanges(renderRange, row, col)) {
return true;
}
}
//#endregion
const visibleRow = spreadsheetSkeleton.worksheet.getRowVisible(row);
const visibleCol = spreadsheetSkeleton.worksheet.getColVisible(col);
if (!visibleRow || !visibleCol) return true;
const cellData = spreadsheetSkeleton.worksheet.getCell(row, col) as ICellDataForSheetInterceptor || {};
if (cellData.fontRenderExtension?.isSkip) {
return true;
}
ctx.save();
ctx.beginPath();
//#region text overflow
renderFontContext.overflowRectangle = overflowRange;
renderFontContext.cellData = cellData;
renderFontContext.startX = startX;
renderFontContext.startY = startY;
renderFontContext.endX = endX;
renderFontContext.endY = endY;
this.clipTextOverflow(renderFontContext, row, col, fontMatrix);
//#endregion
ctx.translate(startX + FIX_ONE_PIXEL_BLUR_OFFSET, startY + FIX_ONE_PIXEL_BLUR_OFFSET);
this._renderDocuments(ctx, fontsConfig, startX, startY, endX, endY, row, col, spreadsheetSkeleton.overflowCache);
ctx.closePath();
ctx.restore();
};
private _renderDocuments(
ctx: UniverRenderingContext,
// docsConfig.documentSkeleton.getSkeletonData().pages[0].sections[0].columns[0].lines[0].divides[0].glyphGroup[0].fontStyle
@@ -251,7 +251,7 @@ export class SpreadsheetSkeleton extends Skeleton {
/**
* Range of visible area(range in viewBounds)
*/
private _rowColumnSegment: IRowColumnRange = {
private _visibleRange: IRowColumnRange = {
startRow: -1,
endRow: -1,
startColumn: -1,
@@ -327,7 +327,11 @@ export class SpreadsheetSkeleton extends Skeleton {
* Range of visible area(range in viewBounds)
*/
get rowColumnSegment(): IRowColumnRange {
return this._rowColumnSegment;
return this._visibleRange;
}
get visibleArea(): IRowColumnRange {
return this._visibleRange;
}
// get dataMergeCache(): IRange[] {
@@ -367,7 +371,7 @@ export class SpreadsheetSkeleton extends Skeleton {
this._columnTotalWidth = 0;
this._rowHeaderWidth = 0;
this._columnHeaderHeight = 0;
this._rowColumnSegment = {
this._visibleRange = {
startRow: -1,
endRow: -1,
startColumn: -1,
@@ -459,7 +463,7 @@ export class SpreadsheetSkeleton extends Skeleton {
}
if (bounds != null) {
this._rowColumnSegment = this.getRowColumnSegment(bounds);
this._visibleRange = this.getRowColumnSegment(bounds);
}
return true;
@@ -1062,6 +1066,12 @@ export class SpreadsheetSkeleton extends Skeleton {
};
}
/**
* New merge info, but position without header.
* @param row
* @param column
* @returns {ISelectionCellWithMergeInfo} cellInfo with merge info
*/
getCellByIndexWithNoHeader(row: number, column: number): ISelectionCellWithMergeInfo {
const { rowHeightAccumulation, columnWidthAccumulation } = this;
@@ -1655,7 +1665,6 @@ export class SpreadsheetSkeleton extends Skeleton {
// }
/**
* @deprecated use _getCellMergeInfo instead.
* get the current row and column segment visible merge data
* @returns {IRange} The visible merge data
*/
@@ -1677,27 +1686,25 @@ export class SpreadsheetSkeleton extends Skeleton {
}
private _calculateStylesCache(): void {
const rowColumnSegment = this._rowColumnSegment;
const rowColumnSegment = this._visibleRange;
const columnWidthAccumulation = this.columnWidthAccumulation;
const { startRow, endRow, startColumn, endColumn } = rowColumnSegment;
if (endColumn === -1 || endRow === -1) return;
// const mergeRanges = this.getCurrentRowColumnSegmentMergeData(this._rowColumnSegment);
// for (const mergeRange of mergeRanges) {
// this._setStylesCache(mergeRange.startRow, mergeRange.startColumn, {
// mergeRange,
// });
// }
const mergeRanges = this.getCurrentRowColumnSegmentMergeData(this._visibleRange);
for (const mergeRange of mergeRanges) {
this._setStylesCache(mergeRange.startRow, mergeRange.startColumn, {
mergeRange,
});
}
// const mergeRange = mergeRanges.length ? mergeRanges[0] : undefined;
for (let r = startRow; r <= endRow; r++) {
if (this.worksheet.getRowVisible(r) === false) continue;
for (let c = startColumn; c <= endColumn; c++) {
this._setStylesCache(r, c, {
cacheItem: { bg: true, border: true },
});
this._setStylesCache(r, c, { cacheItem: { bg: true, border: true } });
}
// Calculate the text length for overflow situations, focusing on the leftmost column within the visible range.
@@ -1844,7 +1851,7 @@ export class SpreadsheetSkeleton extends Skeleton {
* @param col {number}
* @param options {{ mergeRange: IRange; cacheItem: ICacheItem } | undefined}
*/
private _setStylesCache(row: number, col: number, options?: { mergeRange?: IRange; cacheItem?: ICacheItem }): void {
private _setStylesCache(row: number, col: number, options: { mergeRange?: IRange; cacheItem?: ICacheItem }): void {
if (row === -1 || col === -1) {
return;
}
@@ -1862,13 +1869,11 @@ export class SpreadsheetSkeleton extends Skeleton {
}
const { isMerged, isMergedMainCell, startRow, startColumn, endRow, endColumn } = this._getCellMergeInfo(row, col);
if (options) {
options.mergeRange = { startRow, startColumn, endRow, endColumn };
}
options.mergeRange = { startRow, startColumn, endRow, endColumn };
const hidden = this.worksheet.getColVisible(col) === false || this.worksheet.getRowVisible(row) === false;
// hiddene and not in mergeRange return.
// hidden and not in mergeRange return.
if (hidden) {
// If the cell is merged and is not the main cell, the cell is not rendered.
if (isMerged && !isMergedMainCell) {
@@ -16,11 +16,14 @@
import type { IRange, ISelectionCellWithMergeInfo, Nullable, ObjectMatrix } from '@univerjs/core';
import type { IBoundRectNoAngle, IViewportInfo, Vector2 } from '../../basics/vector2';
import type { Canvas } from '../../canvas';
import type { UniverRenderingContext2D } from '../../context';
import type { Engine } from '../../engine';
import type { Scene } from '../../scene';
import type { SceneViewer } from '../../scene-viewer';
import type { IDrawInfo } from '../extension';
import type { Background } from './extensions/background';
import type { Border } from './extensions/border';
import type { Font } from './extensions/font';
@@ -131,7 +134,8 @@ export class Spreadsheet extends SheetComponent {
viewRanges,
checkOutOfViewBound: true,
viewportKey: viewportInfo.viewportKey,
});
viewBound: viewportInfo.cacheBound,
} as IDrawInfo);
this.addRenderFrameTimeMetricToScene(timeKey, Tools.now() - st, scene);
}
}
@@ -15,6 +15,9 @@
*/
import type { IAccessor, ICellData, ICommand, IMutationInfo, IRange, Nullable } from '@univerjs/core';
import type { IMoveRangeMutationParams } from '../mutations/move-range.mutation';
import type { ISetSelectionsOperationParams } from '../operations/selection.operation';
import {
cellToRange,
CommandType,
@@ -29,11 +32,8 @@ import {
sequenceExecute,
Tools,
} from '@univerjs/core';
import { SheetInterceptorService } from '../../services/sheet-interceptor/sheet-interceptor.service';
import type { IMoveRangeMutationParams } from '../mutations/move-range.mutation';
import { MoveRangeMutation } from '../mutations/move-range.mutation';
import type { ISetSelectionsOperationParams } from '../operations/selection.operation';
import { SetSelectionsOperation } from '../operations/selection.operation';
import { alignToMergedCellsBorders, getPrimaryForRange } from './utils/selection-utils';
import { getSheetCommandTarget } from './utils/target-util';
+2 -3
View File
@@ -29,7 +29,7 @@ export default defineConfig({
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['json', { outputFile: 'playwright-report.json' }],
['html'],
['html', { attachments: true }],
],
outputDir: './playwright-report',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@@ -38,9 +38,8 @@ export default defineConfig({
baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
trace: 'retain-on-failure',
},
/* Configure projects for major browsers */
projects: [
{