From 15e3e9805f2f81a8ec92f4491bbccaf161e2be4c Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Wed, 9 Mar 2022 12:39:41 +0000 Subject: [PATCH] [fix] Refresh bounding boxes when fonts load (#612) * remove font face fallbacks * When fonts load, force the document to recalculate bounding boxes. --- packages/tldraw/src/Tldraw.tsx | 13 ++++++ packages/tldraw/src/hooks/useStylesheet.ts | 7 ++- packages/tldraw/src/state/TldrawApp.ts | 46 +++++++++++++++++++ .../src/state/shapes/TextUtil/TextUtil.tsx | 3 +- .../src/state/shapes/shared/TextLabel.tsx | 8 ++-- .../src/state/shapes/shared/getTextSize.ts | 6 ++- .../src/state/shapes/shared/shape-styles.ts | 6 +-- 7 files changed, 78 insertions(+), 11 deletions(-) diff --git a/packages/tldraw/src/Tldraw.tsx b/packages/tldraw/src/Tldraw.tsx index 6d7cfb427..a80ad2205 100644 --- a/packages/tldraw/src/Tldraw.tsx +++ b/packages/tldraw/src/Tldraw.tsx @@ -255,6 +255,19 @@ export function Tldraw({ onExport, ]) + React.useLayoutEffect(() => { + if (typeof window === 'undefined') return + if (!window.document?.fonts) return + + function refreshBoundingBoxes() { + app.refreshBoundingBoxes() + } + window.document.fonts.addEventListener('loadingdone', refreshBoundingBoxes) + return () => { + window.document.fonts.removeEventListener('loadingdone', refreshBoundingBoxes) + } + }, [app]) + // Use the `key` to ensure that new selector hooks are made when the id changes return ( diff --git a/packages/tldraw/src/hooks/useStylesheet.ts b/packages/tldraw/src/hooks/useStylesheet.ts index c3bb74016..7513c5cc3 100644 --- a/packages/tldraw/src/hooks/useStylesheet.ts +++ b/packages/tldraw/src/hooks/useStylesheet.ts @@ -3,18 +3,21 @@ import * as React from 'react' const styles = new Map() const UID = `Tldraw-fonts` +const WEBFONT_URL = + 'https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block' const CSS = ` -@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block'); +@import url(''); ` export function useStylesheet() { React.useLayoutEffect(() => { if (styles.get(UID)) return const style = document.createElement('style') - style.innerHTML = CSS + style.innerHTML = `@import url('${WEBFONT_URL}');` style.setAttribute('id', UID) document.head.appendChild(style) styles.set(UID, style) + return () => { if (style && document.head.contains(style)) { document.head.removeChild(style) diff --git a/packages/tldraw/src/state/TldrawApp.ts b/packages/tldraw/src/state/TldrawApp.ts index 2d9f472e4..30243e4d7 100644 --- a/packages/tldraw/src/state/TldrawApp.ts +++ b/packages/tldraw/src/state/TldrawApp.ts @@ -80,6 +80,7 @@ import { LineTool } from './tools/LineTool' import { ArrowTool } from './tools/ArrowTool' import { StickyTool } from './tools/StickyTool' import { StateManager } from './StateManager' +import { clearPrevSize } from './shapes/shared/getTextSize' const uuid = Utils.uniqueId() @@ -3075,6 +3076,51 @@ export class TldrawApp extends StateManager { this.currentTool.onKeyUp?.(key, info, e) } + /** Force bounding boxes to reset when the document loads. */ + refreshBoundingBoxes = () => { + // force a change to every text shape + const force = this.shapes.map((shape) => { + return [ + shape.id, + { + point: [...shape.point], + ...('label' in shape && { label: '' }), + }, + ] + }) + + const restore = this.shapes.map((shape) => { + return [ + shape.id, + { + point: [...shape.point], + ...('label' in shape && { label: shape.label }), + }, + ] + }) + + clearPrevSize() + + this.patchState({ + document: { + pages: { + [this.currentPageId]: { + shapes: Object.fromEntries(force), + }, + }, + }, + }) + this.patchState({ + document: { + pages: { + [this.currentPageId]: { + shapes: Object.fromEntries(restore), + }, + }, + }, + }) + } + /* ------------- Renderer Event Handlers ------------ */ onDragOver: TLDropEventHandler = (e) => { diff --git a/packages/tldraw/src/state/shapes/TextUtil/TextUtil.tsx b/packages/tldraw/src/state/shapes/TextUtil/TextUtil.tsx index 0647ed840..ea27d5c76 100644 --- a/packages/tldraw/src/state/shapes/TextUtil/TextUtil.tsx +++ b/packages/tldraw/src/state/shapes/TextUtil/TextUtil.tsx @@ -215,8 +215,9 @@ export class TextUtil extends TDShapeUtil { } if (!melm.parentNode) document.body.appendChild(melm) - melm.textContent = this.texts.get(shape.id) ?? shape.text + melm.style.font = getFontStyle(shape.style) + melm.textContent = this.texts.get(shape.id) ?? shape.text // In tests, offsetWidth and offsetHeight will be 0 const width = melm.offsetWidth || 1 diff --git a/packages/tldraw/src/state/shapes/shared/TextLabel.tsx b/packages/tldraw/src/state/shapes/shared/TextLabel.tsx index 9486b6524..2d037b2af 100644 --- a/packages/tldraw/src/state/shapes/shared/TextLabel.tsx +++ b/packages/tldraw/src/state/shapes/shared/TextLabel.tsx @@ -31,7 +31,6 @@ export const TextLabel = React.memo(function TextLabel({ }: TextLabelProps) { const rInput = React.useRef(null) const rIsMounted = React.useRef(false) - const size = getTextLabelSize(text, font) const rTextContent = React.useRef(text) @@ -95,10 +94,11 @@ export const TextLabel = React.memo(function TextLabel({ React.useLayoutEffect(() => { const elm = rInnerWrapper.current if (!elm) return + const size = getTextLabelSize(text, font) elm.style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)` - elm.style.width = size[0] + 'px' - elm.style.height = size[1] + 'px' - }, [size, offsetY, offsetX, scale]) + elm.style.width = size[0] + 1 + 'px' + elm.style.height = size[1] + 1 + 'px' + }, [text, font, offsetY, offsetX, scale]) return ( diff --git a/packages/tldraw/src/state/shapes/shared/getTextSize.ts b/packages/tldraw/src/state/shapes/shared/getTextSize.ts index 1280c1491..5109cc3cb 100644 --- a/packages/tldraw/src/state/shapes/shared/getTextSize.ts +++ b/packages/tldraw/src/state/shapes/shared/getTextSize.ts @@ -42,6 +42,10 @@ let prevText = '' let prevFont = '' let prevSize = [0, 0] +export function clearPrevSize() { + prevText = '' +} + export function getTextLabelSize(text: string, font: string) { if (!text) { return [16, 32] @@ -61,7 +65,7 @@ export function getTextLabelSize(text: string, font: string) { prevText = text prevFont = font - melm.textContent = `${text}` + melm.textContent = text melm.style.font = font // In tests, offsetWidth and offsetHeight will be 0 diff --git a/packages/tldraw/src/state/shapes/shared/shape-styles.ts b/packages/tldraw/src/state/shapes/shared/shape-styles.ts index abb6ec802..9f238ba99 100644 --- a/packages/tldraw/src/state/shapes/shared/shape-styles.ts +++ b/packages/tldraw/src/state/shapes/shared/shape-styles.ts @@ -85,9 +85,9 @@ const fontSizes = { const fontFaces = { [FontStyle.Script]: '"Caveat Brush"', - [FontStyle.Sans]: '"Source Sans Pro", sans-serif', - [FontStyle.Serif]: '"Crimson Pro", serif', - [FontStyle.Mono]: '"Source Code Pro", monospace', + [FontStyle.Sans]: '"Source Sans Pro"', + [FontStyle.Serif]: '"Crimson Pro"', + [FontStyle.Mono]: '"Source Code Pro"', } const fontSizeModifiers = {