editable text: DOM overlay, IME, caret, metrics
텍스트를 GPU texture로 그리는 것과 사용자가 텍스트를 편집하는 것은 다른 문제입니다. IME, caret, selection range, accessibility까지 생각하면 편집 중에는 DOM overlay가 가장 현실적인 시작점입니다.
GPU preview와 DOM edit layer를 분리한다
평소에는 GPU texture나 glyph atlas로 text node를 그립니다. 더블클릭해서 편집할 때만 같은 위치에 DOM textarea를 얹습니다.
function beginTextEdit(node, camera, overlayRoot) {
const screen = worldBoundsToScreen(node.worldBounds, camera);
const textarea = document.createElement("textarea");
Object.assign(textarea.style, {
position: "absolute",
left: `${screen.x}px`,
top: `${screen.y}px`,
width: `${screen.width}px`,
height: `${screen.height}px`,
font: node.fontCss,
transformOrigin: "0 0",
transform: `rotate(${node.rotation}rad)`
});
textarea.value = node.text;
overlayRoot.append(textarea);
textarea.focus();
return textarea;
}
IME composition은 브라우저가 DOM input에서 처리하게 두는 편이 안정적입니다.
편집 중에는 live update와 commit을 분리한다
textarea.addEventListener("input", () => {
dispatch({ type: "previewTextEdit", id: node.id, text: textarea.value });
});
textarea.addEventListener("compositionstart", () => {
dispatch({ type: "imeComposition", active: true });
});
textarea.addEventListener("compositionend", () => {
dispatch({ type: "imeComposition", active: false });
});
Enter, Escape, blur 처리 정책도 tool state machine과 연결해야 합니다.
metrics mismatch를 줄인다
DOM과 canvas text metrics는 완전히 같지 않을 수 있습니다. 그래서 text layout engine을 하나로 정하고, DOM edit layer는 그 결과에 최대한 맞추는 역할로 두는 편이 좋습니다.
font load
measure text
layout lines
GPU preview texture
DOM edit overlay sync
오늘의 핵심
editable text는 renderer 문제가 아니라 input, layout, accessibility가 섞인 editor 기능입니다. GPU preview와 DOM edit layer를 분리해야 구현이 현실적입니다.