fix(grid): sharpen canvas text at fractional scaling

This commit is contained in:
TangT authored and GitHub committed 2026-08-05 02:59:34 +08:00
1 parent eed0a26b6f
commit 16369acbda
5 files changed
+163 -29

No files matched your search

@@ -1,6 +1,6 @@
import { effectScope } from "vue";
import { describe, expect, it, vi } from "vitest";
import { useDataGridCanvasRuntime, type DataGridAnimationFrameDriver } from "@/composables/useDataGridCanvasRuntime";
import { dataGridCanvasDevicePixelSize, useDataGridCanvasRuntime, type DataGridAnimationFrameDriver } from "@/composables/useDataGridCanvasRuntime";
import { useDataGridScrollbars } from "@/composables/useDataGridScrollbars";
function createFrameDriver() {
@@ -26,6 +26,34 @@ function createObserver() {
}
describe("data grid visual runtimes", () => {
it("reads the exact device-pixel content box reported for a canvas", () => {
const size = dataGridCanvasDevicePixelSize({
contentRect: { width: 801, height: 399 },
devicePixelContentBoxSize: [{ inlineSize: 1001, blockSize: 499 }],
} as unknown as ResizeObserverEntry);
expect(size).toEqual({ cssWidth: 801, cssHeight: 399, pixelWidth: 1001, pixelHeight: 499 });
});
it("observes the canvas device-pixel box together with its viewport", () => {
const resize = createObserver();
const viewport = { children: [] } as unknown as Element;
const surface = { children: [] } as unknown as Element;
const runtime = useDataGridCanvasRuntime({
draw: vi.fn(),
syncViewport: vi.fn(),
getViewport: () => viewport,
getSurface: () => surface,
createResizeObserver: () => resize.observer,
});
runtime.observeViewport();
expect(resize.observe).toHaveBeenNthCalledWith(1, viewport);
expect(resize.observe).toHaveBeenNthCalledWith(2, surface, { box: "device-pixel-content-box" });
runtime.dispose();
});
it("coalesces canvas frames and cancels all resources on dispose", () => {
const frames = createFrameDriver();
const resize = createObserver();
@@ -1,4 +1,5 @@
import { getCurrentScope, onScopeDispose } from "vue";
import type { CanvasDevicePixelSize } from "@/lib/dataGrid/canvasDataGridRenderer";
export interface DataGridAnimationFrameDriver {
request(callback: FrameRequestCallback): number;
@@ -7,8 +8,9 @@ export interface DataGridAnimationFrameDriver {
export interface DataGridCanvasRuntimeOptions {
draw(): void;
syncViewport(): void;
syncViewport(entries?: readonly ResizeObserverEntry[]): void;
getViewport(): Element | null;
getSurface?: () => Element | null;
refreshPixelRatio?: () => void;
frameDriver?: DataGridAnimationFrameDriver;
createResizeObserver?: (callback: ResizeObserverCallback) => ResizeObserver | null;
@@ -27,6 +29,18 @@ export interface DataGridCanvasRuntime {
dispose(): void;
}
export function dataGridCanvasDevicePixelSize(entry: ResizeObserverEntry): CanvasDevicePixelSize | null {
const boxes = entry.devicePixelContentBoxSize;
const box = Array.isArray(boxes) ? boxes[0] : (boxes as unknown as ResizeObserverSize | undefined);
if (!box || !Number.isFinite(box.inlineSize) || !Number.isFinite(box.blockSize) || box.inlineSize <= 0 || box.blockSize <= 0) return null;
return {
cssWidth: entry.contentRect.width,
cssHeight: entry.contentRect.height,
pixelWidth: Math.round(box.inlineSize),
pixelHeight: Math.round(box.blockSize),
};
}
function defaultFrameDriver(): DataGridAnimationFrameDriver {
return {
request: (callback) => requestAnimationFrame(callback),
@@ -88,10 +102,18 @@ export function useDataGridCanvasRuntime(options: DataGridCanvasRuntimeOptions):
options.syncViewport();
const viewport = options.getViewport();
if (!viewport) return;
resizeObserver = createResizeObserver(() => {
if (active && !disposed) options.syncViewport();
resizeObserver = createResizeObserver((entries) => {
if (active && !disposed) options.syncViewport(entries);
});
resizeObserver?.observe(viewport);
const surface = options.getSurface?.();
if (resizeObserver && surface && surface !== viewport) {
try {
resizeObserver.observe(surface, { box: "device-pixel-content-box" });
} catch {
resizeObserver.observe(surface);
}
}
},
pause() {
active = false;