mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
fix: image-mirroring in export preview and in exported svg (#5700)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
9929a2be6f
commit
3a776f8795
7 changed files with 163 additions and 9 deletions
20
src/tests/__snapshots__/export.test.tsx.snap
Normal file
20
src/tests/__snapshots__/export.test.tsx.snap
Normal file
File diff suppressed because one or more lines are too long
|
@ -7,6 +7,10 @@ import {
|
|||
decodeSvgMetadata,
|
||||
} from "../data/image";
|
||||
import { serializeAsJSON } from "../data/json";
|
||||
import { exportToSvg } from "../scene/export";
|
||||
import { FileId } from "../element/types";
|
||||
import { getDataURL } from "../data/blob";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
|
@ -101,4 +105,73 @@ describe("export", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("exporting svg containing transformed images", async () => {
|
||||
const normalizeAngle = (angle: number) => (angle / 180) * Math.PI;
|
||||
|
||||
const elements = [
|
||||
API.createElement({
|
||||
type: "image",
|
||||
fileId: "file_A",
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: [1, 1],
|
||||
width: 100,
|
||||
height: 100,
|
||||
angle: normalizeAngle(315),
|
||||
}),
|
||||
API.createElement({
|
||||
type: "image",
|
||||
fileId: "file_A",
|
||||
x: 100,
|
||||
y: 0,
|
||||
scale: [-1, 1],
|
||||
width: 50,
|
||||
height: 50,
|
||||
angle: normalizeAngle(45),
|
||||
}),
|
||||
API.createElement({
|
||||
type: "image",
|
||||
fileId: "file_A",
|
||||
x: 0,
|
||||
y: 100,
|
||||
scale: [1, -1],
|
||||
width: 100,
|
||||
height: 100,
|
||||
angle: normalizeAngle(45),
|
||||
}),
|
||||
API.createElement({
|
||||
type: "image",
|
||||
fileId: "file_A",
|
||||
x: 100,
|
||||
y: 100,
|
||||
scale: [-1, -1],
|
||||
width: 50,
|
||||
height: 50,
|
||||
angle: normalizeAngle(315),
|
||||
}),
|
||||
];
|
||||
const appState = { ...getDefaultAppState(), exportBackground: false };
|
||||
const files = {
|
||||
file_A: {
|
||||
id: "file_A" as FileId,
|
||||
dataURL: await getDataURL(await API.loadFile("./fixtures/deer.png")),
|
||||
mimeType: "image/png",
|
||||
created: Date.now(),
|
||||
},
|
||||
} as const;
|
||||
|
||||
const svg = await exportToSvg(elements, appState, files);
|
||||
|
||||
const svgText = svg.outerHTML;
|
||||
|
||||
// expect 1 <image> element (deduped)
|
||||
expect(svgText.match(/<image/g)?.length).toBe(1);
|
||||
// expect 4 <use> elements (one for each excalidraw image element)
|
||||
expect(svgText.match(/<use/g)?.length).toBe(4);
|
||||
|
||||
// in case of regressions, save the SVG to a file and visually compare to:
|
||||
// src/tests/fixtures/svg-image-exporting-reference.svg
|
||||
expect(svgText).toMatchSnapshot(`svg export output`);
|
||||
});
|
||||
});
|
||||
|
|
BIN
src/tests/fixtures/deer.png
vendored
Normal file
BIN
src/tests/fixtures/deer.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
16
src/tests/fixtures/svg-image-exporting-reference.svg
vendored
Normal file
16
src/tests/fixtures/svg-image-exporting-reference.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
|
@ -4,6 +4,8 @@ import {
|
|||
ExcalidrawTextElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawFreeDrawElement,
|
||||
ExcalidrawImageElement,
|
||||
FileId,
|
||||
} from "../../element/types";
|
||||
import { newElement, newTextElement, newLinearElement } from "../../element";
|
||||
import { DEFAULT_VERTICAL_ALIGN } from "../../constants";
|
||||
|
@ -13,7 +15,7 @@ import fs from "fs";
|
|||
import util from "util";
|
||||
import path from "path";
|
||||
import { getMimeType } from "../../data/blob";
|
||||
import { newFreeDrawElement } from "../../element/newElement";
|
||||
import { newFreeDrawElement, newImageElement } from "../../element/newElement";
|
||||
import { Point } from "../../types";
|
||||
import { getSelectedElements } from "../../scene/selection";
|
||||
|
||||
|
@ -77,6 +79,7 @@ export class API {
|
|||
y?: number;
|
||||
height?: number;
|
||||
width?: number;
|
||||
angle?: number;
|
||||
id?: string;
|
||||
isDeleted?: boolean;
|
||||
groupIds?: string[];
|
||||
|
@ -103,12 +106,17 @@ export class API {
|
|||
: never;
|
||||
points?: T extends "arrow" | "line" ? readonly Point[] : never;
|
||||
locked?: boolean;
|
||||
fileId?: T extends "image" ? string : never;
|
||||
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
|
||||
status?: T extends "image" ? ExcalidrawImageElement["status"] : never;
|
||||
}): T extends "arrow" | "line"
|
||||
? ExcalidrawLinearElement
|
||||
: T extends "freedraw"
|
||||
? ExcalidrawFreeDrawElement
|
||||
: T extends "text"
|
||||
? ExcalidrawTextElement
|
||||
: T extends "image"
|
||||
? ExcalidrawImageElement
|
||||
: ExcalidrawGenericElement => {
|
||||
let element: Mutable<ExcalidrawElement> = null!;
|
||||
|
||||
|
@ -117,6 +125,7 @@ export class API {
|
|||
const base = {
|
||||
x,
|
||||
y,
|
||||
angle: rest.angle ?? 0,
|
||||
strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
|
||||
backgroundColor:
|
||||
rest.backgroundColor ?? appState.currentItemBackgroundColor,
|
||||
|
@ -167,12 +176,23 @@ export class API {
|
|||
...base,
|
||||
width,
|
||||
height,
|
||||
type: type as "arrow" | "line",
|
||||
type,
|
||||
startArrowhead: null,
|
||||
endArrowhead: null,
|
||||
points: rest.points ?? [],
|
||||
});
|
||||
break;
|
||||
case "image":
|
||||
element = newImageElement({
|
||||
...base,
|
||||
width,
|
||||
height,
|
||||
type,
|
||||
fileId: (rest.fileId as string as FileId) ?? null,
|
||||
status: rest.status || "saved",
|
||||
scale: rest.scale || [1, 1],
|
||||
});
|
||||
break;
|
||||
}
|
||||
if (id) {
|
||||
element.id = id;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue