tldraw 5.2.1 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tldraw Documentation
2
2
 
3
- Version: `5.2.1`
3
+ Version: `5.2.2`
4
4
 
5
5
  This file is generated during package publishing from the tldraw docs content for this exact package version.
6
6
 
package/RELEASE_NOTES.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Releases
2
2
 
3
- Version: `5.2.1`
3
+ Version: `5.2.2`
4
4
 
5
5
  This file is generated during package publishing from the tldraw release notes content for this exact package version.
6
6
 
7
- ## v5.2.1
7
+ ## v5.2.2
8
8
 
9
9
 
10
10
 
package/dist-cjs/index.js CHANGED
@@ -648,7 +648,7 @@ var import_buildFromV1Document = require("./lib/utils/tldr/buildFromV1Document")
648
648
  var import_file = require("./lib/utils/tldr/file");
649
649
  (0, import_editor.registerTldrawLibraryVersion)(
650
650
  "tldraw",
651
- "5.2.1",
651
+ "5.2.2",
652
652
  "cjs"
653
653
  );
654
654
  //# sourceMappingURL=index.js.map
@@ -29,6 +29,7 @@ var import_default_shape_constants = require("../shared/default-shape-constants"
29
29
  var import_defaultFonts = require("../shared/defaultFonts");
30
30
  var import_getDisplayValues = require("../shared/getDisplayValues");
31
31
  var import_RichTextLabel = require("../shared/RichTextLabel");
32
+ const TEXT_EXPORT_INK_MARGIN = 0.3;
32
33
  const sizeCache = (0, import_editor.createComputedCache)(
33
34
  "text size",
34
35
  (editor, shape) => {
@@ -156,7 +157,13 @@ class TextShapeUtil extends import_editor.ShapeUtil {
156
157
  const width = bounds.width / (shape.props.scale ?? 1);
157
158
  const height = bounds.height / (shape.props.scale ?? 1);
158
159
  const dv = (0, import_getDisplayValues.getDisplayValues)(this, shape, ctx.colorMode);
159
- const exportBounds = new import_editor.Box(0, 0, width, height);
160
+ const inkMargin = Math.ceil(dv.fontSize * TEXT_EXPORT_INK_MARGIN);
161
+ const exportBounds = new import_editor.Box(
162
+ -inkMargin,
163
+ -inkMargin,
164
+ width + inkMargin * 2,
165
+ height + inkMargin * 2
166
+ );
160
167
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
161
168
  import_RichTextLabel.RichTextSVG,
162
169
  {
@@ -168,7 +175,7 @@ class TextShapeUtil extends import_editor.ShapeUtil {
168
175
  richText: shape.props.richText,
169
176
  labelColor: dv.color,
170
177
  bounds: exportBounds,
171
- padding: 0,
178
+ padding: inkMargin,
172
179
  showTextOutline: this.options.showTextOutline
173
180
  }
174
181
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/shapes/text/TextShapeUtil.tsx"],
4
- "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBox,\n\tEditor,\n\tRectangle2d,\n\tShapeUtil,\n\tSvgExportContext,\n\tTLGeometryOpts,\n\tTLResizeInfo,\n\tTLShapeId,\n\tTLTextShape,\n\tVec,\n\tcreateComputedCache,\n\tgetColorValue,\n\tgetFontsFromRichText,\n\tisEqual,\n\tresizeScaled,\n\ttextShapeMigrations,\n\ttextShapeProps,\n\ttoRichText,\n\tuseColorMode,\n\tuseEditor,\n} from '@tldraw/editor'\nimport { useCallback } from 'react'\nimport {\n\trenderHtmlFromRichTextForMeasurement,\n\trenderPlaintextFromRichText,\n} from '../../utils/text/richText'\nimport { FONT_SIZES, TEXT_PROPS, getFontFamily } from '../shared/default-shape-constants'\nimport { getThemeFontFaces } from '../shared/defaultFonts'\nimport { ShapeOptionsWithDisplayValues, getDisplayValues } from '../shared/getDisplayValues'\nimport { RichTextLabel, RichTextSVG } from '../shared/RichTextLabel'\n\nconst sizeCache = createComputedCache(\n\t'text size',\n\t(editor: Editor, shape: TLTextShape) => {\n\t\teditor.fonts.trackFontsForShape(shape)\n\t\tconst util = editor.getShapeUtil(shape) as TextShapeUtil\n\t\tconst dv = getDisplayValues(util, shape)\n\t\treturn getTextSize(editor, shape.props, dv)\n\t},\n\t{ areRecordsEqual: (a, b) => a.props === b.props }\n)\n/** @public */\nexport interface TextShapeUtilDisplayValues {\n\tcolor: string\n\tfontFamily: string\n\tfontSize: number\n\tlineHeight: number\n\tfontWeight: string\n\tfontStyle: string\n\tfontVariant: string\n}\n\n/** @public */\nexport interface TextShapeOptions extends ShapeOptionsWithDisplayValues<\n\tTLTextShape,\n\tTextShapeUtilDisplayValues\n> {\n\t/** How much addition padding should be added to the horizontal geometry of the shape when binding to an arrow? */\n\textraArrowHorizontalPadding: number\n\t/** Whether to show the outline of the text shape (using the same color as the canvas). This helps with overlapping shapes. It does not show up on Safari, where text outline is a performance issues. */\n\tshowTextOutline: boolean\n}\n\n/** @public */\nexport class TextShapeUtil extends ShapeUtil<TLTextShape> {\n\tstatic override type = 'text' as const\n\tstatic override props = textShapeProps\n\tstatic override migrations = textShapeMigrations\n\n\toverride options: TextShapeOptions = {\n\t\textraArrowHorizontalPadding: 10,\n\t\tshowTextOutline: true,\n\t\tgetDefaultDisplayValues(_editor, shape, theme, colorMode): TextShapeUtilDisplayValues {\n\t\t\tconst { color, font, size } = shape.props\n\t\t\treturn {\n\t\t\t\tcolor: getColorValue(theme.colors[colorMode], color, 'solid'),\n\t\t\t\tfontFamily: getFontFamily(theme, font),\n\t\t\t\tfontSize: theme.fontSize * FONT_SIZES[size],\n\t\t\t\tlineHeight: theme.lineHeight,\n\t\t\t\tfontWeight: TEXT_PROPS.fontWeight,\n\t\t\t\tfontStyle: TEXT_PROPS.fontStyle,\n\t\t\t\tfontVariant: TEXT_PROPS.fontVariant,\n\t\t\t}\n\t\t},\n\t\tgetCustomDisplayValues(): Partial<TextShapeUtilDisplayValues> {\n\t\t\treturn {}\n\t\t},\n\t}\n\n\tgetDefaultProps(): TLTextShape['props'] {\n\t\treturn {\n\t\t\tcolor: 'black',\n\t\t\tsize: 'm',\n\t\t\tw: 8,\n\t\t\tfont: 'draw',\n\t\t\ttextAlign: 'start',\n\t\t\tautoSize: true,\n\t\t\tscale: 1,\n\t\t\trichText: toRichText(''),\n\t\t}\n\t}\n\n\tgetMinDimensions(shape: TLTextShape) {\n\t\treturn sizeCache.get(this.editor, shape.id)!\n\t}\n\n\tgetGeometry(shape: TLTextShape, opts: TLGeometryOpts) {\n\t\tconst { scale } = shape.props\n\t\tconst { width, height } = this.getMinDimensions(shape)!\n\t\tconst context = opts?.context ?? 'none'\n\t\treturn new Rectangle2d({\n\t\t\tx:\n\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t? -this.options.extraArrowHorizontalPadding\n\t\t\t\t\t: 0) * scale,\n\t\t\twidth:\n\t\t\t\t(width +\n\t\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t\t? this.options.extraArrowHorizontalPadding * 2\n\t\t\t\t\t\t: 0)) *\n\t\t\t\tscale,\n\t\t\theight: height * scale,\n\t\t\tisFilled: true,\n\t\t\tisLabel: true,\n\t\t})\n\t}\n\n\toverride getFontFaces(shape: TLTextShape) {\n\t\tconst themeFaces = getThemeFontFaces(this.editor.getCurrentTheme(), shape.props.font)\n\t\tif (themeFaces) return themeFaces\n\t\treturn getFontsFromRichText(this.editor, shape.props.richText, {\n\t\t\tfamily: `tldraw_${shape.props.font}`,\n\t\t\tweight: 'normal',\n\t\t\tstyle: 'normal',\n\t\t})\n\t}\n\n\toverride getText(shape: TLTextShape) {\n\t\treturn renderPlaintextFromRichText(this.editor, shape.props.richText)\n\t}\n\n\toverride canEdit(shape: TLTextShape) {\n\t\treturn true\n\t}\n\n\toverride isAspectRatioLocked(shape: TLTextShape) {\n\t\treturn true\n\t} // WAIT NO THIS IS HARD CODED IN THE RESIZE HANDLER\n\n\tcomponent(shape: TLTextShape) {\n\t\tconst {\n\t\t\tid,\n\t\t\tprops: { richText, scale, textAlign },\n\t\t} = shape\n\n\t\tconst { width, height } = this.getMinDimensions(shape)\n\t\tconst isSelected = shape.id === this.editor.getOnlySelectedShapeId()\n\t\tconst colorMode = useColorMode()\n\t\tconst dv = getDisplayValues(this, shape, colorMode)\n\t\tconst handleKeyDown = useTextShapeKeydownHandler(id)\n\n\t\treturn (\n\t\t\t<RichTextLabel\n\t\t\t\tshapeId={id}\n\t\t\t\tclassNamePrefix=\"tl-text-shape\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={textAlign === 'middle' ? 'center' : textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tisSelected={isSelected}\n\t\t\t\ttextWidth={width}\n\t\t\t\ttextHeight={height}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t\tstyle={{\n\t\t\t\t\ttransform: `scale(${scale})`,\n\t\t\t\t\ttransformOrigin: 'top left',\n\t\t\t\t}}\n\t\t\t\twrap\n\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLTextShape): Path2D | undefined {\n\t\tif (shape.props.autoSize && this.editor.getEditingShapeId() === shape.id) return undefined\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst path = new Path2D()\n\t\tpath.rect(0, 0, bounds.width, bounds.height)\n\t\treturn path\n\t}\n\n\toverride toSvg(shape: TLTextShape, ctx: SvgExportContext) {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst width = bounds.width / (shape.props.scale ?? 1)\n\t\tconst height = bounds.height / (shape.props.scale ?? 1)\n\n\t\tconst dv = getDisplayValues(this, shape, ctx.colorMode)\n\n\t\tconst exportBounds = new Box(0, 0, width, height)\n\t\treturn (\n\t\t\t<RichTextSVG\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={shape.props.textAlign === 'middle' ? 'center' : shape.props.textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={shape.props.richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tbounds={exportBounds}\n\t\t\t\tpadding={0}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride onResize(shape: TLTextShape, info: TLResizeInfo<TLTextShape>) {\n\t\tconst { newPoint, initialBounds, initialShape, scaleX, handle } = info\n\n\t\tif (info.mode === 'scale_shape' || (handle !== 'right' && handle !== 'left')) {\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\t...resizeScaled(shape, info),\n\t\t\t}\n\t\t} else {\n\t\t\tconst nextWidth = Math.max(1, Math.abs(initialBounds.width * scaleX))\n\t\t\tconst { x, y } =\n\t\t\t\tscaleX < 0 ? Vec.Sub(newPoint, Vec.FromAngle(shape.rotation).mul(nextWidth)) : newPoint\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tprops: {\n\t\t\t\t\tw: nextWidth / initialShape.props.scale,\n\t\t\t\t\tautoSize: false,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\toverride onEditEnd(shape: TLTextShape) {\n\t\t// todo: find a way to check if the rich text has any nodes that aren't empty spaces\n\t\tconst trimmedText = renderPlaintextFromRichText(this.editor, shape.props.richText).trimEnd()\n\n\t\tif (trimmedText.length === 0) {\n\t\t\tthis.editor.deleteShapes([shape.id])\n\t\t}\n\t}\n\n\toverride onBeforeUpdate(prev: TLTextShape, next: TLTextShape) {\n\t\tif (!next.props.autoSize) return\n\n\t\tconst styleDidChange =\n\t\t\tprev.props.size !== next.props.size ||\n\t\t\tprev.props.textAlign !== next.props.textAlign ||\n\t\t\tprev.props.font !== next.props.font ||\n\t\t\t(prev.props.scale !== 1 && next.props.scale === 1)\n\n\t\tconst textDidChange = !isEqual(prev.props.richText, next.props.richText)\n\n\t\t// Only update position if either changed\n\t\tif (!styleDidChange && !textDidChange) return\n\n\t\t// Might return a cached value for the bounds\n\t\tconst boundsA = this.getMinDimensions(prev)\n\n\t\t// Will always be a fresh call to getTextSize\n\t\tconst dv = getDisplayValues(this, next)\n\t\tconst boundsB = getTextSize(this.editor, next.props, dv)\n\n\t\tconst wA = boundsA.width * prev.props.scale\n\t\tconst hA = boundsA.height * prev.props.scale\n\t\tconst wB = boundsB.width * next.props.scale\n\t\tconst hB = boundsB.height * next.props.scale\n\n\t\tlet delta: Vec | undefined\n\n\t\tswitch (next.props.textAlign) {\n\t\t\tcase 'middle': {\n\t\t\t\tdelta = new Vec((wB - wA) / 2, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'end': {\n\t\t\t\tdelta = new Vec(wB - wA, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tif (textDidChange) break\n\t\t\t\tdelta = new Vec(0, (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (delta) {\n\t\t\t// account for shape rotation when writing text:\n\t\t\tdelta.rot(next.rotation)\n\t\t\tconst { x, y } = next\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tx: x - delta.x,\n\t\t\t\ty: y - delta.y,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t} else {\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t}\n\t}\n\n\t// \ttodo: The edge doubleclicking feels like a mistake more often than\n\t// not, especially on multiline text. Removed June 16 2024\n\n\t// override onDoubleClickEdge = (shape: TLTextShape) => {\n\t// \t// If the shape has a fixed width, set it to autoSize.\n\t// \tif (!shape.props.autoSize) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tautoSize: true,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// \t// If the shape is scaled, reset the scale to 1.\n\t// \tif (shape.props.scale !== 1) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tscale: 1,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// }\n}\n\nfunction getTextSize(editor: Editor, props: TLTextShape['props'], dv: TextShapeUtilDisplayValues) {\n\tconst { richText, w } = props\n\n\tconst minWidth = 16\n\n\tconst maybeFixedWidth = props.autoSize ? null : Math.max(minWidth, Math.floor(w))\n\n\tconst html = renderHtmlFromRichTextForMeasurement(editor, richText)\n\tconst result = editor.textMeasure.measureHtml(html, {\n\t\tlineHeight: dv.lineHeight,\n\t\tfontWeight: dv.fontWeight,\n\t\tfontStyle: dv.fontStyle,\n\t\tpadding: '0px',\n\t\tfontFamily: dv.fontFamily,\n\t\tfontSize: dv.fontSize,\n\t\tmaxWidth: maybeFixedWidth,\n\t})\n\n\t// If we're autosizing the measureText will essentially `Math.floor`\n\t// the numbers so `19` rather than `19.3`, this means we must +1 to\n\t// whatever we get to avoid wrapping.\n\treturn {\n\t\twidth: maybeFixedWidth ?? Math.max(minWidth, result.w + 1),\n\t\theight: Math.max(dv.fontSize, result.h),\n\t}\n}\n\nfunction useTextShapeKeydownHandler(id: TLShapeId) {\n\tconst editor = useEditor()\n\n\treturn useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (editor.getEditingShapeId() !== id) return\n\n\t\t\tswitch (e.key) {\n\t\t\t\tcase 'Enter': {\n\t\t\t\t\tif (e.ctrlKey || e.metaKey) {\n\t\t\t\t\t\teditor.complete()\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor, id]\n\t)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBox,\n\tEditor,\n\tRectangle2d,\n\tShapeUtil,\n\tSvgExportContext,\n\tTLGeometryOpts,\n\tTLResizeInfo,\n\tTLShapeId,\n\tTLTextShape,\n\tVec,\n\tcreateComputedCache,\n\tgetColorValue,\n\tgetFontsFromRichText,\n\tisEqual,\n\tresizeScaled,\n\ttextShapeMigrations,\n\ttextShapeProps,\n\ttoRichText,\n\tuseColorMode,\n\tuseEditor,\n} from '@tldraw/editor'\nimport { useCallback } from 'react'\nimport {\n\trenderHtmlFromRichTextForMeasurement,\n\trenderPlaintextFromRichText,\n} from '../../utils/text/richText'\nimport { FONT_SIZES, TEXT_PROPS, getFontFamily } from '../shared/default-shape-constants'\nimport { getThemeFontFaces } from '../shared/defaultFonts'\nimport { ShapeOptionsWithDisplayValues, getDisplayValues } from '../shared/getDisplayValues'\nimport { RichTextLabel, RichTextSVG } from '../shared/RichTextLabel'\n\n// Export-only slack (as a fraction of font size) added around a text shape's advance box in `toSvg`,\n// so italic/cursive glyph ink that spills past the box \u2014 side bearings, slanted ascenders \u2014 isn't\n// clipped at the <foreignObject> edge on export (#8802). The text still renders in the same place;\n// the export's auto-trim crops the extra room back to the real pixels, so the PNG stays tight. This\n// is deliberately export-only: the shape's geometry (selection, hit-testing, the text-edit cursor)\n// stays the advance box, which is the right box for those.\nconst TEXT_EXPORT_INK_MARGIN = 0.3\n\nconst sizeCache = createComputedCache(\n\t'text size',\n\t(editor: Editor, shape: TLTextShape) => {\n\t\teditor.fonts.trackFontsForShape(shape)\n\t\tconst util = editor.getShapeUtil(shape) as TextShapeUtil\n\t\tconst dv = getDisplayValues(util, shape)\n\t\treturn getTextSize(editor, shape.props, dv)\n\t},\n\t{ areRecordsEqual: (a, b) => a.props === b.props }\n)\n/** @public */\nexport interface TextShapeUtilDisplayValues {\n\tcolor: string\n\tfontFamily: string\n\tfontSize: number\n\tlineHeight: number\n\tfontWeight: string\n\tfontStyle: string\n\tfontVariant: string\n}\n\n/** @public */\nexport interface TextShapeOptions extends ShapeOptionsWithDisplayValues<\n\tTLTextShape,\n\tTextShapeUtilDisplayValues\n> {\n\t/** How much addition padding should be added to the horizontal geometry of the shape when binding to an arrow? */\n\textraArrowHorizontalPadding: number\n\t/** Whether to show the outline of the text shape (using the same color as the canvas). This helps with overlapping shapes. It does not show up on Safari, where text outline is a performance issues. */\n\tshowTextOutline: boolean\n}\n\n/** @public */\nexport class TextShapeUtil extends ShapeUtil<TLTextShape> {\n\tstatic override type = 'text' as const\n\tstatic override props = textShapeProps\n\tstatic override migrations = textShapeMigrations\n\n\toverride options: TextShapeOptions = {\n\t\textraArrowHorizontalPadding: 10,\n\t\tshowTextOutline: true,\n\t\tgetDefaultDisplayValues(_editor, shape, theme, colorMode): TextShapeUtilDisplayValues {\n\t\t\tconst { color, font, size } = shape.props\n\t\t\treturn {\n\t\t\t\tcolor: getColorValue(theme.colors[colorMode], color, 'solid'),\n\t\t\t\tfontFamily: getFontFamily(theme, font),\n\t\t\t\tfontSize: theme.fontSize * FONT_SIZES[size],\n\t\t\t\tlineHeight: theme.lineHeight,\n\t\t\t\tfontWeight: TEXT_PROPS.fontWeight,\n\t\t\t\tfontStyle: TEXT_PROPS.fontStyle,\n\t\t\t\tfontVariant: TEXT_PROPS.fontVariant,\n\t\t\t}\n\t\t},\n\t\tgetCustomDisplayValues(): Partial<TextShapeUtilDisplayValues> {\n\t\t\treturn {}\n\t\t},\n\t}\n\n\tgetDefaultProps(): TLTextShape['props'] {\n\t\treturn {\n\t\t\tcolor: 'black',\n\t\t\tsize: 'm',\n\t\t\tw: 8,\n\t\t\tfont: 'draw',\n\t\t\ttextAlign: 'start',\n\t\t\tautoSize: true,\n\t\t\tscale: 1,\n\t\t\trichText: toRichText(''),\n\t\t}\n\t}\n\n\tgetMinDimensions(shape: TLTextShape) {\n\t\treturn sizeCache.get(this.editor, shape.id)!\n\t}\n\n\tgetGeometry(shape: TLTextShape, opts: TLGeometryOpts) {\n\t\tconst { scale } = shape.props\n\t\tconst { width, height } = this.getMinDimensions(shape)!\n\t\tconst context = opts?.context ?? 'none'\n\t\treturn new Rectangle2d({\n\t\t\tx:\n\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t? -this.options.extraArrowHorizontalPadding\n\t\t\t\t\t: 0) * scale,\n\t\t\twidth:\n\t\t\t\t(width +\n\t\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t\t? this.options.extraArrowHorizontalPadding * 2\n\t\t\t\t\t\t: 0)) *\n\t\t\t\tscale,\n\t\t\theight: height * scale,\n\t\t\tisFilled: true,\n\t\t\tisLabel: true,\n\t\t})\n\t}\n\n\toverride getFontFaces(shape: TLTextShape) {\n\t\tconst themeFaces = getThemeFontFaces(this.editor.getCurrentTheme(), shape.props.font)\n\t\tif (themeFaces) return themeFaces\n\t\treturn getFontsFromRichText(this.editor, shape.props.richText, {\n\t\t\tfamily: `tldraw_${shape.props.font}`,\n\t\t\tweight: 'normal',\n\t\t\tstyle: 'normal',\n\t\t})\n\t}\n\n\toverride getText(shape: TLTextShape) {\n\t\treturn renderPlaintextFromRichText(this.editor, shape.props.richText)\n\t}\n\n\toverride canEdit(shape: TLTextShape) {\n\t\treturn true\n\t}\n\n\toverride isAspectRatioLocked(shape: TLTextShape) {\n\t\treturn true\n\t} // WAIT NO THIS IS HARD CODED IN THE RESIZE HANDLER\n\n\tcomponent(shape: TLTextShape) {\n\t\tconst {\n\t\t\tid,\n\t\t\tprops: { richText, scale, textAlign },\n\t\t} = shape\n\n\t\tconst { width, height } = this.getMinDimensions(shape)\n\t\tconst isSelected = shape.id === this.editor.getOnlySelectedShapeId()\n\t\tconst colorMode = useColorMode()\n\t\tconst dv = getDisplayValues(this, shape, colorMode)\n\t\tconst handleKeyDown = useTextShapeKeydownHandler(id)\n\n\t\treturn (\n\t\t\t<RichTextLabel\n\t\t\t\tshapeId={id}\n\t\t\t\tclassNamePrefix=\"tl-text-shape\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={textAlign === 'middle' ? 'center' : textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tisSelected={isSelected}\n\t\t\t\ttextWidth={width}\n\t\t\t\ttextHeight={height}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t\tstyle={{\n\t\t\t\t\ttransform: `scale(${scale})`,\n\t\t\t\t\ttransformOrigin: 'top left',\n\t\t\t\t}}\n\t\t\t\twrap\n\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLTextShape): Path2D | undefined {\n\t\tif (shape.props.autoSize && this.editor.getEditingShapeId() === shape.id) return undefined\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst path = new Path2D()\n\t\tpath.rect(0, 0, bounds.width, bounds.height)\n\t\treturn path\n\t}\n\n\toverride toSvg(shape: TLTextShape, ctx: SvgExportContext) {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst width = bounds.width / (shape.props.scale ?? 1)\n\t\tconst height = bounds.height / (shape.props.scale ?? 1)\n\n\t\tconst dv = getDisplayValues(this, shape, ctx.colorMode)\n\n\t\t// Pad the exported foreignObject by a margin so glyph ink can spill past the advance box\n\t\t// without being clipped at its edge; the text is inset by the same margin so it renders in the\n\t\t// same place, and the export's trim crops the slack back to content. See TEXT_EXPORT_INK_MARGIN.\n\t\tconst inkMargin = Math.ceil(dv.fontSize * TEXT_EXPORT_INK_MARGIN)\n\t\tconst exportBounds = new Box(\n\t\t\t-inkMargin,\n\t\t\t-inkMargin,\n\t\t\twidth + inkMargin * 2,\n\t\t\theight + inkMargin * 2\n\t\t)\n\t\treturn (\n\t\t\t<RichTextSVG\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={shape.props.textAlign === 'middle' ? 'center' : shape.props.textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={shape.props.richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tbounds={exportBounds}\n\t\t\t\tpadding={inkMargin}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride onResize(shape: TLTextShape, info: TLResizeInfo<TLTextShape>) {\n\t\tconst { newPoint, initialBounds, initialShape, scaleX, handle } = info\n\n\t\tif (info.mode === 'scale_shape' || (handle !== 'right' && handle !== 'left')) {\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\t...resizeScaled(shape, info),\n\t\t\t}\n\t\t} else {\n\t\t\tconst nextWidth = Math.max(1, Math.abs(initialBounds.width * scaleX))\n\t\t\tconst { x, y } =\n\t\t\t\tscaleX < 0 ? Vec.Sub(newPoint, Vec.FromAngle(shape.rotation).mul(nextWidth)) : newPoint\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tprops: {\n\t\t\t\t\tw: nextWidth / initialShape.props.scale,\n\t\t\t\t\tautoSize: false,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\toverride onEditEnd(shape: TLTextShape) {\n\t\t// todo: find a way to check if the rich text has any nodes that aren't empty spaces\n\t\tconst trimmedText = renderPlaintextFromRichText(this.editor, shape.props.richText).trimEnd()\n\n\t\tif (trimmedText.length === 0) {\n\t\t\tthis.editor.deleteShapes([shape.id])\n\t\t}\n\t}\n\n\toverride onBeforeUpdate(prev: TLTextShape, next: TLTextShape) {\n\t\tif (!next.props.autoSize) return\n\n\t\tconst styleDidChange =\n\t\t\tprev.props.size !== next.props.size ||\n\t\t\tprev.props.textAlign !== next.props.textAlign ||\n\t\t\tprev.props.font !== next.props.font ||\n\t\t\t(prev.props.scale !== 1 && next.props.scale === 1)\n\n\t\tconst textDidChange = !isEqual(prev.props.richText, next.props.richText)\n\n\t\t// Only update position if either changed\n\t\tif (!styleDidChange && !textDidChange) return\n\n\t\t// Might return a cached value for the bounds\n\t\tconst boundsA = this.getMinDimensions(prev)\n\n\t\t// Will always be a fresh call to getTextSize\n\t\tconst dv = getDisplayValues(this, next)\n\t\tconst boundsB = getTextSize(this.editor, next.props, dv)\n\n\t\tconst wA = boundsA.width * prev.props.scale\n\t\tconst hA = boundsA.height * prev.props.scale\n\t\tconst wB = boundsB.width * next.props.scale\n\t\tconst hB = boundsB.height * next.props.scale\n\n\t\tlet delta: Vec | undefined\n\n\t\tswitch (next.props.textAlign) {\n\t\t\tcase 'middle': {\n\t\t\t\tdelta = new Vec((wB - wA) / 2, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'end': {\n\t\t\t\tdelta = new Vec(wB - wA, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tif (textDidChange) break\n\t\t\t\tdelta = new Vec(0, (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (delta) {\n\t\t\t// account for shape rotation when writing text:\n\t\t\tdelta.rot(next.rotation)\n\t\t\tconst { x, y } = next\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tx: x - delta.x,\n\t\t\t\ty: y - delta.y,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t} else {\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t}\n\t}\n\n\t// \ttodo: The edge doubleclicking feels like a mistake more often than\n\t// not, especially on multiline text. Removed June 16 2024\n\n\t// override onDoubleClickEdge = (shape: TLTextShape) => {\n\t// \t// If the shape has a fixed width, set it to autoSize.\n\t// \tif (!shape.props.autoSize) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tautoSize: true,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// \t// If the shape is scaled, reset the scale to 1.\n\t// \tif (shape.props.scale !== 1) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tscale: 1,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// }\n}\n\nfunction getTextSize(editor: Editor, props: TLTextShape['props'], dv: TextShapeUtilDisplayValues) {\n\tconst { richText, w } = props\n\n\tconst minWidth = 16\n\n\tconst maybeFixedWidth = props.autoSize ? null : Math.max(minWidth, Math.floor(w))\n\n\tconst html = renderHtmlFromRichTextForMeasurement(editor, richText)\n\tconst result = editor.textMeasure.measureHtml(html, {\n\t\tlineHeight: dv.lineHeight,\n\t\tfontWeight: dv.fontWeight,\n\t\tfontStyle: dv.fontStyle,\n\t\tpadding: '0px',\n\t\tfontFamily: dv.fontFamily,\n\t\tfontSize: dv.fontSize,\n\t\tmaxWidth: maybeFixedWidth,\n\t})\n\n\t// If we're autosizing the measureText will essentially `Math.floor`\n\t// the numbers so `19` rather than `19.3`, this means we must +1 to\n\t// whatever we get to avoid wrapping.\n\treturn {\n\t\twidth: maybeFixedWidth ?? Math.max(minWidth, result.w + 1),\n\t\theight: Math.max(dv.fontSize, result.h),\n\t}\n}\n\nfunction useTextShapeKeydownHandler(id: TLShapeId) {\n\tconst editor = useEditor()\n\n\treturn useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (editor.getEditingShapeId() !== id) return\n\n\t\t\tswitch (e.key) {\n\t\t\t\tcase 'Enter': {\n\t\t\t\t\tif (e.ctrlKey || e.metaKey) {\n\t\t\t\t\t\teditor.complete()\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor, id]\n\t)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "5.2.1";
25
+ const version = "5.2.2";
26
26
  const publishDates = {
27
27
  major: "2026-05-06T16:28:18.473Z",
28
28
  minor: "2026-07-01T09:20:01.735Z",
29
- patch: "2026-07-01T10:28:54.350Z"
29
+ patch: "2026-07-02T09:30:17.167Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/ui/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.2.1'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-07-01T09:20:01.735Z',\n\tpatch: '2026-07-01T10:28:54.350Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.2.2'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-07-01T09:20:01.735Z',\n\tpatch: '2026-07-02T09:30:17.167Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -634,7 +634,7 @@ import {
634
634
  } from "./lib/utils/tldr/file.mjs";
635
635
  registerTldrawLibraryVersion(
636
636
  "tldraw",
637
- "5.2.1",
637
+ "5.2.2",
638
638
  "esm"
639
639
  );
640
640
  export {
@@ -24,6 +24,7 @@ import { FONT_SIZES, TEXT_PROPS, getFontFamily } from "../shared/default-shape-c
24
24
  import { getThemeFontFaces } from "../shared/defaultFonts.mjs";
25
25
  import { getDisplayValues } from "../shared/getDisplayValues.mjs";
26
26
  import { RichTextLabel, RichTextSVG } from "../shared/RichTextLabel.mjs";
27
+ const TEXT_EXPORT_INK_MARGIN = 0.3;
27
28
  const sizeCache = createComputedCache(
28
29
  "text size",
29
30
  (editor, shape) => {
@@ -151,7 +152,13 @@ class TextShapeUtil extends ShapeUtil {
151
152
  const width = bounds.width / (shape.props.scale ?? 1);
152
153
  const height = bounds.height / (shape.props.scale ?? 1);
153
154
  const dv = getDisplayValues(this, shape, ctx.colorMode);
154
- const exportBounds = new Box(0, 0, width, height);
155
+ const inkMargin = Math.ceil(dv.fontSize * TEXT_EXPORT_INK_MARGIN);
156
+ const exportBounds = new Box(
157
+ -inkMargin,
158
+ -inkMargin,
159
+ width + inkMargin * 2,
160
+ height + inkMargin * 2
161
+ );
155
162
  return /* @__PURE__ */ jsx(
156
163
  RichTextSVG,
157
164
  {
@@ -163,7 +170,7 @@ class TextShapeUtil extends ShapeUtil {
163
170
  richText: shape.props.richText,
164
171
  labelColor: dv.color,
165
172
  bounds: exportBounds,
166
- padding: 0,
173
+ padding: inkMargin,
167
174
  showTextOutline: this.options.showTextOutline
168
175
  }
169
176
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/shapes/text/TextShapeUtil.tsx"],
4
- "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBox,\n\tEditor,\n\tRectangle2d,\n\tShapeUtil,\n\tSvgExportContext,\n\tTLGeometryOpts,\n\tTLResizeInfo,\n\tTLShapeId,\n\tTLTextShape,\n\tVec,\n\tcreateComputedCache,\n\tgetColorValue,\n\tgetFontsFromRichText,\n\tisEqual,\n\tresizeScaled,\n\ttextShapeMigrations,\n\ttextShapeProps,\n\ttoRichText,\n\tuseColorMode,\n\tuseEditor,\n} from '@tldraw/editor'\nimport { useCallback } from 'react'\nimport {\n\trenderHtmlFromRichTextForMeasurement,\n\trenderPlaintextFromRichText,\n} from '../../utils/text/richText'\nimport { FONT_SIZES, TEXT_PROPS, getFontFamily } from '../shared/default-shape-constants'\nimport { getThemeFontFaces } from '../shared/defaultFonts'\nimport { ShapeOptionsWithDisplayValues, getDisplayValues } from '../shared/getDisplayValues'\nimport { RichTextLabel, RichTextSVG } from '../shared/RichTextLabel'\n\nconst sizeCache = createComputedCache(\n\t'text size',\n\t(editor: Editor, shape: TLTextShape) => {\n\t\teditor.fonts.trackFontsForShape(shape)\n\t\tconst util = editor.getShapeUtil(shape) as TextShapeUtil\n\t\tconst dv = getDisplayValues(util, shape)\n\t\treturn getTextSize(editor, shape.props, dv)\n\t},\n\t{ areRecordsEqual: (a, b) => a.props === b.props }\n)\n/** @public */\nexport interface TextShapeUtilDisplayValues {\n\tcolor: string\n\tfontFamily: string\n\tfontSize: number\n\tlineHeight: number\n\tfontWeight: string\n\tfontStyle: string\n\tfontVariant: string\n}\n\n/** @public */\nexport interface TextShapeOptions extends ShapeOptionsWithDisplayValues<\n\tTLTextShape,\n\tTextShapeUtilDisplayValues\n> {\n\t/** How much addition padding should be added to the horizontal geometry of the shape when binding to an arrow? */\n\textraArrowHorizontalPadding: number\n\t/** Whether to show the outline of the text shape (using the same color as the canvas). This helps with overlapping shapes. It does not show up on Safari, where text outline is a performance issues. */\n\tshowTextOutline: boolean\n}\n\n/** @public */\nexport class TextShapeUtil extends ShapeUtil<TLTextShape> {\n\tstatic override type = 'text' as const\n\tstatic override props = textShapeProps\n\tstatic override migrations = textShapeMigrations\n\n\toverride options: TextShapeOptions = {\n\t\textraArrowHorizontalPadding: 10,\n\t\tshowTextOutline: true,\n\t\tgetDefaultDisplayValues(_editor, shape, theme, colorMode): TextShapeUtilDisplayValues {\n\t\t\tconst { color, font, size } = shape.props\n\t\t\treturn {\n\t\t\t\tcolor: getColorValue(theme.colors[colorMode], color, 'solid'),\n\t\t\t\tfontFamily: getFontFamily(theme, font),\n\t\t\t\tfontSize: theme.fontSize * FONT_SIZES[size],\n\t\t\t\tlineHeight: theme.lineHeight,\n\t\t\t\tfontWeight: TEXT_PROPS.fontWeight,\n\t\t\t\tfontStyle: TEXT_PROPS.fontStyle,\n\t\t\t\tfontVariant: TEXT_PROPS.fontVariant,\n\t\t\t}\n\t\t},\n\t\tgetCustomDisplayValues(): Partial<TextShapeUtilDisplayValues> {\n\t\t\treturn {}\n\t\t},\n\t}\n\n\tgetDefaultProps(): TLTextShape['props'] {\n\t\treturn {\n\t\t\tcolor: 'black',\n\t\t\tsize: 'm',\n\t\t\tw: 8,\n\t\t\tfont: 'draw',\n\t\t\ttextAlign: 'start',\n\t\t\tautoSize: true,\n\t\t\tscale: 1,\n\t\t\trichText: toRichText(''),\n\t\t}\n\t}\n\n\tgetMinDimensions(shape: TLTextShape) {\n\t\treturn sizeCache.get(this.editor, shape.id)!\n\t}\n\n\tgetGeometry(shape: TLTextShape, opts: TLGeometryOpts) {\n\t\tconst { scale } = shape.props\n\t\tconst { width, height } = this.getMinDimensions(shape)!\n\t\tconst context = opts?.context ?? 'none'\n\t\treturn new Rectangle2d({\n\t\t\tx:\n\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t? -this.options.extraArrowHorizontalPadding\n\t\t\t\t\t: 0) * scale,\n\t\t\twidth:\n\t\t\t\t(width +\n\t\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t\t? this.options.extraArrowHorizontalPadding * 2\n\t\t\t\t\t\t: 0)) *\n\t\t\t\tscale,\n\t\t\theight: height * scale,\n\t\t\tisFilled: true,\n\t\t\tisLabel: true,\n\t\t})\n\t}\n\n\toverride getFontFaces(shape: TLTextShape) {\n\t\tconst themeFaces = getThemeFontFaces(this.editor.getCurrentTheme(), shape.props.font)\n\t\tif (themeFaces) return themeFaces\n\t\treturn getFontsFromRichText(this.editor, shape.props.richText, {\n\t\t\tfamily: `tldraw_${shape.props.font}`,\n\t\t\tweight: 'normal',\n\t\t\tstyle: 'normal',\n\t\t})\n\t}\n\n\toverride getText(shape: TLTextShape) {\n\t\treturn renderPlaintextFromRichText(this.editor, shape.props.richText)\n\t}\n\n\toverride canEdit(shape: TLTextShape) {\n\t\treturn true\n\t}\n\n\toverride isAspectRatioLocked(shape: TLTextShape) {\n\t\treturn true\n\t} // WAIT NO THIS IS HARD CODED IN THE RESIZE HANDLER\n\n\tcomponent(shape: TLTextShape) {\n\t\tconst {\n\t\t\tid,\n\t\t\tprops: { richText, scale, textAlign },\n\t\t} = shape\n\n\t\tconst { width, height } = this.getMinDimensions(shape)\n\t\tconst isSelected = shape.id === this.editor.getOnlySelectedShapeId()\n\t\tconst colorMode = useColorMode()\n\t\tconst dv = getDisplayValues(this, shape, colorMode)\n\t\tconst handleKeyDown = useTextShapeKeydownHandler(id)\n\n\t\treturn (\n\t\t\t<RichTextLabel\n\t\t\t\tshapeId={id}\n\t\t\t\tclassNamePrefix=\"tl-text-shape\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={textAlign === 'middle' ? 'center' : textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tisSelected={isSelected}\n\t\t\t\ttextWidth={width}\n\t\t\t\ttextHeight={height}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t\tstyle={{\n\t\t\t\t\ttransform: `scale(${scale})`,\n\t\t\t\t\ttransformOrigin: 'top left',\n\t\t\t\t}}\n\t\t\t\twrap\n\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLTextShape): Path2D | undefined {\n\t\tif (shape.props.autoSize && this.editor.getEditingShapeId() === shape.id) return undefined\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst path = new Path2D()\n\t\tpath.rect(0, 0, bounds.width, bounds.height)\n\t\treturn path\n\t}\n\n\toverride toSvg(shape: TLTextShape, ctx: SvgExportContext) {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst width = bounds.width / (shape.props.scale ?? 1)\n\t\tconst height = bounds.height / (shape.props.scale ?? 1)\n\n\t\tconst dv = getDisplayValues(this, shape, ctx.colorMode)\n\n\t\tconst exportBounds = new Box(0, 0, width, height)\n\t\treturn (\n\t\t\t<RichTextSVG\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={shape.props.textAlign === 'middle' ? 'center' : shape.props.textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={shape.props.richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tbounds={exportBounds}\n\t\t\t\tpadding={0}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride onResize(shape: TLTextShape, info: TLResizeInfo<TLTextShape>) {\n\t\tconst { newPoint, initialBounds, initialShape, scaleX, handle } = info\n\n\t\tif (info.mode === 'scale_shape' || (handle !== 'right' && handle !== 'left')) {\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\t...resizeScaled(shape, info),\n\t\t\t}\n\t\t} else {\n\t\t\tconst nextWidth = Math.max(1, Math.abs(initialBounds.width * scaleX))\n\t\t\tconst { x, y } =\n\t\t\t\tscaleX < 0 ? Vec.Sub(newPoint, Vec.FromAngle(shape.rotation).mul(nextWidth)) : newPoint\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tprops: {\n\t\t\t\t\tw: nextWidth / initialShape.props.scale,\n\t\t\t\t\tautoSize: false,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\toverride onEditEnd(shape: TLTextShape) {\n\t\t// todo: find a way to check if the rich text has any nodes that aren't empty spaces\n\t\tconst trimmedText = renderPlaintextFromRichText(this.editor, shape.props.richText).trimEnd()\n\n\t\tif (trimmedText.length === 0) {\n\t\t\tthis.editor.deleteShapes([shape.id])\n\t\t}\n\t}\n\n\toverride onBeforeUpdate(prev: TLTextShape, next: TLTextShape) {\n\t\tif (!next.props.autoSize) return\n\n\t\tconst styleDidChange =\n\t\t\tprev.props.size !== next.props.size ||\n\t\t\tprev.props.textAlign !== next.props.textAlign ||\n\t\t\tprev.props.font !== next.props.font ||\n\t\t\t(prev.props.scale !== 1 && next.props.scale === 1)\n\n\t\tconst textDidChange = !isEqual(prev.props.richText, next.props.richText)\n\n\t\t// Only update position if either changed\n\t\tif (!styleDidChange && !textDidChange) return\n\n\t\t// Might return a cached value for the bounds\n\t\tconst boundsA = this.getMinDimensions(prev)\n\n\t\t// Will always be a fresh call to getTextSize\n\t\tconst dv = getDisplayValues(this, next)\n\t\tconst boundsB = getTextSize(this.editor, next.props, dv)\n\n\t\tconst wA = boundsA.width * prev.props.scale\n\t\tconst hA = boundsA.height * prev.props.scale\n\t\tconst wB = boundsB.width * next.props.scale\n\t\tconst hB = boundsB.height * next.props.scale\n\n\t\tlet delta: Vec | undefined\n\n\t\tswitch (next.props.textAlign) {\n\t\t\tcase 'middle': {\n\t\t\t\tdelta = new Vec((wB - wA) / 2, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'end': {\n\t\t\t\tdelta = new Vec(wB - wA, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tif (textDidChange) break\n\t\t\t\tdelta = new Vec(0, (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (delta) {\n\t\t\t// account for shape rotation when writing text:\n\t\t\tdelta.rot(next.rotation)\n\t\t\tconst { x, y } = next\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tx: x - delta.x,\n\t\t\t\ty: y - delta.y,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t} else {\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t}\n\t}\n\n\t// \ttodo: The edge doubleclicking feels like a mistake more often than\n\t// not, especially on multiline text. Removed June 16 2024\n\n\t// override onDoubleClickEdge = (shape: TLTextShape) => {\n\t// \t// If the shape has a fixed width, set it to autoSize.\n\t// \tif (!shape.props.autoSize) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tautoSize: true,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// \t// If the shape is scaled, reset the scale to 1.\n\t// \tif (shape.props.scale !== 1) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tscale: 1,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// }\n}\n\nfunction getTextSize(editor: Editor, props: TLTextShape['props'], dv: TextShapeUtilDisplayValues) {\n\tconst { richText, w } = props\n\n\tconst minWidth = 16\n\n\tconst maybeFixedWidth = props.autoSize ? null : Math.max(minWidth, Math.floor(w))\n\n\tconst html = renderHtmlFromRichTextForMeasurement(editor, richText)\n\tconst result = editor.textMeasure.measureHtml(html, {\n\t\tlineHeight: dv.lineHeight,\n\t\tfontWeight: dv.fontWeight,\n\t\tfontStyle: dv.fontStyle,\n\t\tpadding: '0px',\n\t\tfontFamily: dv.fontFamily,\n\t\tfontSize: dv.fontSize,\n\t\tmaxWidth: maybeFixedWidth,\n\t})\n\n\t// If we're autosizing the measureText will essentially `Math.floor`\n\t// the numbers so `19` rather than `19.3`, this means we must +1 to\n\t// whatever we get to avoid wrapping.\n\treturn {\n\t\twidth: maybeFixedWidth ?? Math.max(minWidth, result.w + 1),\n\t\theight: Math.max(dv.fontSize, result.h),\n\t}\n}\n\nfunction useTextShapeKeydownHandler(id: TLShapeId) {\n\tconst editor = useEditor()\n\n\treturn useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (editor.getEditingShapeId() !== id) return\n\n\t\t\tswitch (e.key) {\n\t\t\t\tcase 'Enter': {\n\t\t\t\t\tif (e.ctrlKey || e.metaKey) {\n\t\t\t\t\t\teditor.complete()\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor, id]\n\t)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBox,\n\tEditor,\n\tRectangle2d,\n\tShapeUtil,\n\tSvgExportContext,\n\tTLGeometryOpts,\n\tTLResizeInfo,\n\tTLShapeId,\n\tTLTextShape,\n\tVec,\n\tcreateComputedCache,\n\tgetColorValue,\n\tgetFontsFromRichText,\n\tisEqual,\n\tresizeScaled,\n\ttextShapeMigrations,\n\ttextShapeProps,\n\ttoRichText,\n\tuseColorMode,\n\tuseEditor,\n} from '@tldraw/editor'\nimport { useCallback } from 'react'\nimport {\n\trenderHtmlFromRichTextForMeasurement,\n\trenderPlaintextFromRichText,\n} from '../../utils/text/richText'\nimport { FONT_SIZES, TEXT_PROPS, getFontFamily } from '../shared/default-shape-constants'\nimport { getThemeFontFaces } from '../shared/defaultFonts'\nimport { ShapeOptionsWithDisplayValues, getDisplayValues } from '../shared/getDisplayValues'\nimport { RichTextLabel, RichTextSVG } from '../shared/RichTextLabel'\n\n// Export-only slack (as a fraction of font size) added around a text shape's advance box in `toSvg`,\n// so italic/cursive glyph ink that spills past the box \u2014 side bearings, slanted ascenders \u2014 isn't\n// clipped at the <foreignObject> edge on export (#8802). The text still renders in the same place;\n// the export's auto-trim crops the extra room back to the real pixels, so the PNG stays tight. This\n// is deliberately export-only: the shape's geometry (selection, hit-testing, the text-edit cursor)\n// stays the advance box, which is the right box for those.\nconst TEXT_EXPORT_INK_MARGIN = 0.3\n\nconst sizeCache = createComputedCache(\n\t'text size',\n\t(editor: Editor, shape: TLTextShape) => {\n\t\teditor.fonts.trackFontsForShape(shape)\n\t\tconst util = editor.getShapeUtil(shape) as TextShapeUtil\n\t\tconst dv = getDisplayValues(util, shape)\n\t\treturn getTextSize(editor, shape.props, dv)\n\t},\n\t{ areRecordsEqual: (a, b) => a.props === b.props }\n)\n/** @public */\nexport interface TextShapeUtilDisplayValues {\n\tcolor: string\n\tfontFamily: string\n\tfontSize: number\n\tlineHeight: number\n\tfontWeight: string\n\tfontStyle: string\n\tfontVariant: string\n}\n\n/** @public */\nexport interface TextShapeOptions extends ShapeOptionsWithDisplayValues<\n\tTLTextShape,\n\tTextShapeUtilDisplayValues\n> {\n\t/** How much addition padding should be added to the horizontal geometry of the shape when binding to an arrow? */\n\textraArrowHorizontalPadding: number\n\t/** Whether to show the outline of the text shape (using the same color as the canvas). This helps with overlapping shapes. It does not show up on Safari, where text outline is a performance issues. */\n\tshowTextOutline: boolean\n}\n\n/** @public */\nexport class TextShapeUtil extends ShapeUtil<TLTextShape> {\n\tstatic override type = 'text' as const\n\tstatic override props = textShapeProps\n\tstatic override migrations = textShapeMigrations\n\n\toverride options: TextShapeOptions = {\n\t\textraArrowHorizontalPadding: 10,\n\t\tshowTextOutline: true,\n\t\tgetDefaultDisplayValues(_editor, shape, theme, colorMode): TextShapeUtilDisplayValues {\n\t\t\tconst { color, font, size } = shape.props\n\t\t\treturn {\n\t\t\t\tcolor: getColorValue(theme.colors[colorMode], color, 'solid'),\n\t\t\t\tfontFamily: getFontFamily(theme, font),\n\t\t\t\tfontSize: theme.fontSize * FONT_SIZES[size],\n\t\t\t\tlineHeight: theme.lineHeight,\n\t\t\t\tfontWeight: TEXT_PROPS.fontWeight,\n\t\t\t\tfontStyle: TEXT_PROPS.fontStyle,\n\t\t\t\tfontVariant: TEXT_PROPS.fontVariant,\n\t\t\t}\n\t\t},\n\t\tgetCustomDisplayValues(): Partial<TextShapeUtilDisplayValues> {\n\t\t\treturn {}\n\t\t},\n\t}\n\n\tgetDefaultProps(): TLTextShape['props'] {\n\t\treturn {\n\t\t\tcolor: 'black',\n\t\t\tsize: 'm',\n\t\t\tw: 8,\n\t\t\tfont: 'draw',\n\t\t\ttextAlign: 'start',\n\t\t\tautoSize: true,\n\t\t\tscale: 1,\n\t\t\trichText: toRichText(''),\n\t\t}\n\t}\n\n\tgetMinDimensions(shape: TLTextShape) {\n\t\treturn sizeCache.get(this.editor, shape.id)!\n\t}\n\n\tgetGeometry(shape: TLTextShape, opts: TLGeometryOpts) {\n\t\tconst { scale } = shape.props\n\t\tconst { width, height } = this.getMinDimensions(shape)!\n\t\tconst context = opts?.context ?? 'none'\n\t\treturn new Rectangle2d({\n\t\t\tx:\n\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t? -this.options.extraArrowHorizontalPadding\n\t\t\t\t\t: 0) * scale,\n\t\t\twidth:\n\t\t\t\t(width +\n\t\t\t\t\t(context === '@tldraw/arrow-without-arrowhead'\n\t\t\t\t\t\t? this.options.extraArrowHorizontalPadding * 2\n\t\t\t\t\t\t: 0)) *\n\t\t\t\tscale,\n\t\t\theight: height * scale,\n\t\t\tisFilled: true,\n\t\t\tisLabel: true,\n\t\t})\n\t}\n\n\toverride getFontFaces(shape: TLTextShape) {\n\t\tconst themeFaces = getThemeFontFaces(this.editor.getCurrentTheme(), shape.props.font)\n\t\tif (themeFaces) return themeFaces\n\t\treturn getFontsFromRichText(this.editor, shape.props.richText, {\n\t\t\tfamily: `tldraw_${shape.props.font}`,\n\t\t\tweight: 'normal',\n\t\t\tstyle: 'normal',\n\t\t})\n\t}\n\n\toverride getText(shape: TLTextShape) {\n\t\treturn renderPlaintextFromRichText(this.editor, shape.props.richText)\n\t}\n\n\toverride canEdit(shape: TLTextShape) {\n\t\treturn true\n\t}\n\n\toverride isAspectRatioLocked(shape: TLTextShape) {\n\t\treturn true\n\t} // WAIT NO THIS IS HARD CODED IN THE RESIZE HANDLER\n\n\tcomponent(shape: TLTextShape) {\n\t\tconst {\n\t\t\tid,\n\t\t\tprops: { richText, scale, textAlign },\n\t\t} = shape\n\n\t\tconst { width, height } = this.getMinDimensions(shape)\n\t\tconst isSelected = shape.id === this.editor.getOnlySelectedShapeId()\n\t\tconst colorMode = useColorMode()\n\t\tconst dv = getDisplayValues(this, shape, colorMode)\n\t\tconst handleKeyDown = useTextShapeKeydownHandler(id)\n\n\t\treturn (\n\t\t\t<RichTextLabel\n\t\t\t\tshapeId={id}\n\t\t\t\tclassNamePrefix=\"tl-text-shape\"\n\t\t\t\ttype=\"text\"\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={textAlign === 'middle' ? 'center' : textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tisSelected={isSelected}\n\t\t\t\ttextWidth={width}\n\t\t\t\ttextHeight={height}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t\tstyle={{\n\t\t\t\t\ttransform: `scale(${scale})`,\n\t\t\t\t\ttransformOrigin: 'top left',\n\t\t\t\t}}\n\t\t\t\twrap\n\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLTextShape): Path2D | undefined {\n\t\tif (shape.props.autoSize && this.editor.getEditingShapeId() === shape.id) return undefined\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst path = new Path2D()\n\t\tpath.rect(0, 0, bounds.width, bounds.height)\n\t\treturn path\n\t}\n\n\toverride toSvg(shape: TLTextShape, ctx: SvgExportContext) {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst width = bounds.width / (shape.props.scale ?? 1)\n\t\tconst height = bounds.height / (shape.props.scale ?? 1)\n\n\t\tconst dv = getDisplayValues(this, shape, ctx.colorMode)\n\n\t\t// Pad the exported foreignObject by a margin so glyph ink can spill past the advance box\n\t\t// without being clipped at its edge; the text is inset by the same margin so it renders in the\n\t\t// same place, and the export's trim crops the slack back to content. See TEXT_EXPORT_INK_MARGIN.\n\t\tconst inkMargin = Math.ceil(dv.fontSize * TEXT_EXPORT_INK_MARGIN)\n\t\tconst exportBounds = new Box(\n\t\t\t-inkMargin,\n\t\t\t-inkMargin,\n\t\t\twidth + inkMargin * 2,\n\t\t\theight + inkMargin * 2\n\t\t)\n\t\treturn (\n\t\t\t<RichTextSVG\n\t\t\t\tfontSize={dv.fontSize}\n\t\t\t\tfontFamily={dv.fontFamily}\n\t\t\t\tlineHeight={dv.lineHeight}\n\t\t\t\ttextAlign={shape.props.textAlign === 'middle' ? 'center' : shape.props.textAlign}\n\t\t\t\tverticalAlign=\"middle\"\n\t\t\t\trichText={shape.props.richText}\n\t\t\t\tlabelColor={dv.color}\n\t\t\t\tbounds={exportBounds}\n\t\t\t\tpadding={inkMargin}\n\t\t\t\tshowTextOutline={this.options.showTextOutline}\n\t\t\t/>\n\t\t)\n\t}\n\n\toverride onResize(shape: TLTextShape, info: TLResizeInfo<TLTextShape>) {\n\t\tconst { newPoint, initialBounds, initialShape, scaleX, handle } = info\n\n\t\tif (info.mode === 'scale_shape' || (handle !== 'right' && handle !== 'left')) {\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\t...resizeScaled(shape, info),\n\t\t\t}\n\t\t} else {\n\t\t\tconst nextWidth = Math.max(1, Math.abs(initialBounds.width * scaleX))\n\t\t\tconst { x, y } =\n\t\t\t\tscaleX < 0 ? Vec.Sub(newPoint, Vec.FromAngle(shape.rotation).mul(nextWidth)) : newPoint\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tprops: {\n\t\t\t\t\tw: nextWidth / initialShape.props.scale,\n\t\t\t\t\tautoSize: false,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\toverride onEditEnd(shape: TLTextShape) {\n\t\t// todo: find a way to check if the rich text has any nodes that aren't empty spaces\n\t\tconst trimmedText = renderPlaintextFromRichText(this.editor, shape.props.richText).trimEnd()\n\n\t\tif (trimmedText.length === 0) {\n\t\t\tthis.editor.deleteShapes([shape.id])\n\t\t}\n\t}\n\n\toverride onBeforeUpdate(prev: TLTextShape, next: TLTextShape) {\n\t\tif (!next.props.autoSize) return\n\n\t\tconst styleDidChange =\n\t\t\tprev.props.size !== next.props.size ||\n\t\t\tprev.props.textAlign !== next.props.textAlign ||\n\t\t\tprev.props.font !== next.props.font ||\n\t\t\t(prev.props.scale !== 1 && next.props.scale === 1)\n\n\t\tconst textDidChange = !isEqual(prev.props.richText, next.props.richText)\n\n\t\t// Only update position if either changed\n\t\tif (!styleDidChange && !textDidChange) return\n\n\t\t// Might return a cached value for the bounds\n\t\tconst boundsA = this.getMinDimensions(prev)\n\n\t\t// Will always be a fresh call to getTextSize\n\t\tconst dv = getDisplayValues(this, next)\n\t\tconst boundsB = getTextSize(this.editor, next.props, dv)\n\n\t\tconst wA = boundsA.width * prev.props.scale\n\t\tconst hA = boundsA.height * prev.props.scale\n\t\tconst wB = boundsB.width * next.props.scale\n\t\tconst hB = boundsB.height * next.props.scale\n\n\t\tlet delta: Vec | undefined\n\n\t\tswitch (next.props.textAlign) {\n\t\t\tcase 'middle': {\n\t\t\t\tdelta = new Vec((wB - wA) / 2, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'end': {\n\t\t\t\tdelta = new Vec(wB - wA, textDidChange ? 0 : (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tif (textDidChange) break\n\t\t\t\tdelta = new Vec(0, (hB - hA) / 2)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (delta) {\n\t\t\t// account for shape rotation when writing text:\n\t\t\tdelta.rot(next.rotation)\n\t\t\tconst { x, y } = next\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tx: x - delta.x,\n\t\t\t\ty: y - delta.y,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t} else {\n\t\t\treturn {\n\t\t\t\t...next,\n\t\t\t\tprops: { ...next.props, w: wB },\n\t\t\t}\n\t\t}\n\t}\n\n\t// \ttodo: The edge doubleclicking feels like a mistake more often than\n\t// not, especially on multiline text. Removed June 16 2024\n\n\t// override onDoubleClickEdge = (shape: TLTextShape) => {\n\t// \t// If the shape has a fixed width, set it to autoSize.\n\t// \tif (!shape.props.autoSize) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tautoSize: true,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// \t// If the shape is scaled, reset the scale to 1.\n\t// \tif (shape.props.scale !== 1) {\n\t// \t\treturn {\n\t// \t\t\tid: shape.id,\n\t// \t\t\ttype: shape.type,\n\t// \t\t\tprops: {\n\t// \t\t\t\tscale: 1,\n\t// \t\t\t},\n\t// \t\t}\n\t// \t}\n\t// }\n}\n\nfunction getTextSize(editor: Editor, props: TLTextShape['props'], dv: TextShapeUtilDisplayValues) {\n\tconst { richText, w } = props\n\n\tconst minWidth = 16\n\n\tconst maybeFixedWidth = props.autoSize ? null : Math.max(minWidth, Math.floor(w))\n\n\tconst html = renderHtmlFromRichTextForMeasurement(editor, richText)\n\tconst result = editor.textMeasure.measureHtml(html, {\n\t\tlineHeight: dv.lineHeight,\n\t\tfontWeight: dv.fontWeight,\n\t\tfontStyle: dv.fontStyle,\n\t\tpadding: '0px',\n\t\tfontFamily: dv.fontFamily,\n\t\tfontSize: dv.fontSize,\n\t\tmaxWidth: maybeFixedWidth,\n\t})\n\n\t// If we're autosizing the measureText will essentially `Math.floor`\n\t// the numbers so `19` rather than `19.3`, this means we must +1 to\n\t// whatever we get to avoid wrapping.\n\treturn {\n\t\twidth: maybeFixedWidth ?? Math.max(minWidth, result.w + 1),\n\t\theight: Math.max(dv.fontSize, result.h),\n\t}\n}\n\nfunction useTextShapeKeydownHandler(id: TLShapeId) {\n\tconst editor = useEditor()\n\n\treturn useCallback(\n\t\t(e: KeyboardEvent) => {\n\t\t\tif (editor.getEditingShapeId() !== id) return\n\n\t\t\tswitch (e.key) {\n\t\t\t\tcase 'Enter': {\n\t\t\t\t\tif (e.ctrlKey || e.metaKey) {\n\t\t\t\t\t\teditor.complete()\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor, id]\n\t)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "5.2.1";
1
+ const version = "5.2.2";
2
2
  const publishDates = {
3
3
  major: "2026-05-06T16:28:18.473Z",
4
4
  minor: "2026-07-01T09:20:01.735Z",
5
- patch: "2026-07-01T10:28:54.350Z"
5
+ patch: "2026-07-02T09:30:17.167Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/ui/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.2.1'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-07-01T09:20:01.735Z',\n\tpatch: '2026-07-01T10:28:54.350Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.2.2'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-07-01T09:20:01.735Z',\n\tpatch: '2026-07-02T09:30:17.167Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tldraw",
3
3
  "description": "A tiny little drawing editor.",
4
- "version": "5.2.1",
4
+ "version": "5.2.2",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -63,9 +63,9 @@
63
63
  "@tiptap/pm": "^3.12.1",
64
64
  "@tiptap/react": "^3.12.1",
65
65
  "@tiptap/starter-kit": "^3.12.1",
66
- "@tldraw/driver": "5.2.1",
67
- "@tldraw/editor": "5.2.1",
68
- "@tldraw/store": "5.2.1",
66
+ "@tldraw/driver": "5.2.2",
67
+ "@tldraw/editor": "5.2.2",
68
+ "@tldraw/store": "5.2.2",
69
69
  "classnames": "^2.5.1",
70
70
  "idb": "^7.1.1",
71
71
  "lz-string": "^1.5.0",
@@ -31,6 +31,14 @@ import { getThemeFontFaces } from '../shared/defaultFonts'
31
31
  import { ShapeOptionsWithDisplayValues, getDisplayValues } from '../shared/getDisplayValues'
32
32
  import { RichTextLabel, RichTextSVG } from '../shared/RichTextLabel'
33
33
 
34
+ // Export-only slack (as a fraction of font size) added around a text shape's advance box in `toSvg`,
35
+ // so italic/cursive glyph ink that spills past the box — side bearings, slanted ascenders — isn't
36
+ // clipped at the <foreignObject> edge on export (#8802). The text still renders in the same place;
37
+ // the export's auto-trim crops the extra room back to the real pixels, so the PNG stays tight. This
38
+ // is deliberately export-only: the shape's geometry (selection, hit-testing, the text-edit cursor)
39
+ // stays the advance box, which is the right box for those.
40
+ const TEXT_EXPORT_INK_MARGIN = 0.3
41
+
34
42
  const sizeCache = createComputedCache(
35
43
  'text size',
36
44
  (editor: Editor, shape: TLTextShape) => {
@@ -202,7 +210,16 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
202
210
 
203
211
  const dv = getDisplayValues(this, shape, ctx.colorMode)
204
212
 
205
- const exportBounds = new Box(0, 0, width, height)
213
+ // Pad the exported foreignObject by a margin so glyph ink can spill past the advance box
214
+ // without being clipped at its edge; the text is inset by the same margin so it renders in the
215
+ // same place, and the export's trim crops the slack back to content. See TEXT_EXPORT_INK_MARGIN.
216
+ const inkMargin = Math.ceil(dv.fontSize * TEXT_EXPORT_INK_MARGIN)
217
+ const exportBounds = new Box(
218
+ -inkMargin,
219
+ -inkMargin,
220
+ width + inkMargin * 2,
221
+ height + inkMargin * 2
222
+ )
206
223
  return (
207
224
  <RichTextSVG
208
225
  fontSize={dv.fontSize}
@@ -213,7 +230,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
213
230
  richText={shape.props.richText}
214
231
  labelColor={dv.color}
215
232
  bounds={exportBounds}
216
- padding={0}
233
+ padding={inkMargin}
217
234
  showTextOutline={this.options.showTextOutline}
218
235
  />
219
236
  )
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '5.2.1'
4
+ export const version = '5.2.2'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
7
  minor: '2026-07-01T09:20:01.735Z',
8
- patch: '2026-07-01T10:28:54.350Z',
8
+ patch: '2026-07-02T09:30:17.167Z',
9
9
  }