diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..78716e9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..81ddaee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: Tests +on: + push: + branches: + - main + - master + pull_request: + branches: + - '**' + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: imagemagick cargo parallel + version: 1.0 + - name: Install typos-cli from crates.io + uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: typos-cli + - name: Install just from crates.io + uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: just + - name: Install typst-test from github + uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: typst-test + git: https://github.com/tingerrr/typst-test.git + tag: ci-semi-stable + - uses: yusancky/setup-typst@v2 + id: setup-typst + with: + version: 'v0.11.0' + - run: | + just install @local + just install @preview + just manual + just test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be705ac --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/tests/**/*.pdf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a7e77cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ef43cd --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# CeTZ Venn + +A CeTZ library for drawing two- or three-set venn diagrams. + +## Examples + + + + + + + +
+ + + + + + + +
Two set venn diagramThree set vann diagram
+ +*Click on the example image to jump to the code.* + +## Usage + +For information, see the [manual (stable)](https://github.com/johannes-wolf/cetz-venn/blob/stable/manual.pdf?raw=true). + +To use this package, simply add the following code to your document: +``` +#import "@preview/cetz:0.2.1" +#import "@preview/cetz-venn:0.1.0" + +#cetz.canvas({ + cetz-venn.venn2() +}) +``` diff --git a/gallery/venn2.png b/gallery/venn2.png new file mode 100644 index 0000000..08bc3dc Binary files /dev/null and b/gallery/venn2.png differ diff --git a/gallery/venn2.typ b/gallery/venn2.typ new file mode 100644 index 0000000..077133a --- /dev/null +++ b/gallery/venn2.typ @@ -0,0 +1,11 @@ +#set page(width: auto, height: auto, margin: .5cm) +#import "@preview/cetz:0.2.1" +#import "@preview/cetz-venn:0.1.0": venn2 + +#cetz.canvas({ + import cetz.draw: * + + venn2(name: "venn", a-fill: red, ab-fill: green) + content("venn.a", [A]) + content("venn.b", [B]) +}) diff --git a/gallery/venn3.png b/gallery/venn3.png new file mode 100644 index 0000000..ebe1a7b Binary files /dev/null and b/gallery/venn3.png differ diff --git a/gallery/venn3.typ b/gallery/venn3.typ new file mode 100644 index 0000000..93de979 --- /dev/null +++ b/gallery/venn3.typ @@ -0,0 +1,19 @@ +#set page(width: auto, height: auto, margin: .5cm) +#import "@preview/cetz:0.2.1" +#import "@preview/cetz-venn:0.1.0": venn3 + +#cetz.canvas({ + import cetz.draw: * + + scale(1.5) + venn3(name: "venn", a-fill: red, b-fill: green, c-fill: blue, + ab-fill: yellow, abc-fill: gray, bc-fill: gray) + + content("venn.a", [1], angle: 45deg) + content("venn.b", [2]) + content("venn.c", [3]) + content("venn.ab", [4]) + content("venn.bc", [5]) + content("venn.ac", [6]) + content("venn.abc", [7]) +}) diff --git a/justfile b/justfile new file mode 100644 index 0000000..dc85f31 --- /dev/null +++ b/justfile @@ -0,0 +1,22 @@ +# Local Variables: +# mode: makefile +# End: +gallery_dir := "./gallery" + +package target *options: + ./scripts/package "{{target}}" {{options}} + +install target="@local": + ./scripts/package "{{target}}" + +test *filter: + typst-test run {{filter}} + +update-test *filter: + typst-test update {{filter}} + +manual: + typst c manual.typ manual.pdf + +gallery: + for f in "{{gallery_dir}}"/*.typ; do typst c "$f" "${f/typ/png}"; done diff --git a/manual.pdf b/manual.pdf new file mode 100644 index 0000000..3295083 Binary files /dev/null and b/manual.pdf differ diff --git a/manual.typ b/manual.typ new file mode 100644 index 0000000..e69de29 diff --git a/scripts/package b/scripts/package new file mode 100755 index 0000000..d660821 --- /dev/null +++ b/scripts/package @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -eu + +PKG_PREFIX="cetz-venn" + +# List of all files that get packaged +files=( + src/ + gallery/ + typst.toml + LICENSE + README.md + manual.typ + manual.pdf +) + +# Local package directories per platform +if [[ "$OSTYPE" == "linux"* ]]; then + DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}" +elif [[ "$OSTYPE" == "darwin"* ]]; then + DATA_DIR="$HOME/Library/Application Support" +else + DATA_DIR="${APPDATA}" +fi + +if (( $# < 1 )) || [[ "${1:-}" == "help" ]]; then + echo "package TARGET [--relative-paths]" + echo "" + echo "Packages all relevant files into a directory named '${PKG_PREFIX}/'" + echo "at TARGET. If TARGET is set to @local, the local Typst package directory" + echo "will be used so that the package gets installed for local use, if @preview" + echo "is used, Typsts preview cache dir will be used." + echo "The version is read from 'typst.toml' in the project root." + echo "" + echo "Local package prefix: $DATA_DIR/typst/package/local" + exit 1 +fi + +function read-toml() { + local file="$1" + local key="$2" + # Read a key value pair in the format: = "" + # stripping surrounding quotes. + perl -lne "print \"\$1\" if /^${key}\\s*=\\s*\"(.*)\"/" < "$file" +} + +SOURCE="$(cd "$(dirname "$0")"; pwd -P)/.." # macOS has no realpath +TARGET="${1:?Missing target path or @local}"; shift +VERSION="$(read-toml "$SOURCE/typst.toml" "version")" + +OPT_RELATIVE_PATHS=false +while [[ $# -gt 0 ]]; do + case "$1" in + --relative-paths) + OPT_RELATIVE_PATHS=true + shift + ;; + *) + echo "Unexpected option $1!" + exit 1 + ;; + esac +done + +if [[ "$TARGET" == "@local" ]] || [[ "$TARGET" == "install" ]]; then + TARGET="${DATA_DIR}/typst/packages/local/" +elif [[ "$TARGET" == "@preview" ]]; then + TARGET="${DATA_DIR}/typst/packages/preview/" +fi +echo "Install dir: $TARGET" + +TMP="$(mktemp -d)" + +for f in "${files[@]}"; do + mkdir -p "$TMP/$(dirname "$f")" 2>/dev/null + cp -r "$SOURCE/$f" "$TMP/$f" +done + +TARGET="${TARGET:?}/${PKG_PREFIX:?}/${VERSION:?}" +echo "Packaged to: $TARGET" +if rm -rf "${TARGET:?}" 2>/dev/null; then + echo "Overwriting existing version." +fi + +if $OPT_RELATIVE_PATHS; then + echo "Changing imports to relative." + "$SOURCE/scripts/relpaths" "$TMP" +fi + +mkdir -p "$TARGET" +mv "$TMP"/* "$TARGET" diff --git a/scripts/relpaths b/scripts/relpaths new file mode 100755 index 0000000..ef9677a --- /dev/null +++ b/scripts/relpaths @@ -0,0 +1,20 @@ +#!/bin/env python +import glob, os, sys, re + +import_regexp = re.compile(f'#(import|include)\\s*"(/.+)"') + +def replace_imports(filename): + s = None + with open(filename, "r") as file: + s = file.read() + def abs_to_rel(captures): + g = captures.groups() + p = os.path.relpath("." + g[1], os.path.dirname(filename)) + return f'#{g[0]} "{p}"' + s = re.sub(import_regexp, abs_to_rel, s) + with open(filename, "w") as file: + file.write(s) + +os.chdir(sys.argv[1]) +for file in glob.iglob("./**/*.typ", recursive=True): + replace_imports(file) diff --git a/src/lib.typ b/src/lib.typ new file mode 100644 index 0000000..e3f2c27 --- /dev/null +++ b/src/lib.typ @@ -0,0 +1 @@ +#import "/src/venn.typ": venn2, venn3 diff --git a/src/venn.typ b/src/venn.typ new file mode 100644 index 0000000..5f50f29 --- /dev/null +++ b/src/venn.typ @@ -0,0 +1,173 @@ +#import "@preview/cetz:0.2.1" + +#let default-style = ( + stroke: auto, + fill: white, + padding: 2em, +) + +#let venn-prepare-args(num-sets, args, style) = { + assert(2 <= num-sets and num-sets <= 3, + message: "Number of sets must be 2 or 3") + + let set-combinations = if num-sets == 2 { + ("a", "b", "ab", "not-ab") + } else { + ("a", "b", "c", "ab", "ac", "bc", "abc", "not-abc") + } + + let keys = ( + "fill": style.fill, + "stroke": style.stroke, + ) + + let new = (:) + for combo in set-combinations { + for (key, def) in keys { + key = combo + "-" + key + new.insert(key, args.at(key, default: def)) + } + } + return new +} + +/// Draw a venn diagram with two sets a and b +/// +/// - ..args (any): Arguments +/// - name (none, string): Element name +#let venn2(..args, name: none) = { + import cetz.draw: * + + let distance = 1.25 + + group(name: name, ctx => { + let style = cetz.styles.resolve(ctx.style, base: default-style, merge: (:), root: "venn") + let padding = cetz.util.resolve-number(ctx, style.padding) + + let args = venn-prepare-args(2, args.named(), style) + + let pos-a = (-distance / 2,0) + let pos-b = (+distance / 2,0) + + let a = circle(pos-a, radius: 1) + let b = circle(pos-b, radius: 1) + + intersections("ab", { + hide(a); + hide(b); + }) + + rect((rel: (-1 - padding, -1 - padding), to: pos-a), + (rel: (+1 + padding, +1 + padding), to: pos-b), + fill: args.not-ab-fill, stroke: args.not-ab-stroke, name: "frame") + + merge-path(name: "a-shape", { + arc-through("ab.0", (rel: (-1, 0), to: pos-a), "ab.1") + arc-through("ab.1", (rel: (-1, 0), to: pos-b), "ab.0") + }, fill: args.a-fill, stroke: args.a-stroke, close: true) + + merge-path(name: "b-shape", { + arc-through("ab.0", (rel: (+1, 0), to: pos-b), "ab.1") + arc-through("ab.1", (rel: (+1, 0), to: pos-a), "ab.0") + }, fill: args.b-fill, stroke: args.b-stroke, close: true) + + merge-path(name: "ab-shape", { + arc-through("ab.0", (rel: (-1, 0), to: pos-b), "ab.1") + arc-through("ab.1", (rel: (+1, 0), to: pos-a), "ab.0") + }, fill: args.ab-fill, stroke: args.ab-stroke, close: true) + + anchor("a", (rel: (-1 + distance / 2, 0), to: pos-a)) + anchor("b", (rel: (+1 - distance / 2, 0), to: pos-b)) + anchor("ab", (pos-a, 50%, pos-b)) + anchor("not-ab", (rel: (padding / 2, padding / 2), to: "frame.south-west")) + }) +} + +/// Draw a venn diagram with three sets a, b and c +/// +/// - ..args (any): Arguments +/// - name (none, string): Element name +#let venn3(..args, padding: .5, name: none) = { + import cetz.draw: * + + let distance = .75 + + group(name: name, ctx => { + let style = cetz.styles.resolve(ctx.style, base: default-style, root: "venn") + let padding = cetz.util.resolve-number(ctx, style.padding) + + let args = venn-prepare-args(3, args.named(), style) + + let pos-a = cetz.vector.rotate-z((distance,0), -90deg + 2 * 360deg / 3) + let pos-b = cetz.vector.rotate-z((distance,0), -90deg + 360deg / 3) + let pos-c = cetz.vector.rotate-z((distance,0), -90deg) + + let angle-ab = cetz.vector.angle2(pos-a, pos-b) + let angle-ac = cetz.vector.angle2(pos-a, pos-c) + let angle-bc = cetz.vector.angle2(pos-b, pos-c) + + // Distance between set center points + let d-ab = cetz.vector.dist(pos-a, pos-b) + let d-ac = cetz.vector.dist(pos-a, pos-c) + let d-bc = cetz.vector.dist(pos-b, pos-c) + + // Midpoints between set center points + let m-ab = cetz.vector.lerp(pos-a, pos-b, .5) + let m-ac = cetz.vector.lerp(pos-a, pos-c, .5) + let m-bc = cetz.vector.lerp(pos-b, pos-c, .5) + + // Intersections (0 = outer, 1 = inner) + let i-ab-0 = cetz.vector.add(m-ab, cetz.vector.rotate-z((+calc.sqrt(1 - calc.pow(d-ab / 2, 2)), 0), angle-ab + 90deg)) + let i-ab-1 = cetz.vector.add(m-ab, cetz.vector.rotate-z((-calc.sqrt(1 - calc.pow(d-ab / 2, 2)), 0), angle-ab + 90deg)) + let i-ac-0 = cetz.vector.add(m-ac, cetz.vector.rotate-z((-calc.sqrt(1 - calc.pow(d-ac / 2, 2)), 0), angle-ac + 90deg)) + let i-ac-1 = cetz.vector.add(m-ac, cetz.vector.rotate-z((+calc.sqrt(1 - calc.pow(d-ac / 2, 2)), 0), angle-ac + 90deg)) + let i-bc-0 = cetz.vector.add(m-bc, cetz.vector.rotate-z((+calc.sqrt(1 - calc.pow(d-bc / 2, 2)), 0), angle-bc + 90deg)) + let i-bc-1 = cetz.vector.add(m-bc, cetz.vector.rotate-z((-calc.sqrt(1 - calc.pow(d-bc / 2, 2)), 0), angle-bc + 90deg)) + + rect((rel: (-1 - padding, +1 + padding), to: pos-a), + (rel: (+1 + padding, -1 - padding), to: (pos-b.at(0), pos-c.at(1))), + fill: args.not-abc-fill, stroke: args.not-abc-stroke, name: "frame") + + for (name, angle) in (("a", 0deg), ("b", 360deg / 3), ("c", 2 * 360deg / 3)) { + merge-path(name: "a-shape", { + group({ + rotate(angle) + arc-through(i-ab-0, (rel: (-1, 0), to: pos-a), i-ac-0) + arc-through((), (rel: cetz.vector.rotate-z((-1,0), angle-ac), to: pos-c), i-bc-1) + arc-through((), (rel: (-1, 0), to: pos-b), i-ab-0) + }) + }, fill: args.at(name + "-fill"), stroke: args.at(name + "-stroke"), close: true) + } + + for (name, angle) in (("ab", 0deg), ("ac", 360deg / 3), ("bc", 2 * 360deg / 3)) { + merge-path(name: name + "-shape", { + group({ + rotate(angle) + arc-through(i-bc-1, (rel: (-1, 0), to: pos-b), i-ab-0) + arc-through((), (rel: (+1, 0), to: pos-a), i-ac-1) + arc-through((), (rel: (0, +1), to: pos-c), i-bc-1) + }) + }, fill: args.at(name + "-fill"), stroke: args.at(name + "-stroke"), close: true) + } + + merge-path(name: "abc-shape", { + arc-through(i-ab-1, (rel: cetz.vector.rotate-z((+1,0), (angle-ab + angle-ac) / 2), to: pos-a), i-ac-1) + arc-through((), (rel: cetz.vector.rotate-z((-1,0), (angle-ac + angle-bc) / 2), to: pos-c), i-bc-1) + arc-through((), (rel: cetz.vector.rotate-z((-1,0), (180deg + angle-ab + angle-bc) / 2), to: pos-b), i-ab-1) + }, fill: args.abc-fill, stroke: args.abc-stroke, close: true) + + let a-a = cetz.vector.lerp(i-bc-0, i-bc-1, 1.5) + let a-b = cetz.vector.lerp(i-ac-0, i-ac-1, 1.5) + let a-c = cetz.vector.lerp(i-ab-0, i-ab-1, 1.5) + + anchor("a", a-a) + anchor("b", a-b) + anchor("c", a-c) + anchor("ab", cetz.vector.lerp(a-a, a-b, .5)) + anchor("bc", cetz.vector.lerp(a-b, a-c, .5)) + anchor("ac", cetz.vector.lerp(a-a, a-c, .5)) + anchor("abc", (0,0)) + anchor("not-abc", (rel: (padding / 2, padding / 2), to: "frame.south-west")) + }) +} + diff --git a/tests/helper.typ b/tests/helper.typ new file mode 100644 index 0000000..3acdd93 --- /dev/null +++ b/tests/helper.typ @@ -0,0 +1,21 @@ +#import "@preview/cetz:0.2.1" +#import "/src/lib.typ" as venn + +/// Test case canvas surrounded by a red border +#let test-case(body, ..canvas-args, args: none) = { + if type(body) != function { + body = _ => { body } + args = (none,) + } else { + assert(type(args) == array and args.len() > 0, + message: "Function body requires args set!") + } + + for arg in args { + block(stroke: 2pt + red, + cetz.canvas(..canvas-args, { + body(arg) + }) + ) + } +} diff --git a/tests/venn2/ref/1.png b/tests/venn2/ref/1.png new file mode 100644 index 0000000..8c66a1a Binary files /dev/null and b/tests/venn2/ref/1.png differ diff --git a/tests/venn2/test.typ b/tests/venn2/test.typ new file mode 100644 index 0000000..cd24cd7 --- /dev/null +++ b/tests/venn2/test.typ @@ -0,0 +1,34 @@ +#set page(width: auto, height: auto) +#import "/src/lib.typ": venn2 +#import "/tests/helper.typ": * + +#import cetz.draw: content, set-style + +#test-case({ + venn2(name: "v") + content("v.a", [A]) + content("v.b", [B]) + content("v.ab", [AB]) + content("v.not-ab", [not AB], anchor: "south-west") +}) + +#test-case({ + venn2(a-fill: red) +}) + +#test-case({ + venn2(b-fill: red) +}) + +#test-case({ + venn2(ab-fill: red) +}) + +#test-case({ + venn2(not-ab-fill: red) +}) + +#test-case({ + set-style(venn: (stroke: blue, fill: gray)) + venn2(name: "v", a-stroke: black, b-fill: green) +}) diff --git a/tests/venn3/ref/1.png b/tests/venn3/ref/1.png new file mode 100644 index 0000000..25cedcb Binary files /dev/null and b/tests/venn3/ref/1.png differ diff --git a/tests/venn3/test.typ b/tests/venn3/test.typ new file mode 100644 index 0000000..b21f7be --- /dev/null +++ b/tests/venn3/test.typ @@ -0,0 +1,54 @@ +#set page(width: auto, height: auto) +#import "/src/lib.typ": venn3 +#import "/tests/helper.typ": * + +#import cetz.draw: content, set-style, scale + +#test-case({ + venn3(name: "v") + content("v.a", [A]) + content("v.b", [B]) + content("v.c", [C]) + content("v.ab", [AB]) + content("v.bc", [BC]) + content("v.ac", [AC]) + content("v.abc", [\*]) + content("v.not-abc", [not ABC], anchor: "south-west") +}) + +#test-case({ + venn3(a-fill: red) +}) + +#test-case({ + venn3(b-fill: red) +}) + +#test-case({ + venn3(c-fill: red) +}) + +#test-case({ + venn3(ab-fill: red) +}) + +#test-case({ + venn3(bc-fill: red) +}) + +#test-case({ + venn3(ac-fill: red) +}) + +#test-case({ + venn3(abc-fill: red) +}) + +#test-case({ + venn3(not-abc-fill: red) +}) + +#test-case({ + set-style(venn: (stroke: blue, fill: gray)) + venn3(name: "v", a-stroke: black, ab-fill: green) +}) diff --git a/typst.toml b/typst.toml new file mode 100644 index 0000000..d5bbe1e --- /dev/null +++ b/typst.toml @@ -0,0 +1,12 @@ +[package] +name = "cetz-venn" +version = "0.1.0" +repository = "https://github.com/johannes-wolf/cetz-venn" +entrypoint = "src/lib.typ" +authors = [ + "Johannes Wolf " +] +license = "Apache-2.0" +description = "CeTZ library for drawing venn diagrams for two or three sets." +keywords = [ "venn", "diagram", "cetz" ] +exclude = [ "/gallery/*", "manual.pdf", "manual.typ" ]