mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 13:29:02 +08:00
f85b35227b
### 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
116 lines
3.0 KiB
TypeScript
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
|
|
);
|