feat: add first-class text layers
This commit is contained in:
@@ -56,6 +56,14 @@ describe("project format", () => {
|
||||
expect(() => parseProject(serializeProject(document))).toThrow("artboard level");
|
||||
});
|
||||
|
||||
test("round-trips valid text and rejects unsupported typography", () => {
|
||||
const document = projectDocument();
|
||||
document.artboards[0]!.layers.unshift({ id: "text", type: "text", name: "Text", visible: true, locked: false, opacity: .8, transform: { position: { x: 1, y: 2 }, scale: { x: 1, y: 1 }, rotation: .2 }, content: "Hello", style: { fontFamily: "Arial", fontSize: 20, fontWeight: 700, fontStyle: "italic", color: "#123456", alignment: "right", lineHeight: 1.2 } });
|
||||
expect(parseProject(serializeProject(document)).document.artboards[0]?.layers[0]?.type).toBe("text");
|
||||
const invalid = JSON.parse(serializeProject(document)); invalid.document.artboards[0].layers[0].style.fontFamily = "Web Font";
|
||||
expect(() => parseProject(JSON.stringify(invalid))).toThrow("invalid typography");
|
||||
});
|
||||
|
||||
test("creates safe project file names", () => {
|
||||
expect(projectFileName(" Summer / Study ")).toBe("Summer-Study.image-studio.json");
|
||||
expect(projectFileName("***")).toBe("untitled.image-studio.json");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { Layer } from "@core/layer";
|
||||
import type { Rect } from "@core/geometry";
|
||||
import { isValidTextStyle, type TextStyle } from "@core/text-layer";
|
||||
|
||||
export const PROJECT_FORMAT = "image-studio-project";
|
||||
export const CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -115,11 +116,14 @@ function assertLayers(value: unknown[], nested: boolean): asserts value is Layer
|
||||
if (nested) throw new Error("Adjustment layers must stay at artboard level.");
|
||||
if (!isAdjustment(layer.adjustment)) throw new Error("A project adjustment layer contains invalid settings.");
|
||||
if (layer.layerMask !== undefined || layer.clippingMask !== undefined) throw new Error("Adjustment layers do not support masks.");
|
||||
} else if (layer.type === "text") {
|
||||
if (typeof layer.content !== "string" || !layer.content.trim() || !isRecord(layer.style) || !isValidTextStyle(layer.style as TextStyle)) throw new Error("A project text layer contains invalid typography.");
|
||||
if (layer.layerMask !== undefined || layer.clippingMask !== undefined) throw new Error("Text layers do not support masks. Rasterize text before masking.");
|
||||
} else if ((layer.type === "image" || layer.type === "raster") && typeof layer.assetId !== "string") {
|
||||
throw new Error("A project layer is missing its asset reference.");
|
||||
} else if ((layer.type === "image" || layer.type === "raster") && layer.sourceRect !== undefined && (!isRect(layer.sourceRect) || layer.sourceRect.w < 1 || layer.sourceRect.h < 1)) {
|
||||
throw new Error("A project layer contains an invalid source crop.");
|
||||
} else if (layer.type !== "image" && layer.type !== "raster") {
|
||||
} else if (layer.type !== "image" && layer.type !== "raster" && layer.type !== "text") {
|
||||
throw new Error(`Unsupported layer type: ${layer.type}.`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user