Commits: 6

Setup DevEnv with typst and tinymist (Typst LSP)

new file mode 100644
index 0000000..c5d670d
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+if ! has nix_direnv_version || ! nix_direnv_version 3.1.0; then
+  source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.0/direnvrc" "sha256-yMJ2OVMzrFaDPn7q8nCBZFRYpL/f0RcHzhmw/i6btJM="
+fi
+
+export DEVENV_IN_DIRENV_SHELL=true
+
+watch_file flake.nix
+watch_file flake.lock
+if ! use flake . --no-pure-eval; then
+  echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
+fi
new file mode 100644
index 0000000..f03ce8a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.devenv
+.direnv
new file mode 100644
index 0000000..fdbdf50
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,849 @@
+{
+  "nodes": {
+    "cachix": {
+      "inputs": {
+        "devenv": [
+          "devenv"
+        ],
+        "flake-compat": [
+          "devenv",
+          "flake-compat"
+        ],
+        "git-hooks": [
+          "devenv",
+          "git-hooks"
+        ],
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1767714506,
+        "narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
+        "owner": "cachix",
+        "repo": "cachix",
+        "rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "latest",
+        "repo": "cachix",
+        "type": "github"
+      }
+    },
+    "cachix_2": {
+      "inputs": {
+        "devenv": [
+          "devenv",
+          "crate2nix"
+        ],
+        "flake-compat": [
+          "devenv",
+          "crate2nix"
+        ],
+        "git-hooks": "git-hooks",
+        "nixpkgs": "nixpkgs"
+      },
+      "locked": {
+        "lastModified": 1767714506,
+        "narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
+        "owner": "cachix",
+        "repo": "cachix",
+        "rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "latest",
+        "repo": "cachix",
+        "type": "github"
+      }
+    },
+    "cachix_3": {
+      "inputs": {
+        "devenv": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable"
+        ],
+        "flake-compat": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable"
+        ],
+        "git-hooks": "git-hooks_2",
+        "nixpkgs": "nixpkgs_2"
+      },
+      "locked": {
+        "lastModified": 1767714506,
+        "narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
+        "owner": "cachix",
+        "repo": "cachix",
+        "rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "latest",
+        "repo": "cachix",
+        "type": "github"
+      }
+    },
+    "crate2nix": {
+      "inputs": {
+        "cachix": "cachix_2",
+        "crate2nix_stable": "crate2nix_stable",
+        "devshell": "devshell_2",
+        "flake-compat": "flake-compat_2",
+        "flake-parts": "flake-parts_2",
+        "nix-test-runner": "nix-test-runner_2",
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ],
+        "pre-commit-hooks": "pre-commit-hooks_2"
+      },
+      "locked": {
+        "lastModified": 1773440526,
+        "narHash": "sha256-OcX1MYqUdoalY3/vU67PEx8m6RvqGxX0LwKonjzXn7I=",
+        "owner": "nix-community",
+        "repo": "crate2nix",
+        "rev": "e697d3049c909580128caa856ab8eb709556a97b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "crate2nix",
+        "type": "github"
+      }
+    },
+    "crate2nix_stable": {
+      "inputs": {
+        "cachix": "cachix_3",
+        "crate2nix_stable": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable"
+        ],
+        "devshell": "devshell",
+        "flake-compat": "flake-compat",
+        "flake-parts": "flake-parts",
+        "nix-test-runner": "nix-test-runner",
+        "nixpkgs": "nixpkgs_3",
+        "pre-commit-hooks": "pre-commit-hooks"
+      },
+      "locked": {
+        "lastModified": 1769627083,
+        "narHash": "sha256-SUuruvw1/moNzCZosHaa60QMTL+L9huWdsCBN6XZIic=",
+        "owner": "nix-community",
+        "repo": "crate2nix",
+        "rev": "7c33e664668faecf7655fa53861d7a80c9e464a2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "ref": "0.15.0",
+        "repo": "crate2nix",
+        "type": "github"
+      }
+    },
+    "devenv": {
+      "inputs": {
+        "cachix": "cachix",
+        "crate2nix": "crate2nix",
+        "flake-compat": "flake-compat_3",
+        "flake-parts": "flake-parts_3",
+        "git-hooks": "git-hooks_3",
+        "nix": "nix",
+        "nixd": "nixd",
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "rust-overlay": "rust-overlay"
+      },
+      "locked": {
+        "lastModified": 1774052327,
+        "narHash": "sha256-gQhiHj8q5NAa8jGTmoaS8FRgo8bVoAL2difjmcLtdgo=",
+        "owner": "cachix",
+        "repo": "devenv",
+        "rev": "43c650cae3ca65b6095819e4613614c242588cd7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "devenv",
+        "type": "github"
+      }
+    },
+    "devshell": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1768818222,
+        "narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
+        "owner": "numtide",
+        "repo": "devshell",
+        "rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "devshell",
+        "type": "github"
+      }
+    },
+    "devshell_2": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1768818222,
+        "narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
+        "owner": "numtide",
+        "repo": "devshell",
+        "rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "devshell",
+        "type": "github"
+      }
+    },
+    "flake-compat": {
+      "locked": {
+        "lastModified": 1733328505,
+        "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
+        "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
+        "revCount": 69,
+        "type": "tarball",
+        "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
+      },
+      "original": {
+        "type": "tarball",
+        "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
+      }
+    },
+    "flake-compat_2": {
+      "locked": {
+        "lastModified": 1733328505,
+        "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
+        "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
+        "revCount": 69,
+        "type": "tarball",
+        "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
+      },
+      "original": {
+        "type": "tarball",
+        "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
+      }
+    },
+    "flake-compat_3": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1767039857,
+        "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1768135262,
+        "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "flake-parts_2": {
+      "inputs": {
+        "nixpkgs-lib": [
+          "devenv",
+          "crate2nix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1768135262,
+        "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "flake-parts_3": {
+      "inputs": {
+        "nixpkgs-lib": [
+          "devenv",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1772408722,
+        "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "git-hooks": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "crate2nix",
+          "cachix",
+          "flake-compat"
+        ],
+        "gitignore": "gitignore",
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "cachix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1765404074,
+        "narHash": "sha256-+ZDU2d+vzWkEJiqprvV5PR26DVFN2vgddwG5SnPZcUM=",
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "type": "github"
+      }
+    },
+    "git-hooks_2": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "cachix",
+          "flake-compat"
+        ],
+        "gitignore": "gitignore_2",
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "cachix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1765404074,
+        "narHash": "sha256-+ZDU2d+vzWkEJiqprvV5PR26DVFN2vgddwG5SnPZcUM=",
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "type": "github"
+      }
+    },
+    "git-hooks_3": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "flake-compat"
+        ],
+        "gitignore": "gitignore_5",
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1772893680,
+        "narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "type": "github"
+      }
+    },
+    "gitignore": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "cachix",
+          "git-hooks",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "gitignore_2": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "cachix",
+          "git-hooks",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "gitignore_3": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "pre-commit-hooks",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "gitignore_4": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "pre-commit-hooks",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "gitignore_5": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "git-hooks",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "nix": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "flake-compat"
+        ],
+        "flake-parts": [
+          "devenv",
+          "flake-parts"
+        ],
+        "git-hooks-nix": [
+          "devenv",
+          "git-hooks"
+        ],
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ],
+        "nixpkgs-23-11": [
+          "devenv"
+        ],
+        "nixpkgs-regression": [
+          "devenv"
+        ]
+      },
+      "locked": {
+        "lastModified": 1773936165,
+        "narHash": "sha256-iL6V03FP1vLJ/YJr0KHcNP+0lyyM9pT4rnRSk57DSYc=",
+        "owner": "cachix",
+        "repo": "nix",
+        "rev": "185e962dbc1b4925f5da3d05725a11e2ecea4a14",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "devenv-2.32",
+        "repo": "nix",
+        "type": "github"
+      }
+    },
+    "nix-test-runner": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1588761593,
+        "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
+        "owner": "stoeffel",
+        "repo": "nix-test-runner",
+        "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "stoeffel",
+        "repo": "nix-test-runner",
+        "type": "github"
+      }
+    },
+    "nix-test-runner_2": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1588761593,
+        "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
+        "owner": "stoeffel",
+        "repo": "nix-test-runner",
+        "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "stoeffel",
+        "repo": "nix-test-runner",
+        "type": "github"
+      }
+    },
+    "nixd": {
+      "inputs": {
+        "flake-parts": [
+          "devenv",
+          "flake-parts"
+        ],
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ],
+        "treefmt-nix": "treefmt-nix"
+      },
+      "locked": {
+        "lastModified": 1773634079,
+        "narHash": "sha256-49qb4QNMv77VOeEux+sMd0uBhPvvHgVc0r938Bulvbo=",
+        "owner": "nix-community",
+        "repo": "nixd",
+        "rev": "8ecf93d4d93745e05ea53534e8b94f5e9506e6bd",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "nixd",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1765186076,
+        "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-src": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1773597492,
+        "narHash": "sha256-hQ284SkIeNaeyud+LS0WVLX+WL2rxcVZLFEaK0e03zg=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "a07d4ce6bee67d7c838a8a5796e75dff9caa21ef",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1765186076,
+        "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs_3": {
+      "locked": {
+        "lastModified": 1769433173,
+        "narHash": "sha256-Gf1dFYgD344WZ3q0LPlRoWaNdNQq8kSBDLEWulRQSEs=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "13b0f9e6ac78abbbb736c635d87845c4f4bee51b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs_4": {
+      "inputs": {
+        "nixpkgs-src": "nixpkgs-src"
+      },
+      "locked": {
+        "lastModified": 1773704619,
+        "narHash": "sha256-LKtmit8Sr81z8+N2vpIaN/fyiQJ8f7XJ6tMSKyDVQ9s=",
+        "owner": "cachix",
+        "repo": "devenv-nixpkgs",
+        "rev": "906534d75b0e2fe74a719559dfb1ad3563485f43",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "rolling",
+        "repo": "devenv-nixpkgs",
+        "type": "github"
+      }
+    },
+    "pre-commit-hooks": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "flake-compat"
+        ],
+        "gitignore": "gitignore_3",
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "crate2nix_stable",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1769069492,
+        "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "type": "github"
+      }
+    },
+    "pre-commit-hooks_2": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "crate2nix",
+          "flake-compat"
+        ],
+        "gitignore": "gitignore_4",
+        "nixpkgs": [
+          "devenv",
+          "crate2nix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1769069492,
+        "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "pre-commit-hooks.nix",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "devenv": "devenv",
+        "nixpkgs": "nixpkgs_4",
+        "systems": "systems"
+      }
+    },
+    "rust-overlay": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1773630837,
+        "narHash": "sha256-zJhgAGnbVKeBMJOb9ctZm4BGS/Rnrz+5lfSXTVah4HQ=",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "f600ea449c7b5bb596fa1cf21c871cc5b9e31316",
+        "type": "github"
+      },
+      "original": {
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "type": "github"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "treefmt-nix": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "nixd",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1772660329,
+        "narHash": "sha256-IjU1FxYqm+VDe5qIOxoW+pISBlGvVApRjiw/Y/ttJzY=",
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "rev": "3710e0e1218041bbad640352a0440114b1e10428",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
new file mode 100644
index 0000000..74cf48c
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,39 @@
+{
+  inputs = {
+    nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
+    systems.url = "github:nix-systems/default";
+    devenv.url = "github:cachix/devenv";
+    devenv.inputs.nixpkgs.follows = "nixpkgs";
+  };
+
+  nixConfig = {
+    extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
+    extra-substituters = "https://devenv.cachix.org";
+  };
+
+  outputs = { self, nixpkgs, devenv, systems, ... } @ inputs:
+    let
+      forEachSystem = nixpkgs.lib.genAttrs (import systems);
+    in
+    {
+      devShells = forEachSystem
+        (system:
+          let
+            pkgs = nixpkgs.legacyPackages.${system};
+          in
+          {
+            default = devenv.lib.mkShell {
+              inherit inputs pkgs;
+              modules = [
+                {
+                  # https://devenv.sh/reference/options/
+                  packages = with pkgs; [
+                    typst
+                    tinymist
+                  ];
+                }
+              ];
+            };
+          });
+    };
+}
new file mode 100644
index 0000000..d4c65e2
--- /dev/null
+++ b/playground.typ
@@ -0,0 +1,11 @@
+= Layout Playground
+
++ First thing
++ Second thing
+
+Here?
+
+There?
+
+#circle(fill: red, width: 100% )[#align(center)[Hello there]]
+

Describe the problem

index d4c65e2..b166f27 100644
--- a/playground.typ
+++ b/playground.typ
@@ -1,11 +1,17 @@
== Layout Playground
=
-+ First thing
-+ Second thing
+#let images = ((60, 120), (120, 200), (100, 40), (300, 250))
=
-Here?
+We have #images.len() images.
=
-There?
+#for ((width, height)) in images {
+    rect(width: width * 1pt, height: height * 1pt, align(center + horizon)[#width × #height])
+}
+
+The goal is to put them in a row, so that:
+
++ The whole available width of the page is used.
++ All images have the same height.
++ The aspect ratio of every image is preserved.
=
-#circle(fill: red, width: 100% )[#align(center)[Hello there]]
=

Implement the solution and describe the math

I haven't figured out yet how to get the actual dimensions of the parent container. There are layout and measure functions, but they confuse me.

index b166f27..340560d 100644
--- a/playground.typ
+++ b/playground.typ
@@ -1,11 +1,25 @@
== Layout Playground
=
-#let images = ((60, 120), (120, 200), (100, 40), (300, 250))
+#let images = (
+    (60, 120, red),
+    (120, 200, green),
+    (100, 30, blue),
+    (300, 250, yellow)
+)
=
=We have #images.len() images.
=
-#for ((width, height)) in images {
-    rect(width: width * 1pt, height: height * 1pt, align(center + horizon)[#width × #height])
+#let rects = images.map( image => {
+    let (width, height, color) = image
+    rect(
+        width: width * 1pt,
+        height: height * 1pt,
+        fill: color,
+        align(center + horizon)[#width × #height])
+})
+
+#for r in rects {
+    r
=}
=
=The goal is to put them in a row, so that:
@@ -15,3 +29,127 @@ The goal is to put them in a row, so that:
=+ The aspect ratio of every image is preserved.
=
=
+== Step 1
+
+Calculate individual aspect ratios as $w / h$.
+
+#let aspects = images.map(size => (
+    width: size.at(0) * 1pt,
+    height: size.at(1) * 1pt,
+    ratio: size.at(0) / size.at(1))
+)
+
+#table(columns: 2,
+    [*Size*], [*Aspect ratio*],
+    ..for (width, height, ratio) in aspects {
+        ([#width × #height], [#calc.round(digits: 3, ratio)])
+    }
+)
+
+== Step 2
+
+Sum the aspect ratios of all the images to get the total aspect ratio of the row of images.
+
+#let total_aspect_ratio = aspects.map(a => a.ratio).sum()
+
+#rect(
+    height: 100% / total_aspect_ratio,
+    width: 100%,
+    align(center + horizon)[1 × #calc.round(digits: 3, total_aspect_ratio)]
+)
+
+== Step 3
+
+Take available width of the parent container (e.g. page - margins).
+
+// #let available_width = layout(size => size.width)
+// FIXME: This gives content, I want a Length. For now let's just assume it:
+#let available_width = 300pt
+
+#let straightedge = line(length: available_width)
+
+Available width: #available_width
+#straightedge
+
+For each image:
+
++ calculate the `fraction` of the width it can take, as `image_aspect_ratio / total_aspect_ratio`.
++ calculate the `final_width` as `available_width * fraction`
++ calculate the `scaling_factor` as `final_width / image_width`
++ scale the image by the `scaling_factor`
+
+
+
+#box()[
+    #grid(
+        columns: images.len(),
+    
+        ..rects.enumerate().map(((index, this_rect)) => {
+            let aspect = aspects.at(index)
+            let image_aspect_ratio = aspect.ratio
+            let width_fraction = image_aspect_ratio / total_aspect_ratio
+            let final_width = width_fraction * available_width
+            let scaling_factor = final_width / aspect.width * 100%
+
+            scale(this_rect, scaling_factor, reflow: true)
+        })
+    )
+]
+#straightedge
+
+Adding gaps (gutter) between images is not difficult. Just subtract the width of the gutter (its length × number of images - 1) from the available space.
+
+#let gutter = 2pt
+
+
+#let total_gutter = gutter * (rects.len() - 1)
+#box()[
+    With gutter of #gutter
+
+    #straightedge
+    #grid(
+        columns: images.len(),
+        column-gutter: gutter,
+
+        ..rects.enumerate().map(((index, this_rect)) => {
+            let aspect = aspects.at(index)
+            let image_aspect_ratio = aspect.ratio
+            let width_fraction = image_aspect_ratio / total_aspect_ratio
+            let final_width = width_fraction * (available_width - total_gutter)
+            let scaling_factor = final_width / aspect.width * 100%
+
+            scale(this_rect, scaling_factor, reflow: true)
+        })
+    )
+]
+
+#let gutter = 16pt
+
+
+#let total_gutter = gutter * (rects.len() - 1)
+#box()[
+    With gutter of #gutter
+
+    #straightedge
+    #grid(
+        columns: images.len(),
+        column-gutter: gutter,
+
+        ..rects.enumerate().map(((index, this_rect)) => {
+            let aspect = aspects.at(index)
+            let image_aspect_ratio = aspect.ratio
+            let width_fraction = image_aspect_ratio / total_aspect_ratio
+            let final_width = width_fraction * (available_width - total_gutter)
+            let scaling_factor = final_width / aspect.width * 100%
+
+            scale(this_rect, scaling_factor, reflow: true)
+        })
+    )
+]
+
+
+*TODO*: Pack it as a function
+
+*TODO*: Use actual images (figure out how to get their dimensions or aspect ratios)
+
+*TODO*: How to get the actual width of a parent container

