Fix text-wrapping on Safari (#1980)

Co-authored-by: Alex Alex@dytry.ch

closes [#1978](https://github.com/tldraw/tldraw/issues/1978)

Text was wrapping on Safari because the measure text div was rendered
differently on different browsers. Interestingly, when forcing the
text-measure div to be visible and on-screen in Chrome, the same
text-wrapping behaviour was apparent. By setting white-space to 'pre'
when width hasn't been set by the user, we can ensure that only line
breaks the user has inputted are rendered by default on all browsers.

### Change Type

- [x] `patch` — Bug fix
- [ ] `minor` — New feature
- [ ] `major` — Breaking change
- [ ] `dependencies` — Changes to package dependencies[^1]
- [ ] `documentation` — Changes to the documentation only[^2]
- [ ] `tests` — Changes to any test code only[^2]
- [ ] `internal` — Any other changes that don't affect the published
package[^2]
- [ ] I don't know

[^1]: publishes a `patch` release, for devDependencies use `internal`
[^2]: will not publish a new version

### Test Plan

1. On Safari
2. Make a new text shape and start typing
3. At a certain point the text starts to wrap without the width having
been set


### Release Notes

- Fix text wrapping differently on Safari and Chrome/Firefox

Before/After

<image width="350"
src="https://github.com/tldraw/tldraw/assets/98838967/320171b4-61e0-4a41-b8d3-830bd90bea65">
<image width="350"
src="https://github.com/tldraw/tldraw/assets/98838967/b42d7156-0ce9-4894-9692-9338dc931b79">
pull/1981/head
Taha 2023-10-02 12:30:53 +01:00 zatwierdzone przez GitHub
rodzic da33179a31
commit f73bf9a7fe
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 29 dodań i 20 usunięć

Wyświetl plik

@ -7,7 +7,7 @@ export function sleep(ms: number) {
}
const measureTextOptions = {
width: 'fit-content',
width: null,
fontFamily: 'var(--tl-font-draw)',
fontSize: 24,
lineHeight: 1.35,

Wyświetl plik

@ -63,7 +63,12 @@ export class TextManager {
fontFamily: string
fontSize: number
lineHeight: number
width: string
/**
* When width is a number, the text will be wrapped to that width. When
* width is null, the text will be measured without wrapping, but explicit
* line breaks and space are preserved.
*/
width: null | number
minWidth?: string
maxWidth: string
padding: string
@ -77,13 +82,18 @@ export class TextManager {
elm.style.setProperty('font-weight', opts.fontWeight)
elm.style.setProperty('font-size', opts.fontSize + 'px')
elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
elm.style.setProperty('width', opts.width)
if (opts.width === null) {
elm.style.setProperty('white-space', 'pre')
elm.style.setProperty('width', 'fit-content')
} else {
elm.style.setProperty('width', opts.width + 'px')
elm.style.setProperty('white-space', 'pre-wrap')
}
elm.style.setProperty('min-width', opts.minWidth ?? null)
elm.style.setProperty('max-width', opts.maxWidth)
elm.style.setProperty('padding', opts.padding)
elm.textContent = normalizeTextForDom(textToMeasure)
const rect = elm.getBoundingClientRect()
return {

Wyświetl plik

@ -289,7 +289,7 @@ export function registerDefaultExternalContentHandlers(
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[defaultProps.font],
fontSize: FONT_SIZES[defaultProps.size],
width: 'fit-content',
width: null,
})
const minWidth = Math.min(
@ -302,7 +302,7 @@ export function registerDefaultExternalContentHandlers(
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[defaultProps.font],
fontSize: FONT_SIZES[defaultProps.size],
width: minWidth + 'px',
width: minWidth,
})
w = shrunkSize.w
h = shrunkSize.h

Wyświetl plik

@ -112,7 +112,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
width: 'fit-content',
width: null,
})
let width = w
@ -127,7 +127,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
width: width + 'px',
width: width,
}
)
@ -144,7 +144,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
width: width + 'px',
width: width,
}
)

Wyświetl plik

@ -1053,7 +1053,7 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: LABEL_FONT_SIZES[shape.props.size],
width: 'fit-content',
width: null,
maxWidth: '100px',
})
@ -1069,7 +1069,7 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: LABEL_FONT_SIZES[shape.props.size],
width: 'fit-content',
width: null,
minWidth: minSize.w + 'px',
maxWidth:
Math.max(

Wyświetl plik

@ -194,7 +194,7 @@ function getGrowY(editor: Editor, shape: TLNoteShape, prevGrowY = 0) {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: LABEL_FONT_SIZES[shape.props.size],
width: NOTE_SIZE - PADDING * 2 + 'px',
width: NOTE_SIZE - PADDING * 2,
})
const nextHeight = nextTextSize.h + PADDING * 2

Wyświetl plik

@ -373,9 +373,9 @@ function getTextSize(editor: Editor, props: TLTextShape['props']) {
const fontSize = FONT_SIZES[size]
const cw = autoSize
? 'fit-content'
? null
: // `measureText` floors the number so we need to do the same here to avoid issues.
Math.floor(Math.max(minWidth, w)) + 'px'
Math.floor(Math.max(minWidth, w))
const result = editor.textMeasure.measureText(text, {
...TEXT_PROPS,

Wyświetl plik

@ -81,7 +81,7 @@ export class TestEditor extends Editor {
fontFamily: string
fontSize: number
lineHeight: number
width: string
width: null | number
maxWidth: string
}
): Box2dModel => {
@ -95,18 +95,17 @@ export class TestEditor extends Editor {
return {
x: 0,
y: 0,
w: opts.width.includes('px') ? Math.max(w, +opts.width.replace('px', '')) : w,
w: opts.width === null ? w : Math.max(w, opts.width),
h:
(opts.width.includes('px')
? Math.ceil(w % +opts.width.replace('px', '')) + breaks.length
: breaks.length) * opts.fontSize,
(opts.width === null ? breaks.length : Math.ceil(w % opts.width) + breaks.length) *
opts.fontSize,
}
}
this.textMeasure.measureTextSpans = (textToMeasure, opts) => {
const box = this.textMeasure.measureText(textToMeasure, {
...opts,
width: `${opts.width}px`,
width: opts.width,
padding: `${opts.padding}px`,
maxWidth: 'auto',
})