tldraw 5.2.0-next.e2b8d10bf10e → 5.2.0

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.
Files changed (420) hide show
  1. package/DOCS.md +20294 -0
  2. package/README.md +13 -1
  3. package/RELEASE_NOTES.md +2908 -0
  4. package/dist-cjs/index.d.ts +110 -23
  5. package/dist-cjs/index.js +3 -3
  6. package/dist-cjs/index.js.map +2 -2
  7. package/dist-cjs/lib/TldrawImage.js +0 -1
  8. package/dist-cjs/lib/TldrawImage.js.map +2 -2
  9. package/dist-cjs/lib/bindings/arrow/ArrowBindingUtil.js +2 -1
  10. package/dist-cjs/lib/bindings/arrow/ArrowBindingUtil.js.map +2 -2
  11. package/dist-cjs/lib/defaultEmbedDefinitions.js +3 -2
  12. package/dist-cjs/lib/defaultEmbedDefinitions.js.map +2 -2
  13. package/dist-cjs/lib/defaultOverlayUtils.js +11 -11
  14. package/dist-cjs/lib/defaultOverlayUtils.js.map +2 -2
  15. package/dist-cjs/lib/overlays/SelectionForegroundOverlayUtil.js +76 -64
  16. package/dist-cjs/lib/overlays/SelectionForegroundOverlayUtil.js.map +2 -2
  17. package/dist-cjs/lib/overlays/ShapeHandleOverlayUtil.js +1 -1
  18. package/dist-cjs/lib/overlays/ShapeHandleOverlayUtil.js.map +2 -2
  19. package/dist-cjs/lib/overlays/ShapeIndicatorOverlayUtil.js +107 -0
  20. package/dist-cjs/lib/overlays/ShapeIndicatorOverlayUtil.js.map +7 -0
  21. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +3 -0
  22. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
  23. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +12 -3
  24. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
  25. package/dist-cjs/lib/shapes/bookmark/bookmarks.js +54 -6
  26. package/dist-cjs/lib/shapes/bookmark/bookmarks.js.map +2 -2
  27. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +2 -1
  28. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
  29. package/dist-cjs/lib/shapes/draw/getPath.js +1 -1
  30. package/dist-cjs/lib/shapes/draw/getPath.js.map +2 -2
  31. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js +45 -47
  32. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js.map +2 -2
  33. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +2 -1
  34. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +2 -2
  35. package/dist-cjs/lib/shapes/frame/FrameShapeTool.js +23 -18
  36. package/dist-cjs/lib/shapes/frame/FrameShapeTool.js.map +2 -2
  37. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -1
  38. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  39. package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js +3 -0
  40. package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js.map +2 -2
  41. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +11 -7
  42. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
  43. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -11
  44. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
  45. package/dist-cjs/lib/shapes/line/toolStates/Pointing.js +5 -1
  46. package/dist-cjs/lib/shapes/line/toolStates/Pointing.js.map +2 -2
  47. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +31 -41
  48. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  49. package/dist-cjs/lib/shapes/note/noteHelpers.js +1 -1
  50. package/dist-cjs/lib/shapes/note/noteHelpers.js.map +2 -2
  51. package/dist-cjs/lib/shapes/note/toolStates/Pointing.js +28 -6
  52. package/dist-cjs/lib/shapes/note/toolStates/Pointing.js.map +2 -2
  53. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +3 -2
  54. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js.map +2 -2
  55. package/dist-cjs/lib/shapes/shared/RichTextLabel.js +3 -3
  56. package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +2 -2
  57. package/dist-cjs/lib/shapes/shared/defaultStyleDefs.js +17 -3
  58. package/dist-cjs/lib/shapes/shared/defaultStyleDefs.js.map +2 -2
  59. package/dist-cjs/lib/shapes/shared/freehand/core.js +424 -0
  60. package/dist-cjs/lib/shapes/shared/freehand/core.js.map +7 -0
  61. package/dist-cjs/lib/shapes/shared/freehand/fmt.js +106 -0
  62. package/dist-cjs/lib/shapes/shared/freehand/fmt.js.map +7 -0
  63. package/dist-cjs/lib/shapes/shared/freehand/getStroke.js +6 -6
  64. package/dist-cjs/lib/shapes/shared/freehand/getStroke.js.map +2 -2
  65. package/dist-cjs/lib/shapes/shared/freehand/getStrokeOutlinePoints.js +320 -86
  66. package/dist-cjs/lib/shapes/shared/freehand/getStrokeOutlinePoints.js.map +3 -3
  67. package/dist-cjs/lib/shapes/shared/freehand/getStrokePoints.js +20 -96
  68. package/dist-cjs/lib/shapes/shared/freehand/getStrokePoints.js.map +3 -3
  69. package/dist-cjs/lib/shapes/shared/freehand/svg.js +55 -19
  70. package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
  71. package/dist-cjs/lib/shapes/shared/freehand/svgInk.js +189 -97
  72. package/dist-cjs/lib/shapes/shared/freehand/svgInk.js.map +3 -3
  73. package/dist-cjs/lib/shapes/shared/freehand/types.js.map +1 -1
  74. package/dist-cjs/lib/shapes/shared/interpolate-props.js +6 -2
  75. package/dist-cjs/lib/shapes/shared/interpolate-props.js.map +2 -2
  76. package/dist-cjs/lib/shapes/text/toolStates/Pointing.js +3 -0
  77. package/dist-cjs/lib/shapes/text/toolStates/Pointing.js.map +2 -2
  78. package/dist-cjs/lib/tools/HandTool/HandTool.js +16 -6
  79. package/dist-cjs/lib/tools/HandTool/HandTool.js.map +2 -2
  80. package/dist-cjs/lib/tools/HandTool/childStates/Dragging.js +10 -1
  81. package/dist-cjs/lib/tools/HandTool/childStates/Dragging.js.map +2 -2
  82. package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js +84 -0
  83. package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js.map +7 -0
  84. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +12 -0
  85. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
  86. package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js +4 -0
  87. package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js.map +2 -2
  88. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +107 -46
  89. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
  90. package/dist-cjs/lib/ui/TldrawUi.js +4 -3
  91. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  92. package/dist-cjs/lib/ui/components/A11y.js +2 -0
  93. package/dist-cjs/lib/ui/components/A11y.js.map +2 -2
  94. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js +14 -1
  95. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js.map +2 -2
  96. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js +0 -6
  97. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js.map +2 -2
  98. package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +2 -2
  99. package/dist-cjs/lib/ui/components/Dialogs.js +17 -37
  100. package/dist-cjs/lib/ui/components/Dialogs.js.map +2 -2
  101. package/dist-cjs/lib/ui/components/HelperButtons/BackToContent.js +19 -4
  102. package/dist-cjs/lib/ui/components/HelperButtons/BackToContent.js.map +2 -2
  103. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +12 -2
  104. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +2 -2
  105. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +47 -70
  106. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
  107. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +25 -46
  108. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
  109. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.js.map +2 -2
  110. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuContent.js.map +2 -2
  111. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.js.map +2 -2
  112. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js +1 -1
  113. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js.map +2 -2
  114. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +1 -0
  115. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +2 -2
  116. package/dist-cjs/lib/ui/components/menu-items.js +7 -0
  117. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  118. package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js +2 -1
  119. package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js.map +2 -2
  120. package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js +14 -3
  121. package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js.map +2 -2
  122. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +2 -0
  123. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js.map +2 -2
  124. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +2 -0
  125. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +2 -2
  126. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js +0 -2
  127. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js.map +2 -2
  128. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.js +1 -2
  129. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.js.map +2 -2
  130. package/dist-cjs/lib/ui/context/actions.js +70 -1
  131. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  132. package/dist-cjs/lib/ui/context/asset-urls.js +5 -8
  133. package/dist-cjs/lib/ui/context/asset-urls.js.map +2 -2
  134. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  135. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +13 -0
  136. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js.map +2 -2
  137. package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
  138. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  139. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  140. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +2 -0
  141. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  142. package/dist-cjs/lib/ui/version.js +3 -3
  143. package/dist-cjs/lib/ui/version.js.map +1 -1
  144. package/dist-cjs/lib/utils/embeds/embeds.js +4 -4
  145. package/dist-cjs/lib/utils/embeds/embeds.js.map +2 -2
  146. package/dist-cjs/lib/utils/test-helpers.js +2 -2
  147. package/dist-cjs/lib/utils/test-helpers.js.map +2 -2
  148. package/dist-cjs/lib/utils/text/richText.js +3 -1
  149. package/dist-cjs/lib/utils/text/richText.js.map +2 -2
  150. package/dist-esm/index.d.mts +110 -23
  151. package/dist-esm/index.mjs +5 -3
  152. package/dist-esm/index.mjs.map +2 -2
  153. package/dist-esm/lib/TldrawImage.mjs +0 -1
  154. package/dist-esm/lib/TldrawImage.mjs.map +2 -2
  155. package/dist-esm/lib/bindings/arrow/ArrowBindingUtil.mjs +2 -1
  156. package/dist-esm/lib/bindings/arrow/ArrowBindingUtil.mjs.map +2 -2
  157. package/dist-esm/lib/defaultEmbedDefinitions.mjs +3 -2
  158. package/dist-esm/lib/defaultEmbedDefinitions.mjs.map +2 -2
  159. package/dist-esm/lib/defaultOverlayUtils.mjs +11 -11
  160. package/dist-esm/lib/defaultOverlayUtils.mjs.map +2 -2
  161. package/dist-esm/lib/overlays/SelectionForegroundOverlayUtil.mjs +77 -65
  162. package/dist-esm/lib/overlays/SelectionForegroundOverlayUtil.mjs.map +2 -2
  163. package/dist-esm/lib/overlays/ShapeHandleOverlayUtil.mjs +1 -1
  164. package/dist-esm/lib/overlays/ShapeHandleOverlayUtil.mjs.map +2 -2
  165. package/dist-esm/lib/overlays/ShapeIndicatorOverlayUtil.mjs +87 -0
  166. package/dist-esm/lib/overlays/ShapeIndicatorOverlayUtil.mjs.map +7 -0
  167. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +9 -1
  168. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
  169. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +15 -3
  170. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
  171. package/dist-esm/lib/shapes/bookmark/bookmarks.mjs +54 -6
  172. package/dist-esm/lib/shapes/bookmark/bookmarks.mjs.map +2 -2
  173. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +3 -1
  174. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  175. package/dist-esm/lib/shapes/draw/getPath.mjs +1 -1
  176. package/dist-esm/lib/shapes/draw/getPath.mjs.map +2 -2
  177. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs +46 -47
  178. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs.map +2 -2
  179. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +2 -1
  180. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +2 -2
  181. package/dist-esm/lib/shapes/frame/FrameShapeTool.mjs +23 -18
  182. package/dist-esm/lib/shapes/frame/FrameShapeTool.mjs.map +2 -2
  183. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +2 -1
  184. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  185. package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs +4 -0
  186. package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs.map +2 -2
  187. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +12 -7
  188. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  189. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -11
  190. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  191. package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs +6 -1
  192. package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs.map +2 -2
  193. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +32 -42
  194. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  195. package/dist-esm/lib/shapes/note/noteHelpers.mjs +1 -1
  196. package/dist-esm/lib/shapes/note/noteHelpers.mjs.map +2 -2
  197. package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs +29 -6
  198. package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs.map +2 -2
  199. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs +3 -2
  200. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs.map +2 -2
  201. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +4 -3
  202. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
  203. package/dist-esm/lib/shapes/shared/defaultStyleDefs.mjs +17 -3
  204. package/dist-esm/lib/shapes/shared/defaultStyleDefs.mjs.map +2 -2
  205. package/dist-esm/lib/shapes/shared/freehand/core.mjs +404 -0
  206. package/dist-esm/lib/shapes/shared/freehand/core.mjs.map +7 -0
  207. package/dist-esm/lib/shapes/shared/freehand/fmt.mjs +86 -0
  208. package/dist-esm/lib/shapes/shared/freehand/fmt.mjs.map +7 -0
  209. package/dist-esm/lib/shapes/shared/freehand/getStroke.mjs +7 -7
  210. package/dist-esm/lib/shapes/shared/freehand/getStroke.mjs.map +2 -2
  211. package/dist-esm/lib/shapes/shared/freehand/getStrokeOutlinePoints.mjs +332 -86
  212. package/dist-esm/lib/shapes/shared/freehand/getStrokeOutlinePoints.mjs.map +3 -3
  213. package/dist-esm/lib/shapes/shared/freehand/getStrokePoints.mjs +31 -96
  214. package/dist-esm/lib/shapes/shared/freehand/getStrokePoints.mjs.map +3 -3
  215. package/dist-esm/lib/shapes/shared/freehand/svg.mjs +55 -19
  216. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  217. package/dist-esm/lib/shapes/shared/freehand/svgInk.mjs +214 -99
  218. package/dist-esm/lib/shapes/shared/freehand/svgInk.mjs.map +3 -3
  219. package/dist-esm/lib/shapes/shared/interpolate-props.mjs +6 -2
  220. package/dist-esm/lib/shapes/shared/interpolate-props.mjs.map +2 -2
  221. package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs +4 -0
  222. package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs.map +2 -2
  223. package/dist-esm/lib/tools/HandTool/HandTool.mjs +16 -6
  224. package/dist-esm/lib/tools/HandTool/HandTool.mjs.map +2 -2
  225. package/dist-esm/lib/tools/HandTool/childStates/Dragging.mjs +10 -1
  226. package/dist-esm/lib/tools/HandTool/childStates/Dragging.mjs.map +2 -2
  227. package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs +64 -0
  228. package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs.map +7 -0
  229. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +12 -0
  230. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
  231. package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs +4 -0
  232. package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs.map +2 -2
  233. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +108 -46
  234. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
  235. package/dist-esm/lib/ui/TldrawUi.mjs +4 -3
  236. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  237. package/dist-esm/lib/ui/components/A11y.mjs +2 -0
  238. package/dist-esm/lib/ui/components/A11y.mjs.map +2 -2
  239. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs +21 -2
  240. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs.map +2 -2
  241. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs +0 -6
  242. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs.map +2 -2
  243. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +2 -2
  244. package/dist-esm/lib/ui/components/Dialogs.mjs +19 -39
  245. package/dist-esm/lib/ui/components/Dialogs.mjs.map +2 -2
  246. package/dist-esm/lib/ui/components/HelperButtons/BackToContent.mjs +20 -5
  247. package/dist-esm/lib/ui/components/HelperButtons/BackToContent.mjs.map +2 -2
  248. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +12 -2
  249. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +2 -2
  250. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +47 -70
  251. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
  252. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +25 -46
  253. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
  254. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.mjs.map +2 -2
  255. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuContent.mjs.map +2 -2
  256. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.mjs.map +2 -2
  257. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs +1 -1
  258. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs.map +2 -2
  259. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +1 -0
  260. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +2 -2
  261. package/dist-esm/lib/ui/components/menu-items.mjs +7 -0
  262. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  263. package/dist-esm/lib/ui/components/primitives/TldrawUiDropdownMenu.mjs +2 -1
  264. package/dist-esm/lib/ui/components/primitives/TldrawUiDropdownMenu.mjs.map +2 -2
  265. package/dist-esm/lib/ui/components/primitives/TldrawUiIcon.mjs +14 -3
  266. package/dist-esm/lib/ui/components/primitives/TldrawUiIcon.mjs.map +2 -2
  267. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs +2 -0
  268. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs.map +2 -2
  269. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +2 -0
  270. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
  271. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs +0 -2
  272. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs.map +2 -2
  273. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.mjs +1 -2
  274. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.mjs.map +2 -2
  275. package/dist-esm/lib/ui/context/actions.mjs +71 -1
  276. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  277. package/dist-esm/lib/ui/context/asset-urls.mjs +5 -8
  278. package/dist-esm/lib/ui/context/asset-urls.mjs.map +2 -2
  279. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  280. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs +13 -0
  281. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs.map +2 -2
  282. package/dist-esm/lib/ui/hooks/useTools.mjs +1 -1
  283. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  284. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +2 -0
  285. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  286. package/dist-esm/lib/ui/version.mjs +3 -3
  287. package/dist-esm/lib/ui/version.mjs.map +1 -1
  288. package/dist-esm/lib/utils/embeds/embeds.mjs +4 -4
  289. package/dist-esm/lib/utils/embeds/embeds.mjs.map +2 -2
  290. package/dist-esm/lib/utils/test-helpers.mjs +2 -2
  291. package/dist-esm/lib/utils/test-helpers.mjs.map +2 -2
  292. package/dist-esm/lib/utils/text/richText.mjs +3 -1
  293. package/dist-esm/lib/utils/text/richText.mjs.map +2 -2
  294. package/package.json +11 -6
  295. package/src/index.ts +6 -1
  296. package/src/lib/TldrawImage.tsx +2 -2
  297. package/src/lib/bindings/arrow/ArrowBindingUtil.ts +5 -1
  298. package/src/lib/defaultEmbedDefinitions.ts +28 -6
  299. package/src/lib/defaultOverlayUtils.ts +11 -11
  300. package/src/lib/overlays/SelectionForegroundOverlayUtil.ts +91 -72
  301. package/src/lib/overlays/ShapeHandleOverlayUtil.ts +3 -1
  302. package/src/lib/overlays/ShapeIndicatorOverlayUtil.ts +133 -0
  303. package/src/lib/shapes/arrow/toolStates/Pointing.tsx +11 -1
  304. package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +16 -3
  305. package/src/lib/shapes/bookmark/bookmarks.ts +105 -15
  306. package/src/lib/shapes/draw/DrawShapeUtil.tsx +7 -5
  307. package/src/lib/shapes/draw/getPath.ts +1 -1
  308. package/src/lib/shapes/draw/toolStates/Drawing.ts +55 -48
  309. package/src/lib/shapes/embed/EmbedShapeUtil.tsx +14 -4
  310. package/src/lib/shapes/frame/FrameShapeTool.ts +35 -24
  311. package/src/lib/shapes/geo/GeoShapeUtil.test.tsx +46 -0
  312. package/src/lib/shapes/geo/GeoShapeUtil.tsx +2 -1
  313. package/src/lib/shapes/geo/toolStates/Pointing.ts +5 -0
  314. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +14 -11
  315. package/src/lib/shapes/image/ImageShapeUtil.tsx +7 -11
  316. package/src/lib/shapes/line/LineShapeTool.test.ts +41 -0
  317. package/src/lib/shapes/line/toolStates/Pointing.ts +12 -2
  318. package/src/lib/shapes/note/NoteShapeUtil.tsx +36 -51
  319. package/src/lib/shapes/note/noteCloning.test.ts +5 -5
  320. package/src/lib/shapes/note/noteHelpers.ts +1 -1
  321. package/src/lib/shapes/note/toolStates/Pointing.ts +40 -7
  322. package/src/lib/shapes/shared/PlainTextLabel.tsx +3 -3
  323. package/src/lib/shapes/shared/RichTextLabel.tsx +4 -3
  324. package/src/lib/shapes/shared/defaultStyleDefs.tsx +25 -3
  325. package/src/lib/shapes/shared/freehand/core.ts +547 -0
  326. package/src/lib/shapes/shared/freehand/fmt.ts +106 -0
  327. package/src/lib/shapes/shared/freehand/getStroke.ts +7 -7
  328. package/src/lib/shapes/shared/freehand/getStrokeOutlinePoints.ts +451 -139
  329. package/src/lib/shapes/shared/freehand/getStrokePoints.ts +33 -153
  330. package/src/lib/shapes/shared/freehand/svg.ts +72 -23
  331. package/src/lib/shapes/shared/freehand/svgInk.ts +281 -136
  332. package/src/lib/shapes/shared/freehand/types.ts +0 -1
  333. package/src/lib/shapes/shared/interpolate-props.ts +6 -2
  334. package/src/lib/shapes/text/toolStates/Pointing.ts +5 -0
  335. package/src/lib/tools/HandTool/HandTool.ts +19 -6
  336. package/src/lib/tools/HandTool/childStates/Dragging.ts +17 -1
  337. package/src/lib/tools/HandTool/childStates/OneFingerZooming.ts +79 -0
  338. package/src/lib/tools/SelectTool/childStates/Idle.ts +16 -2
  339. package/src/lib/tools/SelectTool/childStates/PointingShape.ts +8 -0
  340. package/src/lib/tools/SelectTool/childStates/Resizing.ts +137 -49
  341. package/src/lib/ui/TldrawUi.tsx +3 -3
  342. package/src/lib/ui/components/A11y.tsx +2 -0
  343. package/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx +32 -2
  344. package/src/lib/ui/components/ContextMenu/DefaultContextMenuContent.tsx +5 -6
  345. package/src/lib/ui/components/DefaultFollowingIndicator.tsx +2 -2
  346. package/src/lib/ui/components/Dialogs.tsx +20 -23
  347. package/src/lib/ui/components/HelperButtons/BackToContent.tsx +32 -4
  348. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +10 -0
  349. package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +60 -74
  350. package/src/lib/ui/components/Minimap/MinimapManager.ts +35 -57
  351. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.tsx +2 -2
  352. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuContent.tsx +2 -1
  353. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.tsx +2 -1
  354. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx +3 -3
  355. package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +5 -0
  356. package/src/lib/ui/components/menu-items.tsx +9 -0
  357. package/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx +3 -1
  358. package/src/lib/ui/components/primitives/TldrawUiIcon.tsx +15 -3
  359. package/src/lib/ui/components/primitives/TldrawUiInput.tsx +4 -0
  360. package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +7 -0
  361. package/src/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.tsx +0 -2
  362. package/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx +0 -1
  363. package/src/lib/ui/context/actions.tsx +88 -1
  364. package/src/lib/ui/context/asset-urls.tsx +9 -8
  365. package/src/lib/ui/context/events.tsx +2 -0
  366. package/src/lib/ui/hooks/useKeyboardShortcuts.ts +45 -7
  367. package/src/lib/ui/hooks/useTools.tsx +7 -1
  368. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +2 -0
  369. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +2 -0
  370. package/src/lib/ui/version.ts +3 -3
  371. package/src/lib/ui.css +20 -9
  372. package/src/lib/utils/embeds/embeds.test.ts +14 -12
  373. package/src/lib/utils/embeds/embeds.ts +10 -4
  374. package/src/lib/utils/test-helpers.ts +3 -3
  375. package/src/lib/utils/text/richText.test.ts +36 -0
  376. package/src/lib/utils/text/richText.ts +9 -1
  377. package/src/test/HandTool.test.ts +93 -0
  378. package/src/test/ImageShapeUtil.test.ts +88 -0
  379. package/src/test/SelectTool.test.ts +102 -1
  380. package/src/test/__snapshots__/drawing.test.ts.snap +2 -1
  381. package/src/test/attribution.test.ts +40 -29
  382. package/src/test/bookmark-shapes.test.ts +256 -28
  383. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +2 -2
  384. package/src/test/commands/clipboardCallbacks.test.ts +3 -1
  385. package/src/test/commands/getSvgString.test.ts +30 -0
  386. package/src/test/commands/penmode.test.ts +97 -1
  387. package/src/test/commands/setCamera.test.ts +12 -0
  388. package/src/test/dragFromToolbar.test.ts +67 -0
  389. package/src/test/drawing.test.ts +48 -7
  390. package/src/test/frames.test.ts +72 -0
  391. package/src/test/freehand/corpus/presets.ts +71 -0
  392. package/src/test/freehand/corpus/real.ts +370 -0
  393. package/src/test/freehand/corpus/strokes.ts +50 -0
  394. package/src/test/freehand/svg.test.ts +20 -0
  395. package/src/test/freehand/wellformed.test.ts +30 -0
  396. package/src/test/long-press-create.test.ts +102 -0
  397. package/src/test/overlays/CollaboratorBrushOverlayUtil.test.ts +4 -4
  398. package/src/test/overlays/CollaboratorCursorOverlayUtil.test.ts +18 -18
  399. package/src/test/overlays/CollaboratorHintOverlayUtil.test.ts +7 -7
  400. package/src/test/overlays/CollaboratorScribbleOverlayUtil.test.ts +3 -3
  401. package/src/test/overlays/OverlayManager.test.ts +1 -1
  402. package/src/test/overlays/SelectionForegroundOverlayUtil.test.ts +58 -0
  403. package/src/test/overlays/ShapeHandleOverlayUtil.test.ts +2 -2
  404. package/src/test/overlays/ShapeIndicatorOverlayUtil.test.ts +36 -0
  405. package/src/test/resizing.test.ts +250 -0
  406. package/src/test/right-click-context-menu.test.ts +90 -0
  407. package/src/test/rightClickPanning.test.ts +11 -0
  408. package/src/test/selection-omnibus.test.ts +15 -2
  409. package/src/test/spatialIndex.test.ts +132 -0
  410. package/src/test/translating.test.ts +0 -6
  411. package/src/test/ui/ContextMenu.test.tsx +146 -3
  412. package/src/test/ui/Dialogs.test.tsx +256 -0
  413. package/src/test/ui/copyHoveredStyles.test.tsx +154 -0
  414. package/src/test/ui/keyboardShortcuts.test.tsx +266 -0
  415. package/tldraw.css +35 -12
  416. package/dist-cjs/lib/shapes/shared/freehand/setStrokePointRadii.js +0 -100
  417. package/dist-cjs/lib/shapes/shared/freehand/setStrokePointRadii.js.map +0 -7
  418. package/dist-esm/lib/shapes/shared/freehand/setStrokePointRadii.mjs +0 -80
  419. package/dist-esm/lib/shapes/shared/freehand/setStrokePointRadii.mjs.map +0 -7
  420. package/src/lib/shapes/shared/freehand/setStrokePointRadii.ts +0 -127
