@excalidraw/excalidraw 0.17.1-7441-4e2c539 → 0.17.1-a38e82f

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 (249) hide show
  1. package/CHANGELOG.md +52 -2
  2. package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js +20279 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js.map +7 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/{chunk-2W5GQUR4.js → chunk-IM4WTX2M.js} +12 -6
  5. package/dist/browser/dev/excalidraw-assets-dev/chunk-IM4WTX2M.js.map +7 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js → en-IOBA4CS2.js} +4 -2
  7. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css +6 -0
  8. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css.map +7 -0
  9. package/dist/browser/dev/excalidraw-assets-dev/{image-HYNUJ3XL.js → image-VKDAL6BQ.js} +2 -4
  10. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js +161 -0
  11. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js.map +7 -0
  12. package/dist/browser/dev/index.css +189 -129
  13. package/dist/browser/dev/index.css.map +3 -3
  14. package/dist/browser/dev/index.js +34707 -26
  15. package/dist/browser/dev/index.js.map +4 -4
  16. package/dist/browser/prod/excalidraw-assets/chunk-LIG3S5TN.js +11 -0
  17. package/dist/browser/prod/excalidraw-assets/chunk-N2C5DK3B.js +55 -0
  18. package/dist/browser/prod/excalidraw-assets/en-WFZVQ7I6.js +1 -0
  19. package/dist/browser/prod/excalidraw-assets/image-4AT7LYMR.js +1 -0
  20. package/dist/browser/prod/excalidraw-assets/image-X66R2EM5.css +1 -0
  21. package/dist/browser/prod/excalidraw-assets/roundRect-2ACQK4DA.js +1 -0
  22. package/dist/browser/prod/index.css +1 -1
  23. package/dist/browser/prod/index.js +203 -1
  24. package/dist/{prod/en-RLIAOBCI.json → dev/en-TDNWCAOT.json} +9 -5
  25. package/dist/dev/index.css +189 -129
  26. package/dist/dev/index.css.map +3 -3
  27. package/dist/dev/index.js +38445 -39402
  28. package/dist/dev/index.js.map +4 -4
  29. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +12 -12
  30. package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
  31. package/dist/excalidraw/actions/actionAlign.js +2 -1
  32. package/dist/excalidraw/actions/actionBoundText.d.ts +8 -8
  33. package/dist/excalidraw/actions/actionBoundText.js +8 -8
  34. package/dist/excalidraw/actions/actionCanvas.d.ts +46 -46
  35. package/dist/excalidraw/actions/actionClipboard.d.ts +27 -27
  36. package/dist/excalidraw/actions/actionClipboard.js +9 -2
  37. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +12 -12
  38. package/dist/excalidraw/actions/actionDeleteSelected.js +3 -2
  39. package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
  40. package/dist/excalidraw/actions/actionDistribute.js +1 -1
  41. package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
  42. package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -3
  43. package/dist/excalidraw/actions/actionElementLock.d.ts +8 -8
  44. package/dist/excalidraw/actions/actionExport.d.ts +35 -35
  45. package/dist/excalidraw/actions/actionExport.js +4 -4
  46. package/dist/excalidraw/actions/actionFinalize.d.ts +7 -7
  47. package/dist/excalidraw/actions/actionFinalize.js +7 -6
  48. package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
  49. package/dist/excalidraw/actions/actionFlip.js +11 -11
  50. package/dist/excalidraw/actions/actionFrame.d.ts +13 -13
  51. package/dist/excalidraw/actions/actionFrame.js +1 -1
  52. package/dist/excalidraw/actions/actionGroup.d.ts +8 -8
  53. package/dist/excalidraw/actions/actionGroup.js +3 -2
  54. package/dist/excalidraw/actions/actionLinearEditor.d.ts +4 -4
  55. package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
  56. package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +28 -50
  57. package/dist/excalidraw/actions/actionLink.js +40 -0
  58. package/dist/excalidraw/actions/actionMenu.d.ts +11 -11
  59. package/dist/excalidraw/actions/actionNavigate.d.ts +8 -8
  60. package/dist/excalidraw/actions/actionNavigate.js +1 -1
  61. package/dist/excalidraw/actions/actionProperties.d.ts +64 -64
  62. package/dist/excalidraw/actions/actionProperties.js +32 -27
  63. package/dist/excalidraw/actions/actionSelectAll.d.ts +4 -4
  64. package/dist/excalidraw/actions/actionSelectAll.js +1 -1
  65. package/dist/excalidraw/actions/actionStyles.d.ts +6 -6
  66. package/dist/excalidraw/actions/actionStyles.js +4 -4
  67. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +4 -4
  68. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +4 -4
  69. package/dist/excalidraw/actions/actionToggleStats.d.ts +4 -4
  70. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +4 -4
  71. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +4 -4
  72. package/dist/excalidraw/actions/index.d.ts +1 -1
  73. package/dist/excalidraw/actions/index.js +1 -1
  74. package/dist/excalidraw/actions/manager.js +2 -1
  75. package/dist/excalidraw/align.d.ts +2 -2
  76. package/dist/excalidraw/align.js +2 -2
  77. package/dist/excalidraw/animated-trail.d.ts +33 -0
  78. package/dist/excalidraw/animated-trail.js +96 -0
  79. package/dist/excalidraw/animation-frame-handler.d.ts +16 -0
  80. package/dist/excalidraw/animation-frame-handler.js +55 -0
  81. package/dist/excalidraw/appState.d.ts +1 -1
  82. package/dist/excalidraw/appState.js +1 -3
  83. package/dist/excalidraw/clipboard.js +5 -5
  84. package/dist/excalidraw/components/Actions.d.ts +3 -3
  85. package/dist/excalidraw/components/Actions.js +18 -7
  86. package/dist/excalidraw/components/App.d.ts +32 -17
  87. package/dist/excalidraw/components/App.js +474 -339
  88. package/dist/excalidraw/components/Button.d.ts +1 -1
  89. package/dist/excalidraw/components/FilledButton.d.ts +2 -2
  90. package/dist/excalidraw/components/FilledButton.js +27 -3
  91. package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
  92. package/dist/excalidraw/components/ImageExportDialog.d.ts +2 -1
  93. package/dist/excalidraw/components/ImageExportDialog.js +16 -12
  94. package/dist/excalidraw/components/JSONExportDialog.js +1 -1
  95. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.d.ts → LaserPointerButton.d.ts} +1 -1
  96. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.js → LaserPointerButton.js} +2 -2
  97. package/dist/excalidraw/components/LayerUI.js +3 -3
  98. package/dist/excalidraw/components/MobileMenu.js +1 -1
  99. package/dist/excalidraw/components/ProjectName.d.ts +0 -1
  100. package/dist/excalidraw/components/ProjectName.js +1 -1
  101. package/dist/excalidraw/components/SVGLayer.d.ts +8 -0
  102. package/dist/excalidraw/components/SVGLayer.js +20 -0
  103. package/dist/excalidraw/components/ShareableLinkDialog.js +10 -10
  104. package/dist/excalidraw/components/Stack.d.ts +2 -2
  105. package/dist/excalidraw/components/TTDDialog/common.js +10 -1
  106. package/dist/excalidraw/components/TextField.d.ts +5 -2
  107. package/dist/excalidraw/components/TextField.js +6 -3
  108. package/dist/excalidraw/components/Toast.d.ts +3 -2
  109. package/dist/excalidraw/components/Toast.js +2 -2
  110. package/dist/excalidraw/components/ToolButton.js +2 -1
  111. package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +2 -2
  112. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +6 -5
  113. package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +4 -3
  114. package/dist/excalidraw/components/canvases/StaticCanvas.js +7 -5
  115. package/dist/excalidraw/components/dropdownMenu/DropdownMenuContent.js +22 -2
  116. package/dist/excalidraw/components/dropdownMenu/common.d.ts +1 -1
  117. package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +19 -0
  118. package/dist/excalidraw/{element → components/hyperlink}/Hyperlink.js +40 -115
  119. package/dist/excalidraw/components/hyperlink/helpers.d.ts +7 -0
  120. package/dist/excalidraw/components/hyperlink/helpers.js +49 -0
  121. package/dist/excalidraw/components/icons.d.ts +2 -1
  122. package/dist/excalidraw/components/icons.js +2 -1
  123. package/dist/excalidraw/components/live-collaboration/LiveCollaborationTrigger.js +3 -2
  124. package/dist/excalidraw/components/main-menu/DefaultItems.js +5 -2
  125. package/dist/excalidraw/constants.d.ts +8 -0
  126. package/dist/excalidraw/constants.js +10 -0
  127. package/dist/excalidraw/data/blob.js +13 -14
  128. package/dist/excalidraw/data/filesystem.d.ts +1 -1
  129. package/dist/excalidraw/data/index.d.ts +2 -1
  130. package/dist/excalidraw/data/index.js +20 -16
  131. package/dist/excalidraw/data/json.d.ts +1 -1
  132. package/dist/excalidraw/data/json.js +5 -3
  133. package/dist/excalidraw/data/resave.d.ts +1 -1
  134. package/dist/excalidraw/data/resave.js +2 -2
  135. package/dist/excalidraw/data/restore.js +8 -13
  136. package/dist/excalidraw/data/transform.js +13 -9
  137. package/dist/excalidraw/distribute.d.ts +2 -2
  138. package/dist/excalidraw/distribute.js +2 -2
  139. package/dist/excalidraw/element/ElementCanvasButtons.d.ts +3 -2
  140. package/dist/excalidraw/element/ElementCanvasButtons.js +4 -4
  141. package/dist/excalidraw/element/binding.d.ts +9 -9
  142. package/dist/excalidraw/element/binding.js +61 -59
  143. package/dist/excalidraw/element/bounds.d.ts +5 -5
  144. package/dist/excalidraw/element/bounds.js +29 -32
  145. package/dist/excalidraw/element/collision.d.ts +11 -11
  146. package/dist/excalidraw/element/collision.js +49 -46
  147. package/dist/excalidraw/element/containerCache.d.ts +11 -0
  148. package/dist/excalidraw/element/containerCache.js +14 -0
  149. package/dist/excalidraw/element/dragElements.js +10 -19
  150. package/dist/excalidraw/element/embeddable.d.ts +11 -12
  151. package/dist/excalidraw/element/embeddable.js +17 -27
  152. package/dist/excalidraw/element/image.js +1 -2
  153. package/dist/excalidraw/element/index.d.ts +0 -1
  154. package/dist/excalidraw/element/index.js +0 -1
  155. package/dist/excalidraw/element/linearElementEditor.d.ts +35 -35
  156. package/dist/excalidraw/element/linearElementEditor.js +79 -80
  157. package/dist/excalidraw/element/newElement.d.ts +4 -6
  158. package/dist/excalidraw/element/newElement.js +11 -16
  159. package/dist/excalidraw/element/resizeElements.d.ts +6 -6
  160. package/dist/excalidraw/element/resizeElements.js +40 -46
  161. package/dist/excalidraw/element/resizeTest.d.ts +3 -3
  162. package/dist/excalidraw/element/resizeTest.js +4 -4
  163. package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
  164. package/dist/excalidraw/element/sizeHelpers.js +2 -2
  165. package/dist/excalidraw/element/textElement.d.ts +18 -20
  166. package/dist/excalidraw/element/textElement.js +80 -111
  167. package/dist/excalidraw/element/textWysiwyg.d.ts +1 -6
  168. package/dist/excalidraw/element/textWysiwyg.js +15 -37
  169. package/dist/excalidraw/element/transformHandles.d.ts +4 -4
  170. package/dist/excalidraw/element/transformHandles.js +6 -6
  171. package/dist/excalidraw/element/typeChecks.js +4 -1
  172. package/dist/excalidraw/element/types.d.ts +24 -11
  173. package/dist/excalidraw/emitter.d.ts +5 -9
  174. package/dist/excalidraw/emitter.js +12 -12
  175. package/dist/excalidraw/frame.d.ts +26 -20
  176. package/dist/excalidraw/frame.js +157 -84
  177. package/dist/excalidraw/groups.d.ts +3 -3
  178. package/dist/excalidraw/groups.js +11 -3
  179. package/dist/excalidraw/history.d.ts +1 -1
  180. package/dist/excalidraw/index.d.ts +7 -3
  181. package/dist/excalidraw/index.js +14 -5
  182. package/dist/excalidraw/laser-trails.d.ts +19 -0
  183. package/dist/excalidraw/laser-trails.js +95 -0
  184. package/dist/excalidraw/locales/en.json +9 -5
  185. package/dist/excalidraw/reactUtils.d.ts +14 -0
  186. package/dist/excalidraw/reactUtils.js +45 -0
  187. package/dist/excalidraw/renderer/helpers.d.ts +13 -0
  188. package/dist/excalidraw/renderer/helpers.js +39 -0
  189. package/dist/excalidraw/renderer/interactiveScene.d.ts +20 -0
  190. package/dist/excalidraw/renderer/{renderScene.js → interactiveScene.js} +199 -474
  191. package/dist/excalidraw/renderer/renderElement.d.ts +6 -6
  192. package/dist/excalidraw/renderer/renderElement.js +54 -366
  193. package/dist/excalidraw/renderer/staticScene.d.ts +11 -0
  194. package/dist/excalidraw/renderer/staticScene.js +205 -0
  195. package/dist/excalidraw/renderer/staticSvgScene.d.ts +5 -0
  196. package/dist/excalidraw/renderer/staticSvgScene.js +385 -0
  197. package/dist/excalidraw/scene/Fonts.js +2 -1
  198. package/dist/excalidraw/scene/Renderer.d.ts +1 -1
  199. package/dist/excalidraw/scene/Renderer.js +32 -20
  200. package/dist/excalidraw/scene/Scene.d.ts +10 -9
  201. package/dist/excalidraw/scene/Scene.js +45 -21
  202. package/dist/excalidraw/scene/Shape.d.ts +3 -1
  203. package/dist/excalidraw/scene/Shape.js +7 -5
  204. package/dist/excalidraw/scene/ShapeCache.d.ts +2 -1
  205. package/dist/excalidraw/scene/ShapeCache.js +1 -0
  206. package/dist/excalidraw/scene/comparisons.js +2 -1
  207. package/dist/excalidraw/scene/export.d.ts +3 -0
  208. package/dist/excalidraw/scene/export.js +20 -40
  209. package/dist/excalidraw/scene/index.d.ts +0 -1
  210. package/dist/excalidraw/scene/index.js +0 -1
  211. package/dist/excalidraw/scene/scrollbars.d.ts +1 -1
  212. package/dist/excalidraw/scene/scrollbars.js +1 -1
  213. package/dist/excalidraw/scene/selection.d.ts +5 -5
  214. package/dist/excalidraw/scene/selection.js +16 -14
  215. package/dist/excalidraw/scene/types.d.ts +11 -5
  216. package/dist/excalidraw/snapping.d.ts +7 -7
  217. package/dist/excalidraw/snapping.js +21 -20
  218. package/dist/excalidraw/types.d.ts +11 -12
  219. package/dist/excalidraw/utility-types.d.ts +5 -0
  220. package/dist/excalidraw/utils.d.ts +25 -16
  221. package/dist/excalidraw/utils.js +52 -45
  222. package/dist/{dev/en-RLIAOBCI.json → prod/en-TDNWCAOT.json} +9 -5
  223. package/dist/prod/index.css +1 -1
  224. package/dist/prod/index.js +45 -45
  225. package/dist/utils/export.d.ts +0 -6
  226. package/dist/utils/export.js +0 -6
  227. package/dist/utils/index.d.ts +3 -0
  228. package/dist/utils/index.js +3 -0
  229. package/dist/utils/withinBounds.js +2 -1
  230. package/package.json +4 -4
  231. package/dist/browser/dev/excalidraw-assets-dev/chunk-2W5GQUR4.js.map +0 -7
  232. package/dist/browser/dev/excalidraw-assets-dev/chunk-SUHLFFEF.js +0 -53449
  233. package/dist/browser/dev/excalidraw-assets-dev/chunk-SUHLFFEF.js.map +0 -7
  234. package/dist/browser/dev/excalidraw-assets-dev/image-NOPDRTTM.css +0 -5797
  235. package/dist/browser/dev/excalidraw-assets-dev/image-NOPDRTTM.css.map +0 -7
  236. package/dist/browser/prod/excalidraw-assets/chunk-HE2P7BQ6.js +0 -257
  237. package/dist/browser/prod/excalidraw-assets/chunk-OWLL6VOG.js +0 -11
  238. package/dist/browser/prod/excalidraw-assets/en-ERQOR3OC.js +0 -1
  239. package/dist/browser/prod/excalidraw-assets/image-DZ6B4AID.js +0 -1
  240. package/dist/browser/prod/excalidraw-assets/image-J2QCCYAR.css +0 -1
  241. package/dist/excalidraw/components/LaserTool/LaserPathManager.d.ts +0 -28
  242. package/dist/excalidraw/components/LaserTool/LaserPathManager.js +0 -225
  243. package/dist/excalidraw/components/LaserTool/LaserTool.d.ts +0 -8
  244. package/dist/excalidraw/components/LaserTool/LaserTool.js +0 -15
  245. package/dist/excalidraw/renderer/renderScene.d.ts +0 -25
  246. package/dist/excalidraw/vite.config.d.mts +0 -2
  247. package/dist/excalidraw/vite.config.mjs +0 -13
  248. /package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js.map → en-IOBA4CS2.js.map} +0 -0
  249. /package/dist/browser/dev/excalidraw-assets-dev/{image-HYNUJ3XL.js.map → image-VKDAL6BQ.js.map} +0 -0
