From 5c98836e987e5fde28ca60fdfd82bb13dffa6656 Mon Sep 17 00:00:00 2001 From: Jordan Parker Date: Tue, 17 Feb 2026 14:35:02 +0200 Subject: [PATCH] fix: wait for fonts to be renderable before text measurement document.fonts.load() resolves when fonts are downloaded but canvas measureText() may still return fallback font metrics. Chain document.fonts.ready and verify with document.fonts.check() in a requestAnimationFrame retry loop before calling convertToExcalidrawElements. Fixes text clipping in labeled shapes and standalone text elements. --- src/App.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index be8980b..8922db9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,10 @@ function setClientName(name: string) { localStorage.setItem('drawbridge:client-name', name); } -// Font preloading — ensure Excalidraw fonts are ready before text measurement +// Font preloading — ensure Excalidraw fonts are ready before text measurement. +// document.fonts.load() resolves when fonts are downloaded, but canvas measureText() +// may still use a fallback font briefly. We verify with document.fonts.check() in a +// retry loop to ensure the font is truly available for measurement. let fontsReady: Promise | null = null; function ensureFontsLoaded(): Promise { if (!fontsReady) { @@ -38,11 +41,28 @@ function ensureFontsLoaded(): Promise { document.fonts.load('400 16px Assistant'), document.fonts.load('500 16px Assistant'), document.fonts.load('700 16px Assistant'), - ]).then(() => {}); + ]) + .then(() => document.fonts.ready) + .then(() => waitForFontCheck('20px Excalifont')); } return fontsReady; } +function waitForFontCheck(font: string, maxRetries = 20): Promise { + return new Promise((resolve) => { + let attempts = 0; + function check() { + if (document.fonts.check(font) || attempts >= maxRetries) { + resolve(); + } else { + attempts++; + requestAnimationFrame(check); + } + } + check(); + }); +} + // Detect whether elements are skeleton format (need conversion) or already fully converted function needsConversion(elements: any[]): boolean { if (elements.some((el: any) => el.label)) return true;