@@ -1,4 +1,17 @@
1
- import { Vec } from '@tldraw/editor'
1
+ import { Vec, VecLike } from '@tldraw/editor'
2
+ import {
3
+ loadSrcFromStrokePoints,
4
+ resolveTaper,
5
+ srcCount,
6
+ srcInputX,
7
+ srcInputY,
8
+ srcIsCap,
9
+ srcRadius,
10
+ srcRunningLength,
11
+ srcX,
12
+ srcY,
13
+ srcZ,
14
+ } from './core'
2
15
  import type { StrokeOptions, StrokePoint } from './types'
3
16
 
4
17
  const { PI } = Math
@@ -6,43 +19,188 @@ const { PI } = Math
6
19
  // Browser strokes seem to be off if PI is regular, a tiny offset seems to fix it
7
20
  const FIXED_PI = PI + 0.0001
8
21
 
22
+ // How far the simplified outline tracks may deviate from the raw tracks, as a fraction of the
23
+ // stroke size. Well below visible thresholds: the parity harness shows sub-0.1px differences at
24
+ // default stroke sizes.
25
+ const TRACK_TOLERANCE_RATIO = 0.05
26
+
27
+ // The maximum number of intermediate points the track simplifier may drop per kept segment.
28
+ const SIMPLIFY_WINDOW = 8
29
+
30
+ // How many steps to take when rounding a corner
31
+ const MIN_ROUNDED_CORNER_STEPS = 8
32
+ const MAX_ROUNDED_CORNER_STEPS = 13
33
+
34
+ // How many steps to take when rounding a corner
35
+ const MIN_CAP_STEPS = 8
36
+ const MAX_CAP_STEPS = 29
37
+
38
+ // Dot product threshold for identifying a hard corner
39
+ const HARD_CORNER_DPR = -0.62
40
+
41
+ // ---------------------------------------------------------------------------------
42
+ // Track buffers: the left and right outline tracks, written by `buildTracks` and read
43
+ // either by svgInk's path writer or materialized into Vecs by the public functions.
44
+ // Reusable and non-reentrant, like the pipeline buffers in core.ts.
45
+ // ---------------------------------------------------------------------------------
46
+
47
+ let trackCapacity = 1024
48
+ export let trackLeftX = new Float64Array(trackCapacity)
49
+ export let trackLeftY = new Float64Array(trackCapacity)
50
+ export let trackRightX = new Float64Array(trackCapacity)
51
+ export let trackRightY = new Float64Array(trackCapacity)
52
+ export let trackLeftCount = 0
53
+ export let trackRightCount = 0
54
+
55
+ // Tracks grow while being written (corners append a variable number of points), so unlike
56
+ // the other buffers a grow here must copy the points written so far.
57
+ function growTracks() {
58
+ trackCapacity *= 2
59
+ const nlx = new Float64Array(trackCapacity)
60
+ nlx.set(trackLeftX)
61
+ trackLeftX = nlx
62
+ const nly = new Float64Array(trackCapacity)
63
+ nly.set(trackLeftY)
64
+ trackLeftY = nly
65
+ const nrx = new Float64Array(trackCapacity)
66
+ nrx.set(trackRightX)
67
+ trackRightX = nrx
68
+ const nry = new Float64Array(trackCapacity)
69
+ nry.set(trackRightY)
70
+ trackRightY = nry
71
+ }
72
+
73
+ /**
74
+ * Drop track points that lie within tolderance (`tol`) of the segment between their kept neighbors.
75
+ * The outline tracks are dense on gentle curves and straight runs where the quadratic smoothing
76
+ * used for rendering needs far fewer points; this keeps the simplified polyline within `tol` of
77
+ * the original one. Works in place: kept points are compacted toward the front of the arrays.
78
+ *
79
+ * @param xs - The x coordinates of the track to simplify
80
+ * @param ys - The y coordinates of the track to simplify
81
+ * @param len - The number of points in the track
82
+ * @param tol - The tolerance
83
+ *
84
+ * @returns The number of points in the simplified track.
85
+ */
86
+ function simplifyTrack(xs: Float64Array, ys: Float64Array, len: number, tol: number): number {
87
+ if (len <= 2 || tol <= 0) return len
88
+ const tol2 = tol * tol
89
+ let out = 1
90
+ let anchor = 0
91
+ const lastIdx = len - 1
92
+ while (anchor < lastIdx) {
93
+ let best = anchor + 1
94
+ const maxJ = anchor + SIMPLIFY_WINDOW > lastIdx ? lastIdx : anchor + SIMPLIFY_WINDOW
95
+ const ax = xs[anchor]
96
+ const ay = ys[anchor]
97
+ outer: for (let j = anchor + 2; j <= maxJ; j++) {
98
+ const acx = xs[j] - ax
99
+ const acy = ys[j] - ay
100
+ const l2 = acx * acx + acy * acy
101
+ for (let k = anchor + 1; k < j; k++) {
102
+ let t = l2 === 0 ? 0 : ((xs[k] - ax) * acx + (ys[k] - ay) * acy) / l2
103
+ t = t < 0 ? 0 : t > 1 ? 1 : t
104
+ const ex = xs[k] - (ax + acx * t)
105
+ const ey = ys[k] - (ay + acy * t)
106
+ if (ex * ex + ey * ey > tol2) break outer
107
+ }
108
+ best = j
109
+ }
110
+ // Compaction never overtakes the read cursor: the `out`th kept index is always >= out.
111
+ xs[out] = xs[best]
112
+ ys[out] = ys[best]
113
+ out++
114
+ anchor = best
115
+ }
116
+ return out
117
+ }
118
+
9
119
  /**
120
+ * Build the left and right outline tracks for the stroke points currently loaded in the
121
+ * track-source buffers, into the track buffers. This is the array core of
122
+ * `getStrokeOutlineTracks`.
123
+ *
124
+ * `hasAnchor`/`anchorX`/`anchorY` carry the original predecessor of point 1 when the
125
+ * caller has cut or altered the sequence in front of it (svgInk's elbow partitions): the
126
+ * second point's vector is derived from the anchor rather than from point 0, preserving
127
+ * the direction it had in the uncut stroke. It only applies when there are more than two
128
+ * points; two-point sequences derive both vectors from each other.
129
+ *
10
130
  * @internal
11
131
  */