@@ -0,0 +1,205 @@
1
+ import { FRAME_STYLE } from "../constants";
2
+ import { getElementAbsoluteCoords } from "../element";
3
+ import { elementOverlapsWithFrame, getTargetFrame, isElementInFrame, } from "../frame";
4
+ import { isEmbeddableElement, isIframeLikeElement, } from "../element/typeChecks";
5
+ import { renderElement } from "../renderer/renderElement";
6
+ import { createPlaceholderEmbeddableLabel } from "../element/embeddable";
7
+ import { EXTERNAL_LINK_IMG, getLinkHandleFromCoords, } from "../components/hyperlink/helpers";
8
+ import { bootstrapCanvas, getNormalizedCanvasDimensions } from "./helpers";
9
+ import { throttleRAF } from "../utils";
10
+ const strokeGrid = (context, gridSize, scrollX, scrollY, zoom, width, height) => {
11
+ const BOLD_LINE_FREQUENCY = 5;
12
+ let GridLineColor;
13
+ (function (GridLineColor) {
14
+ GridLineColor["Bold"] = "#cccccc";
15
+ GridLineColor["Regular"] = "#e5e5e5";
16
+ })(GridLineColor || (GridLineColor = {}));
17
+ const offsetX = -Math.round(zoom.value / gridSize) * gridSize + (scrollX % gridSize);
18
+ const offsetY = -Math.round(zoom.value / gridSize) * gridSize + (scrollY % gridSize);
19
+ const lineWidth = Math.min(1 / zoom.value, 1);
20
+ const spaceWidth = 1 / zoom.value;
21
+ const lineDash = [lineWidth * 3, spaceWidth + (lineWidth + spaceWidth)];
22
+ context.save();
23
+ context.lineWidth = lineWidth;
24
+ for (let x = offsetX; x < offsetX + width + gridSize * 2; x += gridSize) {
25
+ const isBold = Math.round(x - scrollX) % (BOLD_LINE_FREQUENCY * gridSize) === 0;
26
+ context.beginPath();
27
+ context.setLineDash(isBold ? [] : lineDash);
28
+ context.strokeStyle = isBold ? GridLineColor.Bold : GridLineColor.Regular;
29
+ context.moveTo(x, offsetY - gridSize);
30
+ context.lineTo(x, offsetY + height + gridSize * 2);
31
+ context.stroke();
32
+ }
33
+ for (let y = offsetY; y < offsetY + height + gridSize * 2; y += gridSize) {
34
+ const isBold = Math.round(y - scrollY) % (BOLD_LINE_FREQUENCY * gridSize) === 0;
35
+ context.beginPath();
36
+ context.setLineDash(isBold ? [] : lineDash);
37
+ context.strokeStyle = isBold ? GridLineColor.Bold : GridLineColor.Regular;
38
+ context.moveTo(offsetX - gridSize, y);
39
+ context.lineTo(offsetX + width + gridSize * 2, y);
40
+ context.stroke();
41
+ }
42
+ context.restore();
43
+ };
44
+ const frameClip = (frame, context, renderConfig, appState) => {
45
+ context.translate(frame.x + appState.scrollX, frame.y + appState.scrollY);
46
+ context.beginPath();
47
+ if (context.roundRect) {
48
+ context.roundRect(0, 0, frame.width, frame.height, FRAME_STYLE.radius / appState.zoom.value);
49
+ }
50
+ else {
51
+ context.rect(0, 0, frame.width, frame.height);
52
+ }
53
+ context.clip();
54
+ context.translate(-(frame.x + appState.scrollX), -(frame.y + appState.scrollY));
55
+ };
56
+ let linkCanvasCache;
57
+ const renderLinkIcon = (element, context, appState, elementsMap) => {
58
+ if (element.link && !appState.selectedElementIds[element.id]) {
59
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
60
+ const [x, y, width, height] = getLinkHandleFromCoords([x1, y1, x2, y2], element.angle, appState);
61
+ const centerX = x + width / 2;
62
+ const centerY = y + height / 2;
63
+ context.save();
64
+ context.translate(appState.scrollX + centerX, appState.scrollY + centerY);
65
+ context.rotate(element.angle);
66
+ if (!linkCanvasCache || linkCanvasCache.zoom !== appState.zoom.value) {
67
+ linkCanvasCache = document.createElement("canvas");
68
+ linkCanvasCache.zoom = appState.zoom.value;
69
+ linkCanvasCache.width =
70
+ width * window.devicePixelRatio * appState.zoom.value;
71
+ linkCanvasCache.height =
72
+ height * window.devicePixelRatio * appState.zoom.value;
73
+ const linkCanvasCacheContext = linkCanvasCache.getContext("2d");
74
+ linkCanvasCacheContext.scale(window.devicePixelRatio * appState.zoom.value, window.devicePixelRatio * appState.zoom.value);
75
+ linkCanvasCacheContext.fillStyle = "#fff";
76
+ linkCanvasCacheContext.fillRect(0, 0, width, height);
77
+ linkCanvasCacheContext.drawImage(EXTERNAL_LINK_IMG, 0, 0, width, height);
78
+ linkCanvasCacheContext.restore();
79
+ context.drawImage(linkCanvasCache, x - centerX, y - centerY, width, height);
80
+ }
81
+ else {
82
+ context.drawImage(linkCanvasCache, x - centerX, y - centerY, width, height);
83
+ }
84
+ context.restore();
85
+ }
86
+ };
87
+ const _renderStaticScene = ({ canvas, rc, elementsMap, allElementsMap, visibleElements, scale, appState, renderConfig, }) => {
88
+ if (canvas === null) {
89
+ return;
90
+ }
91
+ const { renderGrid = true, isExporting } = renderConfig;
92
+ const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(canvas, scale);
93
+ const context = bootstrapCanvas({
94
+ canvas,
95
+ scale,
96
+ normalizedWidth,
97
+ normalizedHeight,
98
+ theme: appState.theme,
99
+ isExporting,
100
+ viewBackgroundColor: appState.viewBackgroundColor,
101
+ });
102
+ // Apply zoom
103
+ context.scale(appState.zoom.value, appState.zoom.value);
104
+ // Grid
105
+ if (renderGrid && appState.gridSize) {
106
+ strokeGrid(context, appState.gridSize, appState.scrollX, appState.scrollY, appState.zoom, normalizedWidth / appState.zoom.value, normalizedHeight / appState.zoom.value);
107
+ }
108
+ const groupsToBeAddedToFrame = new Set();
109
+ visibleElements.forEach((element) => {
110
+ if (element.groupIds.length > 0 &&
111
+ appState.frameToHighlight &&
112
+ appState.selectedElementIds[element.id] &&
113
+ (elementOverlapsWithFrame(element, appState.frameToHighlight, elementsMap) ||
114
+ element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId)))) {
115
+ element.groupIds.forEach((groupId) => groupsToBeAddedToFrame.add(groupId));
116
+ }
117
+ });
118
+ // Paint visible elements
119
+ visibleElements
120
+ .filter((el) => !isIframeLikeElement(el))
121
+ .forEach((element) => {
122
+ try {
123
+ const frameId = element.frameId || appState.frameToHighlight?.id;
124
+ if (frameId &&
125
+ appState.frameRendering.enabled &&
126
+ appState.frameRendering.clip) {
127
+ context.save();
128
+ const frame = getTargetFrame(element, elementsMap, appState);
129
+ // TODO do we need to check isElementInFrame here?
130
+ if (frame && isElementInFrame(element, elementsMap, appState)) {
131
+ frameClip(frame, context, renderConfig, appState);
132
+ }
133
+ renderElement(element, elementsMap, allElementsMap, rc, context, renderConfig, appState);
134
+ context.restore();
135
+ }
136
+ else {
137
+ renderElement(element, elementsMap, allElementsMap, rc, context, renderConfig, appState);
138
+ }
139
+ if (!isExporting) {
140
+ renderLinkIcon(element, context, appState, elementsMap);
141
+ }
142
+ }
143
+ catch (error) {
144
+ console.error(error);
145
+ }
146
+ });
147
+ // render embeddables on top
148
+ visibleElements
149
+ .filter((el) => isIframeLikeElement(el))
150
+ .forEach((element) => {
151
+ try {
152
+ const render = () => {
153
+ renderElement(element, elementsMap, allElementsMap, rc, context, renderConfig, appState);
154
+ if (isIframeLikeElement(element) &&
155
+ (isExporting ||
156
+ (isEmbeddableElement(element) &&
157
+ renderConfig.embedsValidationStatus.get(element.id) !==
158
+ true)) &&
159
+ element.width &&
160
+ element.height) {
161
+ const label = createPlaceholderEmbeddableLabel(element);
162
+ renderElement(label, elementsMap, allElementsMap, rc, context, renderConfig, appState);
163
+ }
164
+ if (!isExporting) {
165
+ renderLinkIcon(element, context, appState, elementsMap);
166
+ }
167
+ };
168
+ // - when exporting the whole canvas, we DO NOT apply clipping
169
+ // - when we are exporting a particular frame, apply clipping
170
+ // if the containing frame is not selected, apply clipping
171
+ const frameId = element.frameId || appState.frameToHighlight?.id;
172
+ if (frameId &&
173
+ appState.frameRendering.enabled &&
174
+ appState.frameRendering.clip) {
175
+ context.save();
176
+ const frame = getTargetFrame(element, elementsMap, appState);
177
+ if (frame && isElementInFrame(element, elementsMap, appState)) {
178
+ frameClip(frame, context, renderConfig, appState);
179
+ }
180
+ render();
181
+ context.restore();
182
+ }
183
+ else {
184
+ render();
185
+ }
186
+ }
187
+ catch (error) {
188
+ console.error(error);
189
+ }
190
+ });
191
+ };
192
+ /** throttled to animation framerate */
193
+ export const renderStaticSceneThrottled = throttleRAF((config) => {
194
+ _renderStaticScene(config);
195
+ }, { trailing: true });
196
+ /**
197
+ * Static scene is the non-ui canvas where we render elements.
198
+ */
199
+ export const renderStaticScene = (renderConfig, throttle) => {
200
+ if (throttle) {
201
+ renderStaticSceneThrottled(renderConfig);
202
+ return;
203
+ }
204
+ _renderStaticScene(renderConfig);
205
+ };
@@ -0,0 +1,5 @@
1
+ import { RoughSVG } from "roughjs/bin/svg";
2
+ import { NonDeletedExcalidrawElement } from "../element/types";
3
+ import { RenderableElementsMap, SVGRenderConfig } from "../scene/types";
4
+ import { BinaryFiles } from "../types";
5
+ export declare const renderSceneToSvg: (elements: readonly NonDeletedExcalidrawElement[], elementsMap: RenderableElementsMap, rsvg: RoughSVG, svgRoot: SVGElement, files: BinaryFiles, renderConfig: SVGRenderConfig) => void;
@@ -0,0 +1,385 @@
1
+ import { FRAME_STYLE, MAX_DECIMALS_FOR_SVG_EXPORT, MIME_TYPES, SVG_NS, } from "../constants";
2
+ import { normalizeLink, toValidURL } from "../data/url";
3
+ import { getElementAbsoluteCoords } from "../element";
4
+ import { createPlaceholderEmbeddableLabel, getEmbedLink, } from "../element/embeddable";
5
+ import { LinearElementEditor } from "../element/linearElementEditor";
6
+ import { getBoundTextElement, getContainerElement, getLineHeightInPx, getVerticalOffset, } from "../element/textElement";
7
+ import { isArrowElement, isIframeLikeElement, isInitializedImageElement, isTextElement, } from "../element/typeChecks";
8
+ import { getContainingFrame } from "../frame";
9
+ import { getCornerRadius, isPathALoop } from "../math";
10
+ import { ShapeCache } from "../scene/ShapeCache";
11
+ import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
12
+ import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
13
+ const roughSVGDrawWithPrecision = (rsvg, drawable, precision) => {
14
+ if (typeof precision === "undefined") {
15
+ return rsvg.draw(drawable);
16
+ }
17
+ const pshape = {
18
+ sets: drawable.sets,
19
+ shape: drawable.shape,
20
+ options: { ...drawable.options, fixedDecimalPlaceDigits: precision },
21
+ };
22
+ return rsvg.draw(pshape);
23
+ };
24
+ const maybeWrapNodesInFrameClipPath = (element, root, nodes, frameRendering, elementsMap) => {
25
+ if (!frameRendering.enabled || !frameRendering.clip) {
26
+ return null;
27
+ }
28
+ const frame = getContainingFrame(element, elementsMap);
29
+ if (frame) {
30
+ const g = root.ownerDocument.createElementNS(SVG_NS, "g");
31
+ g.setAttributeNS(SVG_NS, "clip-path", `url(#${frame.id})`);
32
+ nodes.forEach((node) => g.appendChild(node));
33
+ return g;
34
+ }
35
+ return null;
36
+ };
37
+ const renderElementToSvg = (element, elementsMap, rsvg, svgRoot, files, offsetX, offsetY, renderConfig) => {
38
+ const offset = { x: offsetX, y: offsetY };
39
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
40
+ let cx = (x2 - x1) / 2 - (element.x - x1);
41
+ let cy = (y2 - y1) / 2 - (element.y - y1);
42
+ if (isTextElement(element)) {
43
+ const container = getContainerElement(element, elementsMap);
44
+ if (isArrowElement(container)) {
45
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(container, elementsMap);
46
+ const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(container, element, elementsMap);
47
+ cx = (x2 - x1) / 2 - (boundTextCoords.x - x1);
48
+ cy = (y2 - y1) / 2 - (boundTextCoords.y - y1);
49
+ offsetX = offsetX + boundTextCoords.x - element.x;
50
+ offsetY = offsetY + boundTextCoords.y - element.y;
51
+ }
52
+ }
53
+ const degree = (180 * element.angle) / Math.PI;
54
+ // element to append node to, most of the time svgRoot
55
+ let root = svgRoot;
56
+ // if the element has a link, create an anchor tag and make that the new root
57
+ if (element.link) {
58
+ const anchorTag = svgRoot.ownerDocument.createElementNS(SVG_NS, "a");
59
+ anchorTag.setAttribute("href", normalizeLink(element.link));
60
+ root.appendChild(anchorTag);
61
+ root = anchorTag;
62
+ }
63
+ const addToRoot = (node, element) => {
64
+ if (isTestEnv()) {
65
+ node.setAttribute("data-id", element.id);
66
+ }
67
+ root.appendChild(node);
68
+ };
69
+ const opacity = ((getContainingFrame(element, elementsMap)?.opacity ?? 100) *
70
+ element.opacity) /
71
+ 10000;
72
+ switch (element.type) {
73
+ case "selection": {
74
+ // Since this is used only during editing experience, which is canvas based,
75
+ // this should not happen
76
+ throw new Error("Selection rendering is not supported for SVG");
77
+ }
78
+ case "rectangle":
79
+ case "diamond":
80
+ case "ellipse": {
81
+ const shape = ShapeCache.generateElementShape(element, null);
82
+ const node = roughSVGDrawWithPrecision(rsvg, shape, MAX_DECIMALS_FOR_SVG_EXPORT);
83
+ if (opacity !== 1) {
84
+ node.setAttribute("stroke-opacity", `${opacity}`);
85
+ node.setAttribute("fill-opacity", `${opacity}`);
86
+ }
87
+ node.setAttribute("stroke-linecap", "round");
88
+ node.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
89
+ const g = maybeWrapNodesInFrameClipPath(element, root, [node], renderConfig.frameRendering, elementsMap);
90
+ addToRoot(g || node, element);
91
+ break;
92
+ }
93
+ case "iframe":
94
+ case "embeddable": {
95
+ // render placeholder rectangle
96
+ const shape = ShapeCache.generateElementShape(element, renderConfig);
97
+ const node = roughSVGDrawWithPrecision(rsvg, shape, MAX_DECIMALS_FOR_SVG_EXPORT);
98
+ const opacity = element.opacity / 100;
99
+ if (opacity !== 1) {
100
+ node.setAttribute("stroke-opacity", `${opacity}`);
101
+ node.setAttribute("fill-opacity", `${opacity}`);
102
+ }
103
+ node.setAttribute("stroke-linecap", "round");
104
+ node.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
105
+ addToRoot(node, element);
106
+ const label = createPlaceholderEmbeddableLabel(element);
107
+ renderElementToSvg(label, elementsMap, rsvg, root, files, label.x + offset.x - element.x, label.y + offset.y - element.y, renderConfig);
108
+ // render embeddable element + iframe
109
+ const embeddableNode = roughSVGDrawWithPrecision(rsvg, shape, MAX_DECIMALS_FOR_SVG_EXPORT);
110
+ embeddableNode.setAttribute("stroke-linecap", "round");
111
+ embeddableNode.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
112
+ while (embeddableNode.firstChild) {
113
+ embeddableNode.removeChild(embeddableNode.firstChild);
114
+ }
115
+ const radius = getCornerRadius(Math.min(element.width, element.height), element);
116
+ const embedLink = getEmbedLink(toValidURL(element.link || ""));
117
+ // if rendering embeddables explicitly disabled or
118
+ // embedding documents via srcdoc (which doesn't seem to work for SVGs)
119
+ // replace with a link instead
120
+ if (renderConfig.renderEmbeddables === false ||
121
+ embedLink?.type === "document") {
122
+ const anchorTag = svgRoot.ownerDocument.createElementNS(SVG_NS, "a");
123
+ anchorTag.setAttribute("href", normalizeLink(element.link || ""));
124
+ anchorTag.setAttribute("target", "_blank");
125
+ anchorTag.setAttribute("rel", "noopener noreferrer");
126
+ anchorTag.style.borderRadius = `${radius}px`;
127
+ embeddableNode.appendChild(anchorTag);
128
+ }
129
+ else {
130
+ const foreignObject = svgRoot.ownerDocument.createElementNS(SVG_NS, "foreignObject");
131
+ foreignObject.style.width = `${element.width}px`;
132
+ foreignObject.style.height = `${element.height}px`;
133
+ foreignObject.style.border = "none";
134
+ const div = foreignObject.ownerDocument.createElementNS(SVG_NS, "div");
135
+ div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
136
+ div.style.width = "100%";
137
+ div.style.height = "100%";
138
+ const iframe = div.ownerDocument.createElement("iframe");
139
+ iframe.src = embedLink?.link ?? "";
140
+ iframe.style.width = "100%";
141
+ iframe.style.height = "100%";
142
+ iframe.style.border = "none";
143
+ iframe.style.borderRadius = `${radius}px`;
144
+ iframe.style.top = "0";
145
+ iframe.style.left = "0";
146
+ iframe.allowFullscreen = true;
147
+ div.appendChild(iframe);
148
+ foreignObject.appendChild(div);
149
+ embeddableNode.appendChild(foreignObject);
150
+ }
151
+ addToRoot(embeddableNode, element);
152
+ break;
153
+ }
154
+ case "line":
155
+ case "arrow": {
156
+ const boundText = getBoundTextElement(element, elementsMap);
157
+ const maskPath = svgRoot.ownerDocument.createElementNS(SVG_NS, "mask");
158
+ if (boundText) {
159
+ maskPath.setAttribute("id", `mask-${element.id}`);
160
+ const maskRectVisible = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect");
161
+ offsetX = offsetX || 0;
162
+ offsetY = offsetY || 0;
163
+ maskRectVisible.setAttribute("x", "0");
164
+ maskRectVisible.setAttribute("y", "0");
165
+ maskRectVisible.setAttribute("fill", "#fff");
166
+ maskRectVisible.setAttribute("width", `${element.width + 100 + offsetX}`);
167
+ maskRectVisible.setAttribute("height", `${element.height + 100 + offsetY}`);
168
+ maskPath.appendChild(maskRectVisible);
169
+ const maskRectInvisible = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect");
170
+ const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(element, boundText, elementsMap);
171
+ const maskX = offsetX + boundTextCoords.x - element.x;
172
+ const maskY = offsetY + boundTextCoords.y - element.y;
173
+ maskRectInvisible.setAttribute("x", maskX.toString());
174
+ maskRectInvisible.setAttribute("y", maskY.toString());
175
+ maskRectInvisible.setAttribute("fill", "#000");
176
+ maskRectInvisible.setAttribute("width", `${boundText.width}`);
177
+ maskRectInvisible.setAttribute("height", `${boundText.height}`);
178
+ maskRectInvisible.setAttribute("opacity", "1");
179
+ maskPath.appendChild(maskRectInvisible);
180
+ }
181
+ const group = svgRoot.ownerDocument.createElementNS(SVG_NS, "g");
182
+ if (boundText) {
183
+ group.setAttribute("mask", `url(#mask-${element.id})`);
184
+ }
185
+ group.setAttribute("stroke-linecap", "round");
186
+ const shapes = ShapeCache.generateElementShape(element, renderConfig);
187
+ shapes.forEach((shape) => {
188
+ const node = roughSVGDrawWithPrecision(rsvg, shape, MAX_DECIMALS_FOR_SVG_EXPORT);
189
+ if (opacity !== 1) {
190
+ node.setAttribute("stroke-opacity", `${opacity}`);
191
+ node.setAttribute("fill-opacity", `${opacity}`);
192
+ }
193
+ node.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
194
+ if (element.type === "line" &&
195
+ isPathALoop(element.points) &&
196
+ element.backgroundColor !== "transparent") {
197
+ node.setAttribute("fill-rule", "evenodd");
198
+ }
199
+ group.appendChild(node);
200
+ });
201
+ const g = maybeWrapNodesInFrameClipPath(element, root, [group, maskPath], renderConfig.frameRendering, elementsMap);
202
+ if (g) {
203
+ addToRoot(g, element);
204
+ root.appendChild(g);
205
+ }
206
+ else {
207
+ addToRoot(group, element);
208
+ root.append(maskPath);
209
+ }
210
+ break;
211
+ }
212
+ case "freedraw": {
213
+ const backgroundFillShape = ShapeCache.generateElementShape(element, renderConfig);
214
+ const node = backgroundFillShape
215
+ ? roughSVGDrawWithPrecision(rsvg, backgroundFillShape, MAX_DECIMALS_FOR_SVG_EXPORT)
216
+ : svgRoot.ownerDocument.createElementNS(SVG_NS, "g");
217
+ if (opacity !== 1) {
218
+ node.setAttribute("stroke-opacity", `${opacity}`);
219
+ node.setAttribute("fill-opacity", `${opacity}`);
220
+ }
221
+ node.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
222
+ node.setAttribute("stroke", "none");
223
+ const path = svgRoot.ownerDocument.createElementNS(SVG_NS, "path");
224
+ path.setAttribute("fill", element.strokeColor);
225
+ path.setAttribute("d", getFreeDrawSvgPath(element));
226
+ node.appendChild(path);
227
+ const g = maybeWrapNodesInFrameClipPath(element, root, [node], renderConfig.frameRendering, elementsMap);
228
+ addToRoot(g || node, element);
229
+ break;
230
+ }
231
+ case "image": {
232
+ const width = Math.round(element.width);
233
+ const height = Math.round(element.height);
234
+ const fileData = isInitializedImageElement(element) && files[element.fileId];
235
+ if (fileData) {
236
+ const symbolId = `image-${fileData.id}`;
237
+ let symbol = svgRoot.querySelector(`#${symbolId}`);
238
+ if (!symbol) {
239
+ symbol = svgRoot.ownerDocument.createElementNS(SVG_NS, "symbol");
240
+ symbol.id = symbolId;
241
+ const image = svgRoot.ownerDocument.createElementNS(SVG_NS, "image");
242
+ image.setAttribute("width", "100%");
243
+ image.setAttribute("height", "100%");
244
+ image.setAttribute("href", fileData.dataURL);
245
+ symbol.appendChild(image);
246
+ root.prepend(symbol);
247
+ }
248
+ const use = svgRoot.ownerDocument.createElementNS(SVG_NS, "use");
249
+ use.setAttribute("href", `#${symbolId}`);
250
+ // in dark theme, revert the image color filter
251
+ if (renderConfig.exportWithDarkMode &&
252
+ fileData.mimeType !== MIME_TYPES.svg) {
253
+ use.setAttribute("filter", IMAGE_INVERT_FILTER);
254
+ }
255
+ use.setAttribute("width", `${width}`);
256
+ use.setAttribute("height", `${height}`);
257
+ use.setAttribute("opacity", `${opacity}`);
258
+ // We first apply `scale` transforms (horizontal/vertical mirroring)
259
+ // on the <use> element, then apply translation and rotation
260
+ // on the <g> element which wraps the <use>.
261
+ // Doing this separately is a quick hack to to work around compositing
262
+ // the transformations correctly (the transform-origin was not being
263
+ // applied correctly).
264
+ if (element.scale[0] !== 1 || element.scale[1] !== 1) {
265
+ const translateX = element.scale[0] !== 1 ? -width : 0;
266
+ const translateY = element.scale[1] !== 1 ? -height : 0;
267
+ use.setAttribute("transform", `scale(${element.scale[0]}, ${element.scale[1]}) translate(${translateX} ${translateY})`);
268
+ }
269
+ const g = svgRoot.ownerDocument.createElementNS(SVG_NS, "g");
270
+ g.appendChild(use);
271
+ g.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
272
+ if (element.roundness) {
273
+ const clipPath = svgRoot.ownerDocument.createElementNS(SVG_NS, "clipPath");
274
+ clipPath.id = `image-clipPath-${element.id}`;
275
+ const clipRect = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect");
276
+ const radius = getCornerRadius(Math.min(element.width, element.height), element);
277
+ clipRect.setAttribute("width", `${element.width}`);
278
+ clipRect.setAttribute("height", `${element.height}`);
279
+ clipRect.setAttribute("rx", `${radius}`);
280
+ clipRect.setAttribute("ry", `${radius}`);
281
+ clipPath.appendChild(clipRect);
282
+ addToRoot(clipPath, element);
283
+ g.setAttributeNS(SVG_NS, "clip-path", `url(#${clipPath.id})`);
284
+ }
285
+ const clipG = maybeWrapNodesInFrameClipPath(element, root, [g], renderConfig.frameRendering, elementsMap);
286
+ addToRoot(clipG || g, element);
287
+ }
288
+ break;
289
+ }
290
+ // frames are not rendered and only acts as a container
291
+ case "frame":
292
+ case "magicframe": {
293
+ if (renderConfig.frameRendering.enabled &&
294
+ renderConfig.frameRendering.outline) {
295
+ const rect = document.createElementNS(SVG_NS, "rect");
296
+ rect.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
297
+ rect.setAttribute("width", `${element.width}px`);
298
+ rect.setAttribute("height", `${element.height}px`);
299
+ // Rounded corners
300
+ rect.setAttribute("rx", FRAME_STYLE.radius.toString());
301
+ rect.setAttribute("ry", FRAME_STYLE.radius.toString());
302
+ rect.setAttribute("fill", "none");
303
+ rect.setAttribute("stroke", FRAME_STYLE.strokeColor);
304
+ rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
305
+ addToRoot(rect, element);
306
+ }
307
+ break;
308
+ }
309
+ default: {
310
+ if (isTextElement(element)) {
311
+ const node = svgRoot.ownerDocument.createElementNS(SVG_NS, "g");
312
+ if (opacity !== 1) {
313
+ node.setAttribute("stroke-opacity", `${opacity}`);
314
+ node.setAttribute("fill-opacity", `${opacity}`);
315
+ }
316
+ node.setAttribute("transform", `translate(${offsetX || 0} ${offsetY || 0}) rotate(${degree} ${cx} ${cy})`);
317
+ const lines = element.text.replace(/\r\n?/g, "\n").split("\n");
318
+ const lineHeightPx = getLineHeightInPx(element.fontSize, element.lineHeight);
319
+ const horizontalOffset = element.textAlign === "center"
320
+ ? element.width / 2
321
+ : element.textAlign === "right"
322
+ ? element.width
323
+ : 0;
324
+ const verticalOffset = getVerticalOffset(element.fontFamily, element.fontSize, lineHeightPx);
325
+ const direction = isRTL(element.text) ? "rtl" : "ltr";
326
+ const textAnchor = element.textAlign === "center"
327
+ ? "middle"
328
+ : element.textAlign === "right" || direction === "rtl"
329
+ ? "end"
330
+ : "start";
331
+ for (let i = 0; i < lines.length; i++) {
332
+ const text = svgRoot.ownerDocument.createElementNS(SVG_NS, "text");
333
+ text.textContent = lines[i];
334
+ text.setAttribute("x", `${horizontalOffset}`);
335
+ text.setAttribute("y", `${i * lineHeightPx + verticalOffset}`);
336
+ text.setAttribute("font-family", getFontFamilyString(element));
337
+ text.setAttribute("font-size", `${element.fontSize}px`);
338
+ text.setAttribute("fill", element.strokeColor);
339
+ text.setAttribute("text-anchor", textAnchor);
340
+ text.setAttribute("style", "white-space: pre;");
341
+ text.setAttribute("direction", direction);
342
+ text.setAttribute("dominant-baseline", "alphabetic");
343
+ node.appendChild(text);
344
+ }
345
+ const g = maybeWrapNodesInFrameClipPath(element, root, [node], renderConfig.frameRendering, elementsMap);
346
+ addToRoot(g || node, element);
347
+ }
348
+ else {
349
+ // @ts-ignore
350
+ throw new Error(`Unimplemented type ${element.type}`);
351
+ }
352
+ }
353
+ }
354
+ };
355
+ export const renderSceneToSvg = (elements, elementsMap, rsvg, svgRoot, files, renderConfig) => {
356
+ if (!svgRoot) {
357
+ return;
358
+ }
359
+ // render elements
360
+ elements
361
+ .filter((el) => !isIframeLikeElement(el))
362
+ .forEach((element) => {
363
+ if (!element.isDeleted) {
364
+ try {
365
+ renderElementToSvg(element, elementsMap, rsvg, svgRoot, files, element.x + renderConfig.offsetX, element.y + renderConfig.offsetY, renderConfig);
366
+ }
367
+ catch (error) {
368
+ console.error(error);
369
+ }
370
+ }
371
+ });
372
+ // render embeddables on top
373
+ elements
374
+ .filter((el) => isIframeLikeElement(el))
375
+ .forEach((element) => {
376
+ if (!element.isDeleted) {
377
+ try {
378
+ renderElementToSvg(element, elementsMap, rsvg, svgRoot, files, element.x + renderConfig.offsetX, element.y + renderConfig.offsetY, renderConfig);
379
+ }
380
+ catch (error) {
381
+ console.error(error);
382
+ }
383
+ }
384
+ });
385
+ };
@@ -1,5 +1,6 @@
1
1
  import { isTextElement, refreshTextDimensions } from "../element";
