mirror of
https://github.com/excalidraw/excalidraw.git
synced 2026-10-20 08:17:56 +00:00
* fix: complete cross-document runtime ownership * fix: schedule cross-document animations in owner window * fix: Prewarm the right font even if it's not Excalifont Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Hyperlink and tooltip host staleness issues Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Various hyperlink fixes * feat: Host abstraction * fix: exportToSvg support for document override Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Remove caches Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Refactor tests (cut non-critical) Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Break out host from App Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: hold after release fixes Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: repaint when link image decode is complete per app Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * chore: Font loading per-doc test Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: Lint Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * fix: draw SVG frame labels without text measurement Frame labels were baked into exports as measured, ellipsis-truncated text elements, which made `exportToSvg` depend on a canvas (through the render environment) for the only text it ever measures. - add `getFrameLabel` (element package): a measurement-free label spec -- position, font, color, arithmetic height, the frame's width as max width - SVG: draw labels from the spec in the frame branch, full title clipped to the frame's width; extract `appendSvgTextLines` so text elements and labels produce identical markup - canvas: unchanged output (measured, truncated text elements), now built from the same spec - fit label boxes into the SVG export bounds explicitly (`extraBounds`) - test: SVG export of a named frame succeeds with a render environment whose `createCanvas` throws and creates no canvas through `document` Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: measurement-free embed placeholders; no render environment in SVG export The placeholder drawn for an embed that isn't rendered live was the last text `exportToSvg` measured, which kept a canvas (via the render environment) in the SVG path and made headless SVG export throw on any scene with an embed unless the process had been polyfilled. - show the placeholder as a single line capped at 30 characters (`getPlaceholderLinkText`: scheme and `www.` dropped, ellipsis) instead of wrapping the whole link: at or below that length the existing `width / length` font size fits the box by construction, so nothing is measured, wrapped or clipped, and output is identical on every host. The full link stays on the element's `<a href>` in SVG - `newTextElement` accepts pre-computed `metrics` to skip measuring - drop `renderEnvironment` from `SVGRenderConfig`, `exportToSvg` and the `@excalidraw/utils` signature: SVG export's only host dependency is its owner document - throw a guided error from the default canvas/image factories when there is no `document`/`Image`, instead of a bare ReferenceError - tests: placeholder text capping and layout; SVG export of an iframe and a long-link embed with a render environment whose `createCanvas` throws Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: scope top picks drag to source window * feat: StickyNotes support Signed-off-by: Mark Tolmacs <mark@lazycat.hu> * Revert "fix: measurement-free embed placeholders; no render environment in SVG export" This reverts commit886bea142. Rich text makes SVG export measure text anyway (explicit x per line, run boundaries, baselines), so placeholder labels go back to being measured and wrapped like on canvas, through the render environment. Kept: the guided errors thrown by the default canvas/image factories when there is no `document`/`Image`. Claude Opus 5.5 (xhigh) * Revert "fix: draw SVG frame labels without text measurement" This reverts commit295e7d88b. SVG export measures text again (rich text needs it regardless), so both formats go back to master's single path: frame labels baked in as measured, truncated text elements, measured through the export's render environment. Drops `getFrameLabel` and the SVG label renderer. Claude Opus 5.5 (xhigh) * export: one `config.environment` for every format, `document` for SVG SVG exports take the same environment as canvas ones: they measure text (frame labels, embed placeholders) through its `createCanvas`, and are built in its `document` (was the SVG-only `ownerDocument`). Claude Opus 5.5 (xhigh) * RenderEnvironment: carry the `document` The environment is the one host parameter: its `document` is what SVG exports are built in (the editor's owner document by default), so export call sites pass a single environment instead of an environment plus an `ownerDocument`. `exportCanvas` drops its `ownerDocument` option, and `config.environment.document` defaults to the environment's. Rich text's DOM layout will read the same member, rather than threading `ownerDocument` through the measuring functions next to the environment. Claude Opus 5.5 (xhigh) * drop the process-wide `setRenderEnvironment` / `resetRenderEnvironment` Every render and export now takes its environment explicitly: exports through `config.environment`, the editor through its `renderEnvironment` prop. Text measured outside both (e.g. creating elements on a server) has `setCustomTextMetricsProvider`. A second, global way to configure the same thing would be state shared by every job in the process, which is what per-call export environments avoid. The default is now a constant (the browser's). Claude Opus 5.5 (xhigh) * render: log per-element failures on export too, rather than throwing As in the editor (and on master): one element that fails to render is logged and skipped, so the rest of the exported scene survives it rather than the whole export failing. Claude Opus 5.5 (xhigh) * RenderEnvironment: carry the text metrics provider `textMetrics` replaces the per-environment registry of `setCustomTextMetricsProvider(provider, env)`, which looked providers up by the environment's identity, so any rebuilt environment (as exports build one from `config.environment`) silently lost them. It now travels with the environment, and `config.environment.textMetrics` measures an export's text. The global `setCustomTextMetricsProvider(provider)` stays for text measured outside both (e.g. elements created on a server). Exports use a complete `config.environment` as-is (the editor's keeps its caches warm), and document that it needn't be the same object across exports. Frame label truncation measures through the environment too, rather than on a canvas of its own. Claude Opus 5.5 (xhigh) * render: restore the canvas state when an element fails to draw The scene renderer logs a failing element and carries on, but the `save()`s it had made were never restored: its transforms and frame clip stayed on the context for every element after it (a failing freedraw at (80, 30) moved the next rectangle from (10, 10) to (90, 40)). Every `save()` around drawing now restores in a `finally` (`withSavedContext`). Pre-existing on master, in the editor and on export. Claude Opus 5.5 (xhigh) * export: truncate frame names down to "...", or none, to fit the frame When not even one character plus the ellipsis fit, the frame name kept its full title (overflowing narrow frames into their neighbors) while reporting the frame's width. It now shrinks to just "...", and is dropped where even that doesn't fit. Pre-existing on master. Claude Opus 5.5 (xhigh) --------- Signed-off-by: Mark Tolmacs <mark@lazycat.hu> Co-authored-by: Mark Tolmacs <mark@lazycat.hu> Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@excalidraw/utils
Install
npm install @excalidraw/utils
If you prefer Yarn over npm, use this command to install the Excalidraw utils package:
yarn add @excalidraw/utils
API
serializeAsJSON
See serializeAsJSON for API and description.
exportToBlob (async)
Export an Excalidraw diagram to a Blob.
exportToSvg
Export an Excalidraw diagram to a SVGElement.
Usage
Excalidraw utils is published as a UMD (Universal Module Definition). If you are using a module bundler (for instance, Webpack), you can import it as an ES6 module:
import { exportToSvg, exportToBlob } from "@excalidraw/utils";
To use it in a browser directly:
<script src="https://unpkg.com/@excalidraw/utils@0.1.0/dist/excalidraw-utils.min.js"></script>
<script>
// ExcalidrawUtils is a global variable defined by excalidraw.min.js
const { exportToSvg, exportToBlob } = ExcalidrawUtils;
</script>
Here's the exportToBlob and exportToSvg functions in action:
const excalidrawDiagram = {
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
elements: [
{
id: "vWrqOAfkind2qcm7LDAGZ",
type: "ellipse",
x: 414,
y: 237,
width: 214,
height: 214,
angle: 0,
strokeColor: "#000000",
backgroundColor: "#15aabf",
fillStyle: "hachure",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
groupIds: [],
roundness: null,
seed: 1041657908,
version: 120,
versionNonce: 1188004276,
isDeleted: false,
boundElementIds: null,
},
],
appState: {
viewBackgroundColor: "#ffffff",
gridSize: null,
},
};
// Export the Excalidraw diagram as SVG string
const svg = exportToSvg(excalidrawDiagram);
console.log(svg.outerHTML);
// Export the Excalidraw diagram as PNG Blob URL
(async () => {
const blob = await exportToBlob({
...excalidrawDiagram,
mimeType: "image/png",
});
const urlCreator = window.URL || window.webkitURL;
console.log(urlCreator.createObjectURL(blob));
})();