Tldraw/packages/editor/src/lib/editor/shapeutils/TextShapeUtil/TextShapeUtil.tsx

400 wiersze
9.7 KiB
TypeScript
Czysty Zwykły widok Historia

2023-04-25 11:01:25 +00:00
/* eslint-disable react-hooks/rules-of-hooks */
import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives'
import { TLTextShape } from '@tldraw/tlschema'
2023-04-25 11:01:25 +00:00
import { HTMLContainer } from '../../../components/HTMLContainer'
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../../../constants'
import { stopEventPropagation } from '../../../utils/dom'
import { WeakMapCache } from '../../../utils/WeakMapCache'
import { Editor } from '../../Editor'
import { ShapeUtil, TLOnEditEndHandler, TLOnResizeHandler, TLShapeUtilFlag } from '../ShapeUtil'
Measure individual words instead of just line breaks for text exports (#1397) This diff fixes a number of issues with text export by completely overhauling how we approach laying out text in exports. Currently, we try to carefully replicate in-browser behaviour around line breaks and whitespace collapsing. We do this using an iterative algorithm that forces the browser to perform a layout for each word, and attempting to re-implement how the browser does things like whitespace collapsing & finding line break opportunities. Lots of export issues come from the fact that this is almost impossible to do well (short of sending a complete text layout algorithm & full unicode lookup tables). Luckily, the browser already has a complete text layout algorithm and full unicode lookup tables! In the new approach, we ask the browser to lay the text out once. Then, we use the [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range) API to loop over every character in the rendered text and measure its position. These character positions are then grouped into "spans". A span is a contiguous range of either whitespace or non-whitespace characters, uninterrupted by any browser-inserting line breaks. When we come to render the SVG, each span gets its own `<tspan>` element, absolutely positioned according to where it ended up in the user's browser. This fixes a bunch of issues: **Misaligned text due to whitespace collapsing at line breaks** ![Kapture 2023-05-17 at 12 07 30](https://github.com/tldraw/tldraw/assets/1489520/5ab66fe0-6ceb-45bb-8787-90ccb124664a) **Hyphenated text (or text with non-trivial/whitespace-based breaking rules like Thai) not splitting correctly** ![Kapture 2023-05-17 at 12 21 40](https://github.com/tldraw/tldraw/assets/1489520/d2d5fd13-3e79-48c4-8e76-ae2c70a6471e) **Weird alignment issues in note shapes** ![Kapture 2023-05-17 at 12 24 59](https://github.com/tldraw/tldraw/assets/1489520/a0e51d57-7c1c-490e-9952-b92417ffdf9e) **Frame labels not respecting multiple spaces & not truncating correctly** ![Kapture 2023-05-17 at 12 27 27](https://github.com/tldraw/tldraw/assets/1489520/39b2f53c-0180-460e-b10a-9fd955a6fa78) #### Quick note on browser compatibility This approach works well across all browsers, but in some cases actually _increases_ x-browser variance. Consider these screenshots of the same element (original above, export below): ![image](https://github.com/tldraw/tldraw/assets/1489520/5633b041-8cb3-4c92-bef6-4f3c202305de) Notice how on chrome, the whitespace at the end of each line of right-aligned text is preserved. On safari, it's collapsed. The safari option looks better - so our manual line-breaking/white-space-collapsing algorithm preferred safari's approach. That meant that in-app, this shape looks very slightly different from browser to browser. But out of the app, the exports would have been the same (although also note that hyphenation is broken). Now, because these shapes look different across browsers, the exports now look different across browsers too. We're relying on the host-browsers text layout algorithm, which means we'll faithfully reproduce any quirks/inconsistencies of that algorithm. I think this is an acceptable tradeoff. ### Change Type - [x] `patch` — Bug Fix ### Test Plan * Comprehensive testing of text in exports, paying close attention to details around white-space, line-breaking and alignment * Consider setting `tldrawDebugSvg = true` * Check text shapes, geo shapes with labels, arrow shapes with labels, note shapes, frame labels * Check different alignments and fonts (including vertical alignment) ### Release Notes - Add a brief release note for your PR here.
2023-05-22 15:10:03 +00:00
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
2023-04-25 11:01:25 +00:00
import { resizeScaled } from '../shared/resizeScaled'
import { TLExportColors } from '../shared/TLExportColors'
import { useEditableText } from '../shared/useEditableText'
[improvement] refactor paste to support multi-line text (#1398) This PR refactors our clipboard handlers. It should simplify the way that things work and better handle the difference between how the native API events are handled vs. the browser's clipboard API events. ![Kapture 2023-05-17 at 13 26 34](https://github.com/tldraw/tldraw/assets/23072548/5dedcc25-a1d2-423f-8bc2-415f761b643b) Everything that used to be supported now also still works. In addition, we now have several useful features: ### Multiline text can be pasted into the app When pasting text that contains more than one line, the text is pasted correctly; even if the clipboard also includes HTML data. Previously, we would try to paste HTML data if we found it, because that data might contain tldraw's own content as a comment; but if that failed, we would paste the data as text instead. This led to pasting text that lost lots of information from that text, such as line breaks and indentations. ### Multiline text shapes are aligned correctly When pasting raw text that has more than one line, the text will be left aligned—or right aligned if the text is likely from a RTL language. ![Kapture 2023-05-17 at 13 42 54](https://github.com/tldraw/tldraw/assets/23072548/f705acd5-136c-4144-80da-6e97ff766a58) ### Common minimum indentation is removed from each line ![Kapture 2023-05-17 at 13 56 28](https://github.com/tldraw/tldraw/assets/23072548/d45c95f6-6d28-4c9f-8cd3-8078700ce928) This is something that absolutely every app should implement, but here we go. When multiline text has "common indentation" on each line, which is often the case when pasting text from code, then that indentation is removed from each line. ### Auto wrapping for big pastes When a line has no text breaks but a lot of text, we now set the width of the text shape. ![Kapture 2023-05-17 at 14 00 04](https://github.com/tldraw/tldraw/assets/23072548/0b7f69c3-bcf9-42e9-a1ed-df026f868793) ## How it works A `ClipboardThing` is the common interface for things that we found on the clipboard, native or otherwise. Both `handlePasteFromClipboardApi` and `handlePasteFromEventClipboardData` parse out `ClipboardThing`s and pass them to `handleClipboardThings`. <img width="905" alt="image" src="https://github.com/tldraw/tldraw/assets/23072548/fd087539-edbb-4527-b5ff-ca7d7c1726b2"> A `ClipboardResult` is the result of processing a `ClipboardThing`, and usually contains text and other information about that text. We make decisions on what to create based on which `ClipboardResult`s we find. When pasting text, we check to see whether the result would be bigger than the viewport, or if the text is multiline, or if the text is of an RTL language by testing certain common RTL characters. We make some adjustments based on those factors, ensuring that the top-left corner of the text is on screen and reasonably positioned within the viewport if possible. ### Change Type - [x] `minor` — New Feature ### Test Plan 1. Copy and paste shapes 2. Copy and paste text from elsewhere into the app 3. Copy and paste images from elsewhere into the app 4. Try on different browsers ### Release Notes - Improves clipboard logic when pasting text - Adds support for pasting multi-line text - Adds maximum widths when pasting single-line text - Adds support for RTL languages when pasting multi-line or wrapped text - Strips leading indentation when pasting text
2023-05-17 16:32:25 +00:00
export { INDENT } from './TextHelpers'
2023-04-25 11:01:25 +00:00
const sizeCache = new WeakMapCache<TLTextShape['props'], { height: number; width: number }>()
/** @public */
export class TextShapeUtil extends ShapeUtil<TLTextShape> {
static override type = 'text'
2023-04-25 11:01:25 +00:00
canEdit = () => true
isAspectRatioLocked: TLShapeUtilFlag<TLTextShape> = () => true
defaultProps(): TLTextShape['props'] {
return {
color: 'black',
size: 'm',
w: 8,
text: '',
font: 'draw',
align: 'middle',
autoSize: true,
scale: 1,
}
}
// @computed
// private get minDimensionsCache() {
// return this.editor.store.createSelectedComputedCache<
2023-04-25 11:01:25 +00:00
// TLTextShape['props'],
// { width: number; height: number },
// TLTextShape
// >(
// 'text measure cache',
// (shape) => {
// return shape.props
// },
// (props) => getTextSize(this.editor, props)
2023-04-25 11:01:25 +00:00
// )
// }
getMinDimensions(shape: TLTextShape) {
return sizeCache.get(shape.props, (props) => getTextSize(this.editor, props))
2023-04-25 11:01:25 +00:00
}
getBounds(shape: TLTextShape) {
const { scale } = shape.props
const { width, height } = this.getMinDimensions(shape)!
return new Box2d(0, 0, width * scale, height * scale)
}
getOutline(shape: TLTextShape) {
const bounds = this.bounds(shape)
return [
new Vec2d(0, 0),
new Vec2d(bounds.width, 0),
new Vec2d(bounds.width, bounds.height),
new Vec2d(0, bounds.height),
]
}
getCenter(shape: TLTextShape): Vec2d {
const bounds = this.bounds(shape)
return new Vec2d(bounds.width / 2, bounds.height / 2)
}
render(shape: TLTextShape) {
const {
id,
type,
props: { text },
} = shape
const { width, height } = this.getMinDimensions(shape)
const {
rInput,
isEmpty,
isEditing,
isEditableFromHover,
handleFocus,
handleChange,
handleKeyDown,
handleBlur,
} = useEditableText(id, type, text)
return (
<HTMLContainer id={shape.id}>
<div
className="tl-text-shape__wrapper tl-text-shadow"
2023-04-25 11:01:25 +00:00
data-font={shape.props.font}
data-align={shape.props.align}
data-hastext={!isEmpty}
data-isediting={isEditing || isEditableFromHover}
data-textwrap={true}
style={{
fontSize: FONT_SIZES[shape.props.size],
lineHeight: FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight + 'px',
transform: `scale(${shape.props.scale})`,
transformOrigin: 'top left',
width: Math.max(1, width),
height: Math.max(FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight, height),
}}
>
<div className="tl-text tl-text-content" dir="ltr">
2023-04-25 11:01:25 +00:00
{text}
</div>
{isEditing || isEditableFromHover ? (
<textarea
ref={rInput}
className="tl-text tl-text-input"
2023-04-25 11:01:25 +00:00
name="text"
tabIndex={-1}
autoComplete="false"
autoCapitalize="false"
autoCorrect="false"
autoSave="false"
autoFocus={isEditing}
placeholder=""
spellCheck="true"
wrap="off"
dir="ltr"
datatype="wysiwyg"
defaultValue={text}
onFocus={handleFocus}
onChange={handleChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
onTouchEnd={stopEventPropagation}
onContextMenu={stopEventPropagation}
/>
) : null}
</div>
</HTMLContainer>
)
}
indicator(shape: TLTextShape) {
const bounds = this.bounds(shape)
return <rect width={toDomPrecision(bounds.width)} height={toDomPrecision(bounds.height)} />
}
toSvg(shape: TLTextShape, font: string | undefined, colors: TLExportColors) {
const bounds = this.bounds(shape)
const text = shape.props.text
const width = bounds.width / (shape.props.scale ?? 1)
const height = bounds.height / (shape.props.scale ?? 1)
const opts = {
fontSize: FONT_SIZES[shape.props.size],
fontFamily: font!,
textAlign: shape.props.align,
verticalTextAlign: 'middle' as const,
2023-04-25 11:01:25 +00:00
width,
height,
padding: 0, // no padding?
lineHeight: TEXT_PROPS.lineHeight,
fontStyle: 'normal',
fontWeight: 'normal',
Measure individual words instead of just line breaks for text exports (#1397) This diff fixes a number of issues with text export by completely overhauling how we approach laying out text in exports. Currently, we try to carefully replicate in-browser behaviour around line breaks and whitespace collapsing. We do this using an iterative algorithm that forces the browser to perform a layout for each word, and attempting to re-implement how the browser does things like whitespace collapsing & finding line break opportunities. Lots of export issues come from the fact that this is almost impossible to do well (short of sending a complete text layout algorithm & full unicode lookup tables). Luckily, the browser already has a complete text layout algorithm and full unicode lookup tables! In the new approach, we ask the browser to lay the text out once. Then, we use the [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range) API to loop over every character in the rendered text and measure its position. These character positions are then grouped into "spans". A span is a contiguous range of either whitespace or non-whitespace characters, uninterrupted by any browser-inserting line breaks. When we come to render the SVG, each span gets its own `<tspan>` element, absolutely positioned according to where it ended up in the user's browser. This fixes a bunch of issues: **Misaligned text due to whitespace collapsing at line breaks** ![Kapture 2023-05-17 at 12 07 30](https://github.com/tldraw/tldraw/assets/1489520/5ab66fe0-6ceb-45bb-8787-90ccb124664a) **Hyphenated text (or text with non-trivial/whitespace-based breaking rules like Thai) not splitting correctly** ![Kapture 2023-05-17 at 12 21 40](https://github.com/tldraw/tldraw/assets/1489520/d2d5fd13-3e79-48c4-8e76-ae2c70a6471e) **Weird alignment issues in note shapes** ![Kapture 2023-05-17 at 12 24 59](https://github.com/tldraw/tldraw/assets/1489520/a0e51d57-7c1c-490e-9952-b92417ffdf9e) **Frame labels not respecting multiple spaces & not truncating correctly** ![Kapture 2023-05-17 at 12 27 27](https://github.com/tldraw/tldraw/assets/1489520/39b2f53c-0180-460e-b10a-9fd955a6fa78) #### Quick note on browser compatibility This approach works well across all browsers, but in some cases actually _increases_ x-browser variance. Consider these screenshots of the same element (original above, export below): ![image](https://github.com/tldraw/tldraw/assets/1489520/5633b041-8cb3-4c92-bef6-4f3c202305de) Notice how on chrome, the whitespace at the end of each line of right-aligned text is preserved. On safari, it's collapsed. The safari option looks better - so our manual line-breaking/white-space-collapsing algorithm preferred safari's approach. That meant that in-app, this shape looks very slightly different from browser to browser. But out of the app, the exports would have been the same (although also note that hyphenation is broken). Now, because these shapes look different across browsers, the exports now look different across browsers too. We're relying on the host-browsers text layout algorithm, which means we'll faithfully reproduce any quirks/inconsistencies of that algorithm. I think this is an acceptable tradeoff. ### Change Type - [x] `patch` — Bug Fix ### Test Plan * Comprehensive testing of text in exports, paying close attention to details around white-space, line-breaking and alignment * Consider setting `tldrawDebugSvg = true` * Check text shapes, geo shapes with labels, arrow shapes with labels, note shapes, frame labels * Check different alignments and fonts (including vertical alignment) ### Release Notes - Add a brief release note for your PR here.
2023-05-22 15:10:03 +00:00
overflow: 'wrap' as const,
2023-04-25 11:01:25 +00:00
}
const color = colors.fill[shape.props.color]
const groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g')
Measure individual words instead of just line breaks for text exports (#1397) This diff fixes a number of issues with text export by completely overhauling how we approach laying out text in exports. Currently, we try to carefully replicate in-browser behaviour around line breaks and whitespace collapsing. We do this using an iterative algorithm that forces the browser to perform a layout for each word, and attempting to re-implement how the browser does things like whitespace collapsing & finding line break opportunities. Lots of export issues come from the fact that this is almost impossible to do well (short of sending a complete text layout algorithm & full unicode lookup tables). Luckily, the browser already has a complete text layout algorithm and full unicode lookup tables! In the new approach, we ask the browser to lay the text out once. Then, we use the [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range) API to loop over every character in the rendered text and measure its position. These character positions are then grouped into "spans". A span is a contiguous range of either whitespace or non-whitespace characters, uninterrupted by any browser-inserting line breaks. When we come to render the SVG, each span gets its own `<tspan>` element, absolutely positioned according to where it ended up in the user's browser. This fixes a bunch of issues: **Misaligned text due to whitespace collapsing at line breaks** ![Kapture 2023-05-17 at 12 07 30](https://github.com/tldraw/tldraw/assets/1489520/5ab66fe0-6ceb-45bb-8787-90ccb124664a) **Hyphenated text (or text with non-trivial/whitespace-based breaking rules like Thai) not splitting correctly** ![Kapture 2023-05-17 at 12 21 40](https://github.com/tldraw/tldraw/assets/1489520/d2d5fd13-3e79-48c4-8e76-ae2c70a6471e) **Weird alignment issues in note shapes** ![Kapture 2023-05-17 at 12 24 59](https://github.com/tldraw/tldraw/assets/1489520/a0e51d57-7c1c-490e-9952-b92417ffdf9e) **Frame labels not respecting multiple spaces & not truncating correctly** ![Kapture 2023-05-17 at 12 27 27](https://github.com/tldraw/tldraw/assets/1489520/39b2f53c-0180-460e-b10a-9fd955a6fa78) #### Quick note on browser compatibility This approach works well across all browsers, but in some cases actually _increases_ x-browser variance. Consider these screenshots of the same element (original above, export below): ![image](https://github.com/tldraw/tldraw/assets/1489520/5633b041-8cb3-4c92-bef6-4f3c202305de) Notice how on chrome, the whitespace at the end of each line of right-aligned text is preserved. On safari, it's collapsed. The safari option looks better - so our manual line-breaking/white-space-collapsing algorithm preferred safari's approach. That meant that in-app, this shape looks very slightly different from browser to browser. But out of the app, the exports would have been the same (although also note that hyphenation is broken). Now, because these shapes look different across browsers, the exports now look different across browsers too. We're relying on the host-browsers text layout algorithm, which means we'll faithfully reproduce any quirks/inconsistencies of that algorithm. I think this is an acceptable tradeoff. ### Change Type - [x] `patch` — Bug Fix ### Test Plan * Comprehensive testing of text in exports, paying close attention to details around white-space, line-breaking and alignment * Consider setting `tldrawDebugSvg = true` * Check text shapes, geo shapes with labels, arrow shapes with labels, note shapes, frame labels * Check different alignments and fonts (including vertical alignment) ### Release Notes - Add a brief release note for your PR here.
2023-05-22 15:10:03 +00:00
const textBgEl = createTextSvgElementFromSpans(
this.editor,
this.editor.textMeasure.measureTextSpans(text, opts),
Measure individual words instead of just line breaks for text exports (#1397) This diff fixes a number of issues with text export by completely overhauling how we approach laying out text in exports. Currently, we try to carefully replicate in-browser behaviour around line breaks and whitespace collapsing. We do this using an iterative algorithm that forces the browser to perform a layout for each word, and attempting to re-implement how the browser does things like whitespace collapsing & finding line break opportunities. Lots of export issues come from the fact that this is almost impossible to do well (short of sending a complete text layout algorithm & full unicode lookup tables). Luckily, the browser already has a complete text layout algorithm and full unicode lookup tables! In the new approach, we ask the browser to lay the text out once. Then, we use the [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range) API to loop over every character in the rendered text and measure its position. These character positions are then grouped into "spans". A span is a contiguous range of either whitespace or non-whitespace characters, uninterrupted by any browser-inserting line breaks. When we come to render the SVG, each span gets its own `<tspan>` element, absolutely positioned according to where it ended up in the user's browser. This fixes a bunch of issues: **Misaligned text due to whitespace collapsing at line breaks** ![Kapture 2023-05-17 at 12 07 30](https://github.com/tldraw/tldraw/assets/1489520/5ab66fe0-6ceb-45bb-8787-90ccb124664a) **Hyphenated text (or text with non-trivial/whitespace-based breaking rules like Thai) not splitting correctly** ![Kapture 2023-05-17 at 12 21 40](https://github.com/tldraw/tldraw/assets/1489520/d2d5fd13-3e79-48c4-8e76-ae2c70a6471e) **Weird alignment issues in note shapes** ![Kapture 2023-05-17 at 12 24 59](https://github.com/tldraw/tldraw/assets/1489520/a0e51d57-7c1c-490e-9952-b92417ffdf9e) **Frame labels not respecting multiple spaces & not truncating correctly** ![Kapture 2023-05-17 at 12 27 27](https://github.com/tldraw/tldraw/assets/1489520/39b2f53c-0180-460e-b10a-9fd955a6fa78) #### Quick note on browser compatibility This approach works well across all browsers, but in some cases actually _increases_ x-browser variance. Consider these screenshots of the same element (original above, export below): ![image](https://github.com/tldraw/tldraw/assets/1489520/5633b041-8cb3-4c92-bef6-4f3c202305de) Notice how on chrome, the whitespace at the end of each line of right-aligned text is preserved. On safari, it's collapsed. The safari option looks better - so our manual line-breaking/white-space-collapsing algorithm preferred safari's approach. That meant that in-app, this shape looks very slightly different from browser to browser. But out of the app, the exports would have been the same (although also note that hyphenation is broken). Now, because these shapes look different across browsers, the exports now look different across browsers too. We're relying on the host-browsers text layout algorithm, which means we'll faithfully reproduce any quirks/inconsistencies of that algorithm. I think this is an acceptable tradeoff. ### Change Type - [x] `patch` — Bug Fix ### Test Plan * Comprehensive testing of text in exports, paying close attention to details around white-space, line-breaking and alignment * Consider setting `tldrawDebugSvg = true` * Check text shapes, geo shapes with labels, arrow shapes with labels, note shapes, frame labels * Check different alignments and fonts (including vertical alignment) ### Release Notes - Add a brief release note for your PR here.
2023-05-22 15:10:03 +00:00
{
...opts,
stroke: colors.background,
strokeWidth: 2,
fill: colors.background,
padding: 0,
}
)
2023-04-25 11:01:25 +00:00
const textElm = textBgEl.cloneNode(true) as SVGTextElement
textElm.setAttribute('fill', color)
textElm.setAttribute('stroke', 'none')
groupEl.append(textBgEl)
groupEl.append(textElm)
return groupEl
}
onResize: TLOnResizeHandler<TLTextShape> = (shape, info) => {
2023-04-25 11:01:25 +00:00
const { initialBounds, initialShape, scaleX, handle } = info
if (info.mode === 'scale_shape' || (handle !== 'right' && handle !== 'left')) {
return resizeScaled(shape, info)
} else {
const prevWidth = initialBounds.width
let nextWidth = prevWidth * scaleX
const offset = new Vec2d(0, 0)
nextWidth = Math.max(1, Math.abs(nextWidth))
if (handle === 'left') {
offset.x = prevWidth - nextWidth
if (scaleX < 0) {
offset.x += nextWidth
}
} else {
if (scaleX < 0) {
offset.x -= nextWidth
}
}
const { x, y } = offset.rot(shape.rotation).add(initialShape)
return {
x,
y,
props: {
w: nextWidth / initialShape.props.scale,
autoSize: false,
},
}
}
}
onBeforeCreate = (shape: TLTextShape) => {
// When a shape is created, center the text at the created point.
// Only center if the shape is set to autosize.
if (!shape.props.autoSize) return
// Only center if the shape is empty when created.
if (shape.props.text.trim()) return
const bounds = this.getMinDimensions(shape)
return {
...shape,
x: shape.x - bounds.width / 2,
y: shape.y - bounds.height / 2,
}
}
onEditEnd: TLOnEditEndHandler<TLTextShape> = (shape) => {
2023-04-25 11:01:25 +00:00
const {
id,
type,
props: { text },
} = shape
const trimmedText = shape.props.text.trimEnd()
2023-04-25 11:01:25 +00:00
if (trimmedText.length === 0) {
this.editor.deleteShapes([shape.id])
2023-04-25 11:01:25 +00:00
} else {
if (trimmedText !== shape.props.text) {
this.editor.updateShapes([
2023-04-25 11:01:25 +00:00
{
id,
type,
props: {
text: text.trimEnd(),
2023-04-25 11:01:25 +00:00
},
},
])
}
}
}
onBeforeUpdate = (prev: TLTextShape, next: TLTextShape) => {
if (!next.props.autoSize) return
const styleDidChange =
prev.props.size !== next.props.size ||
prev.props.align !== next.props.align ||
prev.props.font !== next.props.font ||
(prev.props.scale !== 1 && next.props.scale === 1)
const textDidChange = prev.props.text !== next.props.text
// Only update position if either changed
if (!styleDidChange && !textDidChange) return
// Might return a cached value for the bounds
const boundsA = this.getMinDimensions(prev)
// Will always be a fresh call to getTextSize
const boundsB = getTextSize(this.editor, next.props)
2023-04-25 11:01:25 +00:00
const wA = boundsA.width * prev.props.scale
const hA = boundsA.height * prev.props.scale
const wB = boundsB.width * next.props.scale
const hB = boundsB.height * next.props.scale
let delta: Vec2d | undefined
switch (next.props.align) {
case 'middle': {
delta = new Vec2d((wB - wA) / 2, textDidChange ? 0 : (hB - hA) / 2)
break
}
case 'end': {
delta = new Vec2d(wB - wA, textDidChange ? 0 : (hB - hA) / 2)
break
}
default: {
if (textDidChange) break
delta = new Vec2d(0, (hB - hA) / 2)
break
}
}
if (delta) {
// account for shape rotation when writing text:
delta.rot(next.rotation)
const { x, y } = next
return {
...next,
x: x - delta.x,
y: y - delta.y,
props: { ...next.props, w: wB },
}
} else {
return {
...next,
props: { ...next.props, w: wB },
}
}
}
onDoubleClickEdge = (shape: TLTextShape) => {
// If the shape has a fixed width, set it to autoSize.
if (!shape.props.autoSize) {
return {
id: shape.id,
type: shape.type,
props: {
autoSize: true,
},
}
}
// If the shape is scaled, reset the scale to 1.
if (shape.props.scale !== 1) {
return {
id: shape.id,
type: shape.type,
props: {
scale: 1,
},
}
}
}
}
function getTextSize(editor: Editor, props: TLTextShape['props']) {
2023-04-25 11:01:25 +00:00
const { font, text, autoSize, size, w } = props
const minWidth = 16
const fontSize = FONT_SIZES[size]
const cw = autoSize
? 'fit-content'
: // `measureText` floors the number so we need to do the same here to avoid issues.
Math.floor(Math.max(minWidth, w)) + 'px'
const result = editor.textMeasure.measureText(text, {
2023-04-25 11:01:25 +00:00
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[font],
fontSize: fontSize,
width: cw,
})
// // If we're autosizing the measureText will essentially `Math.floor`
// // the numbers so `19` rather than `19.3`, this means we must +1 to
// // whatever we get to avoid wrapping.
if (autoSize) {
result.w += 1
}
return {
width: Math.max(minWidth, result.w),
height: Math.max(fontSize, result.h),
}
}