[fix] Refresh bounding boxes when fonts load (#612)

* remove font face fallbacks

* When fonts load, force the document to recalculate bounding boxes.
This commit is contained in:
Steve Ruiz 2022-03-09 12:39:41 +00:00 committed by GitHub
parent 1778a49272
commit 15e3e9805f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 11 deletions

View file

@ -255,6 +255,19 @@ export function Tldraw({
onExport, 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 // Use the `key` to ensure that new selector hooks are made when the id changes
return ( return (
<TldrawContext.Provider value={app}> <TldrawContext.Provider value={app}>

View file

@ -3,18 +3,21 @@ import * as React from 'react'
const styles = new Map<string, HTMLStyleElement>() const styles = new Map<string, HTMLStyleElement>()
const UID = `Tldraw-fonts` 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 = ` 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() { export function useStylesheet() {
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
if (styles.get(UID)) return if (styles.get(UID)) return
const style = document.createElement('style') const style = document.createElement('style')
style.innerHTML = CSS style.innerHTML = `@import url('${WEBFONT_URL}');`
style.setAttribute('id', UID) style.setAttribute('id', UID)
document.head.appendChild(style) document.head.appendChild(style)
styles.set(UID, style) styles.set(UID, style)
return () => { return () => {
if (style && document.head.contains(style)) { if (style && document.head.contains(style)) {
document.head.removeChild(style) document.head.removeChild(style)

View file

@ -80,6 +80,7 @@ import { LineTool } from './tools/LineTool'
import { ArrowTool } from './tools/ArrowTool' import { ArrowTool } from './tools/ArrowTool'
import { StickyTool } from './tools/StickyTool' import { StickyTool } from './tools/StickyTool'
import { StateManager } from './StateManager' import { StateManager } from './StateManager'
import { clearPrevSize } from './shapes/shared/getTextSize'
const uuid = Utils.uniqueId() const uuid = Utils.uniqueId()
@ -3075,6 +3076,51 @@ export class TldrawApp extends StateManager<TDSnapshot> {
this.currentTool.onKeyUp?.(key, info, e) 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 ------------ */ /* ------------- Renderer Event Handlers ------------ */
onDragOver: TLDropEventHandler = (e) => { onDragOver: TLDropEventHandler = (e) => {

View file

@ -215,8 +215,9 @@ export class TextUtil extends TDShapeUtil<T, E> {
} }
if (!melm.parentNode) document.body.appendChild(melm) if (!melm.parentNode) document.body.appendChild(melm)
melm.textContent = this.texts.get(shape.id) ?? shape.text
melm.style.font = getFontStyle(shape.style) melm.style.font = getFontStyle(shape.style)
melm.textContent = this.texts.get(shape.id) ?? shape.text
// In tests, offsetWidth and offsetHeight will be 0 // In tests, offsetWidth and offsetHeight will be 0
const width = melm.offsetWidth || 1 const width = melm.offsetWidth || 1

View file

@ -31,7 +31,6 @@ export const TextLabel = React.memo(function TextLabel({
}: TextLabelProps) { }: TextLabelProps) {
const rInput = React.useRef<HTMLTextAreaElement>(null) const rInput = React.useRef<HTMLTextAreaElement>(null)
const rIsMounted = React.useRef(false) const rIsMounted = React.useRef(false)
const size = getTextLabelSize(text, font)
const rTextContent = React.useRef(text) const rTextContent = React.useRef(text)
@ -95,10 +94,11 @@ export const TextLabel = React.memo(function TextLabel({
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
const elm = rInnerWrapper.current const elm = rInnerWrapper.current
if (!elm) return if (!elm) return
const size = getTextLabelSize(text, font)
elm.style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)` elm.style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`
elm.style.width = size[0] + 'px' elm.style.width = size[0] + 1 + 'px'
elm.style.height = size[1] + 'px' elm.style.height = size[1] + 1 + 'px'
}, [size, offsetY, offsetX, scale]) }, [text, font, offsetY, offsetX, scale])
return ( return (
<TextWrapper> <TextWrapper>

View file

@ -42,6 +42,10 @@ let prevText = ''
let prevFont = '' let prevFont = ''
let prevSize = [0, 0] let prevSize = [0, 0]
export function clearPrevSize() {
prevText = ''
}
export function getTextLabelSize(text: string, font: string) { export function getTextLabelSize(text: string, font: string) {
if (!text) { if (!text) {
return [16, 32] return [16, 32]
@ -61,7 +65,7 @@ export function getTextLabelSize(text: string, font: string) {
prevText = text prevText = text
prevFont = font prevFont = font
melm.textContent = `${text}` melm.textContent = text
melm.style.font = font melm.style.font = font
// In tests, offsetWidth and offsetHeight will be 0 // In tests, offsetWidth and offsetHeight will be 0

View file

@ -85,9 +85,9 @@ const fontSizes = {
const fontFaces = { const fontFaces = {
[FontStyle.Script]: '"Caveat Brush"', [FontStyle.Script]: '"Caveat Brush"',
[FontStyle.Sans]: '"Source Sans Pro", sans-serif', [FontStyle.Sans]: '"Source Sans Pro"',
[FontStyle.Serif]: '"Crimson Pro", serif', [FontStyle.Serif]: '"Crimson Pro"',
[FontStyle.Mono]: '"Source Code Pro", monospace', [FontStyle.Mono]: '"Source Code Pro"',
} }
const fontSizeModifiers = { const fontSizeModifiers = {