12
- export function getStrokeOutlineTracks(
13
- strokePoints: StrokePoint[],
14
- options: StrokeOptions = {}
15
- ): { left: Vec[]; right: Vec[] } {
132
+ export function buildTracks(
133
+ options: StrokeOptions,
134
+ hasAnchor: boolean,
135
+ anchorX: number,
136
+ anchorY: number
137
+ ): void {
16
138
  const { size = 16, smoothing = 0.5 } = options
17
139
 
18
- // We can't do anything with an empty array or a stroke with negative size.
19
- if (strokePoints.length === 0 || size <= 0) {
20
- return { left: [], right: [] }
21
- }
140
+ let lc = 0
141
+ let rc = 0
142
+ trackLeftCount = 0
143
+ trackRightCount = 0
22
144
 
23
- const firstStrokePoint = strokePoints[0]
24
- const lastStrokePoint = strokePoints[strokePoints.length - 1]
145
+ const n = srcCount
146
+
147
+ // We can't do anything with an empty array or a stroke with negative size.
148
+ if (n === 0 || size <= 0) return
149
+
150
+ // Local captures of the source buffers, hoisting the binding reads out of the loop.
151
+ // The track buffers can grow mid-loop, so those are re-captured after growth.
152
+ const sx = srcX
153
+ const sy = srcY
154
+ const six = srcInputX
155
+ const siy = srcInputY
156
+ const sr = srcRadius
157
+ const srl = srcRunningLength
158
+ const scap = srcIsCap
159
+ let lxs = trackLeftX
160
+ let lys = trackLeftY
161
+ let rxs = trackRightX
162
+ let rys = trackRightY
25
163
 
26
164
  // The total length of the line
27
- const totalLength = lastStrokePoint.runningLength
165
+ const totalLength = srl[n - 1]
28
166
 
29
167
  // The minimum allowed distance between points (squared)
30
168
  const minDistance = Math.pow(size * smoothing, 2)
31
169
 
32
- // Our collected left and right points
33
- const leftPts: Vec[] = []
34
- const rightPts: Vec[] = []
170
+ // Stroke point vectors are derived on the fly from consecutive points: a point's vector is
171
+ // the unit vector pointing back at its predecessor (matching what getStrokePoints used to
172
+ // store). The first point shares the second point's vector; a lone point keeps the legacy
173
+ // unnormalized (1, 1).
174
+ let curVecX = 1
175
+ let curVecY = 1
176
+ if (n > 1) {
177
+ const dx = sx[0] - sx[1]
178
+ const dy = sy[0] - sy[1]
179
+ const l = (dx * dx + dy * dy) ** 0.5
180
+ if (l === 0) {
181
+ curVecX = dx
182
+ curVecY = dy
183
+ } else {
184
+ curVecX = dx / l
185
+ curVecY = dy / l
186
+ }
187
+ }
35
188
 
36
189
  // Previous vector
37
- let prevVector = strokePoints[0].vector
190
+ let prevVecX = curVecX
191
+ let prevVecY = curVecY
38
192
 
39
193
  // Previous left and right points
40
- let pl = strokePoints[0].point
41
- let pr = pl
194
+ let plx = sx[0]
195
+ let ply = sy[0]
196
+ let prx = plx
197
+ let pry = ply
42
198
 
43
199
  // Temporary left and right points
44
- let tl = pl
45
- let tr = pr
200
+ let tlx = plx
201
+ let tly = ply
202
+ let trx = prx
203
+ let trY = pry
46
204
 
47
205
  // Keep track of whether the previous point is a sharp corner
48
206
  // ... so that we don't detect the same corner twice
@@ -55,11 +213,45 @@ export function getStrokeOutlineTracks(
55
213
  skipping the first and last pointsm, which will get caps later on.
56
214
  */
57
215
 
58
- let strokePoint: StrokePoint
59
-
60
- for (let i = 0; i < strokePoints.length; i++) {
61
- strokePoint = strokePoints[i]
62
- const { point, vector } = strokePoints[i]
216
+ for (let i = 0; i < n; i++) {
217
+ const pointX = sx[i]
218
+ const pointY = sy[i]
219
+ const radius = sr[i]
220
+ const vecX = curVecX
221
+ const vecY = curVecY
222
+
223
+ // Derive the next point's vector (the last point reuses its own), and advance the
224
+ // running vector so the next iteration picks it up regardless of `continue`s below.
225
+ let nextVecX = vecX
226
+ let nextVecY = vecY
227
+ if (i < n - 1) {
228
+ const fromX = i === 0 && n > 2 && hasAnchor ? anchorX : pointX
229
+ const fromY = i === 0 && n > 2 && hasAnchor ? anchorY : pointY
230
+ const dx = fromX - sx[i + 1]
231
+ const dy = fromY - sy[i + 1]
232
+ const l = (dx * dx + dy * dy) ** 0.5
233
+ if (l === 0) {
234
+ nextVecX = dx
235
+ nextVecY = dy
236
+ } else {
237
+ nextVecX = dx / l
238
+ nextVecY = dy / l
239
+ }
240
+ }
241
+ curVecX = nextVecX
242
+ curVecY = nextVecY
243
+
244
+ // Make sure a corner's worth of points will fit on each side.
245
+ if (
246
+ lc + MAX_ROUNDED_CORNER_STEPS + 1 > trackCapacity ||
247
+ rc + MAX_ROUNDED_CORNER_STEPS + 1 > trackCapacity
248
+ ) {
249
+ growTracks()
250
+ lxs = trackLeftX
251
+ lys = trackLeftY
252
+ rxs = trackRightX
253
+ rys = trackRightY
254
+ }
63
255
 
64
256
  /*
65
257
  Handle sharp corners
@@ -69,49 +261,75 @@ export function getStrokeOutlineTracks(
69
261
  draw a cap at the current point.
70
262
  */
71
263
 
72
- const prevDpr = strokePoint.vector.dpr(prevVector)
73
- const nextVector = (i < strokePoints.length - 1 ? strokePoints[i + 1] : strokePoints[i]).vector
74
- const nextDpr = i < strokePoints.length - 1 ? nextVector.dpr(strokePoint.vector) : 1
264
+ const prevDpr = vecX * prevVecX + vecY * prevVecY
265
+ const nextDpr = i < n - 1 ? nextVecX * vecX + nextVecY * vecY : 1
75
266
 
76
267
  const isPointSharpCorner = prevDpr < 0 && !isPrevPointSharpCorner
77
- const isNextPointSharpCorner = nextDpr !== null && nextDpr < 0.2
268
+ const isNextPointSharpCorner = nextDpr < 0.2
78
269
 
79
270
  if (isPointSharpCorner || isNextPointSharpCorner) {
80
271
  // It's a sharp corner. Draw a rounded cap and move on to the next point
81
272
  // Considering saving these and drawing them later? So that we can avoid
82
273
  // crossing future points.
83
274
 
84
- if (nextDpr > -0.62 && totalLength - strokePoint.runningLength > strokePoint.radius) {
275
+ if (nextDpr > HARD_CORNER_DPR && totalLength - srl[i] > radius) {
85
276
  // Draw a "soft" corner
86
- const offset = prevVector.clone().mul(strokePoint.radius)
87
- const cpr = prevVector.clone().cpr(nextVector)
277
+ const offsetX = prevVecX * radius
278
+ const offsetY = prevVecY * radius
279
+ const cpr = prevVecX * nextVecY - prevVecY * nextVecX
88
280
 
89
281
  if (cpr < 0) {
90
- tl = Vec.Add(point, offset)
91
- tr = Vec.Sub(point, offset)
282
+ tlx = pointX + offsetX
283
+ tly = pointY + offsetY
284
+ trx = pointX - offsetX
285
+ trY = pointY - offsetY
92
286
  } else {
93
- tl = Vec.Sub(point, offset)
94
- tr = Vec.Add(point, offset)
287
+ tlx = pointX - offsetX
288
+ tly = pointY - offsetY
289
+ trx = pointX + offsetX
290
+ trY = pointY + offsetY
95
291
  }
96
292
 
97
- leftPts.push(tl)
98
- rightPts.push(tr)
293
+ lxs[lc] = tlx
294
+ lys[lc] = tly
295
+ lc++
296
+ rxs[rc] = trx
297
+ rys[rc] = trY
298
+ rc++
99
299
  } else {
100
- // Draw a "sharp" corner
101
- const offset = prevVector.clone().mul(strokePoint.radius).per()
102
- const start = Vec.Sub(strokePoint.input, offset)
103
-
104
- for (let step = 1 / 13, t = 0; t < 1; t += step) {
105
- tl = Vec.RotWith(start, strokePoint.input, FIXED_PI * t)
106
- leftPts.push(tl)
107
-
108
- tr = Vec.RotWith(start, strokePoint.input, FIXED_PI + FIXED_PI * -t)
109
- rightPts.push(tr)
300
+ // Draw a "sharp" corner: rotate around the input point
301
+ const inX = six[i]
302
+ const inY = siy[i]
303
+ // The arm swept around the point starts perpendicular to the
304
+ // incoming direction, one radius long.
305
+ const dx = -prevVecY * radius
306
+ const dy = prevVecX * radius
307
+
308
+ for (let step = 1 / MAX_ROUNDED_CORNER_STEPS, t = 0; t < 1; t += step) {
309
+ let angle = FIXED_PI * t
310
+ let s = Math.sin(angle)
311
+ let c = Math.cos(angle)
312
+ tlx = inX + (dx * c - dy * s)
313
+ tly = inY + (dx * s + dy * c)
314
+ lxs[lc] = tlx
315
+ lys[lc] = tly
316
+ lc++
317
+
318
+ angle = FIXED_PI + FIXED_PI * -t
319
+ s = Math.sin(angle)
320
+ c = Math.cos(angle)
321
+ trx = inX + (dx * c - dy * s)
322
+ trY = inY + (dx * s + dy * c)
323
+ rxs[rc] = trx
324
+ rys[rc] = trY
325
+ rc++
110
326
  }
111
327
  }
112
328
 
113
- pl = tl
114
- pr = tr
329
+ plx = tlx
330
+ ply = tly
331
+ prx = trx
332
+ pry = trY
115
333
 
116
334
  if (isNextPointSharpCorner) {
117
335
  isPrevPointSharpCorner = true
@@ -122,10 +340,16 @@ export function getStrokeOutlineTracks(
122
340
 
123
341
  isPrevPointSharpCorner = false
124
342
 
125
- if (strokePoint === firstStrokePoint || strokePoint === lastStrokePoint) {
126
- const offset = Vec.Per(vector).mul(strokePoint.radius)
127
- leftPts.push(Vec.Sub(point, offset))
128
- rightPts.push(Vec.Add(point, offset))
343
+ if (scap[i]) {
344
+ // Project one radius to each side, perpendicular to the direction of travel.
345
+ const offsetX = vecY * radius
346
+ const offsetY = -vecX * radius
347
+ lxs[lc] = pointX - offsetX
348
+ lys[lc] = pointY - offsetY
349
+ lc++
350
+ rxs[rc] = pointX + offsetX
351
+ rys[rc] = pointY + offsetY
352
+ rc++
129
353
 
130
354
  continue
131
355
  }
@@ -134,125 +358,161 @@ export function getStrokeOutlineTracks(
134
358
  Add regular points
135
359
 
136
360
  Project points to either side of the current point, using the
137
- calculated size as a distance. If a point's distance to the
361
+ calculated size as a distance. If a point's distance to the
138
362
  previous point on that side greater than the minimum distance
139
363
  (or if the corner is kinda sharp), add the points to the side's
140
364
  points array.
141
365
  */
142
366
 
143
- const offset = Vec.Lrp(nextVector, vector, nextDpr).per().mul(strokePoint.radius)
144
-
145
- tl = Vec.Sub(point, offset)
146
-
147
- if (i <= 1 || Vec.Dist2(pl, tl) > minDistance) {
148
- leftPts.push(tl)
149
- pl = tl
367
+ // Project one radius to each side, perpendicular to the direction of
368
+ // travel. The direction blends the current and next vectors, leaning
369
+ // into the next vector as the upcoming turn sharpens.
370
+ const lerpedX = nextVecX + (vecX - nextVecX) * nextDpr
371
+ const lerpedY = nextVecY + (vecY - nextVecY) * nextDpr
372
+ const offsetX = lerpedY * radius
373
+ const offsetY = -lerpedX * radius
374
+
375
+ tlx = pointX - offsetX
376
+ tly = pointY - offsetY
377
+
378
+ if (i <= 1 || (plx - tlx) ** 2 + (ply - tly) ** 2 > minDistance) {
379
+ lxs[lc] = tlx
380
+ lys[lc] = tly
381
+ lc++
382
+ plx = tlx
383
+ ply = tly
150
384
  }
151
385
 
152
- tr = Vec.Add(point, offset)
386
+ trx = pointX + offsetX
387
+ trY = pointY + offsetY
153
388
 
154
- if (i <= 1 || Vec.Dist2(pr, tr) > minDistance) {
155
- rightPts.push(tr)
156
- pr = tr
389
+ if (i <= 1 || (prx - trx) ** 2 + (pry - trY) ** 2 > minDistance) {
390
+ rxs[rc] = trx
391
+ rys[rc] = trY
392
+ rc++
393
+ prx = trx
394
+ pry = trY
157
395
  }
158
396
 
159
397
  // Set variables for next iteration
160
- prevVector = vector
161
-
162
- continue
398
+ prevVecX = vecX
399
+ prevVecY = vecY
163
400
  }
164
401
 
165
- /*
166
- Return the points in the correct winding order: begin on the left side, then
167
- continue around the end cap, then come back along the right side, and finally
168
- complete the start cap.
169
- */
402
+ const tolerance = size * TRACK_TOLERANCE_RATIO
170
403
 
171
- return {
172
- left: leftPts,
173
- right: rightPts,
174
- }
404
+ trackLeftCount = simplifyTrack(trackLeftX, trackLeftY, lc, tolerance)
405
+ trackRightCount = simplifyTrack(trackRightX, trackRightY, rc, tolerance)
175
406
  }
176
407
 
177
408
  /**
178
- * ## getStrokeOutlinePoints
179
- *
180
- * Get an array of points (as `[x, y]`) representing the outline of a stroke.
409
+ * @internal
181
410
  *
182
- * @param points - An array of StrokePoints as returned from `getStrokePoints`.
183
- * @param options - An object with options.
184
- * @public
411
+ * `vectorAnchor` is the original predecessor of `strokePoints[1]` when the caller has cut or
412
+ * altered the sequence in front of it (svgInk's elbow partitions): the second point's vector is
413
+ * derived from the anchor rather than from `strokePoints[0]`, preserving the direction it had in
414
+ * the uncut stroke. It only applies when there are more than two points; two-point sequences
415
+ * derive both vectors from each other.
185
416
  */
186
- export function getStrokeOutlinePoints(
417
+ export function getStrokeOutlineTracks(
187
418
  strokePoints: StrokePoint[],
188
- options: StrokeOptions = {}
189
- ): Vec[] {
419
+ options: StrokeOptions = {},
420
+ vectorAnchor?: VecLike
421
+ ): { left: Vec[]; right: Vec[] } {
422
+ loadSrcFromStrokePoints(strokePoints)
423
+ buildTracks(
424
+ options,
425
+ !!vectorAnchor,
426
+ vectorAnchor ? vectorAnchor.x : 0,
427
+ vectorAnchor ? vectorAnchor.y : 0
428
+ )
429
+
430
+ const lxs = trackLeftX
431
+ const lys = trackLeftY
432
+ const rxs = trackRightX
433
+ const rys = trackRightY
434
+ const left: Vec[] = new Array(trackLeftCount)
435
+ for (let i = 0; i < trackLeftCount; i++) {
436
+ left[i] = new Vec(lxs[i], lys[i])
437
+ }
438
+ const right: Vec[] = new Array(trackRightCount)
439
+ for (let i = 0; i < trackRightCount; i++) {
440
+ right[i] = new Vec(rxs[i], rys[i])
441
+ }
442
+ return { left, right }
443
+ }
444
+
445
+ /** Pick a step count for a polygonal arc so its chord error stays within `tol`. */
446
+ function arcSteps(radius: number, sweep: number, tol: number, min: number, max: number) {
447
+ if (radius <= tol) return min
448
+ const maxAngle = 2 * Math.acos(1 - tol / radius)
449
+ const steps = Math.ceil(sweep / maxAngle)
450
+ return steps < min ? min : steps > max ? max : steps
451
+ }
452
+
453
+ /**
454
+ * Build the full outline (tracks plus caps) for the stroke points currently loaded in the
455
+ * track-source buffers. This is the shared core of `getStrokeOutlinePoints` and
456
+ * `getStroke`.
457
+ *
458
+ * @internal
459
+ */
460
+ export function outlineFromSrc(options: StrokeOptions = {}): Vec[] {
190
461
  const { size = 16, start = {}, end = {}, last: isComplete = false } = options
191
462
 
192
463
  const { cap: capStart = true } = start
193
464
  const { cap: capEnd = true } = end
194
465
 
466
+ const n = srcCount
467
+
195
468
  // We can't do anything with an empty array or a stroke with negative size.
196
- if (strokePoints.length === 0 || size <= 0) {
469
+ if (n === 0 || size <= 0) {
197
470
  return []
198
471
  }
199
472
 
200
- const firstStrokePoint = strokePoints[0]
201
- const lastStrokePoint = strokePoints[strokePoints.length - 1]
202
-
203
473
  // The total length of the line
204
- const totalLength = lastStrokePoint.runningLength
205
-
206
- const taperStart =
207
- start.taper === false
208
- ? 0
209
- : start.taper === true
210
- ? Math.max(size, totalLength)
211
- : (start.taper as number)
212
-
213
- const taperEnd =
214
- end.taper === false
215
- ? 0
216
- : end.taper === true
217
- ? Math.max(size, totalLength)
218
- : (end.taper as number)
474
+ const totalLength = srcRunningLength[n - 1]
475
+
476
+ const taperStart = resolveTaper(start.taper, size, totalLength)
477
+ const taperEnd = resolveTaper(end.taper, size, totalLength)
219
478
 
220
- // The minimum allowed distance between points (squared)
221
479
  // Our collected left and right points
222
- const { left: leftPts, right: rightPts } = getStrokeOutlineTracks(strokePoints, options)
480
+ buildTracks(options, false, 0, 0)
223
481
 
224
- /*
225
- Drawing caps
226
-
227
- Now that we have our points on either side of the line, we need to
228
- draw caps at the start and end. Tapered lines don't have caps, but
229
- may have dots for very short lines.
230
- */
482
+ // Chord tolerance for the polygonal caps below: caps don't need a fixed number of segments,
483
+ // they need enough segments that the polygon is indistinguishable from the arc.
484
+ const capTolerance = Math.max(0.05, size * 0.02)
231
485
 
232
- const firstPoint = firstStrokePoint.point
486
+ const firstRadius = srcRadius[0]
487
+ const firstPoint = new Vec(srcX[0], srcY[0], srcZ[0])
233
488
 
234
489
  const lastPoint =
235
- strokePoints.length > 1
236
- ? strokePoints[strokePoints.length - 1].point
237
- : Vec.AddXY(firstStrokePoint.point, 1, 1)
490
+ n > 1 ? new Vec(srcX[n - 1], srcY[n - 1], srcZ[n - 1]) : Vec.AddXY(firstPoint, 1, 1)
238
491
 
239
492
  /*
240
493
  Draw a dot for very short or completed strokes
241
-
494
+
242
495
  If the line is too short to gather left or right points and if the line is
243
496
  not tapered on either side, draw a dot. If the line is tapered, then only
244
497
  draw a dot if the line is both very short and complete. If we draw a dot,
245
498
  we can just return those points.
246
499
  */
247
500
 
248
- if (strokePoints.length === 1) {
501
+ if (n === 1) {
249
502
  if (!(taperStart || taperEnd) || isComplete) {
250
503
  const start = Vec.Add(
251
504
  firstPoint,
252
- Vec.Sub(firstPoint, lastPoint).uni().per().mul(-firstStrokePoint.radius)
505
+ Vec.Sub(firstPoint, lastPoint).uni().per().mul(-firstRadius)
253
506
  )
254
507
  const dotPts: Vec[] = []
255
- for (let step = 1 / 13, t = step; t <= 1; t += step) {
508
+ const steps = arcSteps(
509
+ firstRadius,
510
+ FIXED_PI * 2,
511
+ capTolerance,
512
+ MIN_ROUNDED_CORNER_STEPS,
513
+ MAX_ROUNDED_CORNER_STEPS
514
+ )
515
+ for (let step = 1 / steps, t = step; t <= 1; t += step) {
256
516
  dotPts.push(Vec.RotWith(start, firstPoint, FIXED_PI * 2 * t))
257
517
  }
258
518
  return dotPts
@@ -269,17 +529,19 @@ export function getStrokeOutlinePoints(
269
529
  */
270
530
 
271
531
  const startCap: Vec[] = []
272
- if (taperStart || (taperEnd && strokePoints.length === 1)) {
532
+ if (taperStart || (taperEnd && n === 1)) {
273
533
  // The start point is tapered, noop
274
534
  } else if (capStart) {
275
- // Draw the round cap - add thirteen points rotating the right point around the start point to the left point
276
- for (let step = 1 / 8, t = step; t <= 1; t += step) {
277
- const pt = Vec.RotWith(rightPts[0], firstPoint, FIXED_PI * t)
535
+ // Draw the round cap - rotate the right point around the start point to the left point
536
+ const firstRight = new Vec(trackRightX[0], trackRightY[0])
537
+ const steps = arcSteps(firstRadius, FIXED_PI, capTolerance, 4, 8)
538
+ for (let step = 1 / steps, t = step; t <= 1; t += step) {
539
+ const pt = Vec.RotWith(firstRight, firstPoint, FIXED_PI * t)
278
540
  startCap.push(pt)
279
541
  }
280
542
  } else {
281
543
  // Draw the flat cap - add a point to the left and right of the start point
282
- const cornersVector = Vec.Sub(leftPts[0], rightPts[0])
544
+ const cornersVector = new Vec(trackLeftX[0] - trackRightX[0], trackLeftY[0] - trackRightY[0])
283
545
  const offsetA = Vec.Mul(cornersVector, 0.5)
284
546
  const offsetB = Vec.Mul(cornersVector, 0.51)
285
547
 
@@ -302,32 +564,82 @@ export function getStrokeOutlinePoints(
302
564
  */
303
565
 
304
566
  const endCap: Vec[] = []
305
- const direction = lastStrokePoint.vector.clone().per().neg()
567
+ const lastRadius = srcRadius[n - 1]
568
+
569
+ // The exit vector at the last point points back at its predecessor,
570
+ // normalized; a lone point keeps the legacy (1, 1). The cap then starts
571
+ // perpendicular to that vector.
572
+ let lastVecX = 1
573
+ let lastVecY = 1
574
+ if (n > 1) {
575
+ const dx = srcX[n - 2] - srcX[n - 1]
576
+ const dy = srcY[n - 2] - srcY[n - 1]
577
+ const l = (dx * dx + dy * dy) ** 0.5
578
+ if (l === 0) {
579
+ lastVecX = dx
580
+ lastVecY = dy
581
+ } else {
582
+ lastVecX = dx / l
583
+ lastVecY = dy / l
584
+ }
585
+ }
586
+ const direction = new Vec(-lastVecY, lastVecX)
306
587
 
307
- if (taperEnd || (taperStart && strokePoints.length === 1)) {
588
+ if (taperEnd || (taperStart && n === 1)) {
308
589
  // Tapered end - push the last point to the line
309
590
  endCap.push(lastPoint)
310
591
  } else if (capEnd) {
311
592
  // Draw the round end cap
312
- const start = Vec.Add(lastPoint, Vec.Mul(direction, lastStrokePoint.radius))
313
- for (let step = 1 / 29, t = step; t < 1; t += step) {
593
+ const start = Vec.Add(lastPoint, Vec.Mul(direction, lastRadius))
594
+ const steps = arcSteps(lastRadius, FIXED_PI * 3, capTolerance, MIN_CAP_STEPS, MAX_CAP_STEPS)
595
+ for (let step = 1 / steps, t = step; t < 1; t += step) {
314
596
  endCap.push(Vec.RotWith(start, lastPoint, FIXED_PI * 3 * t))
315
597
  }
316
598
  } else {
317
599
  // Draw the flat end cap
318
600
  endCap.push(
319
- Vec.Add(lastPoint, Vec.Mul(direction, lastStrokePoint.radius)),
320
- Vec.Add(lastPoint, Vec.Mul(direction, lastStrokePoint.radius * 0.99)),
321
- Vec.Sub(lastPoint, Vec.Mul(direction, lastStrokePoint.radius * 0.99)),
322
- Vec.Sub(lastPoint, Vec.Mul(direction, lastStrokePoint.radius))
601
+ Vec.Add(lastPoint, Vec.Mul(direction, lastRadius)),
602
+ Vec.Add(lastPoint, Vec.Mul(direction, lastRadius * 0.99)),
603
+ Vec.Sub(lastPoint, Vec.Mul(direction, lastRadius * 0.99)),
604
+ Vec.Sub(lastPoint, Vec.Mul(direction, lastRadius))
323
605
  )
324
606
  }
325
607
 
326
608
  /*
327
- Return the points in the correct winding order: begin on the left side, then
328
- continue around the end cap, then come back along the right side, and finally
609
+ Return the points in the correct winding order: begin on the left side, then
610
+ continue around the end cap, then come back along the right side, and finally
329
611
  complete the start cap.
330
612
  */
331
613
 
332
- return leftPts.concat(endCap, rightPts.reverse(), startCap)
614
+ const lxs = trackLeftX
615
+ const lys = trackLeftY
616
+ const rxs = trackRightX
617
+ const rys = trackRightY
618
+ const leftPts: Vec[] = new Array(trackLeftCount)
619
+ for (let i = 0; i < trackLeftCount; i++) {
620
+ leftPts[i] = new Vec(lxs[i], lys[i])
621
+ }
622
+ const rightPtsReversed: Vec[] = new Array(trackRightCount)
623
+ for (let i = 0; i < trackRightCount; i++) {
624
+ rightPtsReversed[i] = new Vec(rxs[trackRightCount - 1 - i], rys[trackRightCount - 1 - i])
625
+ }
626
+
627
+ return leftPts.concat(endCap, rightPtsReversed, startCap)
628
+ }
629
+
630
+ /**
631
+ * ## getStrokeOutlinePoints
632
+ *
633
+ * Get an array of points (as `[x, y]`) representing the outline of a stroke.
634
+ *
635
+ * @param points - An array of StrokePoints as returned from `getStrokePoints`.
636
+ * @param options - An object with options.
637
+ * @public
638
+ */
639
+ export function getStrokeOutlinePoints(
640
+ strokePoints: StrokePoint[],
641
+ options: StrokeOptions = {}
642
+ ): Vec[] {
643
+ loadSrcFromStrokePoints(strokePoints)
644
+ return outlineFromSrc(options)
333
645
  }