Files
AFFiNE-Mirror/blocksuite/affine/gfx/text/src/element-renderer/index.ts
T

95 lines
2.3 KiB
TypeScript

import {
type ElementRenderer,
ElementRendererExtension,
} from '@blocksuite/affine-block-surface';
import { DefaultTheme, type TextElementModel } from '@blocksuite/affine-model';
import { deltaInsertsToChunks } from '@blocksuite/std/inline';
import {
getFontString,
getLineHeight,
getTextWidth,
isRTL,
wrapTextDeltas,
} from './utils.js';
export const text: ElementRenderer<TextElementModel> = (
model,
ctx,
matrix,
renderer
) => {
const { fontSize, fontWeight, fontStyle, fontFamily, textAlign, rotate } =
model;
const [, , w, h] = model.deserializedXYWH;
const cx = w / 2;
const cy = h / 2;
ctx.setTransform(
matrix.translateSelf(cx, cy).rotateSelf(rotate).translateSelf(-cx, -cy)
);
// const deltas: ITextDelta[] = yText.toDelta() as ITextDelta[];
const font = getFontString({
fontStyle,
fontWeight,
fontSize,
fontFamily,
});
const deltas = wrapTextDeltas(model.text, font, w);
const lines = deltaInsertsToChunks(deltas);
const lineHeightPx = getLineHeight(fontFamily, fontSize, fontWeight);
const horizontalOffset =
textAlign === 'center' ? w / 2 : textAlign === 'right' ? w : 0;
const color = renderer.getColorValue(
model.color,
DefaultTheme.textColor,
true
);
ctx.font = font;
ctx.fillStyle = color;
ctx.textAlign = textAlign;
ctx.textBaseline = 'ideographic';
for (const [lineIndex, line] of lines.entries()) {
let beforeTextWidth = 0;
for (const delta of line) {
const str = delta.insert;
const rtl = isRTL(str);
const shouldTemporarilyAttach = rtl && !ctx.canvas.isConnected;
if (shouldTemporarilyAttach) {
// to correctly render RTL text mixed with LTR, we have to append it
// to the DOM
document.body.append(ctx.canvas);
}
ctx.canvas.setAttribute('dir', rtl ? 'rtl' : 'ltr');
// 0.5 comes from v-line padding
const offset =
textAlign === 'center' ? 0 : textAlign === 'right' ? -0.5 : 0.5;
ctx.fillText(
str,
horizontalOffset + beforeTextWidth + offset,
(lineIndex + 1) * lineHeightPx
);
beforeTextWidth += getTextWidth(str, font);
if (shouldTemporarilyAttach) {
ctx.canvas.remove();
}
}
}
};
export const TextElementRendererExtension = ElementRendererExtension(
'text',
text
);
export * from './utils';