Files
AFFiNE-Mirror/blocksuite/affine/blocks/paragraph/src/turbo/paragraph-painter.worker.ts
T
doodlewind f85b35227b feat(editor): replace flat layout cache with tree in turbo renderer (#11319)
### TL;DR

Refactored the BlockSuite turbo renderer to use a hierarchical tree structure for layouts instead of a flat list, improving rendering accuracy and performance.

### What changed?

- Redesigned the layout system to use a tree structure (`ViewportLayoutTree`) that better represents the document hierarchy
- Added `blockId` to all layout objects for better tracking and debugging
- Updated the layout query mechanism to work with models directly instead of components
- Enhanced error handling with more descriptive warnings and error messages
- Improved the painting process to traverse the layout tree recursively
- Fixed viewport coordinate calculations for more accurate rendering
- Updated the worker communication to support the new tree-based layout structure

### Why make this change?

The previous flat layout structure didn't properly represent the hierarchical nature of documents, leading to rendering issues with nested blocks. This tree-based approach:

1. Better represents the actual document structure
2. Improves rendering accuracy for nested elements
3. Makes debugging easier with more consistent block identification
4. Provides a more robust foundation for future rendering optimizations
5. Reduces the likelihood of rendering artifacts when scrolling or zooming
2025-04-10 08:49:23 +00:00

116 lines
3.0 KiB
TypeScript

import type {
BlockLayout,
BlockLayoutPainter,
TextRect,
WorkerToHostMessage,
} from '@blocksuite/affine-gfx-turbo-renderer';
import {
BlockLayoutPainterExtension,
getBaseline,
} from '@blocksuite/affine-gfx-turbo-renderer/painter';
interface SentenceLayout {
text: string;
rects: TextRect[];
fontSize: number;
}
export interface ParagraphLayout extends BlockLayout {
type: 'affine:paragraph';
sentences: SentenceLayout[];
}
const debugSentenceBorder = false;
function isParagraphLayout(layout: BlockLayout): layout is ParagraphLayout {
return layout.type === 'affine:paragraph';
}
class ParagraphLayoutPainter implements BlockLayoutPainter {
private static readonly supportFontFace =
typeof FontFace !== 'undefined' &&
typeof self !== 'undefined' &&
'fonts' in self;
static readonly font = ParagraphLayoutPainter.supportFontFace
? new FontFace(
'Inter',
`url(https://fonts.gstatic.com/s/inter/v18/UcCo3FwrK3iLTcviYwYZ8UA3.woff2)`
)
: null;
static fontLoaded = !ParagraphLayoutPainter.supportFontFace;
static {
if (ParagraphLayoutPainter.supportFontFace && ParagraphLayoutPainter.font) {
// @ts-expect-error worker fonts API
self.fonts.add(ParagraphLayoutPainter.font);
ParagraphLayoutPainter.font
.load()
.then(() => {
ParagraphLayoutPainter.fontLoaded = true;
})
.catch(error => {
console.error('Failed to load Inter font:', error);
});
}
}
paint(
ctx: OffscreenCanvasRenderingContext2D,
layout: BlockLayout,
layoutBaseX: number,
layoutBaseY: number
): void {
if (!ParagraphLayoutPainter.fontLoaded) {
const message: WorkerToHostMessage = {
type: 'paintError',
error: 'Font not loaded',
blockType: 'affine:paragraph',
};
self.postMessage(message);
return;
}
if (!isParagraphLayout(layout)) {
console.warn(
'Expected paragraph layout but received different format:',
layout
);
return;
}
const renderedPositions = new Set<string>();
layout.sentences.forEach(sentence => {
const fontSize = sentence.fontSize;
const baselineY = getBaseline(fontSize);
if (fontSize !== 15) return; // TODO: fine-tune for heading font sizes
ctx.font = `${fontSize}px Inter`;
ctx.strokeStyle = 'yellow';
sentence.rects.forEach(textRect => {
const x = textRect.rect.x - layoutBaseX;
const y = textRect.rect.y - layoutBaseY;
const posKey = `${x},${y}`;
// Only render if we haven't rendered at this position before
if (renderedPositions.has(posKey)) return;
if (debugSentenceBorder) {
ctx.strokeRect(x, y, textRect.rect.w, textRect.rect.h);
}
ctx.fillStyle = 'black';
ctx.fillText(textRect.text, x, y + baselineY);
renderedPositions.add(posKey);
});
});
}
}
export const ParagraphLayoutPainterExtension = BlockLayoutPainterExtension(
'affine:paragraph',
ParagraphLayoutPainter
);