mirror of
https://github.com/dream-num/univer.git
synced 2026-08-31 01:42:03 +08:00
fix: merged cell bg not fully ender (#3671)
Co-authored-by: GitHub Actions <actions@github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -108,6 +108,7 @@ storybook-static
|
||||
# playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright-assets/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
playwright-report.json
|
||||
|
||||
Vendored
+3
-2
@@ -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();
|
||||
});
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 168 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()}`;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user