diff --git a/apps/examples/e2e/tests/test-text.spec.ts b/apps/examples/e2e/tests/test-text.spec.ts index 26e44d781..0177ed100 100644 --- a/apps/examples/e2e/tests/test-text.spec.ts +++ b/apps/examples/e2e/tests/test-text.spec.ts @@ -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, diff --git a/packages/editor/src/lib/editor/managers/TextManager.ts b/packages/editor/src/lib/editor/managers/TextManager.ts index baae2209a..3d24831f6 100644 --- a/packages/editor/src/lib/editor/managers/TextManager.ts +++ b/packages/editor/src/lib/editor/managers/TextManager.ts @@ -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 { diff --git a/packages/tldraw/src/lib/defaultExternalContentHandlers.ts b/packages/tldraw/src/lib/defaultExternalContentHandlers.ts index d10c51afd..b47e86263 100644 --- a/packages/tldraw/src/lib/defaultExternalContentHandlers.ts +++ b/packages/tldraw/src/lib/defaultExternalContentHandlers.ts @@ -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 diff --git a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx index cd5ce2559..2f3dc7215 100644 --- a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx @@ -112,7 +112,7 @@ export class ArrowShapeUtil extends ShapeUtil { ...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 { ...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 { ...TEXT_PROPS, fontFamily: FONT_FAMILIES[shape.props.font], fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size], - width: width + 'px', + width: width, } ) diff --git a/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx b/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx index 49ae4da34..eb11c4405 100644 --- a/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx @@ -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( diff --git a/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx index fdac856ad..cc9df1973 100644 --- a/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx @@ -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 diff --git a/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx b/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx index a00ccc1e8..dc0ad71d6 100644 --- a/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx @@ -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, diff --git a/packages/tldraw/src/test/TestEditor.ts b/packages/tldraw/src/test/TestEditor.ts index 8595e6a48..d5d953c71 100644 --- a/packages/tldraw/src/test/TestEditor.ts +++ b/packages/tldraw/src/test/TestEditor.ts @@ -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', })