@excalidraw/excalidraw 0.17.1-d2f67e6 → 0.17.1-e63dd02

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 (252) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/browser/dev/excalidraw-assets-dev/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  7. package/dist/browser/dev/excalidraw-assets-dev/{chunk-EM6LVGFW.js → chunk-IT7T3AIK.js} +23 -5
  8. package/dist/browser/dev/excalidraw-assets-dev/chunk-IT7T3AIK.js.map +7 -0
  9. package/dist/browser/dev/excalidraw-assets-dev/{chunk-B4UMSLQQ.js → chunk-RNHSD5AR.js} +7451 -2098
  10. package/dist/browser/dev/excalidraw-assets-dev/chunk-RNHSD5AR.js.map +7 -0
  11. package/dist/browser/dev/excalidraw-assets-dev/{dist-6QVAH5JA.js → dist-DNSPZDOZ.js} +31 -19
  12. package/dist/browser/dev/excalidraw-assets-dev/dist-DNSPZDOZ.js.map +7 -0
  13. package/dist/browser/dev/excalidraw-assets-dev/{en-AZFA5HJJ.js → en-XV7OZCPP.js} +6 -2
  14. package/dist/browser/dev/excalidraw-assets-dev/{image-V7E6IT6R.js → image-77HZYGLG.js} +2 -2
  15. package/dist/browser/dev/excalidraw-assets-dev/{image-O66MQ7WQ.css → image-WDHYGKKP.css} +1 -1
  16. package/dist/browser/dev/excalidraw-assets-dev/{image-O66MQ7WQ.css.map → image-WDHYGKKP.css.map} +2 -2
  17. package/dist/browser/dev/index.css +449 -114
  18. package/dist/browser/dev/index.css.map +3 -3
  19. package/dist/browser/dev/index.js +4143 -5956
  20. package/dist/browser/dev/index.js.map +4 -4
  21. package/dist/browser/prod/excalidraw-assets/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  22. package/dist/browser/prod/excalidraw-assets/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  23. package/dist/browser/prod/excalidraw-assets/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  24. package/dist/browser/prod/excalidraw-assets/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  25. package/dist/browser/prod/excalidraw-assets/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  26. package/dist/browser/prod/excalidraw-assets/chunk-OYEADJSR.js +63 -0
  27. package/dist/browser/prod/excalidraw-assets/{chunk-7DXALCB2.js → chunk-PDYFZJMS.js} +3 -3
  28. package/dist/browser/prod/excalidraw-assets/dist-NLUQPPQQ.js +7 -0
  29. package/dist/browser/prod/excalidraw-assets/en-YVAVVILW.js +1 -0
  30. package/dist/browser/prod/excalidraw-assets/image-X3GFZHNN.js +1 -0
  31. package/dist/browser/prod/index.css +1 -1
  32. package/dist/browser/prod/index.js +40 -50
  33. package/dist/dev/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  34. package/dist/dev/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  35. package/dist/dev/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  36. package/dist/dev/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  37. package/dist/dev/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  38. package/dist/dev/{en-EB2MBPAV.json → en-YNVBSAIL.json} +18 -4
  39. package/dist/dev/index.css +449 -114
  40. package/dist/dev/index.css.map +3 -3
  41. package/dist/dev/index.js +21626 -18122
  42. package/dist/dev/index.js.map +4 -4
  43. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +9 -3
  44. package/dist/excalidraw/actions/actionBoundText.d.ts +6 -2
  45. package/dist/excalidraw/actions/actionCanvas.d.ts +36 -12
  46. package/dist/excalidraw/actions/actionClipboard.d.ts +22 -7
  47. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +12 -5
  48. package/dist/excalidraw/actions/actionDeleteSelected.js +24 -5
  49. package/dist/excalidraw/actions/actionDuplicateSelection.js +1 -2
  50. package/dist/excalidraw/actions/actionElementLock.d.ts +6 -2
  51. package/dist/excalidraw/actions/actionExport.d.ts +27 -9
  52. package/dist/excalidraw/actions/actionFinalize.d.ts +6 -2
  53. package/dist/excalidraw/actions/actionFinalize.js +2 -2
  54. package/dist/excalidraw/actions/actionFlip.js +2 -2
  55. package/dist/excalidraw/actions/actionFrame.d.ts +12 -4
  56. package/dist/excalidraw/actions/actionGroup.d.ts +6 -2
  57. package/dist/excalidraw/actions/actionHistory.js +4 -4
  58. package/dist/excalidraw/actions/actionLinearEditor.d.ts +3 -1
  59. package/dist/excalidraw/actions/actionLinearEditor.js +3 -2
  60. package/dist/excalidraw/actions/actionLink.d.ts +3 -1
  61. package/dist/excalidraw/actions/actionMenu.d.ts +9 -3
  62. package/dist/excalidraw/actions/actionNavigate.d.ts +6 -2
  63. package/dist/excalidraw/actions/actionProperties.d.ts +411 -56
  64. package/dist/excalidraw/actions/actionProperties.js +383 -58
  65. package/dist/excalidraw/actions/actionSelectAll.d.ts +3 -1
  66. package/dist/excalidraw/actions/actionStyles.d.ts +3 -1
  67. package/dist/excalidraw/actions/actionStyles.js +3 -2
  68. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +3 -1
  69. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +3 -1
  70. package/dist/excalidraw/actions/actionToggleStats.d.ts +3 -1
  71. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +3 -1
  72. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +3 -1
  73. package/dist/excalidraw/actions/types.d.ts +1 -1
  74. package/dist/excalidraw/analytics.js +9 -7
  75. package/dist/excalidraw/appState.d.ts +1 -0
  76. package/dist/excalidraw/appState.js +9 -1
  77. package/dist/excalidraw/binaryheap.d.ts +12 -0
  78. package/dist/excalidraw/binaryheap.js +93 -0
  79. package/dist/excalidraw/change.d.ts +2 -1
  80. package/dist/excalidraw/change.js +6 -4
  81. package/dist/excalidraw/charts.js +0 -10
  82. package/dist/excalidraw/components/Actions.js +7 -5
  83. package/dist/excalidraw/components/App.d.ts +5 -9
  84. package/dist/excalidraw/components/App.js +218 -161
  85. package/dist/excalidraw/components/ButtonIcon.d.ts +15 -0
  86. package/dist/excalidraw/components/ButtonIcon.js +8 -0
  87. package/dist/excalidraw/components/ButtonIconSelect.js +2 -3
  88. package/dist/excalidraw/components/ButtonSeparator.d.ts +2 -0
  89. package/dist/excalidraw/components/ButtonSeparator.js +7 -0
  90. package/dist/excalidraw/components/ColorPicker/ColorPicker.js +47 -79
  91. package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
  92. package/dist/excalidraw/components/FontPicker/FontPicker.d.ts +21 -0
  93. package/dist/excalidraw/components/FontPicker/FontPicker.js +49 -0
  94. package/dist/excalidraw/components/FontPicker/FontPickerList.d.ts +25 -0
  95. package/dist/excalidraw/components/FontPicker/FontPickerList.js +119 -0
  96. package/dist/excalidraw/components/FontPicker/FontPickerTrigger.d.ts +7 -0
  97. package/dist/excalidraw/components/FontPicker/FontPickerTrigger.js +13 -0
  98. package/dist/excalidraw/components/FontPicker/keyboardNavHandlers.d.ts +14 -0
  99. package/dist/excalidraw/components/FontPicker/keyboardNavHandlers.js +38 -0
  100. package/dist/excalidraw/components/HelpDialog.js +1 -1
  101. package/dist/excalidraw/components/HintViewer.js +6 -3
  102. package/dist/excalidraw/components/PropertiesPopover.d.ts +15 -0
  103. package/dist/excalidraw/components/PropertiesPopover.js +31 -0
  104. package/dist/excalidraw/components/QuickSearch.d.ts +9 -0
  105. package/dist/excalidraw/components/QuickSearch.js +8 -0
  106. package/dist/excalidraw/components/ScrollableList.d.ts +9 -0
  107. package/dist/excalidraw/components/ScrollableList.js +8 -0
  108. package/dist/excalidraw/components/Stats/Angle.d.ts +7 -3
  109. package/dist/excalidraw/components/Stats/Angle.js +39 -31
  110. package/dist/excalidraw/components/Stats/Dimension.d.ts +6 -3
  111. package/dist/excalidraw/components/Stats/Dimension.js +51 -49
  112. package/dist/excalidraw/components/Stats/DragInput.d.ts +15 -6
  113. package/dist/excalidraw/components/Stats/DragInput.js +59 -26
  114. package/dist/excalidraw/components/Stats/FontSize.d.ts +8 -4
  115. package/dist/excalidraw/components/Stats/FontSize.js +39 -36
  116. package/dist/excalidraw/components/Stats/MultiAngle.d.ts +5 -3
  117. package/dist/excalidraw/components/Stats/MultiAngle.js +43 -34
  118. package/dist/excalidraw/components/Stats/MultiDimension.d.ts +5 -3
  119. package/dist/excalidraw/components/Stats/MultiDimension.js +101 -99
  120. package/dist/excalidraw/components/Stats/MultiFontSize.d.ts +6 -3
  121. package/dist/excalidraw/components/Stats/MultiFontSize.js +47 -32
  122. package/dist/excalidraw/components/Stats/MultiPosition.d.ts +3 -1
  123. package/dist/excalidraw/components/Stats/MultiPosition.js +52 -48
  124. package/dist/excalidraw/components/Stats/Position.d.ts +5 -1
  125. package/dist/excalidraw/components/Stats/Position.js +31 -29
  126. package/dist/excalidraw/components/Stats/index.js +5 -17
  127. package/dist/excalidraw/components/Stats/utils.d.ts +14 -3
  128. package/dist/excalidraw/components/Stats/utils.js +48 -9
  129. package/dist/excalidraw/components/TTDDialog/common.d.ts +2 -2
  130. package/dist/excalidraw/components/TTDDialog/common.js +3 -7
  131. package/dist/excalidraw/components/UserList.js +22 -22
  132. package/dist/excalidraw/components/canvases/StaticCanvas.js +1 -0
  133. package/dist/excalidraw/components/dropdownMenu/DropdownMenu.d.ts +12 -3
  134. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItem.d.ts +24 -4
  135. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItem.js +55 -14
  136. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContent.d.ts +2 -1
  137. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContent.js +2 -2
  138. package/dist/excalidraw/components/dropdownMenu/common.d.ts +1 -1
  139. package/dist/excalidraw/components/dropdownMenu/common.js +3 -2
  140. package/dist/excalidraw/components/icons.d.ts +4 -0
  141. package/dist/excalidraw/components/icons.js +7 -0
  142. package/dist/excalidraw/components/main-menu/MainMenu.d.ts +12 -3
  143. package/dist/excalidraw/components/welcome-screen/WelcomeScreen.Center.js +2 -2
  144. package/dist/excalidraw/components/welcome-screen/WelcomeScreen.Hints.js +3 -3
  145. package/dist/excalidraw/constants.d.ts +17 -2
  146. package/dist/excalidraw/constants.js +21 -4
  147. package/dist/excalidraw/data/reconcile.js +18 -1
  148. package/dist/excalidraw/data/restore.js +55 -9
  149. package/dist/excalidraw/data/transform.js +8 -5
  150. package/dist/excalidraw/element/binding.d.ts +28 -9
  151. package/dist/excalidraw/element/binding.js +303 -71
  152. package/dist/excalidraw/element/collision.d.ts +1 -1
  153. package/dist/excalidraw/element/collision.js +4 -1
  154. package/dist/excalidraw/element/dragElements.d.ts +2 -2
  155. package/dist/excalidraw/element/dragElements.js +13 -3
  156. package/dist/excalidraw/element/embeddable.d.ts +3 -1
  157. package/dist/excalidraw/element/heading.d.ts +11 -0
  158. package/dist/excalidraw/element/heading.js +81 -0
  159. package/dist/excalidraw/element/index.d.ts +1 -1
  160. package/dist/excalidraw/element/index.js +1 -1
  161. package/dist/excalidraw/element/linearElementEditor.d.ts +21 -13
  162. package/dist/excalidraw/element/linearElementEditor.js +133 -56
  163. package/dist/excalidraw/element/newElement.d.ts +8 -3
  164. package/dist/excalidraw/element/newElement.js +15 -2
  165. package/dist/excalidraw/element/resizeElements.d.ts +4 -3
  166. package/dist/excalidraw/element/resizeElements.js +47 -23
  167. package/dist/excalidraw/element/routing.d.ts +13 -0
  168. package/dist/excalidraw/element/routing.js +641 -0
  169. package/dist/excalidraw/element/textElement.d.ts +3 -26
  170. package/dist/excalidraw/element/textElement.js +54 -110
  171. package/dist/excalidraw/element/textWysiwyg.js +39 -47
  172. package/dist/excalidraw/element/transformHandles.js +7 -2
  173. package/dist/excalidraw/element/typeChecks.d.ts +5 -2
  174. package/dist/excalidraw/element/typeChecks.js +17 -0
  175. package/dist/excalidraw/element/types.d.ts +12 -1
  176. package/dist/excalidraw/fonts/ExcalidrawFont.d.ts +21 -0
  177. package/dist/excalidraw/fonts/ExcalidrawFont.js +112 -0
  178. package/dist/excalidraw/fonts/index.d.ts +58 -0
  179. package/dist/excalidraw/fonts/index.js +240 -0
  180. package/dist/excalidraw/fonts/metadata.d.ts +36 -0
  181. package/dist/excalidraw/fonts/metadata.js +91 -0
  182. package/dist/excalidraw/fractionalIndex.d.ts +11 -4
  183. package/dist/excalidraw/fractionalIndex.js +38 -6
  184. package/dist/excalidraw/frame.d.ts +1 -1
  185. package/dist/excalidraw/frame.js +3 -3
  186. package/dist/excalidraw/history.d.ts +4 -3
  187. package/dist/excalidraw/history.js +8 -8
  188. package/dist/excalidraw/index.d.ts +1 -1
  189. package/dist/excalidraw/index.js +3 -3
  190. package/dist/excalidraw/locales/en.json +18 -4
  191. package/dist/excalidraw/math.d.ts +43 -0
  192. package/dist/excalidraw/math.js +110 -0
  193. package/dist/excalidraw/mermaid.js +4 -3
  194. package/dist/excalidraw/renderer/interactiveScene.js +33 -17
  195. package/dist/excalidraw/renderer/renderElement.d.ts +2 -0
  196. package/dist/excalidraw/renderer/renderElement.js +74 -54
  197. package/dist/excalidraw/renderer/staticSvgScene.js +2 -1
  198. package/dist/excalidraw/scene/Scene.js +9 -3
  199. package/dist/excalidraw/scene/Shape.js +56 -5
  200. package/dist/excalidraw/scene/comparisons.d.ts +1 -0
  201. package/dist/excalidraw/scene/comparisons.js +1 -1
  202. package/dist/excalidraw/scene/export.d.ts +2 -1
  203. package/dist/excalidraw/scene/export.js +33 -35
  204. package/dist/excalidraw/scene/types.d.ts +1 -4
  205. package/dist/excalidraw/shapes.d.ts +8 -0
  206. package/dist/excalidraw/shapes.js +57 -0
  207. package/dist/excalidraw/types.d.ts +8 -3
  208. package/dist/excalidraw/utils.d.ts +11 -1
  209. package/dist/excalidraw/utils.js +22 -0
  210. package/dist/prod/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  211. package/dist/prod/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  212. package/dist/prod/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  213. package/dist/prod/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  214. package/dist/prod/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  215. package/dist/prod/{en-EB2MBPAV.json → en-YNVBSAIL.json} +18 -4
  216. package/dist/prod/index.css +1 -1
  217. package/dist/prod/index.js +49 -53
  218. package/dist/utils/export.d.ts +2 -1
  219. package/dist/utils/export.js +2 -1
  220. package/dist/utils/geometry/geometry.d.ts +2 -1
  221. package/dist/utils/geometry/geometry.js +5 -1
  222. package/dist/utils/index.d.ts +1 -0
  223. package/dist/utils/index.js +1 -0
  224. package/history.ts +9 -2
  225. package/package.json +2 -2
  226. package/dist/browser/dev/Cascadia-CYPE3OJC.woff2 +0 -0
  227. package/dist/browser/dev/Virgil-UZN6MUT6.woff2 +0 -0
  228. package/dist/browser/dev/excalidraw-assets-dev/chunk-B4UMSLQQ.js.map +0 -7
  229. package/dist/browser/dev/excalidraw-assets-dev/chunk-EM6LVGFW.js.map +0 -7
  230. package/dist/browser/dev/excalidraw-assets-dev/dist-6QVAH5JA.js.map +0 -7
  231. package/dist/browser/prod/Cascadia-CYPE3OJC.woff2 +0 -0
  232. package/dist/browser/prod/Virgil-UZN6MUT6.woff2 +0 -0
  233. package/dist/browser/prod/excalidraw-assets/chunk-EGOLGOLD.js +0 -55
  234. package/dist/browser/prod/excalidraw-assets/dist-567JAXHK.js +0 -7
  235. package/dist/browser/prod/excalidraw-assets/en-6E7MYLWO.js +0 -1
  236. package/dist/browser/prod/excalidraw-assets/image-SI7BKULC.js +0 -1
  237. package/dist/dev/Cascadia-CYPE3OJC.woff2 +0 -0
  238. package/dist/dev/Virgil-UZN6MUT6.woff2 +0 -0
  239. package/dist/excalidraw/scene/Fonts.d.ts +0 -19
  240. package/dist/excalidraw/scene/Fonts.js +0 -66
  241. package/dist/prod/Cascadia-CYPE3OJC.woff2 +0 -0
  242. package/dist/prod/Virgil-UZN6MUT6.woff2 +0 -0
  243. /package/dist/browser/dev/{Assistant-Bold-ZDZZ6JHA.woff2 → excalidraw-assets-dev/Assistant-Bold-ZDZZ6JHA.woff2} +0 -0
  244. /package/dist/browser/dev/{Assistant-Medium-DZ25RZU3.woff2 → excalidraw-assets-dev/Assistant-Medium-DZ25RZU3.woff2} +0 -0
  245. /package/dist/browser/dev/{Assistant-Regular-PLF2XOGW.woff2 → excalidraw-assets-dev/Assistant-Regular-PLF2XOGW.woff2} +0 -0
  246. /package/dist/browser/dev/{Assistant-SemiBold-CZ5MX6FK.woff2 → excalidraw-assets-dev/Assistant-SemiBold-CZ5MX6FK.woff2} +0 -0
  247. /package/dist/browser/dev/excalidraw-assets-dev/{en-AZFA5HJJ.js.map → en-XV7OZCPP.js.map} +0 -0
  248. /package/dist/browser/dev/excalidraw-assets-dev/{image-V7E6IT6R.js.map → image-77HZYGLG.js.map} +0 -0
  249. /package/dist/browser/prod/{Assistant-Bold-ZDZZ6JHA.woff2 → excalidraw-assets/Assistant-Bold-ZDZZ6JHA.woff2} +0 -0
  250. /package/dist/browser/prod/{Assistant-Medium-DZ25RZU3.woff2 → excalidraw-assets/Assistant-Medium-DZ25RZU3.woff2} +0 -0
  251. /package/dist/browser/prod/{Assistant-Regular-PLF2XOGW.woff2 → excalidraw-assets/Assistant-Regular-PLF2XOGW.woff2} +0 -0
  252. /package/dist/browser/prod/{Assistant-SemiBold-CZ5MX6FK.woff2 → excalidraw-assets/Assistant-SemiBold-CZ5MX6FK.woff2} +0 -0
