fix(grid): sharpen canvas text at fractional scaling
This commit is contained in:
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;
|
||||
|
||||
Reference in new issue
Block a user