mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
fix: restore svg image DataURL dimensions (#8730)
This commit is contained in:
parent
f9815b8b4f
commit
79b181bcdc
13 changed files with 196 additions and 94 deletions
|
@ -8,13 +8,14 @@ import { calculateScrollCenter } from "../scene";
|
|||
import type { AppState, DataURL, LibraryItem } from "../types";
|
||||
import type { ValueOf } from "../utility-types";
|
||||
import { bytesToHexString, isPromiseLike } from "../utils";
|
||||
import { base64ToString, stringToBase64, toByteString } from "./encode";
|
||||
import type { FileSystemHandle } from "./filesystem";
|
||||
import { nativeFileSystemSupported } from "./filesystem";
|
||||
import { isValidExcalidrawData, isValidLibrary } from "./json";
|
||||
import { restore, restoreLibraryItems } from "./restore";
|
||||
import type { ImportedLibraryData } from "./types";
|
||||
|
||||
const parseFileContents = async (blob: Blob | File) => {
|
||||
const parseFileContents = async (blob: Blob | File): Promise<string> => {
|
||||
let contents: string;
|
||||
|
||||
if (blob.type === MIME_TYPES.png) {
|
||||
|
@ -46,9 +47,7 @@ const parseFileContents = async (blob: Blob | File) => {
|
|||
}
|
||||
if (blob.type === MIME_TYPES.svg) {
|
||||
try {
|
||||
return await (
|
||||
await import("./image")
|
||||
).decodeSvgMetadata({
|
||||
return (await import("./image")).decodeSvgMetadata({
|
||||
svg: contents,
|
||||
});
|
||||
} catch (error: any) {
|
||||
|
@ -249,6 +248,7 @@ export const generateIdFromFile = async (file: File): Promise<FileId> => {
|
|||
}
|
||||
};
|
||||
|
||||
/** async. For sync variant, use getDataURL_sync */
|
||||
export const getDataURL = async (file: Blob | File): Promise<DataURL> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
@ -261,6 +261,16 @@ export const getDataURL = async (file: Blob | File): Promise<DataURL> => {
|
|||
});
|
||||
};
|
||||
|
||||
export const getDataURL_sync = (
|
||||
data: string | Uint8Array | ArrayBuffer,
|
||||
mimeType: ValueOf<typeof MIME_TYPES>,
|
||||
): DataURL => {
|
||||
return `data:${mimeType};base64,${stringToBase64(
|
||||
toByteString(data),
|
||||
true,
|
||||
)}` as DataURL;
|
||||
};
|
||||
|
||||
export const dataURLToFile = (dataURL: DataURL, filename = "") => {
|
||||
const dataIndexStart = dataURL.indexOf(",");
|
||||
const byteString = atob(dataURL.slice(dataIndexStart + 1));
|
||||
|
@ -274,6 +284,10 @@ export const dataURLToFile = (dataURL: DataURL, filename = "") => {
|
|||
return new File([ab], filename, { type: mimeType });
|
||||
};
|
||||
|
||||
export const dataURLToString = (dataURL: DataURL) => {
|
||||
return base64ToString(dataURL.slice(dataURL.indexOf(",") + 1));
|
||||
};
|
||||
|
||||
export const resizeImageFile = async (
|
||||
file: File,
|
||||
opts: {
|
||||
|
|
|
@ -5,24 +5,23 @@ import { encryptData, decryptData } from "./encryption";
|
|||
// byte (binary) strings
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// fast, Buffer-compatible implem
|
||||
export const toByteString = (
|
||||
data: string | Uint8Array | ArrayBuffer,
|
||||
): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const blob =
|
||||
typeof data === "string"
|
||||
? new Blob([new TextEncoder().encode(data)])
|
||||
: new Blob([data instanceof Uint8Array ? data : new Uint8Array(data)]);
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (!event.target || typeof event.target.result !== "string") {
|
||||
return reject(new Error("couldn't convert to byte string"));
|
||||
}
|
||||
resolve(event.target.result);
|
||||
};
|
||||
reader.readAsBinaryString(blob);
|
||||
});
|
||||
// Buffer-compatible implem.
|
||||
//
|
||||
// Note that in V8, spreading the uint8array (by chunks) into
|
||||
// `String.fromCharCode(...uint8array)` tends to be faster for large
|
||||
// strings/buffers, in case perf is needed in the future.
|
||||
export const toByteString = (data: string | Uint8Array | ArrayBuffer) => {
|
||||
const bytes =
|
||||
typeof data === "string"
|
||||
? new TextEncoder().encode(data)
|
||||
: data instanceof Uint8Array
|
||||
? data
|
||||
: new Uint8Array(data);
|
||||
let bstring = "";
|
||||
for (const byte of bytes) {
|
||||
bstring += String.fromCharCode(byte);
|
||||
}
|
||||
return bstring;
|
||||
};
|
||||
|
||||
const byteStringToArrayBuffer = (byteString: string) => {
|
||||
|
@ -46,12 +45,12 @@ const byteStringToString = (byteString: string) => {
|
|||
* @param isByteString set to true if already byte string to prevent bloat
|
||||
* due to reencoding
|
||||
*/
|
||||
export const stringToBase64 = async (str: string, isByteString = false) => {
|
||||
return isByteString ? window.btoa(str) : window.btoa(await toByteString(str));
|
||||
export const stringToBase64 = (str: string, isByteString = false) => {
|
||||
return isByteString ? window.btoa(str) : window.btoa(toByteString(str));
|
||||
};
|
||||
|
||||
// async to align with stringToBase64
|
||||
export const base64ToString = async (base64: string, isByteString = false) => {
|
||||
export const base64ToString = (base64: string, isByteString = false) => {
|
||||
return isByteString
|
||||
? window.atob(base64)
|
||||
: byteStringToString(window.atob(base64));
|
||||
|
@ -82,18 +81,18 @@ type EncodedData = {
|
|||
/**
|
||||
* Encodes (and potentially compresses via zlib) text to byte string
|
||||
*/
|
||||
export const encode = async ({
|
||||
export const encode = ({
|
||||
text,
|
||||
compress,
|
||||
}: {
|
||||
text: string;
|
||||
/** defaults to `true`. If compression fails, falls back to bstring alone. */
|
||||
compress?: boolean;
|
||||
}): Promise<EncodedData> => {
|
||||
}): EncodedData => {
|
||||
let deflated!: string;
|
||||
if (compress !== false) {
|
||||
try {
|
||||
deflated = await toByteString(deflate(text));
|
||||
deflated = toByteString(deflate(text));
|
||||
} catch (error: any) {
|
||||
console.error("encode: cannot deflate", error);
|
||||
}
|
||||
|
@ -102,11 +101,11 @@ export const encode = async ({
|
|||
version: "1",
|
||||
encoding: "bstring",
|
||||
compressed: !!deflated,
|
||||
encoded: deflated || (await toByteString(text)),
|
||||
encoded: deflated || toByteString(text),
|
||||
};
|
||||
};
|
||||
|
||||
export const decode = async (data: EncodedData): Promise<string> => {
|
||||
export const decode = (data: EncodedData): string => {
|
||||
let decoded: string;
|
||||
|
||||
switch (data.encoding) {
|
||||
|
@ -114,7 +113,7 @@ export const decode = async (data: EncodedData): Promise<string> => {
|
|||
// if compressed, do not double decode the bstring
|
||||
decoded = data.compressed
|
||||
? data.encoded
|
||||
: await byteStringToString(data.encoded);
|
||||
: byteStringToString(data.encoded);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`decode: unknown encoding "${data.encoding}"`);
|
||||
|
|
|
@ -32,7 +32,7 @@ export const encodePngMetadata = async ({
|
|||
const metadataChunk = tEXt.encode(
|
||||
MIME_TYPES.excalidraw,
|
||||
JSON.stringify(
|
||||
await encode({
|
||||
encode({
|
||||
text: metadata,
|
||||
compress: true,
|
||||
}),
|
||||
|
@ -59,7 +59,7 @@ export const decodePngMetadata = async (blob: Blob) => {
|
|||
}
|
||||
throw new Error("FAILED");
|
||||
}
|
||||
return await decode(encodedData);
|
||||
return decode(encodedData);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
throw new Error("FAILED");
|
||||
|
@ -72,9 +72,9 @@ export const decodePngMetadata = async (blob: Blob) => {
|
|||
// SVG
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const encodeSvgMetadata = async ({ text }: { text: string }) => {
|
||||
const base64 = await stringToBase64(
|
||||
JSON.stringify(await encode({ text })),
|
||||
export const encodeSvgMetadata = ({ text }: { text: string }) => {
|
||||
const base64 = stringToBase64(
|
||||
JSON.stringify(encode({ text })),
|
||||
true /* is already byte string */,
|
||||
);
|
||||
|
||||
|
@ -87,7 +87,7 @@ export const encodeSvgMetadata = async ({ text }: { text: string }) => {
|
|||
return metadata;
|
||||
};
|
||||
|
||||
export const decodeSvgMetadata = async ({ svg }: { svg: string }) => {
|
||||
export const decodeSvgMetadata = ({ svg }: { svg: string }) => {
|
||||
if (svg.includes(`payload-type:${MIME_TYPES.excalidraw}`)) {
|
||||
const match = svg.match(
|
||||
/<!-- payload-start -->\s*(.+?)\s*<!-- payload-end -->/,
|
||||
|
@ -100,7 +100,7 @@ export const decodeSvgMetadata = async ({ svg }: { svg: string }) => {
|
|||
const isByteString = version !== "1";
|
||||
|
||||
try {
|
||||
const json = await base64ToString(match[1], isByteString);
|
||||
const json = base64ToString(match[1], isByteString);
|
||||
const encodedData = JSON.parse(json);
|
||||
if (!("encoded" in encodedData)) {
|
||||
// legacy, un-encoded scene JSON
|
||||
|
@ -112,7 +112,7 @@ export const decodeSvgMetadata = async ({ svg }: { svg: string }) => {
|
|||
}
|
||||
throw new Error("FAILED");
|
||||
}
|
||||
return await decode(encodedData);
|
||||
return decode(encodedData);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
throw new Error("FAILED");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue