mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-04 04:44:31 +01:00 
			
		
		
		
	* feat: decouple package deps and introduce yarn workspaces * update root directory * fix * fix scripts * fix lint * update path in scripts * remove yarn.lock files from packages * ignore workspace * dummy * dummy * remove comment check * revert workflow changes * ignore ws when installing gh actions * remove log * update path * fix * fix typo
		
			
				
	
	
		
			226 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
  exportToCanvas as _exportToCanvas,
 | 
						|
  exportToSvg as _exportToSvg,
 | 
						|
} from "./excalidraw/scene/export";
 | 
						|
import { getDefaultAppState } from "./excalidraw/appState";
 | 
						|
import { AppState, BinaryFiles } from "./excalidraw/types";
 | 
						|
import {
 | 
						|
  ExcalidrawElement,
 | 
						|
  ExcalidrawFrameLikeElement,
 | 
						|
  NonDeleted,
 | 
						|
} from "./excalidraw/element/types";
 | 
						|
import { restore } from "./excalidraw/data/restore";
 | 
						|
import { MIME_TYPES } from "./excalidraw/constants";
 | 
						|
import { encodePngMetadata } from "./excalidraw/data/image";
 | 
						|
import { serializeAsJSON } from "./excalidraw/data/json";
 | 
						|
import {
 | 
						|
  copyBlobToClipboardAsPng,
 | 
						|
  copyTextToSystemClipboard,
 | 
						|
  copyToClipboard,
 | 
						|
} from "./excalidraw/clipboard";
 | 
						|
 | 
						|
export { MIME_TYPES };
 | 
						|
 | 
						|
type ExportOpts = {
 | 
						|
  elements: readonly NonDeleted<ExcalidrawElement>[];
 | 
						|
  appState?: Partial<Omit<AppState, "offsetTop" | "offsetLeft">>;
 | 
						|
  files: BinaryFiles | null;
 | 
						|
  maxWidthOrHeight?: number;
 | 
						|
  exportingFrame?: ExcalidrawFrameLikeElement | null;
 | 
						|
  getDimensions?: (
 | 
						|
    width: number,
 | 
						|
    height: number,
 | 
						|
  ) => { width: number; height: number; scale?: number };
 | 
						|
};
 | 
						|
 | 
						|
export const exportToCanvas = ({
 | 
						|
  elements,
 | 
						|
  appState,
 | 
						|
  files,
 | 
						|
  maxWidthOrHeight,
 | 
						|
  getDimensions,
 | 
						|
  exportPadding,
 | 
						|
  exportingFrame,
 | 
						|
}: ExportOpts & {
 | 
						|
  exportPadding?: number;
 | 
						|
}) => {
 | 
						|
  const { elements: restoredElements, appState: restoredAppState } = restore(
 | 
						|
    { elements, appState },
 | 
						|
    null,
 | 
						|
    null,
 | 
						|
  );
 | 
						|
  const { exportBackground, viewBackgroundColor } = restoredAppState;
 | 
						|
  return _exportToCanvas(
 | 
						|
    restoredElements,
 | 
						|
    { ...restoredAppState, offsetTop: 0, offsetLeft: 0, width: 0, height: 0 },
 | 
						|
    files || {},
 | 
						|
    { exportBackground, exportPadding, viewBackgroundColor, exportingFrame },
 | 
						|
    (width: number, height: number) => {
 | 
						|
      const canvas = document.createElement("canvas");
 | 
						|
 | 
						|
      if (maxWidthOrHeight) {
 | 
						|
        if (typeof getDimensions === "function") {
 | 
						|
          console.warn(
 | 
						|
            "`getDimensions()` is ignored when `maxWidthOrHeight` is supplied.",
 | 
						|
          );
 | 
						|
        }
 | 
						|
 | 
						|
        const max = Math.max(width, height);
 | 
						|
 | 
						|
        // if content is less then maxWidthOrHeight, fallback on supplied scale
 | 
						|
        const scale =
 | 
						|
          maxWidthOrHeight < max
 | 
						|
            ? maxWidthOrHeight / max
 | 
						|
            : appState?.exportScale ?? 1;
 | 
						|
 | 
						|
        canvas.width = width * scale;
 | 
						|
        canvas.height = height * scale;
 | 
						|
 | 
						|
        return {
 | 
						|
          canvas,
 | 
						|
          scale,
 | 
						|
        };
 | 
						|
      }
 | 
						|
 | 
						|
      const ret = getDimensions?.(width, height) || { width, height };
 | 
						|
 | 
						|
      canvas.width = ret.width;
 | 
						|
      canvas.height = ret.height;
 | 
						|
 | 
						|
      return {
 | 
						|
        canvas,
 | 
						|
        scale: ret.scale ?? 1,
 | 
						|
      };
 | 
						|
    },
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
export const exportToBlob = async (
 | 
						|
  opts: ExportOpts & {
 | 
						|
    mimeType?: string;
 | 
						|
    quality?: number;
 | 
						|
    exportPadding?: number;
 | 
						|
  },
 | 
						|
): Promise<Blob> => {
 | 
						|
  let { mimeType = MIME_TYPES.png, quality } = opts;
 | 
						|
 | 
						|
  if (mimeType === MIME_TYPES.png && typeof quality === "number") {
 | 
						|
    console.warn(`"quality" will be ignored for "${MIME_TYPES.png}" mimeType`);
 | 
						|
  }
 | 
						|
 | 
						|
  // typo in MIME type (should be "jpeg")
 | 
						|
  if (mimeType === "image/jpg") {
 | 
						|
    mimeType = MIME_TYPES.jpg;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mimeType === MIME_TYPES.jpg && !opts.appState?.exportBackground) {
 | 
						|
    console.warn(
 | 
						|
      `Defaulting "exportBackground" to "true" for "${MIME_TYPES.jpg}" mimeType`,
 | 
						|
    );
 | 
						|
    opts = {
 | 
						|
      ...opts,
 | 
						|
      appState: { ...opts.appState, exportBackground: true },
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  const canvas = await exportToCanvas(opts);
 | 
						|
 | 
						|
  quality = quality ? quality : /image\/jpe?g/.test(mimeType) ? 0.92 : 0.8;
 | 
						|
 | 
						|
  return new Promise((resolve, reject) => {
 | 
						|
    canvas.toBlob(
 | 
						|
      async (blob) => {
 | 
						|
        if (!blob) {
 | 
						|
          return reject(new Error("couldn't export to blob"));
 | 
						|
        }
 | 
						|
        if (
 | 
						|
          blob &&
 | 
						|
          mimeType === MIME_TYPES.png &&
 | 
						|
          opts.appState?.exportEmbedScene
 | 
						|
        ) {
 | 
						|
          blob = await encodePngMetadata({
 | 
						|
            blob,
 | 
						|
            metadata: serializeAsJSON(
 | 
						|
              // NOTE as long as we're using the Scene hack, we need to ensure
 | 
						|
              // we pass the original, uncloned elements when serializing
 | 
						|
              // so that we keep ids stable
 | 
						|
              opts.elements,
 | 
						|
              opts.appState,
 | 
						|
              opts.files || {},
 | 
						|
              "local",
 | 
						|
            ),
 | 
						|
          });
 | 
						|
        }
 | 
						|
        resolve(blob);
 | 
						|
      },
 | 
						|
      mimeType,
 | 
						|
      quality,
 | 
						|
    );
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
export const exportToSvg = async ({
 | 
						|
  elements,
 | 
						|
  appState = getDefaultAppState(),
 | 
						|
  files = {},
 | 
						|
  exportPadding,
 | 
						|
  renderEmbeddables,
 | 
						|
  exportingFrame,
 | 
						|
}: Omit<ExportOpts, "getDimensions"> & {
 | 
						|
  exportPadding?: number;
 | 
						|
  renderEmbeddables?: boolean;
 | 
						|
}): Promise<SVGSVGElement> => {
 | 
						|
  const { elements: restoredElements, appState: restoredAppState } = restore(
 | 
						|
    { elements, appState },
 | 
						|
    null,
 | 
						|
    null,
 | 
						|
  );
 | 
						|
 | 
						|
  const exportAppState = {
 | 
						|
    ...restoredAppState,
 | 
						|
    exportPadding,
 | 
						|
  };
 | 
						|
 | 
						|
  return _exportToSvg(restoredElements, exportAppState, files, {
 | 
						|
    exportingFrame,
 | 
						|
    renderEmbeddables,
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
export const exportToClipboard = async (
 | 
						|
  opts: ExportOpts & {
 | 
						|
    mimeType?: string;
 | 
						|
    quality?: number;
 | 
						|
    type: "png" | "svg" | "json";
 | 
						|
  },
 | 
						|
) => {
 | 
						|
  if (opts.type === "svg") {
 | 
						|
    const svg = await exportToSvg(opts);
 | 
						|
    await copyTextToSystemClipboard(svg.outerHTML);
 | 
						|
  } else if (opts.type === "png") {
 | 
						|
    await copyBlobToClipboardAsPng(exportToBlob(opts));
 | 
						|
  } else if (opts.type === "json") {
 | 
						|
    await copyToClipboard(opts.elements, opts.files);
 | 
						|
  } else {
 | 
						|
    throw new Error("Invalid export type");
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
export * from "./bbox";
 | 
						|
export {
 | 
						|
  elementsOverlappingBBox,
 | 
						|
  isElementInsideBBox,
 | 
						|
  elementPartiallyOverlapsWithOrContainsBBox,
 | 
						|
} from "./withinBounds";
 | 
						|
export {
 | 
						|
  serializeAsJSON,
 | 
						|
  serializeLibraryAsJSON,
 | 
						|
} from "./excalidraw/data/json";
 | 
						|
export {
 | 
						|
  loadFromBlob,
 | 
						|
  loadSceneOrLibraryFromBlob,
 | 
						|
  loadLibraryFromBlob,
 | 
						|
} from "./excalidraw/data/blob";
 | 
						|
export { getFreeDrawSvgPath } from "./excalidraw/renderer/renderElement";
 | 
						|
export { mergeLibraryItems } from "./excalidraw/data/library";
 |