2
2
  import { newElementWith } from "../element/mutateElement";
3
+ import { getContainerElement } from "../element/textElement";
3
4
  import { isBoundToContainer } from "../element/typeChecks";
4
5
  import { getFontString } from "../utils";
5
6
  import { ShapeCache } from "./ShapeCache";
@@ -42,7 +43,7 @@ export class Fonts {
42
43
  ShapeCache.delete(element);
43
44
  didUpdate = true;
44
45
  return newElementWith(element, {
45
- ...refreshTextDimensions(element),
46
+ ...refreshTextDimensions(element, getContainerElement(element, this.scene.getNonDeletedElementsMap()), this.scene.getNonDeletedElementsMap()),
46
47
  });
47
48
  }
48
49
  return element;
@@ -16,7 +16,7 @@ export declare class Renderer {
16
16
  pendingImageElementId: AppState["pendingImageElementId"];
17
17
  versionNonce: ReturnType<InstanceType<typeof Scene>["getVersionNonce"]>;
18
18
  }) => {
19
- canvasElements: NonDeletedExcalidrawElement[];
19
+ elementsMap: Map<string, NonDeletedExcalidrawElement> & import("../utility-types").MakeBrand<"NonDeletedElementsMap"> & import("../utility-types").MakeBrand<"RenderableElementsMap">;
20
20
  visibleElements: readonly NonDeletedExcalidrawElement[];
21
21
  }) & {
22
22
  clear: () => void;