diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/LICENSE b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/LICENSE new file mode 100644 index 0000000000..8130aaf4e6 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Dominik Schwaiger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/README.md b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/README.md new file mode 100644 index 0000000000..2adba33e55 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/README.md @@ -0,0 +1,261 @@ +# ethz-cadmo-inspired-thesis + +A template for writing a (bachelor's) thesis at ETH Zürich. + +It provides general styling and some useful helper functions. + +It is an **unofficial** Typst adaptation of the [CADMO LaTeX thesis +template](https://cadmo.ethz.ch/education/thesis/template.html) which is maintained by +Kostas Lakis at CADMO (Center for Algorithms, Discrete Mathematics and +Optimization), ETH Zürich. It is not affiliated with or endorsed by CADMO or +ETH Zürich. + +## Usage + +Start a new thesis from this template with: + +```sh +typst init @preview/ethz-cadmo-inspired-thesis +``` + +or directly with: + +```typ +#show: setup.with( + "My Thesis Title", // title + "Jane Doe", // author + ("Advisor One", "Advisor Two"), // advisors + thesis-type: "Bachelor Thesis", + department: "Department of Computer Science", + bib: bibliography("bib.bib", title: none), +) +``` + +## Configuration + +The template exports `setup`, applied as a show rule at the top of your +document: + +| Parameter | Type | Default Value | Description / Note | +| :------------ | :------------------- | :--------------------------------- | :------------------------------------- | +| `title` | content \| str | _Required_ | The title of the thesis | +| `author` | str | _Required_ | The author's name | +| `advisors` | array of str | _Required_ | List of academic advisors | +| `department` | str | `"Department of Computer Science"` | The academic department | +| `thesis-type` | str | `"Bachelor Thesis"` | The degree level or type of thesis | +| `date` | datetime | `datetime.today()` | Date of publication/submission | +| `bib` | bibliography \| none | `none` | Takes a Typst `bibliography()` element | +| `dev` | bool | `false` | Force Work in Progress (WiP) mode | +| `doc` | content | _Required_ | The rest of the document content | + +Applied with `#show: setup.with(...)`, it sets up the page and general structure needed for a thesis. + +If `bib` is set, a `References` chapter with that bibliography is appended at the +very end. If you need the bibliography to appear before an appendix, leave `bib: none` and place the bibliography +yourself instead (see [Bibliography and appendix](#bibliography-and-appendix)). + +## Fonts + +The template typesets in **TeX Gyre Pagella**. Typst cannot bundle a font into a +package and load it automatically, so the font has to be available at compile +time. + +Download the OTF files from the official GUST e-foundry page and install them, or place them on your `--font-path`: + + + +The font is distributed under the GUST Font License. + +## Document structure + +| Helper | Purpose | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `frontchapter(title)` | An unnumbered level-1 heading for front matter such as _Abstract_ or _Acknowledgments_. | +| `#show: mainmatter` | Starts the numbered body: resets the page and chapter counters and switches to arabic page numbers. Apply once, after the front matter. | +| `#show: appendix` | Switches heading numbering to letters (`A`, `A.1`, `A.2`, …). Apply once, at the start of the appendix. | + +Headings render as: + +- **Level 1**: a centered `Chapter N` label above the title, framed by rules + (the label is omitted for unnumbered chapters). +- **Level 2 / 3**: bold section and subsection titles. + +The running header shows `Chapter N - N.M Section Title` and is hidden on pages +that begin a new chapter. + +### Bibliography and appendix + +To keep the references and appendix out of the word count, wrap them in a +``-labelled block and set `bib: none`: + +```typ +#[ + #set page(numbering: "1") + #heading(level: 1, numbering: none)[References] + #bibliography("bib.bib", title: none) + + #include "chapters/appendix.typ" +] +``` + +An appendix file then looks like: + +```typ +#show: appendix +#frontchapter[Appendix] + +== First Appendix Section // numbered "A" +=== A Subsection // numbered "A.1" +``` + +## Theorem environments + +All environments take a body and an optional `name`, and are numbered +`chapter.n`, resetting at every chapter: + +```typ +#theorem(name: "Fermat")[ + There are no positive integers $a, b, c$ with $a^n + b^n = c^n$ for $n > 2$. +] + +#proof[ + The margin is too small. #h(1fr) // proof already appends a QED square +] +``` + +Available: `theorem`, `lemma`, `corollary`, `proposition`, `definition`, +`example`, `remark`, `claim`, and `proof`. + +## Code listings + +`code` renders a single raw block with line numbers, in a rounded gray frame: + +````typ +#code( + ```rust + fn main() { + println!("hello"); + } + ```, + filename: "main.rs", // optional file label in the header + projectname: "my-crate", // optional right-aligned label + start: 1, // first line number + breakable: false, // allow the listing to split across pages +) +```` + +`code-file` embeds code read from a file, optionally limited to a line range. +The caller reads the file so Typst tracks it as a dependency: + +```typ +#code-file( + read("../src/lib.rs"), + lang: "rust", + from: 10, to: 25, // inclusive, 1-indexed; line numbers stay aligned to the file + filename: "lib.rs", +) +``` + +To keep parts of a file out of the listing (for example a test-harness preamble +that has to stay in the file to keep it runnable), wrap them between a line +containing `` and a line containing ``. Both marker lines and +everything between them are dropped from the display, and the remaining lines +are renumbered contiguously from `start`. Because the markers live in comments, +the source file still compiles and runs unchanged: + +```rust +// +//@compile-flags: -Zmiri-tree-borrows +// +use std::io::Read; // shown as line 1 +``` + +Hiding is meant for file-only preambles and is not combined with `from`/`to`. + +`multi-code` shows several excerpts of the same listing separated by a dashed +"…" line, for skipping over irrelevant lines. It takes an array of +`(raw, start-line)` tuples: + +````typ +#multi-code(( + (```rust + fn first() {} + ```, 1), + (```rust + fn later() {} + ```, 42), +)) +```` + +Inside any listing, the token `` is replaced by a newline, which lets +you force a break within a single source line. + +## Utilities + +`breakable-id(n)` returns a string with zero-width spaces inserted after every +`-` and `_`, so long identifiers (package names, crate names, symbol paths) +can break across lines without an explicit hyphen: + +```typ +#raw(breakable-id("my-very-long-crate-name")) // breaks at each `-` +#breakable-id("some_module::long_symbol") // use in any content +``` + +`display-pr(url)` displays a GitHub or GitLab Pull Request in a shorter, more readable format. + +## Cross references + +`nameref(label)` links to a labelled heading and prints its supplement, number +and title, e.g. `#nameref()` → "Section 3.2: Method". For unnumbered +headings it falls back to the heading text alone. + +```typ +== Method +... +As shown in #nameref(), ... +``` + +## TODOs and WiP mode + +`todo(body, inline: false)` marks unfinished work: + +```typ +#todo[rewrite this paragraph] // block +Some text #todo(inline: true)[fix ref]. // inline +``` + +While any `TODO` is present (or `dev: true` is passed to `setup`), the document +is in **work in progress** mode: the title page shows a red _Work in Progress_ +banner and every running header shows `WIP · N words`. Once the last `TODO` is +removed, both disappear automatically. + +## Colours + +This template also exposes some colours. The official ETH colours can be found at `eth`. The custom colours used for some functions can be found at `custom`. + +## Exports + +`setup`, `mainmatter`, `appendix`, `frontchapter`, `display-pr`, `nameref`, `todo`, +`code`, `code-file`, `multi-code`, `breakable-id`, `theorem`, `lemma`, `corollary`, +`proposition`, `definition`, `example`, `remark`, `claim`, `proof`, `eth`, `custom`. + +## Contribution + +I'm always open to contributions / feedback. As the repo currently only tracked in typst packages, just send me an email with a diff / comment and I will integrate it: mail@dominik-schwaiger.ch + +## Full Example + +A full example how the template can be used can be found here: https://gitlab.dominik-schwaiger.ch/quio/bachelor-thesis/-/tree/main/Thesis?ref_type=heads + +## Acknowledgments + +This template is a Typst port of the [CADMO LaTeX thesis +template](https://cadmo.ethz.ch/education/thesis/template.html) by Kostas Lakis +(CADMO, ETH Zürich). + +Many thanks to [VirtCode](https://github.com/VirtCode), whose work the +code-listing helpers are based on. + +The body font is [TeX Gyre +Pagella](https://www.gust.org.pl/projects/e-foundry/tex-gyre/pagella) by the +GUST e-foundry, distributed under the GUST Font License. diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/assets/eth-logo.png b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/assets/eth-logo.png new file mode 100755 index 0000000000..04d1699904 Binary files /dev/null and b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/assets/eth-logo.png differ diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/code.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/code.typ new file mode 100644 index 0000000000..fc4d424c71 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/code.typ @@ -0,0 +1,132 @@ +// Code blocks +// --- Thanks to VirtCode (https://github.com/VirtCode) + +#let padnum(num, width, padding: "0") = { + let num = str(num) + + let diff = calc.max(width - num.len(), 0) + return padding * diff + num +} + +/// formats multiple blocks of code with line numbers, adding ellipses between the blocks +#let multi-code(blocks, filename: none, projectname: none, breakable: false) = { + let radius = 4pt + let background = gray.lighten(70%) + let foreground = background.darken(15%) + + let separator = line(length: 100%, stroke: (paint: foreground, cap: "round", dash: "dashed")) + let numpad = "0" + + // all raws need to be blocks to not get the highlight + let rraw(..args) = raw(..args, block: true) + + // split blocks into lines with numbers + let blocks = blocks.map(((code, start)) => code + .text + .split("\n") + .enumerate() + .map(((i, line)) => (start + i, rraw(line.replace("", "\n"), lang: code.lang)))) + + // width of the line numbers + let width = str(calc.max(..blocks.join().map(s => s.at(0)))).len() + + // replace numbers with padded raw + let blocks = blocks.map(lines => lines.map(((line, code)) => (rraw(padnum(line, width, padding: numpad)), code))) + + // join lines with separator of choice + let lines = blocks.join(( + (rraw("." * width), table.cell(separator, align: horizon)), + )) + + let header = if filename != none { + ( + table.header(table.cell(colspan: 2, if projectname != none { + grid( + columns: (1fr, auto), + rraw(filename), rraw(projectname), + ) + } else { + rraw(filename) + })), + ) + } else { + () + } + + block(fill: gray.lighten(70%), radius: radius, clip: not breakable, breakable: breakable)[ + #table( + columns: (auto, 1fr), + align: left, // don't inherit alignment (e.g. center inside a figure) + stroke: (x, y) => (top: if x == 0 { foreground }, right: if x == 0 { foreground }), // otherwise zathura renders ugly + fill: (x, y) => if x == 0 or (y == 0 and header.len() > 0) { foreground }, + ..header, + ..lines.flatten() + ) + ] +} + +/// formats a single block of code +#let code(code, start: 1, filename: none, projectname: none, breakable: false) = multi-code( + ((code, start),), + filename: filename, + projectname: projectname, + breakable: breakable, +) + +/// formats a single block of code read from a file +/// +/// `content` is the file's text, read by the caller, e.g. `code-file(read("snippet.rs"), ...)`. +/// +/// `from` and `to` optionally select an inclusive, 1-indexed line range; the +/// displayed line numbers keep matching the original file. +/// +/// Any line containing `` starts a hidden region and any line containing +/// `` ends it. The marker lines and everything between them are dropped +/// from the display (but stay in the file), and the remaining lines are +/// renumbered contiguously. Hiding is meant to strip file-only preambles such +/// as test harness directives and is not combined with `from`/`to`. +#let code-file(content, lang: none, from: none, to: none, filename: none, projectname: none) = { + // drop a single trailing newline so files don't render an empty last line + let lines = content.trim("\n", at: end, repeat: false).split("\n") + + // strip `` … `` regions from the display + let visible = () + let hidden = false + let did-hide = false + for line in lines { + if line.contains("") { + if hidden { + panic("code-file: nested `` (a region is already open, close it with `` first)") + } + hidden = true + did-hide = true + } else if line.contains("") { + if not hidden { + panic("code-file: stray `` without a matching ``") + } + hidden = false + } else if not hidden { + visible.push(line) + } + } + // a `` left open would silently swallow the rest of the file + if hidden { + panic("code-file: unclosed `` region (missing a line with ``)") + } + // drop blank lines a stripped preamble leaves at the top of the listing + while did-hide and visible.len() > 0 and visible.first().trim() == "" { + let _ = visible.remove(0) + } + let lines = visible + + let start = if from != none { from } else { 1 } + let end = if to != none { to } else { lines.len() } + let lines = lines.slice(start - 1, end) + + code( + raw(lines.join("\n"), lang: lang), + start: start, + filename: filename, + projectname: projectname, + ) +} diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/colours.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/colours.typ new file mode 100644 index 0000000000..510452f868 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/colours.typ @@ -0,0 +1,6 @@ +// ETH Color Palette +#import "eth-colors.typ": eth + +#let custom = ( + "light-gray": luma(220), +) diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/eth-colors.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/eth-colors.typ new file mode 100644 index 0000000000..84c5457f14 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/eth-colors.typ @@ -0,0 +1,31 @@ +// ETH Zürich corporate design colour palette. +// https://ethz.ch/staffnet/en/service/communication/corporate-design/colours.html +// +// Each corporate-design colour is a dictionary of its published shades. The key +// is the shade percentage (`s10` … `s120`); `s100` is the base colour, also +// exposed as `base`. Access e.g. `eth.blue.base` or `eth.red.s40`. + +// Build a shade palette from the seven published hex values. +#let palette(s10, s20, s40, s60, s80, base, s120) = ( + s10: rgb(s10), + s20: rgb(s20), + s40: rgb(s40), + s60: rgb(s60), + s80: rgb(s80), + s100: rgb(base), + base: rgb(base), + s120: rgb(s120), +) + +// ETH Zürich corporate-design colours, with the published shades. +#let eth = ( + blue: palette("#e9eff7", "#d3deef", "#a6bedf", "#7a9dcf", "#4d7dbf", "#215caf", "#08407e"), + petrol: palette("#e7f4f7", "#cce4ea", "#99cad5", "#66afc0", "#3395ab", "#007894", "#00596d"), + green: palette("#eef1e7", "#e0e3d0", "#c0c7a1", "#a1ab71", "#818f42", "#627313", "#365213"), + bronze: palette("#f4f0e7", "#e8e1d0", "#d2c2a1", "#bba471", "#a58542", "#8e6713", "#704f12"), + red: palette("#f8ebea", "#f1d7d5", "#e2aeab", "#d48681", "#c55d57", "#b7352d", "#96272d"), + purple: palette("#f8e8f3", "#efd0e3", "#dc9ec9", "#ca6cae", "#b73b92", "#a7117a", "#8c0a59"), + grey: palette("#f1f1f1", "#e2e2e2", "#c5c5c5", "#a9a9a9", "#8c8c8c", "#6f6f6f", "#575757"), + black: rgb("#000000"), + white: rgb("#ffffff"), +) diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/helpers.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/helpers.typ new file mode 100644 index 0000000000..139ec4885e --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/helpers.typ @@ -0,0 +1,58 @@ +// Create a "TODO" box for work-in-progress notes. The `inline` option controls whether the box is inline with text or a block. +#import "colours.typ": * + +#let todo(body, inline: false) = if inline [ + #box(fill: custom.light-gray, outset: (x: 1pt, y: 2pt))[ + *TODO:* #body + ] +] else [ + #block(fill: custom.light-gray, inset: 0.6em, radius: 2pt, width: 100%)[ + *TODO:* #body + ] +] + +// Inserts sym.zws (zero-width space) after `-`, `_`, `:` so long identifiers can break across lines. +#let breakable-id(n) = ( + n.replace("-", "-" + str(sym.zws)).replace("_", "_" + str(sym.zws)).replace(":", ":" + str(sym.zws)) +) + +/// Takes a GitHub or GitLab Pull Requests and creates a compact link for displaying it. +#let display-pr(url) = { + // Regex to match GitHub PRs + // Captures: 1: owner/repo, 2: PR number + let github-pattern = regex("github\.com/([^/]+/[^/]+)/pull/(\d+)") + + // Regex to match GitLab MRs + // Captures: 1: owner/repo (can include nested subgroups), 2: MR number + let gitlab-pattern = regex("gitlab\.com/([^/]+(?:/[^/]+)+)/-/(?:merge_requests|pulls)/(\d+)") + + let gh-match = url.match(github-pattern) + let gl-match = url.match(gitlab-pattern) + + if gh-match != none { + let repo = gh-match.captures.at(0) + let pr-num = gh-match.captures.at(1) + link(url, "github:" + repo + "#" + pr-num) + } else if gl-match != none { + let repo = gl-match.captures.at(0) + let mr-num = gl-match.captures.at(1) + link(url, "gitlab:" + repo + "!" + mr-num) + } else { + // Fallback: If it doesn't match either, just render the raw link + link(url) + } +} + +// Link to a labeled heading, rendered as "Supplement N: Heading text" +// (e.g. "Section 3.2: LLVM"). Inspired by LaTeX's `\nameref` from the +// hyperref package, extended with the heading's number and supplement. +// Falls back to just the body for unnumbered headings. +#let nameref(label) = context { + let el = query(label).first() + if el.numbering == none { + link(label, el.body) + } else { + let num = numbering(el.numbering, ..counter(heading).at(el.location())) + link(label, [#el.supplement #num: #el.body]) + } +} diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/lib.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/lib.typ new file mode 100644 index 0000000000..9aad2257b6 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/lib.typ @@ -0,0 +1,14 @@ +// Colours used in the template +#import "colours.typ": * + +// Helper functions +#import "helpers.typ": breakable-id, display-pr, nameref, todo + +// Code formatting +#import "code.typ": code, code-file, multi-code + +// Theorem environment +#import "theorem.typ": claim, corollary, definition, example, lemma, proof, proposition, remark, theorem + +// Document structure helpers +#import "main-structure.typ": appendix, frontchapter, mainmatter, setup diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/main-structure.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/main-structure.typ new file mode 100644 index 0000000000..9b29cbc36f --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/main-structure.typ @@ -0,0 +1,219 @@ +// Contains the code which is responsible for the main structure of the thesis, including the title page, table of contents, and chapter headings. +#import "@preview/wordometer:0.1.5": total-words, word-count +#import "theorem.typ": * + +// Unnumbered chapter-style heading for front matter (Abstract, Acknowledgments, …) +#let frontchapter(title) = heading(level: 1, numbering: none, title) + +// Apply as `#show: mainmatter` to start the main body: +// resets page and chapter counters and switches to arabic page numbers. +// The weak pagebreak ensures the counter update lands on the new page, +// not the last front-matter page (which would corrupt its roman numeral). +#let mainmatter(doc) = { + pagebreak(weak: true) + counter(page).update(1) + counter(heading).update(0) + set page(numbering: "1") + doc +} + +// Apply as `#show: appendix` to switch to lettered appendix numbering: +// level 1 → no number (use frontchapter for the title) +// level 2 → A, B, C, … +// level 3 → A.1, A.2, … +#let appendix(doc) = { + set heading( + numbering: (..nums) => { + let levels = nums.pos() + // Level 1 is the unnumbered "Appendix" title; deeper levels get letters + if levels.len() == 1 { none } else { numbering("A.1", ..levels.slice(1)) } + }, + supplement: [Appendix], + ) + counter(heading).update(0) + doc +} + +// Main setup + +#let setup( + title, + author, + advisors, + department: "Department of Computer Science", + thesis-type: "Bachelor Thesis", + date: datetime.today(), + bib: none, + dev: false, + doc, +) = { + // WiP / dev mode is active when `dev: true` is passed or any TODO is present. + // Must be called from within a `context` block. + let is-wip() = dev or query().len() > 0 + + set document(author: author, title: title, date: date) + set text(size: 11pt, lang: "en", font: "TeX Gyre Pagella") + set par(justify: true, spacing: 1em, first-line-indent: 0pt) + set heading(numbering: "1.1") + show heading.where(level: 1): set heading(supplement: [Chapter]) + set page( + paper: "a4", + margin: (x: 3.5cm, y: 3cm), + numbering: "i", + + // Running header: "Chapter N - N.M Section Title" with a rule below. + // Hidden on pages that start a new chapter. + header: context { + // Suppress on chapter-start pages + let current-page = here().page() + let chapters-on-page = query(heading.where(level: 1)).filter(h => h.location().page() == current-page) + if chapters-on-page.len() > 0 { return none } + + // Find the most recent chapter heading + let preceding-chapters = query(heading.where(level: 1).before(here())) + if preceding-chapters.len() == 0 { return none } + let current-chapter = preceding-chapters.last() + + // Build the chapter label: "Chapter N" for numbered chapters, body text for unnumbered + let chapter-label = if current-chapter.numbering != none { + let chapter-num = counter(heading).at(current-chapter.location()).first() + [Chapter #chapter-num] + } else { + current-chapter.body + } + + // If a section heading exists after the chapter start, append it + let preceding-sections = query(heading.where(level: 2).before(here())) + let header-text = if ( + preceding-sections.len() > 0 + and preceding-sections.last().location().page() >= current-chapter.location().page() + ) { + let current-section = preceding-sections.last() + let section-counter = counter(heading).at(current-section.location()) + [#chapter-label - #numbering("1.1", ..section-counter) #current-section.body] + } else { + chapter-label + } + + set text(size: 0.85em) + block(width: 100%, { + header-text + if is-wip() { place(horizon + right, text(fill: red, [WIP #sym.dot.c #total-words words])) } + }) + v(2pt) + line(length: 100%, stroke: 0.5pt) + }, + + // Footer: rule above centered page number + footer: context { + line(length: 100%, stroke: 0.5pt) + v(2pt) + align(center, text(size: 0.85em, counter(page).display())) + }, + ) + + // Chapter headings: + // "Chapter N" (large, regular weight) <- only for numbered chapters + // ------------------------------------------ + // Title (huge, bold) + // ------------------------------------------ + // + // NOTE: `align(center, { ... })` keeps us in code mode so that function + // calls like `block()` and `text()` are not treated as literal markup. + show heading.where(level: 1): it => { + pagebreak(weak: true) + v(1.5em) + align(center, { + if it.numbering != none { + text(size: 1.2em, weight: "regular", [Chapter #counter(heading).display("1")]) + v(2em) + } + // Wrap lines in block to suppress implicit spacing around them + block(above: 0pt, below: 0pt, line(length: 100%)) + v(1em) + text(size: 1.7em, weight: "bold", it.body) + v(1em) + block(above: 0pt, below: 0pt, line(length: 100%)) + }) + v(1em) + thm-counter.update(0) + } + + // Section headings (level 2) + show heading.where(level: 2): it => { + v(1em) + if it.numbering != none { + text(size: 1.15em, weight: "bold", counter(heading).display(it.numbering) + [ ] + it.body) + } else { + text(size: 1.15em, weight: "bold", it.body) + } + v(0.4em) + } + + // Subsection headings (level 3) + show heading.where(level: 3): it => { + v(0.8em) + if it.numbering != none { + // numbering can be a string or a function (e.g. in the appendix), + // so we only append ".1" when it is a plain string + let fmt = if type(it.numbering) == str { it.numbering + ".1" } else { it.numbering } + text(size: 1em, weight: "bold", counter(heading).display(fmt) + [ ] + it.body) + } else { + text(size: 1em, weight: "bold", it.body) + } + v(0.3em) + } + + show figure.caption: set text(size: 0.9em) + // Only color external URL links; keep cross-references in the default text color + show link: it => if type(it.dest) == str { + text(fill: rgb("#0645AD"), it) + } else { + it + } + + // Title page + page( + margin: (x: 3.5cm, y: 3cm), + numbering: none, + header: none, + footer: none, + { + image("../assets/eth-logo.png", width: 2in) + context { + if is-wip() { + v(1fr) + align(center, text("Work in Progress", size: 2em, weight: "bold", fill: red)) + } + } + v(1fr) + align(center, { + text(size: 2em, weight: "bold", title) + v(8em) + text(size: 1.3em, thesis-type) + v(0.8em) + text(size: 1.3em, author) + v(0.4em) + text(size: 1.3em, date.display("[month repr:long] [year]")) + }) + v(1fr) + align(right)[ + Advisors: #advisors.join(", ", last: " and ") \ + #department, ETH Zürich + ] + }, + ) + + // Count words for the header; skip non-prose and the end matter. + show: word-count.with(exclude: (raw, heading, outline, , )) + + doc + + if bib != none { + set page(numbering: "1") + heading(level: 1, numbering: none)[References] + + bib + } +} + diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/theorem.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/theorem.typ new file mode 100644 index 0000000000..a0de1d111d --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/src/theorem.typ @@ -0,0 +1,29 @@ +// Single counter shared across all theorem-like environments, reset per chapter +#let thm-counter = counter("theorem") + +// Internal helper: renders a numbered block with a bold label +#let thm-block(supplement, body, name: none) = { + thm-counter.step() + context { + let chapter-num = counter(heading).get().first() + let thm-num = thm-counter.get().first() + let label = str(chapter-num) + "." + str(thm-num) + let header = if name != none { + [*#supplement #label* (#name)*.*] + } else { + [*#supplement #label.*] + } + block(above: 1em, below: 1em)[#header #body] + } +} + +#let theorem(body, name: none) = thm-block([Theorem], body, name: name) +#let lemma(body, name: none) = thm-block([Lemma], body, name: name) +#let corollary(body, name: none) = thm-block([Corollary], body, name: name) +#let proposition(body, name: none) = thm-block([Proposition], body, name: name) +#let definition(body, name: none) = thm-block([Definition], body, name: name) +#let example(body, name: none) = thm-block([Example], body, name: name) +#let remark(body, name: none) = thm-block([Remark], body, name: name) +#let claim(body, name: none) = thm-block([Claim], body, name: name) + +#let proof(body) = block(above: 1em, below: 1em)[_Proof._ #body #h(1fr) $square$] diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/template/bib.bib b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/template/bib.bib new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/template/main.typ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/template/main.typ new file mode 100644 index 0000000000..60bd80836d --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/template/main.typ @@ -0,0 +1,34 @@ +#import "@preview/ethz-cadmo-inspired-thesis:0.1.0": * + +#show: setup.with( + "My Thesis Title", // title + "Jane Doe", // author + ("Advisor One", "Advisor Two"), // advisors + thesis-type: "Bachelor Thesis", + department: "Department of Computer Science", + bib: bibliography("bib.bib", title: none), +) + +// Front matter (unnumbered, roman page numbers) +#frontchapter[Abstract] + +This thesis is about ... + +#outline(depth: 3) + +// Main body (arabic page numbers, counters reset) +#show: mainmatter + += Introduction + +Some introductory text. + += Background + +Some background. + +#show: appendix + +#frontchapter[Appendix] + +== Appendix A diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/thumbnail.png b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/thumbnail.png new file mode 100644 index 0000000000..22f8c5d6ae Binary files /dev/null and b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/thumbnail.png differ diff --git a/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/typst.toml b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/typst.toml new file mode 100644 index 0000000000..aa1a2650c7 --- /dev/null +++ b/packages/preview/ethz-cadmo-inspired-thesis/0.1.0/typst.toml @@ -0,0 +1,14 @@ +[package] +name = "ethz-cadmo-inspired-thesis" +version = "0.1.0" +entrypoint = "src/lib.typ" +authors = ["Dominik Schwaiger"] +license = "MIT" +description = "Thesis inspired by the ETHZ CADMO group" +keywords = ["thesis", "bachelor thesis", "eth", "ethz", "cadmo"] +categories = ["thesis"] + +[template] +path = "template" +entrypoint = "main.typ" +thumbnail = "thumbnail.png"