@tldraw/editor 4.5.2 → 4.6.0-canary.00a8c03b5687
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +37 -6
- package/dist-cjs/index.js +6 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +7 -5
- package/dist-cjs/lib/TldrawEditor.js.map +3 -3
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +3 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +8 -5
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js +8 -5
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +3 -2
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +1 -0
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/config/createTLUser.js.map +1 -1
- package/dist-cjs/lib/editor/Editor.js +511 -366
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +25 -64
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +22 -5
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +4 -3
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +5 -0
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +71 -112
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +20 -55
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +1 -1
- package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js +11 -52
- package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +1 -1
- package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +19 -56
- package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +1 -1
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +16 -55
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +1 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +60 -70
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/exports/ExportDelay.js +12 -53
- package/dist-cjs/lib/exports/ExportDelay.js.map +1 -1
- package/dist-cjs/lib/exports/FontEmbedder.js +23 -65
- package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/StyleEmbedder.js +27 -15
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +3 -3
- package/dist-cjs/lib/exports/domUtils.js +15 -0
- package/dist-cjs/lib/exports/domUtils.js.map +2 -2
- package/dist-cjs/lib/exports/embedMedia.js +15 -12
- package/dist-cjs/lib/exports/embedMedia.js.map +2 -2
- package/dist-cjs/lib/exports/exportToSvg.js +8 -7
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +181 -29
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +3 -3
- package/dist-cjs/lib/exports/getSvgJsx.js +21 -9
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +4 -3
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +13 -11
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +3 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useScreenBounds.js +10 -6
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/hooks/useViewportHeight.js +13 -11
- package/dist-cjs/lib/hooks/useViewportHeight.js.map +3 -3
- package/dist-cjs/lib/license/Watermark.js +10 -0
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +25 -25
- package/dist-cjs/lib/primitives/Box.js.map +1 -1
- package/dist-cjs/lib/primitives/Vec.js +36 -23
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +6 -13
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +31 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +9 -0
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +10 -1
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +32 -18
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +13 -1
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -6
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +1 -1
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js +52 -13
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js +12 -0
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/geometry.bench.js +133 -0
- package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +7 -0
- package/dist-cjs/lib/primitives/intersect.js +16 -15
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +0 -1
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/SharedStylesMap.js +1 -1
- package/dist-cjs/lib/utils/SharedStylesMap.js.map +1 -1
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js +3 -2
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +15 -2
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +2 -1
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +37 -6
- package/dist-esm/index.mjs +8 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +7 -5
- package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +2 -1
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +8 -5
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +8 -5
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +3 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +1 -0
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/config/createTLUser.mjs.map +1 -1
- package/dist-esm/lib/editor/Editor.mjs +512 -368
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +25 -64
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +24 -5
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +4 -3
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +5 -0
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +71 -112
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +20 -55
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +1 -1
- package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs +11 -52
- package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +1 -1
- package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +19 -56
- package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +1 -1
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +16 -55
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +1 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +60 -70
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/exports/ExportDelay.mjs +12 -53
- package/dist-esm/lib/exports/ExportDelay.mjs.map +1 -1
- package/dist-esm/lib/exports/FontEmbedder.mjs +23 -65
- package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +29 -16
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +3 -3
- package/dist-esm/lib/exports/domUtils.mjs +15 -0
- package/dist-esm/lib/exports/domUtils.mjs.map +2 -2
- package/dist-esm/lib/exports/embedMedia.mjs +16 -13
- package/dist-esm/lib/exports/embedMedia.mjs.map +2 -2
- package/dist-esm/lib/exports/exportToSvg.mjs +8 -7
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +181 -29
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +3 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs +21 -9
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +4 -3
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +13 -11
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +3 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useScreenBounds.mjs +10 -6
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/hooks/useViewportHeight.mjs +13 -11
- package/dist-esm/lib/hooks/useViewportHeight.mjs.map +3 -3
- package/dist-esm/lib/license/Watermark.mjs +10 -0
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +25 -25
- package/dist-esm/lib/primitives/Box.mjs.map +1 -1
- package/dist-esm/lib/primitives/Vec.mjs +36 -23
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +6 -13
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +31 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +9 -0
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +10 -1
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +32 -18
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +14 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -6
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +1 -1
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +52 -13
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +13 -1
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +132 -0
- package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +7 -0
- package/dist-esm/lib/primitives/intersect.mjs +17 -16
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +0 -1
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/SharedStylesMap.mjs +1 -1
- package/dist-esm/lib/utils/SharedStylesMap.mjs.map +1 -1
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +3 -2
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +15 -2
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +2 -1
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +8 -8
- package/src/index.ts +10 -6
- package/src/lib/TldrawEditor.tsx +7 -5
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +2 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +9 -5
- package/src/lib/config/TLSessionStateSnapshot.ts +8 -5
- package/src/lib/config/TLUserPreferences.ts +3 -2
- package/src/lib/config/createTLStore.ts +3 -0
- package/src/lib/config/createTLUser.ts +3 -3
- package/src/lib/editor/Editor.ts +53 -15
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +7 -6
- package/src/lib/editor/managers/FocusManager/FocusManager.ts +10 -7
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +1 -0
- package/src/lib/editor/managers/FontManager/FontManager.ts +4 -3
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +16 -0
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +7 -2
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +30 -30
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +4 -5
- package/src/lib/editor/managers/TextManager/TextManager.ts +2 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +3 -2
- package/src/lib/editor/types/misc-types.ts +8 -2
- package/src/lib/exports/FontEmbedder.ts +10 -9
- package/src/lib/exports/StyleEmbedder.ts +33 -15
- package/src/lib/exports/domUtils.ts +20 -0
- package/src/lib/exports/embedMedia.ts +23 -17
- package/src/lib/exports/exportToSvg.tsx +8 -7
- package/src/lib/exports/getSvgAsImage.ts +292 -32
- package/src/lib/exports/getSvgJsx.test.ts +103 -101
- package/src/lib/exports/getSvgJsx.tsx +33 -10
- package/src/lib/globals/environment.ts +4 -3
- package/src/lib/hooks/useCanvasEvents.ts +2 -3
- package/src/lib/hooks/useDocumentEvents.ts +16 -11
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +3 -3
- package/src/lib/hooks/useScreenBounds.ts +10 -6
- package/src/lib/hooks/useViewportHeight.ts +13 -11
- package/src/lib/license/Watermark.tsx +10 -0
- package/src/lib/primitives/Box.ts +25 -25
- package/src/lib/primitives/Vec.ts +52 -25
- package/src/lib/primitives/geometry/Arc2d.ts +10 -15
- package/src/lib/primitives/geometry/Circle2d.ts +40 -2
- package/src/lib/primitives/geometry/CubicBezier2d.ts +10 -0
- package/src/lib/primitives/geometry/CubicSpline2d.ts +11 -1
- package/src/lib/primitives/geometry/Edge2d.ts +41 -18
- package/src/lib/primitives/geometry/Ellipse2d.ts +15 -2
- package/src/lib/primitives/geometry/Geometry2d.ts +6 -6
- package/src/lib/primitives/geometry/Polyline2d.ts +61 -13
- package/src/lib/primitives/geometry/Stadium2d.ts +14 -1
- package/src/lib/primitives/geometry/geometry.bench.ts +179 -0
- package/src/lib/primitives/intersect.ts +27 -27
- package/src/lib/primitives/utils.ts +4 -4
- package/src/lib/test/TestEditor.ts +1 -0
- package/src/lib/utils/SharedStylesMap.ts +1 -1
- package/src/lib/utils/browserCanvasMaxSize.ts +4 -2
- package/src/lib/utils/dom.ts +34 -2
- package/src/lib/utils/sync/TLLocalSyncClient.ts +1 -0
- package/src/version.ts +3 -3
|
@@ -4,10 +4,11 @@ import { flushSync } from 'react-dom'
|
|
|
4
4
|
import { createRoot } from 'react-dom/client'
|
|
5
5
|
import type { Editor } from '../editor/Editor'
|
|
6
6
|
import { TLSvgExportOptions } from '../editor/types/misc-types'
|
|
7
|
-
import {
|
|
8
|
-
import { StyleEmbedder } from './StyleEmbedder'
|
|
7
|
+
import { getOwnerWindow } from './domUtils'
|
|
9
8
|
import { embedMedia } from './embedMedia'
|
|
9
|
+
import { SVG_EXPORT_CLASSNAME } from './FontEmbedder'
|
|
10
10
|
import { getSvgJsx } from './getSvgJsx'
|
|
11
|
+
import { StyleEmbedder } from './StyleEmbedder'
|
|
11
12
|
|
|
12
13
|
let idCounter = 1
|
|
13
14
|
|
|
@@ -26,7 +27,7 @@ export async function exportToSvg(
|
|
|
26
27
|
// without this CSS and layout aren't computed correctly, which we need to make sure any
|
|
27
28
|
// <foreignObject> elements have their styles and content inlined correctly.
|
|
28
29
|
const container = editor.getContainer()
|
|
29
|
-
const renderTarget =
|
|
30
|
+
const renderTarget = container.ownerDocument.createElement('div')
|
|
30
31
|
renderTarget.className = SVG_EXPORT_CLASSNAME
|
|
31
32
|
// we hide the element visually, but we don't want it to be focusable or interactive in any way either
|
|
32
33
|
renderTarget.inert = true
|
|
@@ -60,7 +61,7 @@ export async function exportToSvg(
|
|
|
60
61
|
|
|
61
62
|
// Extract the rendered SVG element from the react root
|
|
62
63
|
const svg = renderTarget.firstElementChild
|
|
63
|
-
assert(svg instanceof SVGSVGElement, 'Expected an SVG element')
|
|
64
|
+
assert(svg instanceof getOwnerWindow(container).SVGSVGElement, 'Expected an SVG element')
|
|
64
65
|
|
|
65
66
|
// And apply any changes to <foreignObject> elements that we need to make. while we're in
|
|
66
67
|
// the document, these elements work exactly as we'd expect from other dom elements - they
|
|
@@ -70,7 +71,7 @@ export async function exportToSvg(
|
|
|
70
71
|
// apply any styles directly to the elements themselves.
|
|
71
72
|
await applyChangesToForeignObjects(svg)
|
|
72
73
|
|
|
73
|
-
return { svg, width: result.width, height: result.height }
|
|
74
|
+
return { svg, width: result.width, height: result.height, trimPadding: result.trimPadding }
|
|
74
75
|
} finally {
|
|
75
76
|
// eslint-disable-next-line no-restricted-globals
|
|
76
77
|
setTimeout(() => {
|
|
@@ -95,7 +96,7 @@ async function applyChangesToForeignObjects(svg: SVGSVGElement) {
|
|
|
95
96
|
|
|
96
97
|
try {
|
|
97
98
|
// begin traversing stylesheets to find @font-face declarations we might need to embed
|
|
98
|
-
styleEmbedder.fonts.
|
|
99
|
+
styleEmbedder.fonts.startFindingDocumentFontFaces(svg.ownerDocument)
|
|
99
100
|
|
|
100
101
|
// embed any media elements in the foreignObject children. images will get converted to data
|
|
101
102
|
// urls, and things like videos will be converted to images.
|
|
@@ -126,7 +127,7 @@ async function applyChangesToForeignObjects(svg: SVGSVGElement) {
|
|
|
126
127
|
|
|
127
128
|
// add the CSS to the SVG
|
|
128
129
|
if (fontCss || pseudoCss) {
|
|
129
|
-
const style =
|
|
130
|
+
const style = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'style')
|
|
130
131
|
style.textContent = `${fontCss}\n${pseudoCss}`
|
|
131
132
|
svg.prepend(style)
|
|
132
133
|
}
|
|
@@ -2,6 +2,7 @@ import { FileHelpers, Image, PngHelpers, sleep } from '@tldraw/utils'
|
|
|
2
2
|
import { tlenv } from '../globals/environment'
|
|
3
3
|
import { clampToBrowserMaxCanvasSize } from '../utils/browserCanvasMaxSize'
|
|
4
4
|
import { debugFlags } from '../utils/debug-flags'
|
|
5
|
+
import { getGlobalDocument } from '../utils/dom'
|
|
5
6
|
|
|
6
7
|
/** @public */
|
|
7
8
|
export async function getSvgAsImage(
|
|
@@ -14,7 +15,26 @@ export async function getSvgAsImage(
|
|
|
14
15
|
pixelRatio?: number
|
|
15
16
|
}
|
|
16
17
|
) {
|
|
17
|
-
const
|
|
18
|
+
const result = await getSvgAsImageWithOptions(svgString, options)
|
|
19
|
+
return result?.blob ?? null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @internal */
|
|
23
|
+
export async function getSvgAsImageWithOptions(
|
|
24
|
+
svgString: string,
|
|
25
|
+
options: {
|
|
26
|
+
type: 'png' | 'jpeg' | 'webp'
|
|
27
|
+
width: number
|
|
28
|
+
height: number
|
|
29
|
+
quality?: number
|
|
30
|
+
pixelRatio?: number
|
|
31
|
+
trimPadding?: number
|
|
32
|
+
scale?: number
|
|
33
|
+
}
|
|
34
|
+
): Promise<{ blob: Blob; width: number; height: number } | null> {
|
|
35
|
+
const { type, width, height, quality = 1, pixelRatio = 2, trimPadding = 0, scale = 1 } = options
|
|
36
|
+
|
|
37
|
+
if (width <= 0 || height <= 0) return null
|
|
18
38
|
|
|
19
39
|
let [clampedWidth, clampedHeight] = clampToBrowserMaxCanvasSize(
|
|
20
40
|
width * pixelRatio,
|
|
@@ -24,12 +44,58 @@ export async function getSvgAsImage(
|
|
|
24
44
|
clampedHeight = Math.floor(clampedHeight)
|
|
25
45
|
const effectiveScale = clampedWidth / width
|
|
26
46
|
|
|
47
|
+
const canvas = await renderSvgToCanvas(svgString, clampedWidth, clampedHeight)
|
|
48
|
+
|
|
49
|
+
if (!canvas) return null
|
|
50
|
+
|
|
51
|
+
// If we rendered with extra padding to capture visual overflow, trim it now
|
|
52
|
+
const outputCanvas =
|
|
53
|
+
trimPadding > 0
|
|
54
|
+
? trimExtraPadding(canvas, trimPadding * scale * effectiveScale)
|
|
55
|
+
: { canvas, width: clampedWidth, height: clampedHeight }
|
|
56
|
+
|
|
57
|
+
const blob = await new Promise<Blob | null>((resolve) =>
|
|
58
|
+
outputCanvas.canvas.toBlob(
|
|
59
|
+
(blob) => {
|
|
60
|
+
if (!blob || debugFlags.throwToBlob.get()) {
|
|
61
|
+
resolve(null)
|
|
62
|
+
}
|
|
63
|
+
resolve(blob)
|
|
64
|
+
},
|
|
65
|
+
'image/' + type,
|
|
66
|
+
quality
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if (!blob) return null
|
|
71
|
+
|
|
72
|
+
let resultBlob: Blob
|
|
73
|
+
if (type === 'png') {
|
|
74
|
+
resultBlob = PngHelpers.setPhysChunk(new DataView(await blob.arrayBuffer()), effectiveScale, {
|
|
75
|
+
type: 'image/' + type,
|
|
76
|
+
})
|
|
77
|
+
} else {
|
|
78
|
+
resultBlob = blob
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
blob: resultBlob,
|
|
83
|
+
width: outputCanvas.width / effectiveScale,
|
|
84
|
+
height: outputCanvas.height / effectiveScale,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function renderSvgToCanvas(
|
|
89
|
+
svgString: string,
|
|
90
|
+
width: number,
|
|
91
|
+
height: number
|
|
92
|
+
): Promise<HTMLCanvasElement | null> {
|
|
27
93
|
// usually we would use `URL.createObjectURL` here, but chrome has a bug where `blob:` URLs of
|
|
28
94
|
// SVGs that use <foreignObject> mark the canvas as tainted, where data: ones do not.
|
|
29
95
|
// https://issues.chromium.org/issues/41054640
|
|
30
96
|
const svgUrl = await FileHelpers.blobToDataUrl(new Blob([svgString], { type: 'image/svg+xml' }))
|
|
31
97
|
|
|
32
|
-
|
|
98
|
+
return new Promise<HTMLCanvasElement | null>((resolve) => {
|
|
33
99
|
const image = Image()
|
|
34
100
|
image.crossOrigin = 'anonymous'
|
|
35
101
|
|
|
@@ -42,18 +108,13 @@ export async function getSvgAsImage(
|
|
|
42
108
|
await sleep(250)
|
|
43
109
|
}
|
|
44
110
|
|
|
45
|
-
const canvas =
|
|
111
|
+
const canvas = getGlobalDocument().createElement('canvas') as HTMLCanvasElement
|
|
46
112
|
const ctx = canvas.getContext('2d')!
|
|
47
|
-
|
|
48
|
-
canvas.
|
|
49
|
-
canvas.height = clampedHeight
|
|
50
|
-
|
|
113
|
+
canvas.width = width
|
|
114
|
+
canvas.height = height
|
|
51
115
|
ctx.imageSmoothingEnabled = true
|
|
52
116
|
ctx.imageSmoothingQuality = 'high'
|
|
53
|
-
ctx.drawImage(image, 0, 0,
|
|
54
|
-
|
|
55
|
-
URL.revokeObjectURL(svgUrl)
|
|
56
|
-
|
|
117
|
+
ctx.drawImage(image, 0, 0, width, height)
|
|
57
118
|
resolve(canvas)
|
|
58
119
|
}
|
|
59
120
|
|
|
@@ -63,30 +124,229 @@ export async function getSvgAsImage(
|
|
|
63
124
|
|
|
64
125
|
image.src = svgUrl
|
|
65
126
|
})
|
|
127
|
+
}
|
|
66
128
|
|
|
67
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Scans a canvas from each edge inward (up to trimPaddingPx pixels) to find
|
|
131
|
+
* the first row/column containing non-background content. Returns the crop
|
|
132
|
+
* rectangle in canvas pixel coordinates, or null if no trimming is needed.
|
|
133
|
+
*/
|
|
134
|
+
function measureContentBounds(
|
|
135
|
+
canvas: HTMLCanvasElement,
|
|
136
|
+
trimPaddingPx: number
|
|
137
|
+
): { cropLeft: number; cropTop: number; cropRight: number; cropBottom: number } | null {
|
|
138
|
+
const w = canvas.width
|
|
139
|
+
const h = canvas.height
|
|
140
|
+
const ctx = canvas.getContext('2d')!
|
|
68
141
|
|
|
69
|
-
const
|
|
70
|
-
canvas.toBlob(
|
|
71
|
-
(blob) => {
|
|
72
|
-
if (!blob || debugFlags.throwToBlob.get()) {
|
|
73
|
-
resolve(null)
|
|
74
|
-
}
|
|
75
|
-
resolve(blob)
|
|
76
|
-
},
|
|
77
|
-
'image/' + type,
|
|
78
|
-
quality
|
|
79
|
-
)
|
|
80
|
-
)
|
|
142
|
+
const extraPx = Math.ceil(trimPaddingPx)
|
|
81
143
|
|
|
82
|
-
if
|
|
144
|
+
// Nothing to trim if the extra padding is negligible or larger than half the canvas
|
|
145
|
+
// (extraPx * 2 >= w means declaredRight <= declaredLeft, producing zero/negative crop)
|
|
146
|
+
if (extraPx <= 0 || extraPx * 2 >= w || extraPx * 2 >= h) return null
|
|
83
147
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
148
|
+
const imageData = ctx.getImageData(0, 0, w, h)
|
|
149
|
+
const data = imageData.data
|
|
150
|
+
|
|
151
|
+
// Determine how to detect "empty" pixels.
|
|
152
|
+
// Sample the corner pixel to detect the background color.
|
|
153
|
+
const cornerR = data[0]
|
|
154
|
+
const cornerG = data[1]
|
|
155
|
+
const cornerB = data[2]
|
|
156
|
+
const cornerA = data[3]
|
|
157
|
+
const hasTransparentBackground = cornerA === 0
|
|
158
|
+
|
|
159
|
+
function isContentPixel(offset: number): boolean {
|
|
160
|
+
if (hasTransparentBackground) {
|
|
161
|
+
// For transparent background, any non-transparent pixel is content
|
|
162
|
+
return data[offset + 3] > 0
|
|
163
|
+
} else {
|
|
164
|
+
// For opaque background, look for pixels that differ from the background
|
|
165
|
+
const a = data[offset + 3]
|
|
166
|
+
if (a !== cornerA) return true
|
|
167
|
+
const r = data[offset]
|
|
168
|
+
const g = data[offset + 1]
|
|
169
|
+
const b = data[offset + 2]
|
|
170
|
+
return r !== cornerR || g !== cornerG || b !== cornerB
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// The declared bounds area (content area without extra padding)
|
|
175
|
+
const declaredLeft = extraPx
|
|
176
|
+
const declaredTop = extraPx
|
|
177
|
+
const declaredRight = w - extraPx
|
|
178
|
+
const declaredBottom = h - extraPx
|
|
179
|
+
|
|
180
|
+
// Scan from top edge inward: find first row with content or declared bounds
|
|
181
|
+
let cropTop = declaredTop
|
|
182
|
+
for (let y = 0; y < declaredTop; y++) {
|
|
183
|
+
let hasContent = false
|
|
184
|
+
for (let x = 0; x < w; x++) {
|
|
185
|
+
if (isContentPixel((y * w + x) * 4)) {
|
|
186
|
+
hasContent = true
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (hasContent) {
|
|
191
|
+
cropTop = y
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Scan from bottom edge inward
|
|
197
|
+
let cropBottom = declaredBottom
|
|
198
|
+
for (let y = h - 1; y >= declaredBottom; y--) {
|
|
199
|
+
let hasContent = false
|
|
200
|
+
for (let x = 0; x < w; x++) {
|
|
201
|
+
if (isContentPixel((y * w + x) * 4)) {
|
|
202
|
+
hasContent = true
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (hasContent) {
|
|
207
|
+
cropBottom = y + 1
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Scan from left edge inward
|
|
213
|
+
let cropLeft = declaredLeft
|
|
214
|
+
for (let x = 0; x < declaredLeft; x++) {
|
|
215
|
+
let hasContent = false
|
|
216
|
+
for (let y = cropTop; y < cropBottom; y++) {
|
|
217
|
+
if (isContentPixel((y * w + x) * 4)) {
|
|
218
|
+
hasContent = true
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (hasContent) {
|
|
223
|
+
cropLeft = x
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Scan from right edge inward
|
|
229
|
+
let cropRight = declaredRight
|
|
230
|
+
for (let x = w - 1; x >= declaredRight; x--) {
|
|
231
|
+
let hasContent = false
|
|
232
|
+
for (let y = cropTop; y < cropBottom; y++) {
|
|
233
|
+
if (isContentPixel((y * w + x) * 4)) {
|
|
234
|
+
hasContent = true
|
|
235
|
+
break
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (hasContent) {
|
|
239
|
+
cropRight = x + 1
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// If no trimming needed (content fills or exceeds the entire render area)
|
|
245
|
+
if (cropLeft === 0 && cropTop === 0 && cropRight === w && cropBottom === h) {
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { cropLeft, cropTop, cropRight, cropBottom }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Trims extra padding from a canvas by scanning from each edge inward to find
|
|
254
|
+
* non-transparent (or non-background) pixels. Stops at either content pixels or
|
|
255
|
+
* the declared bounds (the area without extra padding).
|
|
256
|
+
*/
|
|
257
|
+
function trimExtraPadding(
|
|
258
|
+
canvas: HTMLCanvasElement,
|
|
259
|
+
trimPaddingPx: number
|
|
260
|
+
): { canvas: HTMLCanvasElement; width: number; height: number } {
|
|
261
|
+
const w = canvas.width
|
|
262
|
+
const h = canvas.height
|
|
263
|
+
|
|
264
|
+
const bounds = measureContentBounds(canvas, trimPaddingPx)
|
|
265
|
+
if (!bounds) return { canvas, width: w, height: h }
|
|
266
|
+
|
|
267
|
+
const { cropLeft, cropTop, cropRight, cropBottom } = bounds
|
|
268
|
+
const cropW = cropRight - cropLeft
|
|
269
|
+
const cropH = cropBottom - cropTop
|
|
270
|
+
|
|
271
|
+
// Create a new cropped canvas
|
|
272
|
+
const croppedCanvas = getGlobalDocument().createElement('canvas')
|
|
273
|
+
croppedCanvas.width = cropW
|
|
274
|
+
croppedCanvas.height = cropH
|
|
275
|
+
const croppedCtx = croppedCanvas.getContext('2d')!
|
|
276
|
+
croppedCtx.drawImage(canvas, cropLeft, cropTop, cropW, cropH, 0, 0, cropW, cropH)
|
|
277
|
+
|
|
278
|
+
return { canvas: croppedCanvas, width: cropW, height: cropH }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Trims an SVG string to its visual content bounds by rendering it to a
|
|
283
|
+
* temporary canvas, measuring the actual content area, then adjusting the
|
|
284
|
+
* SVG's viewBox and dimensions to match.
|
|
285
|
+
*
|
|
286
|
+
* @param svgString - The SVG string to trim.
|
|
287
|
+
* @param options - Options for trimming.
|
|
288
|
+
* @returns The trimmed SVG string with updated dimensions, or null if no trimming was needed.
|
|
289
|
+
*
|
|
290
|
+
* @internal
|
|
291
|
+
*/
|
|
292
|
+
export async function trimSvgToContent(
|
|
293
|
+
svgString: string,
|
|
294
|
+
options: {
|
|
295
|
+
width: number
|
|
296
|
+
height: number
|
|
297
|
+
trimPadding: number
|
|
298
|
+
scale: number
|
|
91
299
|
}
|
|
300
|
+
): Promise<{ svg: string; width: number; height: number } | null> {
|
|
301
|
+
const { width, height, trimPadding, scale } = options
|
|
302
|
+
|
|
303
|
+
if (trimPadding <= 0) return null
|
|
304
|
+
|
|
305
|
+
// Render SVG to a temporary canvas at 1:1 pixel ratio
|
|
306
|
+
const canvasWidth = Math.floor(width)
|
|
307
|
+
const canvasHeight = Math.floor(height)
|
|
308
|
+
|
|
309
|
+
if (canvasWidth <= 0 || canvasHeight <= 0) return null
|
|
310
|
+
|
|
311
|
+
const canvas = await renderSvgToCanvas(svgString, canvasWidth, canvasHeight)
|
|
312
|
+
|
|
313
|
+
if (!canvas) return null
|
|
314
|
+
|
|
315
|
+
// Measure content bounds on the canvas
|
|
316
|
+
const trimPaddingPx = trimPadding * scale
|
|
317
|
+
const bounds = measureContentBounds(canvas, trimPaddingPx)
|
|
318
|
+
if (!bounds) return null
|
|
319
|
+
|
|
320
|
+
const { cropLeft, cropTop, cropRight, cropBottom } = bounds
|
|
321
|
+
|
|
322
|
+
// Parse the SVG to get the current viewBox
|
|
323
|
+
const parser = new DOMParser()
|
|
324
|
+
const doc = parser.parseFromString(svgString, 'image/svg+xml')
|
|
325
|
+
const svgEl = doc.documentElement
|
|
326
|
+
|
|
327
|
+
const viewBoxAttr = svgEl.getAttribute('viewBox')
|
|
328
|
+
if (!viewBoxAttr) return null
|
|
329
|
+
|
|
330
|
+
const [vbMinX, vbMinY, vbW, vbH] = viewBoxAttr.split(/\s+/).map(Number)
|
|
331
|
+
|
|
332
|
+
// Convert canvas pixel coords to viewBox coords
|
|
333
|
+
const newMinX = vbMinX + (cropLeft / canvasWidth) * vbW
|
|
334
|
+
const newMinY = vbMinY + (cropTop / canvasHeight) * vbH
|
|
335
|
+
const newVbW = ((cropRight - cropLeft) / canvasWidth) * vbW
|
|
336
|
+
const newVbH = ((cropBottom - cropTop) / canvasHeight) * vbH
|
|
337
|
+
|
|
338
|
+
// New SVG dimensions maintain the same scale
|
|
339
|
+
const newWidth = newVbW * scale
|
|
340
|
+
const newHeight = newVbH * scale
|
|
341
|
+
|
|
342
|
+
// Update SVG attributes
|
|
343
|
+
svgEl.setAttribute('viewBox', `${newMinX} ${newMinY} ${newVbW} ${newVbH}`)
|
|
344
|
+
svgEl.setAttribute('width', String(newWidth))
|
|
345
|
+
svgEl.setAttribute('height', String(newHeight))
|
|
346
|
+
|
|
347
|
+
// Serialize back
|
|
348
|
+
const serializer = new XMLSerializer()
|
|
349
|
+
const newSvgString = serializer.serializeToString(svgEl)
|
|
350
|
+
|
|
351
|
+
return { svg: newSvgString, width: newWidth, height: newHeight }
|
|
92
352
|
}
|