diff --git a/README.md b/README.md
index 9d09cb9..7bb3f9a 100644
--- a/README.md
+++ b/README.md
@@ -79,8 +79,8 @@ We can run it to generate an SVG:
deno run drawing.ts render --outfile drawing.svg
```
- (This is generated with
-[examples/cubic-disarray.ts](examples/cubic-disarray.ts])
+ (This is generated with
+[examples/cubic-disarray.ts](examples/cubic-disarray.ts))
Or output to stdout.
@@ -94,6 +94,19 @@ Or override settings
deno run drawing.ts render --outfile drawing.svg --setting.rotationStrength=10
```
+## Examples
+
+The [examples directory](examples/) contains runnable example drawings. Each
+example has a page in [doc/examples/](doc/examples/) with a short explanation
+and the SVGs it generates.
+
+All example SVGs are generated and committed, and can be regenerated with a
+single command:
+
+```bash
+deno task generate:examples
+```
+
## Pick and Choose
The full example above is by no means how it must be set up. This is not a
diff --git a/__tests__/integration/generate_examples_test.ts b/__tests__/integration/generate_examples_test.ts
new file mode 100644
index 0000000..1715281
--- /dev/null
+++ b/__tests__/integration/generate_examples_test.ts
@@ -0,0 +1,39 @@
+import { assert, assertEquals } from "@std/assert";
+
+const expectedSvgs = [
+ "doc/examples/cubic-disarray/svg/01.svg",
+ "doc/examples/cubic-disarray/svg/02.svg",
+ "doc/examples/noodle-love/svg/01.svg",
+];
+
+Deno.test("generate:examples produces deterministic SVGs", async () => {
+ const repoRoot = new URL("../../", import.meta.url).pathname;
+
+ const runTask = async () => {
+ const command = new Deno.Command(Deno.execPath(), {
+ args: ["task", "generate:examples"],
+ cwd: repoRoot,
+ });
+ const { code, stderr } = await command.output();
+ assertEquals(
+ code,
+ 0,
+ `generate:examples exited nonzero. Error: ${
+ new TextDecoder().decode(stderr)
+ }`,
+ );
+ };
+
+ const readSvg = (path: string) => Deno.readTextFile(repoRoot + path);
+
+ await runTask();
+ const first = await Promise.all(expectedSvgs.map(readSvg));
+
+ await runTask();
+ const second = await Promise.all(expectedSvgs.map(readSvg));
+
+ for (const svg of second) {
+ assert(svg.startsWith("
\ No newline at end of file
diff --git a/doc/examples/cubic-disarray/svg/02.svg b/doc/examples/cubic-disarray/svg/02.svg
new file mode 100644
index 0000000..c7b73a7
--- /dev/null
+++ b/doc/examples/cubic-disarray/svg/02.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/doc/examples/noodle-love/README.md b/doc/examples/noodle-love/README.md
new file mode 100644
index 0000000..cd9a4ef
--- /dev/null
+++ b/doc/examples/noodle-love/README.md
@@ -0,0 +1,10 @@
+# Noodle Love
+
+A grid of round and cross tiles, each placed in a cell and rotated by a random
+multiple of 90 degrees, with a heart in the center. It showcases grids with
+square cells, the seedable `Random` generator, and reusing external SVG assets
+with `readSvg` and defs.
+
+
+
+Source: [examples/noodle_love.ts](../../../examples/noodle_love.ts)
diff --git a/doc/examples/noodle-love/svg/01.svg b/doc/examples/noodle-love/svg/01.svg
new file mode 100644
index 0000000..9047a32
--- /dev/null
+++ b/doc/examples/noodle-love/svg/01.svg
@@ -0,0 +1,39 @@
+
\ No newline at end of file
diff --git a/doc/cubic-disarray.svg b/doc/readme-example.svg
similarity index 100%
rename from doc/cubic-disarray.svg
rename to doc/readme-example.svg
diff --git a/examples/noodle_love.ts b/examples/noodle_love.ts
index 8e3be43..3376a8d 100644
--- a/examples/noodle_love.ts
+++ b/examples/noodle_love.ts
@@ -1,63 +1,89 @@
-import { Drawing, Grid, Random, readSvg } from "@berkes/vormen";
-
-const drawing = new Drawing()
- .withA4Size()
- .withMargin(20)
- .withBackgroundColor("#fff");
-
-const canvas = drawing.build();
-
-const COLUMNS = 20;
-const ROWS = 25;
-const grid = new Grid()
- .withSize(drawing.getInnerWidth(), drawing.getInnerHeight())
- .withCols(COLUMNS)
- .withRows(ROWS)
- .withSquareCells();
-
-const rand = new Random();
-
-const tileNames = ["tile_round", "tile_cross"];
-const tiles = tileNames.map((tileName) => {
- const tile = readSvg(`examples/assets/${tileName}.svg`);
- tile.id(tileName);
- return tile;
+/**
+ * Noodle Love.
+ *
+ * A grid of round and cross tiles, rotated randomly, with a heart in the
+ * center.
+ *
+ * Run it and redirect the output to a file:
+ * deno run examples/noodle_love.ts > noodle-love.svg
+ */
+import {
+ Drawing,
+ Grid,
+ Random,
+ readSvg,
+ Settings,
+ Vormen,
+} from "@berkes/vormen";
+
+const settings = new Settings({
+ seed: "noodle-love",
});
-const centerTile = readSvg("examples/assets/heart.svg");
-centerTile.id("center-tile");
-canvas.defs().add(centerTile);
+function draw(settings: Settings): Drawing {
+ const drawing = new Drawing()
+ .withA4Size()
+ .withMargin(20)
+ .withBackgroundColor("#fff");
-tiles.forEach((tile) => {
- canvas.defs().add(tile);
-});
+ const canvas = drawing.build();
-grid.cells().forEach((cell) => {
- const tileName = tileNames[rand.between(0, tileNames.length - 1)];
- const tile = canvas.use(tileName);
- tile.x(cell.x).y(cell.y).width(cell.width).height(cell.height);
- tile.rotate(rand.between(0, 3) * 90, cell.centerX, cell.centerY);
-});
+ const COLUMNS = 20;
+ const ROWS = 25;
+
+ const grid = new Grid()
+ .withSize(drawing.getInnerWidth(), drawing.getInnerHeight())
+ .withCols(COLUMNS)
+ .withRows(ROWS)
+ .withSquareCells();
+
+ const rand = new Random(settings.getString("seed"));
+
+ const tileNames = ["tile_round", "tile_cross"];
+ const tiles = tileNames.map((tileName) => {
+ const tile = readSvg(`examples/assets/${tileName}.svg`);
+ tile.id(tileName);
+ return tile;
+ });
+
+ const centerTile = readSvg("examples/assets/heart.svg");
+ centerTile.id("center-tile");
+ canvas.defs().add(centerTile);
+
+ tiles.forEach((tile) => {
+ canvas.defs().add(tile);
+ });
+
+ grid.cells().forEach((cell) => {
+ const tileName = tileNames[rand.between(0, tileNames.length - 1)];
+ const tile = canvas.use(tileName);
+ tile.x(cell.x).y(cell.y).width(cell.width).height(cell.height);
+ tile.rotate(rand.between(0, 3) * 90, cell.centerX, cell.centerY);
+ });
+
+ // Our center tile is 3 x 2 cells we add our center over the grid at this position
+ const centerCell = grid.pickCell(
+ Math.floor(COLUMNS / 2) - 2,
+ Math.floor(ROWS / 2) - 1,
+ )!;
+ const centerTileX = centerCell.x;
+ const centerTileY = centerCell.y;
+ const centerTileWidth = centerCell.width * 3;
+ const centerTileHeight = centerCell.height * 2;
+
+ canvas.rect(centerTileWidth, centerTileHeight).x(centerTileX).y(centerTileY)
+ .fill("#ffffff");
-// Our center tile is 3 x 2 cells we add our center over the grid at this position
-const centerCell = grid.pickCell(
- Math.floor(COLUMNS / 2) - 2,
- Math.floor(ROWS / 2) - 1,
-)!;
-const centerTileX = centerCell.x;
-const centerTileY = centerCell.y;
-const centerTileWidth = centerCell.width * 3;
-const centerTileHeight = centerCell.height * 2;
-
-canvas.rect(centerTileWidth, centerTileHeight).x(centerTileX).y(centerTileY)
- .fill("#ffffff");
-canvas.use("center-tile").x(centerTileX).y(centerTileY).width(centerTileWidth)
- .height(
+ canvas.use("center-tile").x(centerTileX).y(centerTileY).width(
+ centerTileWidth,
+ ).height(
centerTileHeight,
);
-const text = canvas.text(`Noodle ${rand.seed}`);
+ const text = canvas.text(`Noodle ${rand.seed}`);
+ text.x(0).y(drawing.getInnerHeight());
-text.x(0).y(drawing.getInnerHeight());
+ return drawing;
+}
-console.log(drawing.svg());
+Vormen(draw, settings);
diff --git a/examples/update-example-svgs.sh b/examples/update-example-svgs.sh
new file mode 100755
index 0000000..9b66be6
--- /dev/null
+++ b/examples/update-example-svgs.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+deno run --allow-read --allow-write=doc/examples examples/cubic-disarray.ts render -o doc/examples/cubic-disarray/svg/01.svg
+deno run --allow-read --allow-write=doc/examples examples/cubic-disarray.ts render -o doc/examples/cubic-disarray/svg/02.svg --setting.rotationStrength=5
+deno run --allow-read --allow-write=doc/examples examples/noodle_love.ts render -o doc/examples/noodle-love/svg/01.svg