Improve the document layout

Put the "images" in a grid, break page on h2

index 340560d..fbfd23b 100644
--- a/playground.typ
+++ b/playground.typ
@@ -18,9 +18,11 @@ We have #images.len() images.
=        align(center + horizon)[#width × #height])
=})
=
-#for r in rects {
-    r
-}
+#grid(
+    columns: (auto, auto),
+    gutter: 10pt,
+    ..rects
+)
=
=The goal is to put them in a row, so that:
=
@@ -28,6 +30,10 @@ The goal is to put them in a row, so that:
=+ All images have the same height.
=+ The aspect ratio of every image is preserved.
=
+#show heading.where(depth: 2): body => {
+    pagebreak(weak: true)
+    body
+}
=
=== Step 1
=

Add some images from placedog.net (cute)

Get the dimensions of sample 2 images.

new file mode 100644
index 0000000..e637019
Binary files /dev/null and b/300x600.jpg differ
new file mode 100644
index 0000000..64dd9f1
Binary files /dev/null and b/360x480.jpg differ
new file mode 100644
index 0000000..1dfa8ab
Binary files /dev/null and b/500x480.jpg differ
new file mode 100644
index 0000000..d0c7387
Binary files /dev/null and b/780x300.jpg differ
index fbfd23b..cb902a2 100644
--- a/playground.typ
+++ b/playground.typ
@@ -154,8 +154,26 @@ Adding gaps (gutter) between images is not difficult. Just subtract the width of
=]
=
=
-*TODO*: Pack it as a function
+== Use actual images
+
+#let i = image("300x600.jpg")
+
+#box[
+    The following image size is #context measure(i)
+
+    #i
+]
+
+#let i = image("500x480.jpg")
+#box[
+    The following image size is #context measure(i)
+
+    #i
+]
+
+== *TODO*: Pack it as a function
+
+
+== *TODO*: How to get the actual width of a parent container
=
-*TODO*: Use actual images (figure out how to get their dimensions or aspect ratios)
=
-*TODO*: How to get the actual width of a parent container

Implement the solution

Works with actual images and fills the parent container. Wrapped in a image-row function.

index cb902a2..ef39395 100644
--- a/playground.typ
+++ b/playground.typ
@@ -171,9 +171,121 @@ Adding gaps (gutter) between images is not difficult. Just subtract the width of
=    #i
=]
=
-== *TODO*: Pack it as a function
+== How to get the actual width of a parent container
=
+#context layout(size => [
+    The parent layout can only be accessed inside a `context`. Here the width is  #size.width. It is best to wrap the whole computation in ```typst context layout(size => { ... })``` expression.
+])
=
-== *TODO*: How to get the actual width of a parent container
+== Pack it as a function
=
+#let image-row(gutter: 0pt, ..paths) = {
+    context {
+        let images = paths.pos().map(path => {
+            let content = image(path)
+            let size = measure(content)
+            let ratio = size.width / size.height
+            return (path: path, content: content, ratio: ratio, ..size)
+        })
+
+
+        let total_ratio = images.fold(0, (acc, item) => acc + item.ratio)
+        let total_gutter = gutter * (images.len() - 1)
+
+        layout(parent => grid(
+            columns: images.len(),
+            column-gutter: gutter,
+
+            ..images.map((item) => {
+                let width_fraction = item.ratio / total_ratio
+                let final_width = width_fraction * (parent.width - total_gutter)
+                let scaling_factor = final_width / item.width * 100%
+
+                scale(item.content, scaling_factor, reflow: true)
+            })
+        ))
+    }
+}
+
+Now we have everything we need! Here are some examples.
+
+=== 4 images, no gutter
+
+#image-row(
+    "300x600.jpg",
+    "500x480.jpg",
+    "360x480.jpg",
+    "780x300.jpg",
+)
+
+=== 2 image, gutter of 2t
+
+#image-row(
+    gutter: 2pt,
+    "300x600.jpg",
+    "780x300.jpg",
+)
+
+=== 1 image, gutter set, but has no effect
+
+#image-row(
+    gutter: 2pt,
+    "780x300.jpg",
+)
+
+=== Inside a figure
+
+
+#figure(
+    caption: [Some lovely dogs here])[
+        #image-row(
+            gutter: 4pt,
+            "360x480.jpg",
+            "780x300.jpg",
+            "300x600.jpg",
+            "500x480.jpg",
+        )
+    ]
+
+#pagebreak()
+
+=== Inside containers of various sizes
+
+#for width in (40%, 60%, 85%) {
+    align(center, rect(
+        width: width       
+        
+    )[
+        #layout(size => [This container is #size.width wide])
+        #image-row(
+            gutter: 4pt,
+            "360x480.jpg",
+            "780x300.jpg",
+            "300x600.jpg",
+            "500x480.jpg",
+        )
+    ])
+}
+
+
+#pagebreak()
+
+=== Multiple rows
+
+#image-row(
+    gutter: 4pt,
+    "300x600.jpg",
+    "780x300.jpg",
+)
+#v(4pt, weak: true)
+#image-row(
+    gutter: 4pt,
+    "360x480.jpg",
+    "500x480.jpg",
+)
+
+== TODO: Implement `image-rows` function
=
++ Multiple rows, each in an array
++ Automatic vertical spacing
++ In a box, to prevent page breaks inside