Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions bin/modular-svg
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,25 @@ async function readInput(arg) {
}

try {
const inputArg = process.argv[2];
let margin = 3;
const args = process.argv.slice(2);
const files = [];
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-m" || arg === "--margin") {
const val = args[i + 1];
margin = val ? Number(val) : margin;
i++;
continue;
}
const match = /^--margin=(.*)$/.exec(arg);
if (match) {
margin = Number(match[1]);
continue;
}
files.push(arg);
}
const inputArg = files[0];
const raw = await readInput(inputArg);
const data = JSON.parse(raw);
try {
Expand All @@ -35,8 +53,8 @@ try {
}
const scene = buildSceneFromJson(data);
const layout = solveLayout(scene);
const svg = layoutToSvg(layout, scene.nodes);
const out = process.argv[3];
const svg = layoutToSvg(layout, scene.nodes, margin);
const out = files[1];
if (out && out !== "-") {
writeFileSync(out, svg);
} else {
Expand Down
24 changes: 22 additions & 2 deletions bin/modular-svg.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { spawnSync } from "node:child_process";
import { readFileSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { buildSceneFromJson, layoutToSvg, solveLayout } from "../src/index";

const bin = join(__dirname, "modular-svg");
const example = join(__dirname, "../examples/stack.json");

describe("CLI", () => {
it("runs with stdin", () => {
it("uses default margin", () => {
const json = readFileSync(example, "utf8");
const out = join(__dirname, "tmp.svg");
const { status } = spawnSync(bin, ["-", out], {
Expand All @@ -16,7 +17,26 @@ describe("CLI", () => {
});
expect(status).toBe(0);
const svg = readFileSync(out, "utf8");
expect(svg.startsWith("<svg")).toBe(true);
const scene = buildSceneFromJson(JSON.parse(json));
const layout = solveLayout(scene);
const expected = layoutToSvg(layout, scene.nodes, 3);
expect(svg.trim()).toBe(expected);
unlinkSync(out);
});

it("allows setting margin", () => {
const json = readFileSync(example, "utf8");
const out = join(__dirname, "tmp.svg");
const { status } = spawnSync(bin, ["-", out, "--margin", "10"], {
input: json,
encoding: "utf8",
});
expect(status).toBe(0);
const svg = readFileSync(out, "utf8");
const scene = buildSceneFromJson(JSON.parse(json));
const layout = solveLayout(scene);
const expected = layoutToSvg(layout, scene.nodes, 10);
expect(svg.trim()).toBe(expected);
unlinkSync(out);
});
});
10 changes: 7 additions & 3 deletions src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,17 @@ function arrow(
export function layoutToSvg(
layout: LayoutResult,
nodes?: NodeRecord[],
margin = 0,
): string {
const byId = new Map<string, NodeRecord>();
if (nodes) for (const n of nodes) byId.set(n.id, n);
const bounds = layoutBounds(layout, nodes);
const min = bounds.start;
const max = bounds.end;
const offset = vec(min.x < 0 ? -min.x : 0, min.y < 0 ? -min.y : 0);
const offset = vec(
(min.x < 0 ? -min.x : 0) + margin,
(min.y < 0 ? -min.y : 0) + margin,
);
const body = Object.entries(layout).map(([id, box]) => {
const n = byId.get(id);
if (!n?.type) return "";
Expand All @@ -233,8 +237,8 @@ export function layoutToSvg(
if (n.type === "arrow") return arrow(id, n, layout, offset) ?? "";
return rect(id, box, n, offset);
});
const width = max.x - min.x;
const height = max.y - min.y;
const width = max.x - min.x + margin * 2;
const height = max.y - min.y + margin * 2;
return xml(
"svg",
{ xmlns: "http://www.w3.org/2000/svg", width, height },
Expand Down