diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5a14495 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: Automatic tests +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + pull_request: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: imagemagick cargo + version: 1.0 + - name: Install just from crates.io + uses: baptiste0928/cargo-install@v2 + with: + crate: just + - uses: yusancky/setup-typst@v2 + id: setup-typst + with: + version: 'v0.6.0' + - run: | + just test diff --git a/.github/workflows/update-test.yml b/.github/workflows/update-test.yml new file mode 100644 index 0000000..5a84229 --- /dev/null +++ b/.github/workflows/update-test.yml @@ -0,0 +1,29 @@ +name: Update Tests images +on: + workflow_dispatch: + +jobs: + update-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: imagemagick cargo + version: 1.0 + - name: Install just from crates.io + uses: baptiste0928/cargo-install@v2 + with: + crate: just + - uses: yusancky/setup-typst@v2 + id: setup-typst + with: + version: 'v0.6.0' + - run: | + just update-test + - name: Archive production artifacts + uses: actions/upload-artifact@v3 + with: + name: tests + path: | + tests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..195af76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +res*.png +diff*.png \ No newline at end of file diff --git a/README.md b/README.md index 98aa4d2..33788d7 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,17 @@ Here is a template: ``` Also see `examples/` for some examples. +## Testing + +This package comes with some unit tests under the `tests` directory. +To run all tests you can run the `just test` target. + +You need to have ImageMagick installed on your system, which is needed for image comparison. + +### Windows +If you are using the [Chocolatey](https://chocolatey.org/) package manager, you can install imagemagick using `choco install imagemagick`. +Otherwise download and install a matching package from the [ImageMagick](https://imagemagick.org/script/download.php) website. + ## Requirements for First Stable Release - [ ] Reference manual (doesn't have to inclued tutorials, just a list of currently supported components) - [ ] Tests diff --git a/circuitypst.typ b/circuitypst.typ index 80c175d..38de2e6 100644 --- a/circuitypst.typ +++ b/circuitypst.typ @@ -1,9 +1,8 @@ #import "components.typ" #import "utils.typ" -#import "../typst-canvas/draw.typ": * -#import "../typst-canvas/coordinate.typ" -#import "../typst-canvas/vector.typ" +#import "@preview/cetz:0.0.1" +#import cetz.draw: fill, stroke, group, anchor, rotate, set-origin, line, content, move-to #let canvas-fill = fill #let canvas-stroke = stroke @@ -17,7 +16,7 @@ #let node(component, position, label: none, name: none, anchor: none, fill: auto, stroke: auto, ..options) = { assert(component in components.node, message: "Component '" + component + "' is unknown") group(name: name, anchor: anchor, { - import "../typst-canvas/draw.typ": anchor + import cetz.draw: anchor if fill != auto { canvas-fill(fill) diff --git a/components.typ b/components.typ index 9f85ed8..dfeb464 100644 --- a/components.typ +++ b/components.typ @@ -1,4 +1,6 @@ -#import "../typst-canvas/draw.typ": * +#import "@preview/cetz:0.0.1" + +#import cetz.draw: line, circle, fill, rect #import "parts.typ" #import "utils.typ": anchors diff --git a/examples/current-shunt.pdf b/examples/current-shunt.pdf index 6476958..1aca3d4 100644 Binary files a/examples/current-shunt.pdf and b/examples/current-shunt.pdf differ diff --git a/examples/current-shunt.typ b/examples/current-shunt.typ index 83bd18c..516f04b 100644 --- a/examples/current-shunt.typ +++ b/examples/current-shunt.typ @@ -1,11 +1,11 @@ -#import "../../typst-canvas/canvas.typ": canvas +#import "@preview/cetz:0.0.1" #lorem(30) #figure( - canvas(length: 1cm, debug: false, { - import "../../typst-canvas/draw.typ": * - import "../circuitypst.typ": node, to + cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line + import "@local/circuitypst:0.0.1": node, to to("isourceAM", (0,0), (0,3), label: $I_0$, v: h(0.5em) + $V_0$) to("short", (), (2,3), i: $I_0$,) diff --git a/examples/exclusive-or.pdf b/examples/exclusive-or.pdf index 8b2bf80..e8ab54d 100644 Binary files a/examples/exclusive-or.pdf and b/examples/exclusive-or.pdf differ diff --git a/examples/exclusive-or.typ b/examples/exclusive-or.typ index b8064c9..51b5dc8 100644 --- a/examples/exclusive-or.typ +++ b/examples/exclusive-or.typ @@ -1,11 +1,11 @@ -#import "../../typst-canvas/canvas.typ": canvas +#import "@preview/cetz:0.0.1" #lorem(30) #figure( - canvas({ - import "../../typst-canvas/draw.typ": * - import "../circuitypst.typ": node + cetz.canvas({ + import cetz.draw: line, content + import "@local/circuitypst:0.0.1": node node("nand gate", (0,1), name: "g1") content("g1.out", [#h(0.5em) Q], anchor: "left") diff --git a/examples/half-adder.pdf b/examples/half-adder.pdf index ffa1c3d..88ad580 100644 Binary files a/examples/half-adder.pdf and b/examples/half-adder.pdf differ diff --git a/examples/half-adder.typ b/examples/half-adder.typ index 81c2ebb..29d45e0 100644 --- a/examples/half-adder.typ +++ b/examples/half-adder.typ @@ -1,11 +1,11 @@ -#import "../../typst-canvas/canvas.typ": canvas +#import "@preview/cetz:0.0.1" #lorem(30) #figure( - canvas(length: 1cm, debug: false, { - import "../../typst-canvas/draw.typ": * - import "../circuitypst.typ": node + cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "@local/circuitypst:0.0.1": node node("and gate", (0,0), name: "g1") content("g1.out", h(0.5em)+[Carry], anchor: "left") diff --git a/justfile b/justfile new file mode 100644 index 0000000..3c6355f --- /dev/null +++ b/justfile @@ -0,0 +1,10 @@ +# Local Variables: +# mode: makefile +# End: +test_dir := "./tests" + +test: + ./scripts/test test + +update-test: + ./scripts/test update diff --git a/parts.typ b/parts.typ index 0639e55..1949ede 100644 --- a/parts.typ +++ b/parts.typ @@ -1,8 +1,10 @@ -#import "../typst-canvas/draw.typ": * +#import "@preview/cetz:0.0.1" +#import cetz.draw: merge-path, arc, line, circle + #import "utils.typ": anchors #let and-gate-body = { merge-path(close: true, { - arc((0,0), -90deg, 90deg, radius: 0.5, name: "curve", anchor: "origin") + arc((0,0), start: -90deg, stop: 90deg, radius: 0.5, name: "curve", anchor: "origin") line( (0, 0.5), (-0.5, 0.5), @@ -27,10 +29,10 @@ #let or-gate-body = { merge-path(close: true, { - arc((0.5, 0), -90deg, -30deg, anchor: "end", name: "bcurve") - arc((0.5,0), 30deg, 90deg, anchor: "start", name: "tcurve") + arc((0.5, 0), start: -90deg, stop: -30deg, anchor: "end", name: "bcurve") + arc((0.5,0), start: 30deg, stop: 90deg, anchor: "start", name: "tcurve") line("tcurve.end", (-0.5, 0.5)) - arc((), -30deg, 30deg, anchor: "end") + arc((), start: 30deg, stop: -30deg, anchor: "start") }) // x coordinate of where the input legs touch the body of the gate @@ -62,7 +64,7 @@ } #let xor-bar = { - arc((-0.6, -0.5), -30deg, 30deg) + arc((-0.6, -0.5), start: -30deg, stop: 30deg) anchors(( "ibin 1": "bin 1", "ibin 2": "bin 2", @@ -75,4 +77,4 @@ #let logic-gate-legs = for a in ("in 1", "in 2", "out") { line("b" + a, a) -} \ No newline at end of file +} diff --git a/scripts/test b/scripts/test new file mode 100755 index 0000000..7c4f4e9 --- /dev/null +++ b/scripts/test @@ -0,0 +1,142 @@ +#!/bin/env bash +set -eu +set -o pipefail + +DIR="$(dirname "${BASH_SOURCE[0]}")" + +MODE="${1:-test}"; +if (( $# > 1)); then shift; fi + +FLAG_INSTALL=0 + +while :; do + case "${1:-}" in + '--install') FLAG_INSTALL=1 ;; + *) break + esac + shift +done + + +TYPST_VERSION="v0.6.0" +TYPST_BASE_URL="https://github.com/typst/typst/releases/download" +TYPST_ARCHIVE="typst-x86_64-unknown-linux-musl.tar.xz" + +TYPST_ROOT="$(realpath "$DIR/..")" +TEST_ROOT="$(realpath "$DIR/../tests")" + +if hash magick 2>/dev/null; then + MAGICK_COMPARE="magick compare" +elif hash compare 2>/dev/null; then + MAGICK_COMPARE="compare" +else + >&2 echo "Could not find 'magick' nor 'compare' binary. Make sure you have image magick installed and in your PATH." + exit 1 +fi + + +function install_typst() +{ + if [[ "$OSTYPE" != "linux"* ]]; then + >&2 echo "Automatic installation of typst on a non linux system is currently unsupported." + exit 1 + fi + + #TMP="$(mktemp -d)" + TMP="${TMP:-/tmp}/typst-${TYPST_VERSION}" + if mkdir -p "$TMP" 2> /dev/null ; then + local PKG="${TMP}/typst.tar.xz" + + echo "Installing typst from $TYPST_BASE_URL/$TYPST_ARCHIVE" + wget "${TYPST_BASE_URL}/${TYPST_VERSION}/${TYPST_ARCHIVE}" \ + --quiet \ + -O "$PKG" + mkdir -p "${TMP}/typst" + tar -xf "$PKG" -C "${TMP}/typst" --strip-components=1 + rm "$PKG" + fi + + PATH="${TMP}/typst/:$PATH" + export PATH +} + +if [[ "$FLAG_INSTALL" == "1" ]]; then + install_typst +fi + +if ! hash typst; then + >&2 echo "Could not find 'typst' binary. Run this script with the --install argument to temporarily install typst." + exit 1 +fi + +function img_compare() +{ + $MAGICK_COMPARE -metric rmse "$1" "$2" null: 2>&1 | cut -d\ -f1 +} + +function update_test_ref() +{ + ( + cd "$1" + local NAME + NAME="$(basename "$1")" + + echo "[UPDATING] ${NAME}" + + typst --root "$TYPST_ROOT" compile test.typ "ref{n}.png" + ) +} + +function run_test() +{ + ( + cd "$1" + local NAME + NAME="$(basename "$1")" + + echo "[TEST] ${NAME} ..." + + if [[ ! -f test.typ ]]; then echo "Missing file 'test.typ'!"; exit 1; fi + + typst --root "$TYPST_ROOT" compile test.typ "res{n}.png" + + + for res_file in res*.png; do + if [[ $res_file =~ res([0-9]+)\.png ]]; then + number="${BASH_REMATCH[1]}" + ref_file="ref${number}.png" + diff_file="diff${number}.png" + + if [ -f "$ref_file" ]; then + echo "Comparing $res_file with $ref_file..." + if [[ $(img_compare $res_file $ref_file) != 0 ]] ; then + $MAGICK_COMPARE -compose src $res_file $ref_file $diff_file || true + echo "[FAIL] see $(pwd)/$diff_file for differences" + exit 1 + fi + else + echo "File $ref_file not found for $res_file." + exit 1 + fi + fi + done + + echo "[ OK]" + ) +} + +echo "Typst: $(typst --version)" + +find "$TEST_ROOT" -type d | \ + while read -r test + do + if [[ "$test" == "$TEST_ROOT" ]]; then continue; fi + + if [[ "$MODE" == "test" ]]; then + run_test "$test" + elif [[ "$MODE" == "update" ]]; then + update_test_ref "$test" + else + echo "Unknown mode '$MODE'"; exit 1 + fi + done diff --git a/tests/circuits-current-shunt/ref1.png b/tests/circuits-current-shunt/ref1.png new file mode 100644 index 0000000..c33f2cb Binary files /dev/null and b/tests/circuits-current-shunt/ref1.png differ diff --git a/tests/circuits-current-shunt/test.typ b/tests/circuits-current-shunt/test.typ new file mode 100644 index 0000000..29885c3 --- /dev/null +++ b/tests/circuits-current-shunt/test.typ @@ -0,0 +1,14 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line + import "../../circuitypst.typ": node, to + + to("isourceAM", (0,0), (0,3), label: $I_0$, v: h(0.5em) + $V_0$) + to("short", (), (2,3), i: $I_0$,) + to("R", (), (2, 0), label: [$R_1$], poles: "*-*", i: (">_", $i_1$)) + line((2,3), (4,3)) + to("R", (), (4, 0), label: [$R_2$], name: "R", i: (">_", $i_2$)) + line((), (0,0)) +}) diff --git a/tests/circuits-exclusive-or/ref1.png b/tests/circuits-exclusive-or/ref1.png new file mode 100644 index 0000000..e8aff28 Binary files /dev/null and b/tests/circuits-exclusive-or/ref1.png differ diff --git a/tests/circuits-exclusive-or/test.typ b/tests/circuits-exclusive-or/test.typ new file mode 100644 index 0000000..8ee98ab --- /dev/null +++ b/tests/circuits-exclusive-or/test.typ @@ -0,0 +1,28 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas({ + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + node("nand gate", (0,1), name: "g1") + content("g1.out", [#h(0.5em) Q], anchor: "left") + line("g1.in 1", (rel: (0, 0.5)), name: "l1") + line("g1.in 2", (rel: (0, -0.5)), name: "l2") + node("nand gate", "l1.end", anchor: "out", name: "g2") + node("nand gate", "l2.end", anchor: "out", name: "g3") + line("g2.in 2", "g3.in 1", name: "l3") + node("nand gate", "l3.center", anchor: "out", name: "g4") + line("g4.in 1", (rel: (0, 0.75)), name: "l4") + line("g4.in 2", (rel: (0, -0.75)), name: "l5") + line("g2.in 1", "l4.end") + line("g3.in 2", "l5.end") + line("l4.end", (rel: (-0.5, 0)), name: "l6") + line("l5.end", (rel: (-0.5, 0)), name: "l7") + content("l6.end", [A #h(0.5em)], anchor: "right") + content("l7.end", [B #h(0.5em)], anchor: "right") + + node("circ", "g4.out") + node("circ", "l4.end") + node("circ", "l5.end") +}) diff --git a/tests/circuits-half-adder/ref1.png b/tests/circuits-half-adder/ref1.png new file mode 100644 index 0000000..6df0765 Binary files /dev/null and b/tests/circuits-half-adder/ref1.png differ diff --git a/tests/circuits-half-adder/test.typ b/tests/circuits-half-adder/test.typ new file mode 100644 index 0000000..f72780e --- /dev/null +++ b/tests/circuits-half-adder/test.typ @@ -0,0 +1,22 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + node("and gate", (0,0), name: "g1") + content("g1.out", h(0.5em)+[Carry], anchor: "left") + node("xor gate", (0,-1.5), name: "g2") + content("g2.out", h(0.5em)+[Sum], anchor: "left") + + line("g2.in 1", (rel: (-1, 0)), name: "l1") + line("g1.in 1", (rel: (-1, 0)), "l1.end", (rel: (-1, 0))) + content((), [B]+h(0.5em), anchor: "right") + line("g1.in 2", (rel: (-0.5, 0)), name: "l2") + line("g2.in 2", (rel: (-0.5, 0)), "l2.end", (rel: (-1.5, 0))) + content((), [A]+h(0.5em), anchor: "right") + + node("circ", "l1.end") + node("circ", "l2.end") +}) diff --git a/tests/components-and-gate/ref1.png b/tests/components-and-gate/ref1.png new file mode 100644 index 0000000..3192a01 Binary files /dev/null and b/tests/components-and-gate/ref1.png differ diff --git a/tests/components-and-gate/test.typ b/tests/components-and-gate/test.typ new file mode 100644 index 0000000..18a35e2 --- /dev/null +++ b/tests/components-and-gate/test.typ @@ -0,0 +1,9 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + node("and gate", (0,0), name: "g1") +}) diff --git a/tests/components-isourceAM/ref1.png b/tests/components-isourceAM/ref1.png new file mode 100644 index 0000000..dc195f0 Binary files /dev/null and b/tests/components-isourceAM/ref1.png differ diff --git a/tests/components-isourceAM/test.typ b/tests/components-isourceAM/test.typ new file mode 100644 index 0000000..5b05d2e --- /dev/null +++ b/tests/components-isourceAM/test.typ @@ -0,0 +1,9 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + to("isourceAM", (0,0), (0,3), label: $I_0$, v: h(0.5em) + $V_0$) +}) diff --git a/tests/components-nand-gate/ref1.png b/tests/components-nand-gate/ref1.png new file mode 100644 index 0000000..729d78c Binary files /dev/null and b/tests/components-nand-gate/ref1.png differ diff --git a/tests/components-nand-gate/test.typ b/tests/components-nand-gate/test.typ new file mode 100644 index 0000000..83d939f --- /dev/null +++ b/tests/components-nand-gate/test.typ @@ -0,0 +1,9 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + node("nand gate", (0,0), name: "g1") +}) diff --git a/tests/components-op-amp/ref1.png b/tests/components-op-amp/ref1.png new file mode 100644 index 0000000..3835a34 Binary files /dev/null and b/tests/components-op-amp/ref1.png differ diff --git a/tests/components-op-amp/test.typ b/tests/components-op-amp/test.typ new file mode 100644 index 0000000..205d13f --- /dev/null +++ b/tests/components-op-amp/test.typ @@ -0,0 +1,9 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + node("op amp", (0,-1.5), name: "g2") +}) diff --git a/tests/components-xor-gate/ref1.png b/tests/components-xor-gate/ref1.png new file mode 100644 index 0000000..e124785 Binary files /dev/null and b/tests/components-xor-gate/ref1.png differ diff --git a/tests/components-xor-gate/test.typ b/tests/components-xor-gate/test.typ new file mode 100644 index 0000000..8027b4f --- /dev/null +++ b/tests/components-xor-gate/test.typ @@ -0,0 +1,9 @@ +#set page(width: auto, height: auto, margin: 0.5cm) +#import "@preview/cetz:0.0.1" + +#cetz.canvas(length: 1cm, debug: false, { + import cetz.draw: line, content + import "../../circuitypst.typ": node, to + + node("xor gate", (0,-1.5), name: "g2") +}) diff --git a/typst.toml b/typst.toml new file mode 100644 index 0000000..5dabf17 --- /dev/null +++ b/typst.toml @@ -0,0 +1,4 @@ +[package] +name = "circuitypst" +version = "0.0.1" +entrypoint = "circuitypst.typ" diff --git a/utils.typ b/utils.typ index 6d327c3..9904738 100644 --- a/utils.typ +++ b/utils.typ @@ -1,5 +1,6 @@ -#import "../typst-canvas/vector.typ" -#import "../typst-canvas/draw.typ": anchor +#import "@preview/cetz:0.0.1" +#import cetz.draw: anchor, vector + #let vector-dist(v1, v2) = { return calc.abs( @@ -56,4 +57,4 @@ for (k, v) in anchors { anchor(k, v) } -} \ No newline at end of file +}