Selaa lähdekoodia

Fix embedding scene to PNG on Safari (#2235)

David Luzar 4 vuotta sitten
vanhempi
commit
f40a2230ec
4 muutettua tiedostoa jossa 51 lisäystä ja 26 poistoa
  1. 1 1
      src/components/ExportDialog.tsx
  2. 4 14
      src/data/blob.ts
  3. 4 11
      src/data/index.ts
  4. 42 0
      src/data/png.ts

+ 1 - 1
src/components/ExportDialog.tsx

@@ -156,7 +156,6 @@ const ExportModal = ({
           </Stack.Row>
         </div>
         {actionManager.renderAction("changeExportBackground")}
-        {actionManager.renderAction("changeExportEmbedScene")}
         {someElementIsSelected && (
           <div>
             <label>
@@ -171,6 +170,7 @@ const ExportModal = ({
             </label>
           </div>
         )}
+        {actionManager.renderAction("changeExportEmbedScene")}
         {actionManager.renderAction("changeShouldAddWatermark")}
       </Stack.Col>
     </div>

+ 4 - 14
src/data/blob.ts

@@ -6,24 +6,14 @@ import { LibraryData, ImportedDataState } from "./types";
 import { calculateScrollCenter } from "../scene";
 import { MIME_TYPES } from "../constants";
 import { base64ToString } from "../base64";
-
 export const parseFileContents = async (blob: Blob | File) => {
   let contents: string;
   if (blob.type === "image/png") {
-    const { default: decodePng } = await import("png-chunks-extract");
-    const { default: tEXt } = await import("png-chunk-text");
-    const chunks = decodePng(new Uint8Array(await blob.arrayBuffer()));
-
-    const metadataChunk = chunks.find((chunk) => chunk.name === "tEXt");
-    if (metadataChunk) {
-      const metadata = tEXt.decode(metadataChunk.data);
-      if (metadata.keyword === MIME_TYPES.excalidraw) {
-        return metadata.text;
-      }
-      throw new Error(t("alerts.imageDoesNotContainScene"));
-    } else {
-      throw new Error(t("alerts.imageDoesNotContainScene"));
+    const metadata = await (await import("./png")).getTEXtChunk(blob);
+    if (metadata?.keyword === MIME_TYPES.excalidraw) {
+      return metadata.text;
     }
+    throw new Error(t("alerts.imageDoesNotContainScene"));
   } else {
     if ("text" in Blob) {
       contents = await blob.text();

+ 4 - 11
src/data/index.ts

@@ -345,17 +345,10 @@ export const exportCanvas = async (
     tempCanvas.toBlob(async (blob) => {
       if (blob) {
         if (appState.exportEmbedScene) {
-          const { default: tEXt } = await import("png-chunk-text");
-          const { default: encodePng } = await import("png-chunks-encode");
-          const { default: decodePng } = await import("png-chunks-extract");
-          const chunks = decodePng(new Uint8Array(await blob.arrayBuffer()));
-          const metadata = tEXt.encode(
-            MIME_TYPES.excalidraw,
-            serializeAsJSON(elements, appState),
-          );
-          // insert metadata before last chunk (iEND)
-          chunks.splice(-1, 0, metadata);
-          blob = new Blob([encodePng(chunks)], { type: "image/png" });
+          blob = await (await import("./png")).encodeTEXtChunk(blob, {
+            keyword: MIME_TYPES.excalidraw,
+            text: serializeAsJSON(elements, appState),
+          });
         }
 
         await fileSave(blob, {

+ 42 - 0
src/data/png.ts

@@ -0,0 +1,42 @@
+import decodePng from "png-chunks-extract";
+import tEXt from "png-chunk-text";
+import encodePng from "png-chunks-encode";
+
+const blobToArrayBuffer = (blob: Blob): Promise<ArrayBuffer> => {
+  if ("arrayBuffer" in blob) {
+    return blob.arrayBuffer();
+  }
+  // Safari
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = (event) => {
+      if (!event.target?.result) {
+        return reject(new Error("couldn't convert blob to ArrayBuffer"));
+      }
+      resolve(event.target.result as ArrayBuffer);
+    };
+    reader.readAsArrayBuffer(blob);
+  });
+};
+
+export const getTEXtChunk = async (
+  blob: Blob,
+): Promise<{ keyword: string; text: string } | null> => {
+  const chunks = decodePng(new Uint8Array(await blobToArrayBuffer(blob)));
+  const metadataChunk = chunks.find((chunk) => chunk.name === "tEXt");
+  if (metadataChunk) {
+    return tEXt.decode(metadataChunk.data);
+  }
+  return null;
+};
+
+export const encodeTEXtChunk = async (
+  blob: Blob,
+  chunk: { keyword: string; text: string },
+): Promise<Blob> => {
+  const chunks = decodePng(new Uint8Array(await blobToArrayBuffer(blob)));
+  const metadata = tEXt.encode(chunk.keyword, chunk.text);
+  // insert metadata before last chunk (iEND)
+  chunks.splice(-1, 0, metadata);
+  return new Blob([encodePng(chunks)], { type: "image/png" });
+};