@@ -6,10 +6,11 @@ import rough from "roughjs/bin/rough";
6
6
  import { getDefaultAppState } from "../appState";
7
7
  import { BOUND_TEXT_PADDING, ELEMENT_READY_TO_ERASE_OPACITY, FRAME_STYLE, MIME_TYPES, THEME, } from "../constants";
8
8
  import { getStroke } from "perfect-freehand";
9
- import { getBoundTextElement, getContainerCoords, getContainerElement, getLineHeightInPx, getBoundTextMaxHeight, getBoundTextMaxWidth, getVerticalOffset, } from "../element/textElement";
9
+ import { getBoundTextElement, getContainerCoords, getContainerElement, getLineHeightInPx, getBoundTextMaxHeight, getBoundTextMaxWidth, } from "../element/textElement";
10
10
  import { LinearElementEditor } from "../element/linearElementEditor";
11
11
  import { getContainingFrame } from "../frame";
12
12
  import { ShapeCache } from "../scene/ShapeCache";
13
+ import { getVerticalOffset } from "../fonts";
13
14
  // using a stronger invert (100% vs our regular 93%) and saturate
14
15
  // as a temp hack to make images in dark theme look closer to original
15
16
  // color scheme (it's still not quite there and the colors look slightly
@@ -24,7 +25,16 @@ const shouldResetImageFilter = (element, renderConfig, appState) => {
24
25
  !isPendingImageElement(element, renderConfig) &&
25
26
  renderConfig.imageCache.get(element.fileId)?.mimeType !== MIME_TYPES.svg);
26
27
  };
27
- const getCanvasPadding = (element) => element.type === "freedraw" ? element.strokeWidth * 12 : 20;
28
+ const getCanvasPadding = (element) => {
29
+ switch (element.type) {
30
+ case "freedraw":
31
+ return element.strokeWidth * 12;
32
+ case "text":
33
+ return element.fontSize / 2;
34
+ default:
35
+ return 20;
36
+ }
37
+ };
28
38
  export const getRenderOpacity = (element, containingFrame, elementsPendingErasure) => {
29
39
  // multiplying frame opacity with element opacity to combine them
30
40
  // (e.g. frame 50% and element 50% opacity should result in 25% opacity)
@@ -78,7 +88,7 @@ const generateElementCanvas = (element, elementsMap, zoom, renderConfig, appStat
78
88
  const { width, height, scale } = cappedElementCanvasSize(element, elementsMap, zoom);
79
89
  canvas.width = width;
80
90
  canvas.height = height;
81
- let canvasOffsetX = 0;
91
+ let canvasOffsetX = -100;
82
92
  let canvasOffsetY = 0;
83
93
  if (isLinearElement(element) || isFreeDrawElement(element)) {
84
94
  const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
@@ -102,6 +112,45 @@ const generateElementCanvas = (element, elementsMap, zoom, renderConfig, appStat
102
112
  }
103
113
  drawElementOnCanvas(element, rc, context, renderConfig, appState);
104
114
  context.restore();
115
+ const boundTextElement = getBoundTextElement(element, elementsMap);
116
+ const boundTextCanvas = document.createElement("canvas");
117
+ const boundTextCanvasContext = boundTextCanvas.getContext("2d");
118
+ if (isArrowElement(element) && boundTextElement) {
119
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
120
+ // Take max dimensions of arrow canvas so that when canvas is rotated
121
+ // the arrow doesn't get clipped
122
+ const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
123
+ boundTextCanvas.width =
124
+ maxDim * window.devicePixelRatio * scale + padding * scale * 10;
125
+ boundTextCanvas.height =
126
+ maxDim * window.devicePixelRatio * scale + padding * scale * 10;
127
+ boundTextCanvasContext.translate(boundTextCanvas.width / 2, boundTextCanvas.height / 2);
128
+ boundTextCanvasContext.rotate(element.angle);
129
+ boundTextCanvasContext.drawImage(canvas, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
130
+ const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(boundTextElement, elementsMap);
131
+ boundTextCanvasContext.rotate(-element.angle);
132
+ const offsetX = (boundTextCanvas.width - canvas.width) / 2;
133
+ const offsetY = (boundTextCanvas.height - canvas.height) / 2;
134
+ const shiftX = boundTextCanvas.width / 2 -
135
+ (boundTextCx - x1) * window.devicePixelRatio * scale -
136
+ offsetX -
137
+ padding * scale;
138
+ const shiftY = boundTextCanvas.height / 2 -
139
+ (boundTextCy - y1) * window.devicePixelRatio * scale -
140
+ offsetY -
141
+ padding * scale;
142
+ boundTextCanvasContext.translate(-shiftX, -shiftY);
143
+ // Clear the bound text area
144
+ boundTextCanvasContext.clearRect(-(boundTextElement.width / 2 + BOUND_TEXT_PADDING) *
145
+ window.devicePixelRatio *
146
+ scale, -(boundTextElement.height / 2 + BOUND_TEXT_PADDING) *
147
+ window.devicePixelRatio *
148
+ scale, (boundTextElement.width + BOUND_TEXT_PADDING * 2) *
149
+ window.devicePixelRatio *
150
+ scale, (boundTextElement.height + BOUND_TEXT_PADDING * 2) *
151
+ window.devicePixelRatio *
152
+ scale);
153
+ }
105
154
  return {
106
155
  element,
107
156
  canvas,
@@ -112,6 +161,8 @@ const generateElementCanvas = (element, elementsMap, zoom, renderConfig, appStat
112
161
  canvasOffsetY,
113
162
  boundTextElementVersion: getBoundTextElement(element, elementsMap)?.version || null,
114
163
  containingFrameOpacity: getContainingFrame(element, elementsMap)?.opacity || 100,
164
+ boundTextCanvas,
165
+ angle: element.angle,
115
166
  };
116
167
  };
117
168
  export const DEFAULT_LINK_SIZE = 14;
@@ -224,13 +275,21 @@ const generateElementWithCanvas = (element, elementsMap, renderConfig, appState)
224
275
  const shouldRegenerateBecauseZoom = prevElementWithCanvas &&
225
276
  prevElementWithCanvas.zoomValue !== zoom.value &&
226
277
  !appState?.shouldCacheIgnoreZoom;
227
- const boundTextElementVersion = getBoundTextElement(element, elementsMap)?.version || null;
278
+ const boundTextElement = getBoundTextElement(element, elementsMap);
279
+ const boundTextElementVersion = boundTextElement?.version || null;
228
280
  const containingFrameOpacity = getContainingFrame(element, elementsMap)?.opacity || 100;
229
281
  if (!prevElementWithCanvas ||
230
282
  shouldRegenerateBecauseZoom ||
231
283
  prevElementWithCanvas.theme !== appState.theme ||
232
284
  prevElementWithCanvas.boundTextElementVersion !== boundTextElementVersion ||
233
- prevElementWithCanvas.containingFrameOpacity !== containingFrameOpacity) {
285
+ prevElementWithCanvas.containingFrameOpacity !== containingFrameOpacity ||
286
+ // since we rotate the canvas when copying from cached canvas, we don't
287
+ // regenerate the cached canvas. But we need to in case of labels which are
288
+ // cached alongside the arrow, and we want the labels to remain unrotated
289
+ // with respect to the arrow.
290
+ (isArrowElement(element) &&
291
+ boundTextElement &&
292
+ element.angle !== prevElementWithCanvas.angle)) {
234
293
  const elementWithCanvas = generateElementCanvas(element, elementsMap, zoom, renderConfig, appState);
235
294
  elementWithCanvasCache.set(element, elementWithCanvas);
236
295
  return elementWithCanvas;
@@ -241,60 +300,21 @@ const drawElementFromCanvas = (elementWithCanvas, context, renderConfig, appStat
241
300
  const element = elementWithCanvas.element;
242
301
  const padding = getCanvasPadding(element);
243
302
  const zoom = elementWithCanvas.scale;
244
- let [x1, y1, x2, y2] = getElementAbsoluteCoords(element, allElementsMap);
245
- // Free draw elements will otherwise "shuffle" as the min x and y change
246
- if (isFreeDrawElement(element)) {
247
- x1 = Math.floor(x1);
248
- x2 = Math.ceil(x2);
249
- y1 = Math.floor(y1);
250
- y2 = Math.ceil(y2);
251
- }
303
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, allElementsMap);
252
304
  const cx = ((x1 + x2) / 2 + appState.scrollX) * window.devicePixelRatio;
253
305
  const cy = ((y1 + y2) / 2 + appState.scrollY) * window.devicePixelRatio;
254
306
  context.save();
255
307
  context.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio);
256
308
  const boundTextElement = getBoundTextElement(element, allElementsMap);
257
309
  if (isArrowElement(element) && boundTextElement) {
258
- const tempCanvas = document.createElement("canvas");
259
- const tempCanvasContext = tempCanvas.getContext("2d");
260
- // Take max dimensions of arrow canvas so that when canvas is rotated
261
- // the arrow doesn't get clipped
262
- const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
263
- tempCanvas.width =
264
- maxDim * window.devicePixelRatio * zoom +
265
- padding * elementWithCanvas.scale * 10;
266
- tempCanvas.height =
267
- maxDim * window.devicePixelRatio * zoom +
268
- padding * elementWithCanvas.scale * 10;
269
- const offsetX = (tempCanvas.width - elementWithCanvas.canvas.width) / 2;
270
- const offsetY = (tempCanvas.height - elementWithCanvas.canvas.height) / 2;
271
- tempCanvasContext.translate(tempCanvas.width / 2, tempCanvas.height / 2);
272
- tempCanvasContext.rotate(element.angle);
273
- tempCanvasContext.drawImage(elementWithCanvas.canvas, -elementWithCanvas.canvas.width / 2, -elementWithCanvas.canvas.height / 2, elementWithCanvas.canvas.width, elementWithCanvas.canvas.height);
274
- const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(boundTextElement, allElementsMap);
275
- tempCanvasContext.rotate(-element.angle);
276
- // Shift the canvas to the center of the bound text element
277
- const shiftX = tempCanvas.width / 2 -
278
- (boundTextCx - x1) * window.devicePixelRatio * zoom -
279
- offsetX -
280
- padding * zoom;
281
- const shiftY = tempCanvas.height / 2 -
282
- (boundTextCy - y1) * window.devicePixelRatio * zoom -
283
- offsetY -
284
- padding * zoom;
285
- tempCanvasContext.translate(-shiftX, -shiftY);
286
- // Clear the bound text area
287
- tempCanvasContext.clearRect(-(boundTextElement.width / 2 + BOUND_TEXT_PADDING) *
288
- window.devicePixelRatio *
289
- zoom, -(boundTextElement.height / 2 + BOUND_TEXT_PADDING) *
290
- window.devicePixelRatio *
291
- zoom, (boundTextElement.width + BOUND_TEXT_PADDING * 2) *
292
- window.devicePixelRatio *
293
- zoom, (boundTextElement.height + BOUND_TEXT_PADDING * 2) *
294
- window.devicePixelRatio *
295
- zoom);
310
+ const offsetX = (elementWithCanvas.boundTextCanvas.width -
311
+ elementWithCanvas.canvas.width) /
312
+ 2;
313
+ const offsetY = (elementWithCanvas.boundTextCanvas.height -
314
+ elementWithCanvas.canvas.height) /
315
+ 2;
296
316
  context.translate(cx, cy);
297
- context.drawImage(tempCanvas, (-(x2 - x1) / 2) * window.devicePixelRatio - offsetX / zoom - padding, (-(y2 - y1) / 2) * window.devicePixelRatio - offsetY / zoom - padding, tempCanvas.width / zoom, tempCanvas.height / zoom);
317
+ context.drawImage(elementWithCanvas.boundTextCanvas, (-(x2 - x1) / 2) * window.devicePixelRatio - offsetX / zoom - padding, (-(y2 - y1) / 2) * window.devicePixelRatio - offsetY / zoom - padding, elementWithCanvas.boundTextCanvas.width / zoom, elementWithCanvas.boundTextCanvas.height / zoom);
298
318
  }
299
319
  else {
300
320
  // we translate context to element center so that rotation and scale
@@ -386,7 +406,7 @@ export const renderElement = (element, elementsMap, allElementsMap, rc, context,
386
406
  context.restore();
387
407
  }
388
408
  else {
389
- const elementWithCanvas = generateElementWithCanvas(element, elementsMap, renderConfig, appState);
409
+ const elementWithCanvas = generateElementWithCanvas(element, allElementsMap, renderConfig, appState);
390
410
  drawElementFromCanvas(elementWithCanvas, context, renderConfig, appState, allElementsMap);
391
411
  }
392
412
  break;
@@ -470,7 +490,7 @@ export const renderElement = (element, elementsMap, allElementsMap, rc, context,
470
490
  // canvases)
471
491
  }
472
492
  else {
473
- const elementWithCanvas = generateElementWithCanvas(element, elementsMap, renderConfig, appState);
493
+ const elementWithCanvas = generateElementWithCanvas(element, allElementsMap, renderConfig, appState);
474
494
  const currentImageSmoothingStatus = context.imageSmoothingEnabled;
475
495
  if (
476
496
  // do not disable smoothing during zoom as blurry shapes look better
@@ -3,13 +3,14 @@ import { normalizeLink, toValidURL } from "../data/url";
3
3
  import { getElementAbsoluteCoords } from "../element";
4
4
  import { createPlaceholderEmbeddableLabel, getEmbedLink, } from "../element/embeddable";
5
5
  import { LinearElementEditor } from "../element/linearElementEditor";
6
- import { getBoundTextElement, getContainerElement, getLineHeightInPx, getVerticalOffset, } from "../element/textElement";
6
+ import { getBoundTextElement, getContainerElement, getLineHeightInPx, } from "../element/textElement";
7
7
  import { isArrowElement, isIframeLikeElement, isInitializedImageElement, isTextElement, } from "../element/typeChecks";
8
8
  import { getContainingFrame } from "../frame";
9
9
  import { getCornerRadius, isPathALoop } from "../math";
10
10
  import { ShapeCache } from "../scene/ShapeCache";
11
11
  import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
12
12
  import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
13
+ import { getVerticalOffset } from "../fonts";
13
14
  const roughSVGDrawWithPrecision = (rsvg, drawable, precision) => {
14
15
  if (typeof precision === "undefined") {
15
16
  return rsvg.draw(drawable);
@@ -170,9 +170,15 @@ class Scene {
170
170
  ? nextElements
171
171
  : Array.from(nextElements.values());
172
172
  const nextFrameLikes = [];
173
- if (import.meta.env.DEV || import.meta.env.MODE === ENV.TEST) {
174
- // throw on invalid indices in test / dev to potentially detect cases were we forgot to sync moved elements
175
- validateFractionalIndices(_nextElements.map((x) => x.index));
173
+ if (import.meta.env.DEV ||
174
+ import.meta.env.MODE === ENV.TEST ||
175
+ window?.DEBUG_FRACTIONAL_INDICES) {
176
+ validateFractionalIndices(_nextElements, {
177
+ // validate everything
178
+ includeBoundTextValidation: true,
179
+ // throw only in dev & test, to remain functional on `DEBUG_FRACTIONAL_INDICES`
180
+ shouldThrow: import.meta.env.DEV || import.meta.env.MODE === ENV.TEST,
181
+ });
176
182
  }
177
183
  this.elements = syncInvalidIndices(_nextElements);
178
184
  this.elementsMap.clear();
@@ -1,10 +1,10 @@
1
1
  import { getDiamondPoints, getArrowheadPoints } from "../element";
2
- import { isPathALoop, getCornerRadius } from "../math";
2
+ import { isPathALoop, getCornerRadius, distanceSq2d } from "../math";
3
3
  import { generateFreeDrawShape } from "../renderer/renderElement";
4
4
  import { isTransparent, assertNever } from "../utils";
5
5
  import { simplify } from "points-on-curve";
6
6
  import { ROUGHNESS } from "../constants";
7
- import { isEmbeddableElement, isIframeElement, isIframeLikeElement, isLinearElement, } from "../element/typeChecks";
7
+ import { isElbowArrow, isEmbeddableElement, isIframeElement, isIframeLikeElement, isLinearElement, } from "../element/typeChecks";
8
8
  import { canChangeRoundness } from "./comparisons";
9
9
  const getDashArrayDashed = (strokeWidth) => [8, 8 + strokeWidth];
10
10
  const getDashArrayDotted = (strokeWidth) => [1.5, 6 + strokeWidth];
@@ -261,9 +261,14 @@ export const _generateElementShape = (element, generator, { isExporting, canvasB
261
261
  // points array can be empty in the beginning, so it is important to add
262
262
  // initial position to it
263
263
  const points = element.points.length ? element.points : [[0, 0]];
264
- // curve is always the first element
265
- // this simplifies finding the curve for an element
266
- if (!element.roundness) {
264
+ if (isElbowArrow(element)) {
265
+ shape = [
266
+ generator.path(generateElbowArrowShape(points, 16), generateRoughOptions(element, true)),
267
+ ];
268
+ }
269
+ else if (!element.roundness) {
270
+ // curve is always the first element
271
+ // this simplifies finding the curve for an element
267
272
  if (options.fill) {
268
273
  shape = [generator.polygon(points, options)];
269
274
  }
@@ -322,3 +327,49 @@ export const _generateElementShape = (element, generator, { isExporting, canvasB
322
327
  }
323
328
  }
324
329
  };
330
+ const generateElbowArrowShape = (points, radius) => {
331
+ const subpoints = [];
332
+ for (let i = 1; i < points.length - 1; i += 1) {
333
+ const prev = points[i - 1];
334
+ const next = points[i + 1];
335
+ const corner = Math.min(radius, Math.sqrt(distanceSq2d(points[i], next)) / 2, Math.sqrt(distanceSq2d(points[i], prev)) / 2);
336
+ if (prev[0] < points[i][0] && prev[1] === points[i][1]) {
337
+ // LEFT
338
+ subpoints.push([points[i][0] - corner, points[i][1]]);
339
+ }
340
+ else if (prev[0] === points[i][0] && prev[1] < points[i][1]) {
341
+ // UP
342
+ subpoints.push([points[i][0], points[i][1] - corner]);
343
+ }
344
+ else if (prev[0] > points[i][0] && prev[1] === points[i][1]) {
345
+ // RIGHT
346
+ subpoints.push([points[i][0] + corner, points[i][1]]);
347
+ }
348
+ else {
349
+ subpoints.push([points[i][0], points[i][1] + corner]);
350
+ }
351
+ subpoints.push(points[i]);
352
+ if (next[0] < points[i][0] && next[1] === points[i][1]) {
353
+ // LEFT
354
+ subpoints.push([points[i][0] - corner, points[i][1]]);
355
+ }
356
+ else if (next[0] === points[i][0] && next[1] < points[i][1]) {
357
+ // UP
358
+ subpoints.push([points[i][0], points[i][1] - corner]);
359
+ }
360
+ else if (next[0] > points[i][0] && next[1] === points[i][1]) {
361
+ // RIGHT
362
+ subpoints.push([points[i][0] + corner, points[i][1]]);
363
+ }
364
+ else {
365
+ subpoints.push([points[i][0], points[i][1] + corner]);
366
+ }
367
+ }
368
+ const d = [`M ${points[0][0]} ${points[0][1]}`];
369
+ for (let i = 0; i < subpoints.length; i += 3) {
370
+ d.push(`L ${subpoints[i][0]} ${subpoints[i][1]}`);
371
+ d.push(`Q ${subpoints[i + 1][0]} ${subpoints[i + 1][1]}, ${subpoints[i + 2][0]} ${subpoints[i + 2][1]}`);
372
+ }
373
+ d.push(`L ${points[points.length - 1][0]} ${points[points.length - 1][1]}`);
374
+ return d.join(" ");
375
+ };
@@ -5,6 +5,7 @@ export declare const hasStrokeColor: (type: ElementOrToolType) => boolean;
5
5
  export declare const hasStrokeWidth: (type: ElementOrToolType) => boolean;
6
6
  export declare const hasStrokeStyle: (type: ElementOrToolType) => boolean;
7
7
  export declare const canChangeRoundness: (type: ElementOrToolType) => boolean;
8
+ export declare const toolIsArrow: (type: ElementOrToolType) => boolean;
8
9
  export declare const canHaveArrowheads: (type: ElementOrToolType) => boolean;
9
10
  export declare const getElementAtPosition: (elements: readonly NonDeletedExcalidrawElement[], isAtPositionFn: (element: NonDeletedExcalidrawElement) => boolean) => NonDeletedExcalidrawElement | null;
10
11
  export declare const getElementsAtPosition: (elements: readonly NonDeletedExcalidrawElement[], isAtPositionFn: (element: NonDeletedExcalidrawElement) => boolean) => NonDeletedExcalidrawElement[];
@@ -25,10 +25,10 @@ export const hasStrokeStyle = (type) => type === "rectangle" ||
25
25
  export const canChangeRoundness = (type) => type === "rectangle" ||
26
26
  type === "iframe" ||
27
27
  type === "embeddable" ||
28
- type === "arrow" ||
29
28
  type === "line" ||
30
29
  type === "diamond" ||
31
30
  type === "image";
31
+ export const toolIsArrow = (type) => type === "arrow";
32
32
  export const canHaveArrowheads = (type) => type === "arrow";
33
33
  export const getElementAtPosition = (elements, isAtPositionFn) => {
34
34
  let hitElement = null;
@@ -8,7 +8,7 @@ export declare const exportToCanvas: (elements: readonly NonDeletedExcalidrawEle
8
8
  }, createCanvas?: (width: number, height: number) => {
9
9
  canvas: HTMLCanvasElement;
10
10
  scale: number;
11
- }) => Promise<HTMLCanvasElement>;
11
+ }, loadFonts?: () => Promise<void>) => Promise<HTMLCanvasElement>;
12
12
  export declare const exportToSvg: (elements: readonly NonDeletedExcalidrawElement[], appState: {
13
13
  exportBackground: boolean;
14
14
  exportPadding?: number;
@@ -23,5 +23,6 @@ export declare const exportToSvg: (elements: readonly NonDeletedExcalidrawElemen
23
23
  */
24
24
  renderEmbeddables?: boolean;
25
25
  exportingFrame?: ExcalidrawFrameLikeElement | null;
26
+ skipInliningFonts?: true;
26
27
  }) => Promise<SVGSVGElement>;
27
28
  export declare const getExportSize: (elements: readonly NonDeletedExcalidrawElement[], exportPadding: number, scale: number) => [number, number];
@@ -2,16 +2,17 @@ import rough from "roughjs/bin/rough";
2
2
  import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds";
3
3
  import { renderSceneToSvg } from "../renderer/staticSvgScene";
4
4
  import { arrayToMap, distance, getFontString, toBrandedType } from "../utils";
5
- import { DEFAULT_EXPORT_PADDING, FONT_FAMILY, FRAME_STYLE, SVG_NS, THEME, THEME_FILTER, } from "../constants";
5
+ import { DEFAULT_EXPORT_PADDING, FRAME_STYLE, FONT_FAMILY, SVG_NS, THEME, THEME_FILTER, } from "../constants";
6
6
  import { getDefaultAppState } from "../appState";
7
7
  import { serializeAsJSON } from "../data/json";
8
8
  import { getInitializedImageElements, updateImageCache, } from "../element/image";
9
9
  import { getElementsOverlappingFrame, getFrameLikeElements, getFrameLikeTitle, getRootElements, } from "../frame";
10
10
  import { newTextElement } from "../element";
11
11
  import { newElementWith } from "../element/mutateElement";
12
- import { isFrameElement, isFrameLikeElement } from "../element/typeChecks";
12
+ import { isFrameLikeElement, isTextElement } from "../element/typeChecks";
13
13
  import { syncInvalidIndices } from "../fractionalIndex";
14
14
  import { renderStaticScene } from "../renderer/staticScene";
15
+ import { Fonts } from "../fonts";
15
16
  const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
16
17
  const truncateText = (element, maxWidth) => {
17
18
  if (element.width <= maxWidth) {
@@ -48,26 +49,18 @@ const truncateText = (element, maxWidth) => {
48
49
  */
49
50
  const addFrameLabelsAsTextElements = (elements, opts) => {
50
51
  const nextElements = [];
51
- let frameIndex = 0;
52
- let magicFrameIndex = 0;
53
52
  for (const element of elements) {
54
53
  if (isFrameLikeElement(element)) {
55
- if (isFrameElement(element)) {
56
- frameIndex++;
57
- }
58
- else {
59
- magicFrameIndex++;
60
- }
61
54
  let textElement = newTextElement({
62
55
  x: element.x,
63
56
  y: element.y - FRAME_STYLE.nameOffsetY,
64
- fontFamily: FONT_FAMILY.Assistant,
57
+ fontFamily: FONT_FAMILY.Helvetica,
65
58
  fontSize: FRAME_STYLE.nameFontSize,
66
59
  lineHeight: FRAME_STYLE.nameLineHeight,
67
60
  strokeColor: opts.exportWithDarkMode
68
61
  ? FRAME_STYLE.nameColorDarkTheme
69
62
  : FRAME_STYLE.nameColorLightTheme,
70
- text: getFrameLikeTitle(element, isFrameElement(element) ? frameIndex : magicFrameIndex),
63
+ text: getFrameLikeTitle(element),
71
64
  });
72
65
  textElement.y -= textElement.height;
73
66
  textElement = truncateText(textElement, element.width);
@@ -106,7 +99,11 @@ export const exportToCanvas = async (elements, appState, files, { exportBackgrou
106
99
  canvas.width = width * appState.exportScale;
107
100
  canvas.height = height * appState.exportScale;
108
101
  return { canvas, scale: appState.exportScale };
102
+ }, loadFonts = async () => {
103
+ await Fonts.loadFontsForElements(elements);
109
104
  }) => {
105
+ // load font faces before continuing, by default leverages browsers' [FontFace API](https://developer.mozilla.org/en-US/docs/Web/API/FontFace)
106
+ await loadFonts();
110
107
  const frameRendering = getFrameRenderingConfig(exportingFrame ?? null, appState.frameRendering ?? null);
111
108
  const elementsForRender = prepareElementsForRender({
112
109
  elements,
@@ -195,17 +192,6 @@ export const exportToSvg = async (elements, appState, files, opts) => {
195
192
  if (exportWithDarkMode) {
196
193
  svgRoot.setAttribute("filter", THEME_FILTER);
197
194
  }
198
- let assetPath = "https://excalidraw.com/";
199
- // Asset path needs to be determined only when using package
200
- if (import.meta.env.VITE_IS_EXCALIDRAW_NPM_PACKAGE) {
201
- assetPath =
202
- window.EXCALIDRAW_ASSET_PATH ||
203
- `https://unpkg.com/${import.meta.env.VITE_PKG_NAME}@${import.meta.env.VITE_PKG_VERSION}`;
204
- if (assetPath?.startsWith("/")) {
205
- assetPath = assetPath.replace("/", `${window.location.origin}/`);
206
- }
207
- assetPath = `${assetPath}/dist/excalidraw-assets/`;
208
- }
209
195
  const offsetX = -minX + exportPadding;
210
196
  const offsetY = -minY + exportPadding;
211
197
  const frameElements = getFrameLikeElements(elements);
@@ -223,23 +209,35 @@ export const exportToSvg = async (elements, appState, files, opts) => {
223
209
  </rect>
224
210
  </clipPath>`;
225
211
  }
212
+ const fontFamilies = elements.reduce((acc, element) => {
213
+ if (isTextElement(element)) {
214
+ acc.add(element.fontFamily);
215
+ }
216
+ return acc;
217
+ }, new Set());
218
+ const fontFaces = opts?.skipInliningFonts
219
+ ? []
220
+ : await Promise.all(Array.from(fontFamilies).map(async (x) => {
221
+ const { fonts, metadata } = Fonts.registered.get(x) ?? {};
222
+ if (!Array.isArray(fonts)) {
223
+ console.error(`Couldn't find registered fonts for font-family "${x}"`, Fonts.registered);
224
+ return;
225
+ }
226
+ if (metadata?.local) {
227
+ // don't inline local fonts
228
+ return;
229
+ }
230
+ return Promise.all(fonts.map(async (font) => `@font-face {
231
+ font-family: ${font.fontFace.family};
232
+ src: url(${await font.getContent()});
233
+ }`));
234
+ }));
226
235
  svgRoot.innerHTML = `
227
236
  ${SVG_EXPORT_TAG}
228
237
  ${metadata}
229
238
  <defs>
230
239
  <style class="style-fonts">
231
- @font-face {
232
- font-family: "Virgil";
233
- src: url("${assetPath}Virgil.woff2");
234
- }
235
- @font-face {
236
- font-family: "Cascadia";
237
- src: url("${assetPath}Cascadia.woff2");
238
- }
239
- @font-face {
240
- font-family: "Assistant";
241
- src: url("${assetPath}Assistant-Regular.woff2");
242
- }
240
+ ${fontFaces.flat().filter(Boolean).join("\n")}
243
241
  </style>
244
242
  ${exportingFrameClipPath}
245
243
  </defs>
@@ -1,6 +1,6 @@
1
1
  import type { RoughCanvas } from "roughjs/bin/canvas";
2
2
  import type { Drawable } from "roughjs/bin/core";
3
- import type { ExcalidrawElement, ExcalidrawTextElement, NonDeletedElementsMap, NonDeletedExcalidrawElement, NonDeletedSceneElementsMap } from "../element/types";
3
+ import type { ExcalidrawElement, NonDeletedElementsMap, NonDeletedExcalidrawElement, NonDeletedSceneElementsMap } from "../element/types";
4
4
  import type { AppClassProperties, AppState, EmbedsValidationStatus, ElementsPendingErasure, InteractiveCanvasAppState, StaticCanvasAppState, SocketId, UserIdleState, Device } from "../types";
5
5
  import type { MakeBrand } from "../utility-types";
6
6
  export type RenderableElementsMap = NonDeletedElementsMap & MakeBrand<"RenderableElementsMap">;
@@ -67,9 +67,6 @@ export type SceneScroll = {
67
67
  scrollX: number;
68
68
  scrollY: number;
69
69
  };
70
- export interface Scene {
71
- elements: ExcalidrawTextElement[];
72
- }
73
70
  export type ExportType = "png" | "clipboard" | "clipboard-svg" | "backend" | "svg";
74
71
  export type ScrollBars = {
75
72
  horizontal: {
@@ -1,4 +1,6 @@
1
1
  /// <reference types="react" />
2
+ import { type GeometricShape } from "../utils/geometry/shape";
3
+ import type { ElementsMap, ExcalidrawElement } from "./element/types";
2
4
  export declare const SHAPES: readonly [{
3
5
  readonly icon: JSX.Element;
4
6
  readonly value: "selection";
@@ -61,3 +63,9 @@ export declare const SHAPES: readonly [{
61
63
  readonly fillable: false;
62
64
  }];
63
65
  export declare const findShapeByKey: (key: string) => "text" | "image" | "selection" | "rectangle" | "diamond" | "ellipse" | "arrow" | "line" | "freedraw" | "eraser" | null;
66
+ /**
67
+ * get the pure geometric shape of an excalidraw element
68
+ * which is then used for hit detection
69
+ */
70
+ export declare const getElementShape: (element: ExcalidrawElement, elementsMap: ElementsMap) => GeometricShape;
71
+ export declare const getBoundTextShape: (element: ExcalidrawElement, elementsMap: ElementsMap) => GeometricShape | null;
@@ -1,5 +1,11 @@
1
+ import { getClosedCurveShape, getCurveShape, getEllipseShape, getFreedrawShape, getPolygonShape, } from "../utils/geometry/shape";
1
2
  import { ArrowIcon, DiamondIcon, EllipseIcon, EraserIcon, FreedrawIcon, ImageIcon, LineIcon, RectangleIcon, SelectionIcon, TextIcon, } from "./components/icons";
3
+ import { getElementAbsoluteCoords } from "./element";
4
+ import { shouldTestInside } from "./element/collision";
5
+ import { LinearElementEditor } from "./element/linearElementEditor";
6
+ import { getBoundTextElement } from "./element/textElement";
2
7
  import { KEYS } from "./keys";
8
+ import { ShapeCache } from "./scene/ShapeCache";
3
9
  export const SHAPES = [
4
10
  {
5
11
  icon: SelectionIcon,
@@ -82,3 +88,54 @@ export const findShapeByKey = (key) => {
82
88
  });
83
89
  return shape?.value || null;
84
90
  };
91
+ /**
92
+ * get the pure geometric shape of an excalidraw element
93
+ * which is then used for hit detection
94
+ */
95
+ export const getElementShape = (element, elementsMap) => {
96
+ switch (element.type) {
97
+ case "rectangle":
98
+ case "diamond":
99
+ case "frame":
100
+ case "magicframe":
101
+ case "embeddable":
102
+ case "image":
103
+ case "iframe":
104
+ case "text":
105
+ case "selection":
106
+ return getPolygonShape(element);
107
+ case "arrow":
108
+ case "line": {
109
+ const roughShape = ShapeCache.get(element)?.[0] ??
110
+ ShapeCache.generateElementShape(element, null)[0];
111
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
112
+ return shouldTestInside(element)
113
+ ? getClosedCurveShape(element, roughShape, [element.x, element.y], element.angle, [cx, cy])
114
+ : getCurveShape(roughShape, [element.x, element.y], element.angle, [
115
+ cx,
116
+ cy,
117
+ ]);
118
+ }
119
+ case "ellipse":
120
+ return getEllipseShape(element);
121
+ case "freedraw": {
122
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
123
+ return getFreedrawShape(element, [cx, cy], shouldTestInside(element));
124
+ }
125
+ }
126
+ };
127
+ export const getBoundTextShape = (element, elementsMap) => {
128
+ const boundTextElement = getBoundTextElement(element, elementsMap);
129
+ if (boundTextElement) {
130
+ if (element.type === "arrow") {
131
+ return getElementShape({
132
+ ...boundTextElement,
133
+ // arrow's bound text accurate position is not stored in the element's property
134
+ // but rather calculated and returned from the following static method
135
+ ...LinearElementEditor.getBoundTextElementPosition(element, boundTextElement, elementsMap),
136
+ }, elementsMap);
137
+ }
138
+ return getElementShape(boundTextElement, elementsMap);
139
+ }
140
+ return null;
141
+ };
@@ -119,6 +119,7 @@ export type StaticCanvasAppState = Readonly<_CommonCanvasAppState & {
119
119
  selectedElementsAreBeingDragged: AppState["selectedElementsAreBeingDragged"];
120
120
  gridSize: AppState["gridSize"];
121
121
  frameRendering: AppState["frameRendering"];
122
+ currentHoveredFontFamily: AppState["currentHoveredFontFamily"];
122
123
  }>;
123
124
  export type InteractiveCanvasAppState = Readonly<_CommonCanvasAppState & {
124
125
  activeEmbeddable: AppState["activeEmbeddable"];
@@ -205,7 +206,9 @@ export interface AppState {
205
206
  currentItemTextAlign: TextAlign;
206
207
  currentItemStartArrowhead: Arrowhead | null;
207
208
  currentItemEndArrowhead: Arrowhead | null;
209
+ currentHoveredFontFamily: FontFamilyValues | null;
208
210
  currentItemRoundness: StrokeRoundness;
211
+ currentItemArrowType: "sharp" | "round" | "elbow";
209
212
  viewBackgroundColor: string;
210
213
  scrollX: number;
211
214
  scrollY: number;
@@ -216,7 +219,7 @@ export interface AppState {
216
219
  isRotating: boolean;
217
220
  zoom: Zoom;
218
221
  openMenu: "canvas" | "shape" | null;
219
- openPopup: "canvasBackground" | "elementBackground" | "elementStroke" | null;
222
+ openPopup: "canvasBackground" | "elementBackground" | "elementStroke" | "fontFamily" | null;
220
223
  openSidebar: {
221
224
  name: SidebarName;
222
225
  tab?: SidebarTabName;
@@ -350,7 +353,7 @@ export type OnUserFollowedPayload = {
350
353
  };
351
354
  export interface ExcalidrawProps {
352
355
  onChange?: (elements: readonly OrderedExcalidrawElement[], appState: AppState, files: BinaryFiles) => void;
353
- initialData?: MaybePromise<ExcalidrawInitialDataState | null>;
356
+ initialData?: (() => MaybePromise<ExcalidrawInitialDataState | null>) | MaybePromise<ExcalidrawInitialDataState | null>;
354
357
  excalidrawAPI?: (api: ExcalidrawImperativeAPI) => void;
355
358
  isCollaborating?: boolean;
356
359
  onPointerUpdate?: (payload: {
@@ -390,6 +393,7 @@ export interface ExcalidrawProps {
390
393
  validateEmbeddable?: boolean | string[] | RegExp | RegExp[] | ((link: string) => boolean | undefined);
391
394
  renderEmbeddable?: (element: NonDeleted<ExcalidrawEmbeddableElement>, appState: AppState) => JSX.Element | null;
392
395
  aiEnabled?: boolean;
396
+ showDeprecatedFonts?: boolean;
393
397
  }
394
398
  export type SceneData = {
395
399
  elements?: ImportedDataState["elements"];
@@ -454,6 +458,7 @@ export type AppClassProperties = {
454
458
  device: App["device"];
455
459
  scene: App["scene"];
456
460
  syncActionResult: App["syncActionResult"];
461
+ fonts: App["fonts"];
457
462
  pasteFromClipboard: App["pasteFromClipboard"];
458
463
  id: App["id"];
459
464
  onInsertElements: App["onInsertElements"];
@@ -468,8 +473,8 @@ export type AppClassProperties = {
468
473
  setOpenDialog: App["setOpenDialog"];
469
474
  insertEmbeddableElement: App["insertEmbeddableElement"];
470
475
  onMagicframeToolSelect: App["onMagicframeToolSelect"];
471
- getElementShape: App["getElementShape"];
472
476
  getName: App["getName"];
477
+ dismissLinearEditor: App["dismissLinearEditor"];
473
478
  };
474
479
  export type PointerDownState = Readonly<{
475
480
  origin: Readonly<{