diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/CHANGELOG.md b/packages/preview/modern-zhaw-thesis/0.4.0/CHANGELOG.md new file mode 100644 index 0000000000..d62fba4c13 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +## 0.4.0 + +- **BREAKING (layout shifts)**: Tweaked H1 font size and spacing based on feedback +- **BREAKING**: `biblio: (file:, style:)` replaced by `bibliography:` — pass a `bibliography()` object directly +- **BREAKING**: `hide-frontmatter` replaced by `preset` — use `preset: "draft"` for equivalent behaviour +- Added `preset` parameter: `"thesis"` (default, show everything), `"draft"` (hide all frontmatter), `"exercise"` (show cover page + TOC, hide abstract/acknowledgements/declaration) +- Replaced custom Glossy patch with official 0.9.1 release. Should not be a breaking change. + +## 0.3.0 + +- Allow setting overrideable options to `none` +- Fixed edge cases when some options were set to `none` and caused errors +- Fixed declaration of originality appendix ref +- Fixed bug displaying supervisor names in acknowledgements page +- Fixed localisation of "Chapter" label in page header +- Fixed long H1s overlapping with next section + +## 0.2.0 + +- Added custom quote styling +- Fixed glossary issues when no entries are provided +- Localised outline and appendix headings +- Fixed Glossy init-glossary validation bug +- Fixed empty optional supervisor fields +- Fixed translation strings +- Fixed custom declaration of originality body rendering +- Fixed README typos + +## 0.1.1 + +Improved README + +## 0.1.0 + +Initial release diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/LICENSE b/packages/preview/modern-zhaw-thesis/0.4.0/LICENSE new file mode 100644 index 0000000000..03e8c82773 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/LICENSE @@ -0,0 +1,17 @@ +MIT-0 (MIT No Attribution License) +Copyright (c) 2026 Stanislas Laurent + +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. + +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/modern-zhaw-thesis/0.4.0/README.md b/packages/preview/modern-zhaw-thesis/0.4.0/README.md new file mode 100644 index 0000000000..89b6c859d9 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/README.md @@ -0,0 +1,182 @@ +# `modern-zhaw-thesis` template + +`modern-zhaw-thesis` is a comprehensive template for ZHAW academic works (theses, exercises, reports...), adapted from the official branding and following official requirements. It supports both English and German. + +[Here's a showcase](./showcase.pdf) of how it looks like. + +## Setup + +1. Download the font files from the repo's font directory: + + [GitHub](https://github.com/stanlrt/typst-zhaw-thesis/tree/28927077165d31305c4438657eb0cdefa32bbcbd/fonts) + + [Direct download](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fstanlrt%2Ftypst-zhaw-thesis%2Ftree%2F28927077165d31305c4438657eb0cdefa32bbcbd%2Ffonts) + +### Web-app (easiest) + +2. [Create a project using the template](https://typst.app/app?template=modern-zhaw-thesis&version=0.4.0) +3. In the left sidebar, under "Files", create a folder called `fonts` +4. Drag and drop the files into this `fonts` directory + +### Local + +2. Install Typst from https://typst.app/open-source/ +3. Make the downloaded fonts accessible to the compiler (https://typst.app/docs/reference/text/text/#parameters-font) +4. Run `typst init @preview/modern-zhaw-thesis:0.4.0` in your project directory + +## All configuration options + +Below is the complete list of configuration options, including default values and explanations. Most are optional. + +```typ +#import "@preview/modern-zhaw-thesis:0.4.0": zhaw-thesis, languages + +// Note: `override` options accept Typst files (`override: [#include "my-override.typ"]`), and `none` + +#show: zhaw-thesis.with( + language: languages.de, // Document language + preset: "thesis", // "thesis": show everything (default) + // "draft": hide all frontmatter (to focus on writing) + // "exercise": show cover+TOC (no abstract/acks./declaration) + + cover: ( + school: none, // E.g., "School of Engineering" REQUIRED + institute: none, // E.g., "Computer Science" REQUIRED + work-type: none, // E.g., "Bachelor Thesis" REQUIRED + title: none, // Work's title REQUIRED + authors: none, // Author name(s), e.g. ("Max Muster", "Erika Muster") REQUIRED + supervisors: none, // Supervisor name(s), two formats: + // 1. Simple: `("Prof. Dr. John Doe", "Dr. Jane Doe")` + // 2. Categorised: `(main: "Prof. Dr. John Doe", + // secondary: "Dr. Jane Doe", external: "Dr. External")` + industry-partner: none, // Industry partner name, e.g. "Schweizer AG" + study-program: none, // E.g., "Computer Science BSc" + override: none, // Override the cover page with your own file + ), + + abstract: ( + keywords: none, // List of keywords, e.g. `("machine learning")` REQUIRED + en: none, // English abstract text + de: none, // German abstract text. REQUIRED by ZHAW even when lang is English. + override: none, // Override the abstract page with your own file + ), + + acknowledgements: ( + text: none, // Custom acknowledgements text + override: none, // Override the acknowledgements page with your own file + ), + + declaration-of-originality: ( + location: none, // E.g., "Zurich" REQUIRED + text: none, // Custom declaration text + override: none, // Override the declaration page with your own file + ), + + bibliography: none, // bibliography() object + // e.g. `bibliography("refs.bib", style: "ieee")` (see Typst's docs) + + glossary-entries: none, // Variable containing glossary entries (see template) + appendix: none, // Appendix Typst file, e.g. [#include "appendix.typ"] + page-border: true, // Enable/disable page border + print-mode: false, // Gives a white background to the cover page to reduce ink usage +) +``` + +## Exported symbols + +The package exports the following symbols for you to use if needed: + +```typ +#import "@preview/modern-zhaw-thesis:0.4.0": ( + zhaw-thesis, // Main template function, see docu above + callout, // Coloured callout box to highlight important text + + centered, // Vertically centred layout + today, // Current date in local format + languages, // Available languages + push-lang, // Set new language (see docu below) + pop-lang, // Switch back to previous language (see docu below) +) +``` + +You can refer to the [demo document](./template/main.typ) for usage examples. + +## Configuration of dependencies + +### Glossary + +The template uses the [Glossy package](https://typst.app/universe/package/glossy/) to power the glossary feature under the hood. +You can find instructions about how to use all its options directly on the [package page](https://typst.app/universe/package/glossy/). + +### Code blocks + +The template uses the [Codly package](https://typst.app/universe/package/codly/) to style code snippets. +You can find instructions about how to use all its options in [its PDF manual](https://github.com/Dherse/codly/blob/ece8099b46cd899ea99a835ebd78c57ef2dd0b74/docs.pdf). + +### Multi-linguism + +Should you need use different languages for different pages of your work, you can refer to [Tieflang's documentation](https://typst.app/universe/package/tieflang/). + +## Feature requests & problems + +Feel free to [request features or report problems here](https://github.com/stanlrt/typst-zhaw-thesis/issues) + +## License and Trademark Notice + +### Software License + +The Typst code and template structure in this package are licensed under the **MIT-0 License** (MIT No Attribution). See the [LICENSE](LICENSE) file for details. + +### ZHAW Logo and Trademark + +**Important:** The ZHAW logo and trademark included in this package are **NOT** covered by the MIT-0 license. + +- **Trademark Owner:** The ZHAW logo and brand are the exclusive property of Zürcher Hochschule für Angewandte Wissenschaften (ZHAW). +- **Rights:** Users of this package do **not** receive any rights to sublicense, sell, or redistribute the ZHAW logo under the MIT-0 license terms. +- **Usage:** The logo is included for the specific purpose of creating ZHAW-compliant academic documents. Any other use requires permission from ZHAW. + +As specified in section 2.b.2 of Creative Commons and similar public licenses: **"The trademark ZHAW is not affected by the license. Patent and trademark rights are not licensed under this Public License."** + +For information about ZHAW's branding guidelines, refer to the official [ZHAW trademark guidelines for open educational resources](https://gpmpublic.zhaw.ch/GPMDocProdDPublic/Vorgabedokumente_ZHAW/Z_MB_Merkblatt_CC_Lizenzen_von_OER.pdf). + +### Unofficial Template Notice + +This is an **unofficial** template created by a student. It is not endorsed, maintained, or supported by ZHAW. The template attempts to follow official ZHAW branding guidelines and academic requirements, but users should verify compliance with current university standards. + +## Changelog + +### 0.4.0 + +- **BREAKING (layout shifts)**: Tweaked H1 font size and spacing based on feedback +- **BREAKING**: `biblio: (file:, style:)` replaced by `bibliography:` — pass a `bibliography()` object directly (refer to [Typst's docs](https://typst.app/docs/reference/model/bibliography/)) +- **BREAKING**: `hide-frontmatter` replaced by `preset` — use `preset: "draft"` for equivalent behaviour +- Added `preset` parameter: `"thesis"` (default, show everything), `"draft"` (hide all frontmatter), `"exercise"` (show cover page + TOC, hide abstract/acknowledgements/declaration) +- Replaced custom Glossy patch with official 0.9.1 release. Should not be a breaking change. + +### 0.3.0 + +- Allow setting overrideable options to `none` +- Fixed edge cases when some options were set to `none` and caused errors +- Fixed declaration of originality appendix ref +- Fixed bug displaying supervisor names in acknowledgements page +- Fixed localisation of "Chapter" label in page header +- Fixed long H1s overlapping with next section + +### 0.2.0 + +- Added custom quote styling +- Fixed glossary issues when no entries are provided +- Localised outline and appendix headings +- Fixed Glossy init-glossary validation bug +- Fixed empty optional supervisor fields +- Fixed translation strings +- Fixed custom declaration of originality body rendering +- Fixed README typos + +### 0.1.1 + +Improved README + +### 0.1.0 + +Initial release diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/lib.typ b/packages/preview/modern-zhaw-thesis/0.4.0/lib.typ new file mode 100644 index 0000000000..f479956484 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/lib.typ @@ -0,0 +1,4 @@ +#import "src/zhaw-thesis.typ": callout, zhaw-thesis +#import "src/styling/tokens.typ": tokens +#import "src/utils.typ": centered, today +#import "src/translations.typ": languages, pop-lang, push-lang diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/showcase.pdf b/packages/preview/modern-zhaw-thesis/0.4.0/showcase.pdf new file mode 100644 index 0000000000..24c3d55bf7 Binary files /dev/null and b/packages/preview/modern-zhaw-thesis/0.4.0/showcase.pdf differ diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/assets/zhaw_logo.png b/packages/preview/modern-zhaw-thesis/0.4.0/src/assets/zhaw_logo.png new file mode 100644 index 0000000000..6ffd46b837 Binary files /dev/null and b/packages/preview/modern-zhaw-thesis/0.4.0/src/assets/zhaw_logo.png differ diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/assets/zhaw_sw_pos.jpg b/packages/preview/modern-zhaw-thesis/0.4.0/src/assets/zhaw_sw_pos.jpg new file mode 100644 index 0000000000..c3c439c1b3 Binary files /dev/null and b/packages/preview/modern-zhaw-thesis/0.4.0/src/assets/zhaw_sw_pos.jpg differ diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/frontmatter.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/frontmatter.typ new file mode 100644 index 0000000000..a9b3a0144e --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/frontmatter.typ @@ -0,0 +1,91 @@ +#import "pages/title.typ": title-page +#import "pages/abstract.typ": abstract-page +#import "pages/acknowledgements.typ": acknowledgements-page +#import "pages/originality.typ": declaration-of-originality-page + +#let zhaw-cover-page(cover, print-mode: false) = { + if cover == none { + return + } + if cover.override != none { + cover.override + } else { + title-page( + school: cover.school, + institute: cover.institute, + work_type: cover.work-type, + title: cover.title, + authors: cover.authors, + supervisors: cover.supervisors, + co-supervisors: cover.co-supervisors, + industry-partner: cover.industry-partner, + print-mode: print-mode, + ) + } +} + +#let zhaw-abstract-section(abstract, cover) = { + if abstract == none { + return + } + if abstract.override != none { + abstract.override + } else { + if cover == none { + panic( + "`cover` metadata is required when the abstract section is included. Set `abstract: none` to omit the section, or provide a `cover` dictionary.", + ) + } + if abstract.de == none { + panic("ZHAW requires a German abstract even for English works.") + } + abstract-page( + en: abstract.en, + de: abstract.de, + keywords: abstract.keywords, + authors: cover.authors, + title: cover.title, + ) + } +} + +#let zhaw-acknowledgements-section(acknowledgements, cover) = { + if acknowledgements == none { + return + } + if acknowledgements.override != none { + acknowledgements.override + } else { + if cover == none { + panic( + "`cover` metadata is required when the acknowledgements section is included. Set `acknowledgements: none` to omit the section, or provide a `cover` dictionary.", + ) + } + acknowledgements-page( + acknowledgements: acknowledgements.text, + supervisors: cover.supervisors, + co-supervisors: cover.co-supervisors, + authors: cover.authors, + ) + } +} + +#let zhaw-declaration-section(declaration-of-originality, cover) = { + if declaration-of-originality == none { + return + } + if declaration-of-originality.override != none { + declaration-of-originality.override + } else { + if cover == none { + panic( + "`cover` metadata is required when the declaration of originality section is included. Set `declaration-of-originality: none` to omit the section, or provide a `cover` dictionary.", + ) + } + declaration-of-originality-page( + declaration_of_originality: declaration-of-originality.text, + location: declaration-of-originality.location, + authors: cover.authors, + ) + } +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/abstract.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/abstract.typ new file mode 100644 index 0000000000..c467ef7df1 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/abstract.typ @@ -0,0 +1,50 @@ +#import "../utils.typ": centered, ensure-array, today +#import "@preview/tieflang:0.1.0": pop-lang, push-lang, tr + +#let render-abstract(lang-code, abstract-text, keywords, authors, title) = context { + push-lang(lang-code) + + show: doc => centered(tr().abstract, doc) + show grid.cell.where(x: 0): set text(weight: "bold") + + let authors = ensure-array(authors) + let keywords = ensure-array(keywords) + + grid( + columns: (2cm, 1fr), + column-gutter: 0.4cm, + row-gutter: 0.3cm, + tr().title, title, + (tr().author)(authors.len()), authors.join(", "), + tr().date, today(), + tr().institution, tr().institution_name, + ) + + v(0.5cm) + abstract-text + v(0.5cm) + + grid( + columns: (auto, 1fr), + column-gutter: 0.4cm, + row-gutter: 0.3cm, + tr().keywords, keywords.join(", "), + ) + + pop-lang() +} + +#let abstract-page( + en: none, + de: none, + keywords: none, + authors: none, + title: none, +) = { + if en != none { + render-abstract("en", en, keywords, authors, title) + } + if de != none { + render-abstract("de", de, keywords, authors, title) + } +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/acknowledgements.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/acknowledgements.typ new file mode 100644 index 0000000000..1742b211d9 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/acknowledgements.typ @@ -0,0 +1,26 @@ +#import "../utils.typ": centered, ensure-array, flatten-supervisor-names +#import "@preview/tieflang:0.1.0": tr + +#let acknowledgements-page( + acknowledgements: none, + supervisors: none, + co-supervisors: none, + authors: none, +) = { + let authors = ensure-array(authors) + let supervisors = flatten-supervisor-names(supervisors) + let co-supervisors = ensure-array(co-supervisors) + + let count = supervisors.len() + co-supervisors.len() + let thanked = (supervisors + co-supervisors).join(", ", last: " " + tr().and + " ") + show: doc => centered(tr().acknowledgements.title, doc) + + if acknowledgements != none [ + + #acknowledgements + ] else [ + #let plural = authors.len() > 1 + + #(tr().acknowledgements.text)(plural, count, thanked) + ] +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/originality.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/originality.typ new file mode 100644 index 0000000000..c0c3e5d7f4 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/originality.typ @@ -0,0 +1,43 @@ +#import "../utils.typ": centered, today, ensure-array +#import "@preview/tieflang:0.1.0": tr + +#let declaration-of-originality-page( + declaration_of_originality: none, + location: none, + authors: none, +) = { + show: doc => centered(tr().declaration_of_originality.title, doc) + + let authors = ensure-array(authors) + + if (declaration_of_originality != none) [ + + #declaration_of_originality + ] else [ + #let plural = authors.len() > 1 + + #(tr().declaration_of_originality.text)(plural) + ] + + v(1cm) + + align(left)[ + *#location, #today()* + ] + + v(0.5cm) + + grid( + columns: (auto, 4cm), + column-gutter: 0.4cm, + row-gutter: 1.2cm, + ..for author in authors { + ( + author + ":", + align(bottom, pad(bottom: -0.2cm, box( + line(length: 5cm, stroke: 0.8pt), + ))), + ) + } + ) +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/title.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/title.typ new file mode 100644 index 0000000000..ee5c2fb1fe --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/pages/title.typ @@ -0,0 +1,159 @@ +#import "../utils.typ": * +#import "../styling/tokens.typ": tokens +#import "@preview/tieflang:0.1.0": tr +#import "@preview/biceps:0.0.1": flexwrap + +#let title-page( + school: none, + institute: none, + work_type: none, + title: none, + authors: none, + supervisors: none, + co-supervisors: none, + industry-partner: none, + print-mode: false, +) = { + set page(fill: if (not print-mode) { tokens.colour.main } else { none }, margin: 1cm, background: none) + set par(leading: 0.8em) + set text( + fill: if (not print-mode) { white } else { tokens.colour.main }, + weight: "bold", + font: tokens.font-families.headers, + ) + + let authors = ensure-array(authors) + + // Handle two formats for supervisors: + // 1. String or array of strings: supervisors: ("Name 1", "Name 2") + // 2. Dictionary: supervisors: (main: "Name", secondary: "Name", external: "Name") + let supervisors-dict = none + let supervisors-list = none + + if supervisors != none and type(supervisors) == dictionary { + supervisors-dict = supervisors + } else { + supervisors-list = ensure-array(supervisors) + } + + let co-supervisors = ensure-array(co-supervisors) + + let logo = ( + image(if (print-mode) { "../assets/zhaw_sw_pos.jpg" } else { "../assets/zhaw_logo.png" }), + pad(right: 30%, top: 18pt, text( + size: 18pt, + tr().school_of + " " + school + linebreak() + institute, + )), + ) + + let optical_alignment = 8pt + + let title = align(horizon, block(inset: (left: optical_alignment))[ + #pad(bottom: -1.2em, upper(text(size: 16pt, weight: "regular", font: tokens.font-families.body, work_type))) + #linebreak() + #par(leading: 2em, justify: false, first-line-indent: 0pt, text(size: 32pt, title)) + ]) + + let info() = { + set par(justify: false) + set text(size: 10pt) + + let info-items = ( + block[ + #(tr().author)(authors.len()):\ + #text(font: tokens.font-families.body, weight: "regular", authors.join(linebreak())) + ], + ) + + // Handle simple supervisors format + if supervisors-list != none and supervisors-list.len() > 0 { + info-items.push( + align(start + top)[ + #(tr().supervisor)(supervisors-list.len()):\ + #text(font: tokens.font-families.body, weight: "regular", supervisors-list.join(linebreak())) \ + ], + ) + } + + // Handle dictionary supervisors format + if supervisors-dict != none { + if "main" in supervisors-dict and supervisors-dict.main != none { + let main-list = ensure-array(supervisors-dict.main) + info-items.push( + align(start + top)[ + #(tr().main_supervisor)(main-list.len()):\ + #text(font: tokens.font-families.body, weight: "regular", main-list.join(linebreak())) \ + ], + ) + } + + if "secondary" in supervisors-dict and supervisors-dict.secondary != none { + let secondary-list = ensure-array(supervisors-dict.secondary) + info-items.push( + align(start + top)[ + #(tr().secondary_supervisor)(secondary-list.len()):\ + #text(font: tokens.font-families.body, weight: "regular", secondary-list.join(linebreak())) \ + ], + ) + } + + if "external" in supervisors-dict and supervisors-dict.external != none { + let external-list = ensure-array(supervisors-dict.external) + info-items.push( + align(start + top)[ + #(tr().external_supervisor)(external-list.len()):\ + #text(font: tokens.font-families.body, weight: "regular", external-list.join(linebreak())) \ + ], + ) + } + } + + if co-supervisors.len() > 0 { + info-items.push( + align(start + top)[ + #(tr().co_supervisor)(co-supervisors.len()):\ + #text(font: tokens.font-families.body, weight: "regular", co-supervisors.join(linebreak())) \ + ], + ) + } + + if industry-partner != none { + info-items.push( + align(start + top)[ + #tr().industry_partner:\ + #text(font: tokens.font-families.body, weight: "regular", industry-partner) \ + ], + ) + } + + info-items.push( + align(start + top)[ + #tr().submitted_on:\ + #text(font: tokens.font-families.body, weight: "regular", today()) + ], + ) + + let min-gutter = 1.5cm + + block(inset: (left: optical_alignment))[ + #flexwrap( + main-dir: ltr, + cross-dir: ttb, + main-spacing: min-gutter, + cross-spacing: 1cm, + ..info-items.map(item => block(width: auto, breakable: false, item)), + ) + ] + } + + grid( + columns: (3cm, 1fr), + rows: (3cm, 1fr, auto), + gutter: 0.5cm, + ..logo, + "", + title, + "", + info(), + ) +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/figures.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/figures.typ new file mode 100644 index 0000000000..6e05c61255 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/figures.typ @@ -0,0 +1,20 @@ +#let figure-styles(doc) = { + let fig_padding = 0.4cm + + show figure: fig => { + v(fig_padding) + fig + } + + show figure.caption: caption => { + set text(style: "italic") + v(fig_padding) + text(style: "normal", weight: "bold", caption.supplement + " " + caption.counter.display() + ": ") + caption.body + v(fig_padding) + } + + // show figure.where(kind: raw): set figure(supplement: [Code Snippet]) + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/glossary.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/glossary.typ new file mode 100644 index 0000000000..1962114907 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/glossary.typ @@ -0,0 +1,52 @@ +#import "@preview/glossy:0.9.1": * + +#let glossary-theme = ( + section: (title, body) => { + heading(level: 1, numbering: none)[#title] + body + }, + group: (name, index, total, body) => { + if name != "" and total > 1 { + heading(level: 2, numbering: none)[#name] + } + body + }, + entry: (entry, index, total) => { + let label = text(weight: "bold")[ + #entry.short#entry.label + #if entry.long != none { + " (" + entry.long + ")" + } + ] + let description = entry.description + + block(breakable: false)[ + #grid( + columns: (auto, 1fr, auto), + label, h(1fr), entry.pages.join(", "), + ) + #v(-0.1cm) + #description + #v(0.1cm) + ] + }, +) + +#let styled-glossary = context { + // Glossy creates metadata for each defined term during init-glossary + // Check if any terms are actually used/referenced in the document + let all-metadata = query(metadata) + + // Get all defined term keys from metadata + let term-keys = all-metadata.filter(m => type(m.value) == str).map(m => m.value) + + // Check if any of these terms have been referenced + // Glossy creates labels with "__gloss:" prefix for term references + let has-referenced-terms = term-keys.any(key => { + query(label("__gloss:" + key)).len() > 0 + }) + + if has-referenced-terms { + glossary(theme: glossary-theme) + } +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/math-and-code.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/math-and-code.typ new file mode 100644 index 0000000000..77cf0e7355 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/math-and-code.typ @@ -0,0 +1,16 @@ +#import "@preview/codly:1.3.0": codly, codly-init +#import "@preview/codly-languages:0.1.10": codly-languages + +#let code-styles(doc) = { + show: codly-init + codly( + languages: codly-languages, + fill: rgb("#fafafa"), + ) + + set math.equation(numbering: "(1)") + // show math.equation: set text(font: "Fira Math") + show raw: set text(font: "Fira Code") //Code blocks + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/page-border.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/page-border.typ new file mode 100644 index 0000000000..9252e511ab --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/page-border.typ @@ -0,0 +1,19 @@ +#import "tokens.typ": tokens + +#let page-border-styles(page-border, doc) = { + let blue-frame = if page-border { + rect( + width: 100%, + height: 100%, + stroke: ( + paint: tokens.colour.main, + thickness: 16pt, + cap: "round", + ), + ) + } else { none } + + set page(paper: "a4", margin: tokens.margin, background: blue-frame) + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/page-header.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/page-header.typ new file mode 100644 index 0000000000..f6e16ead9b --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/page-header.typ @@ -0,0 +1,53 @@ +#import "tokens.typ": tokens +#import "@preview/tieflang:0.1.0": tr +#import "../utils.typ": ellipsisise + +#let page-header-styles(doc) = { + set page( + header-ascent: 30%, + header: context { + let fill-line(left-text, right-text) = { + set par( + first-line-indent: ( + amount: 0em, + ), + ) + + [#left-text #h(1fr) #ellipsisise(80%, right-text)] + } + + let page-number = here().page() + + // If the current page is the start of a chapter, don't show a header + let target = heading.where(level: 1) + if query(target).any(it => it.location().page() == page-number) { + return [] + } + + // Find the chapter of the section we are currently in. + let before = query(target.before(here())) + if before.len() > 0 { + let current = before.last() + + let chapter-title = current.body + let chapter-number = counter(heading.where(level: 1)).display() + let chapter-number-text = [#tr().chapter #chapter-number] + + if current.numbering != none { + let (left-text, right-text) = if calc.odd(page-number) { + (chapter-number-text, chapter-title) + } else { + (chapter-title, chapter-number-text) + } + // text(weight: "regular", font: tokens.font-families.headers, + fill-line(left-text, right-text) + // ) + v(-tokens.spacing) + line(length: 100%, stroke: 0.5pt) + } + } + }, + ) + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/table.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/table.typ new file mode 100644 index 0000000000..4942f5c681 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/table.typ @@ -0,0 +1,31 @@ +#import "tokens.typ": tokens + +#let table-styles(doc) = { + show table.cell.where(y: 0): strong + set table( + fill: (_, y) => if y == 0 { tokens.colour.main } else { tokens.colour.lightest }, + stroke: none, + gutter: 0.08cm, + align: horizon + left, + inset: (x: 8pt, y: 10pt), + ) + show table.cell.where(y: 0): c => { + text(fill: white, font: tokens.font-families.headers, size: tokens.font-sizes.body - 1.4pt, c) + } + + show table.cell: c => { + set par(justify: false) + set text(size: tokens.font-sizes.body - 1pt) + c + } + + show table: t => { + block( + clip: true, + radius: tokens.radius, + t, + ) + } + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/text.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/text.typ new file mode 100644 index 0000000000..e58a11a36b --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/text.typ @@ -0,0 +1,115 @@ +#import "tokens.typ": tokens +#import "../utils.typ": show_if_heading_within_distance, v-after-numbered-chapter-heading + +#let text-styles(doc) = { + set par( + spacing: 1.4em, + leading: 0.7em, + justify: true, + linebreaks: "optimized", + ) + + set text( + font: tokens.font-families.body, + size: tokens.font-sizes.body, + ) + + show heading.where(level: 4): set heading(numbering: none) + set heading(numbering: (first, ..nums) => numbering("1.1", first, ..nums)) + + show heading: it => { + set text(font: tokens.font-families.headers, fill: tokens.colour.main, weight: "bold") + let level_based_spacing = if it.level == 2 { 0.4 } else { 1.4 / it.level } + + // Reduce spacing between 2 consecutive headings + show_if_heading_within_distance( + it: it, + distance: 2 * tokens.spacing + 1cm, + look: "before", + to-show: v(-2 * level_based_spacing * tokens.spacing), + ) + + v(tokens.spacing * level_based_spacing) + it + v(tokens.spacing * level_based_spacing - 0.05cm) + } + + show heading.where(level: 1): it => { + set text(font: tokens.font-families.headers) + if it.numbering == none { + pagebreak() + text(fill: tokens.colour.main, size: tokens.font-sizes.h1, it) + v(tokens.spacing) + } else { + set par(justify: false) + let heading_number = counter(heading.where(level: 1)).display() + + context { + // Only add pagebreak if this is not the first numbered chapter + let chapter_num = counter(heading.where(level: 1)).at(here()).first() + // If chapter > 1, always pagebreak + // If chapter = 1, only pagebreak if there's body content before (abstract, acknowledgements) + if chapter_num > 1 { + pagebreak() + } else { + let before_pars = query(selector(par).before(here())) + if before_pars.len() > 0 { + pagebreak() + } + } + } + + grid( + columns: (3cm, 1fr), + rows: auto, + gutter: -0.8cm, + align: top + start, + block(width: 100%, pad(left: -0.5em, text(fill: tokens.colour.main, size: tokens.font-sizes.h1 + 58pt, heading_number))), + pad(top:0.1cm, text(fill: tokens.colour.main, size: tokens.font-sizes.h1, it.body)), + ) + v-after-numbered-chapter-heading(it) + } + } + + show heading.where(level: 3): set text(size: tokens.font-sizes.h3) + + show heading.where(level: 4): it => { + set text( + font: tokens.font-families.headers, + fill: tokens.colour.main, + weight: "regular", + size: tokens.font-sizes.h4, + ) + it + } + + set quote(block: true) + + show quote: it => { + if it.block { + pad(left: 0.21em, block( + stroke: ( + left: ( + thickness: 2pt, + paint: tokens.colour.main, + cap: "round", + ), + ), + inset: (left: 1em), + { + it.body + + if it.attribution != none { + parbreak() + // h(1fr) + text(fill: tokens.colour.main)[— #it.attribution] + } + }, + )) + } else { + it + } + } + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/tokens.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/tokens.typ new file mode 100644 index 0000000000..2b29079955 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/styling/tokens.typ @@ -0,0 +1,21 @@ + +#let tokens = ( + colour: ( + "main": rgb("#0064A6"), + "light": rgb("#8FC8EE"), + "lightest": rgb("#deeef8"), + ), + font-families: ( + body: "Tex Gyre Heros", + headers: "Arial Rounded MT", //Nunito + ), + font-sizes: ( + "h1": 28pt, + "h3": 11pt, + "h4": 11pt, + "body": 11pt, + ), + margin: 2.5cm, + radius: 4pt, + spacing: 0.4cm, +) diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/translations.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/translations.typ new file mode 100644 index 0000000000..4d22365df3 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/translations.typ @@ -0,0 +1,144 @@ +#import "@preview/tieflang:0.1.0": configure-translations, pop-lang, push-lang, tr +#import "utils.typ": title-case + +#let translations = ( + de: ( + abstract: [Zusammenfassung], + title: [Titel], + author: count => if count == 1 { [Autor] } else { [Autoren] }, + supervisor: count => if count == 1 { [Betreuer] } else { [Betreuer] }, + co_supervisor: count => if count == 1 { [Mitbetreuer] } else { [Mitbetreuer] }, + main_supervisor: count => if count == 1 { [Hauptbetreuer] } else { [Hauptbetreuer] }, + secondary_supervisor: count => if count == 1 { [Nebenbetreuer] } else { [Nebenbetreuer] }, + external_supervisor: count => if count == 1 { [Externer Betreuer] } else { [Externe Betreuer] }, + industry_partner: [Industriepartner], + acknowledgements: ( + title: [Danksagung], + text: (plural, supervisor-count, supervisors) => context { + let p = (tr().pronoun)(plural) + let verb = if plural { "danken" } else { "danke" } + let possessive = if supervisor-count > 1 { "ihre" } else { "seine/ihre" } + let was = if supervisor-count > 1 { "waren" } else { "war" } + + [#title-case(p) #verb #supervisors herzlich für #possessive Expertise und kontinuierliche Beratung. #title-case(possessive) Führung und Unterstützung #was von unschätzbarem Wert für den erfolgreichen Abschluss dieses Projekts.] + }, + ), + "and": [und], + pronoun: plural => if plural { "wir" } else { "ich" }, + have: plural => if plural { "haben" } else { "habe" }, + be: plural => if plural { "sind" } else { "bin" }, + verb_suffix_first: plural => if plural { "en" } else { "e" }, + declaration_of_originality: ( + title: [Eidesstattliche Erklärung], + text: plural => { + let student = if plural { "die Studierenden" } else { "der/die Studierende" } + let pronoun = if plural { "sie" } else { "er/sie" } + let versichert = if plural { "versichern" } else { "versichert" } + let hat = if plural { "haben" } else { "hat" } + let erklaert = if plural { "erklären" } else { "erklärt" } + + [ + Mit der Abgabe dieser Projektarbeit #versichert #student, dass #pronoun die Arbeit selbständig und ohne fremde Hilfe verfasst #hat. (Bei Gruppenarbeiten gelten die Leistungen der übrigen Gruppenmitglieder nicht als fremde Hilfe.) + + Der/die unterzeichnende Studierende #erklaert, dass alle zitierten Quellen (auch Internetseiten) im Text oder Anhang korrekt nachgewiesen sind, d.h. dass die Projektarbeit keine Plagiate enthält, also keine Teile, die teilweise oder vollständig aus einem fremden Text oder einer fremden Arbeit unter Vorgabe der eigenen Urheberschaft bzw. ohne Quellenangabe übernommen worden sind. + + #context if query(label("appendix:ai")).len() > 0 [ + KI-Systeme wurden im Rahmen dieser Arbeit wie in @appendix:ai angegeben verwendet. + ] + + Bei Verfehlungen aller Art treten die Paragraphen 39 und 40 (Unredlichkeit und Verfahren bei Unredlichkeit) der ZHAW Prüfungsordnung sowie die Bestimmungen der Disziplinarmassnahmen der Hochschulordnung in Kraft. + ] + }, + ), + location: [Ort], + date: [Datum], + institution: [Institution], + institution_name: [ZHAW Zürcher Hochschule für Angewandte Wissenschaften], + keywords: [Schlüsselwörter], + submitted_on: [Eingereicht am], + school_of: [School of], + institute_of: [Institut für], + table_of_contents: [Inhaltsverzeichnis], + glossary: [Glossar], + code_snippet: [Code-Snippet], + chapter: [Kapitel], + appendix: [Anhang], + ), + en: ( + abstract: [Abstract], + title: [Title], + author: count => if count == 1 { [Author] } else { [Authors] }, + supervisor: count => if count == 1 { [Supervisor] } else { [Supervisors] }, + co_supervisor: count => if count == 1 { [Co-supervisor] } else { [Co-supervisors] }, + main_supervisor: count => if count == 1 { [Main supervisor] } else { [Main supervisors] }, + secondary_supervisor: count => if count == 1 { [Secondary supervisor] } else { [Secondary supervisors] }, + external_supervisor: count => if count == 1 { [External supervisor] } else { [External supervisors] }, + industry_partner: [Industry partner], + acknowledgements: ( + title: [Acknowledgements], + text: (plural, supervisor-count, supervisors) => context { + let p = (tr().pronoun)(plural) + let possessive = "their" + let was = if supervisor-count > 1 { "were" } else { "was" } + + [#title-case(p) sincerely thank #supervisors for #possessive expertise and continuous advice. #title-case(possessive) guidance and support #was invaluable to the successful completion of this project.] + }, + ), + "and": [and], + pronoun: plural => if plural { "we" } else { "I" }, + have: plural => if plural { "have" } else { "have" }, + be: plural => if plural { "are" } else { "am" }, + verb_suffix_first: plural => if plural { "" } else { "s" }, + declaration_of_originality: ( + title: [Declaration of Originality], + text: plural => { + let student = if plural { "students" } else { "student" } + let confirm = if plural { "confirm" } else { "confirms" } + let their = "their" + let declare = if plural { "declare" } else { "declares" } + + [ + By submitting this project work, the undersigned #student #confirm that this work is #their own work and was written without the help of a third party. (Group works: the performance of the other group members are not considered as third party). + + The #student #declare that all sources in the text (including Internet pages) and appendices have been correctly disclosed. This means that there has been no plagiarism, i.e. no sections of the project work have been partially or wholly taken from other texts and represented as the student's own work or included without being correctly referenced. + + #context if query(label("appendix:ai")).len() > 0 [ + AI systems were used in the process of this work, as specified in @appendix:ai. + ] + + Any misconduct will be dealt with according to paragraphs 39 and 40 of the General Academic Regulations for Bachelor's and Master's Degree courses at the Zurich University of Applied Sciences (Rahmenprüfungsordnung ZHAW (RPO)) and subject to the provisions for disciplinary action stipulated in the University regulations. + ] + }, + ), + location: [Location], + date: [Date], + institution: [Institution], + institution_name: [ZHAW University of Applied Sciences], + keywords: [Keywords], + submitted_on: [Submitted on], + school_of: [School of], + institute_of: [Institute of], + table_of_contents: [Table of Contents], + glossary: [Glossary], + code_snippet: [Code Snippet], + chapter: [Chapter], + appendix: [Appendix], + ), +) + +#let languages = ( + de: "de", + en: "en", +) + +#let setup-language(lang, doc) = { + configure-translations(translations) + push-lang(lang) + + set text( + lang: lang, + region: if lang == "en" { "gb" } else { "ch" }, + ) + + doc +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/utils.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/utils.typ new file mode 100644 index 0000000000..50fd14607d --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/utils.typ @@ -0,0 +1,209 @@ +#import "styling/tokens.typ": tokens +#import "@preview/datify:1.0.1": * + +// See https://github.com/typst/typst/issues/2953 +#let show_if_heading_within_distance( + it: none, + distance: 1cm, + look: "before", + to-show: v(-1cm), + if-not: false, +) = { + if it == none { + panic("Parameter \"it\" of \"show_if_heading_within_distance\" must be defined.") + } + + // 1. Find neighbouring headings + let sel = selector(heading) + let neighbours = () + + if look == "before" { + neighbours = query(sel.before(here(), inclusive: false)) + } else if look == "after" { + neighbours = query(sel.after(here(), inclusive: false)) + } else { + panic("Parameter \"look\" of \"show_if_heading_within_distance\" must have value \"before\" or \"after\".") + } + + if neighbours.len() > 0 { + // 2. Get the closest heading (last 'before' or first 'after'). + let neighbour = if look == "before" { + neighbours.last() + } else { + neighbours.first() + } + + // 3. Get the absolute positions of the current element and the neighbour. + let iloc = it.location().position() + let nloc = neighbour.location().position() + + // 4. Check if distance is within threshold + if iloc.page == nloc.page and iloc.x == nloc.x { + let vertical-distance = if look == "before" { + iloc.y - nloc.y + } else { + nloc.y - iloc.y + } + + if vertical-distance < distance { + if (if-not) { return none } + return to-show + } + } + } + + if (if-not) { return to-show } + return none +} + +#let content-to-string(c) = { + if type(c) == str { c } + else if c == [ ] { " " } + else if c.has("text") { c.text } + else if c.has("children") { c.children.map(content-to-string).join("") } + else if c.has("body") { content-to-string(c.body) } + else { "" } +} + +#let ellipsisise(max-width, text) = box(layout(size => context { + let width(t) = measure(t).width + let limit = if type(max-width) == ratio { max-width * size.width } else { max-width } + let s = content-to-string(text) + let s_orig = s + let ellipsis = "..." + + while s.len() > 0 and width(s) + width(ellipsis) > limit { + s = s.slice(0, calc.max(0, s.len() - 1)) + } + + if s == s_orig { + s + } else { + s + ellipsis + } +})) + +#let v-after-numbered-chapter-heading(it) = context { + if it == none { + panic("Parameter \"it\" of \"v-after-numbered-chapter-heading\" must be defined.") + } + let loc = it.location() + let heads-after = query(selector(heading).after(loc, inclusive: false)) + let pars-after = query(selector(par).after(loc, inclusive: false)) + let next-h = if heads-after.len() > 0 { heads-after.first() } + let next-p = if pars-after.len() > 0 { pars-after.first() } + + if next-h == none { + v(1cm) + } else if next-p == none { + v(0.5cm) + } else { + let h-pos = next-h.location().position() + let p-pos = next-p.location().position() + let heading-first = h-pos.page < p-pos.page or (h-pos.page == p-pos.page and h-pos.y < p-pos.y) + if heading-first { + v(0.5cm) + } else { + v(1cm) + } + } +} + +#let today() = context { + let current-date = datetime.today() + [#custom-date-format(current-date, pattern: "long", lang: text.lang)] +} + +#let ensure-array(unsafe-input) = { + if unsafe-input == none { + () + } else if type(unsafe-input) != array { + (unsafe-input,) + } else { + unsafe-input + } +} + +// Same supervisor shapes as on the title page: plain array, or dict with main / secondary / external (each string or array). +#let flatten-supervisor-names(supervisors) = { + if supervisors == none { + () + } else if type(supervisors) == dictionary { + let names = () + for key in ("main", "secondary", "external") { + if key in supervisors { + let value = supervisors.at(key) + if value != none { + names = names + ensure-array(value) + } + } + } + names + } else { + ensure-array(supervisors) + } +} + +#let centered(title, content) = { + pagebreak() + set align(center + horizon) + set par(first-line-indent: 0cm) + align(start + horizon)[ + #text( + font: tokens.font-families.headers, + fill: tokens.colour.main, + weight: "bold", + size: tokens.font-sizes.h1, + title, + ) + #content + ] +} + + +// When `override` is `none`, the whole optional block is omitted (`none`), not replaced by defaults. +// For settings where `none` should mean "fall back to defaults" (e.g. bibliography), use `deep-merge-or-default`. +#let deep-merge(base, override) = { + if override == none { + return none + } + + let result = (:) + + // Copy all keys from base + for (key, value) in base { + result.insert(key, value) + } + + // Apply overrides + for (key, value) in override { + // Skip empty arrays - keep the base value + if type(value) == array and value.len() == 0 { + continue + } + + if key in base and type(value) == dictionary and type(base.at(key)) == dictionary { + result.insert(key, deep-merge(base.at(key), value)) + } else { + result.insert(key, value) + } + } + + result +} + +#let deep-merge-or-default(base, override) = { + let merged = deep-merge(base, override) + if merged == none { + base + } else { + merged + } +} + +#let title-case(string) = { + return str(string).replace( + regex("[A-Za-z]+('[A-Za-z]+)?"), + word => upper(word.text.first()) + lower(word.text.slice(1)), + ) +} diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/src/zhaw-thesis.typ b/packages/preview/modern-zhaw-thesis/0.4.0/src/zhaw-thesis.typ new file mode 100644 index 0000000000..48679cb9af --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/src/zhaw-thesis.typ @@ -0,0 +1,179 @@ +#import "utils.typ": deep-merge +#import "frontmatter.typ": ( + zhaw-abstract-section, zhaw-acknowledgements-section, zhaw-cover-page, zhaw-declaration-section, +) +#import "styling/tokens.typ": tokens +#import "styling/math-and-code.typ": code-styles +#import "styling/glossary.typ": init-glossary, styled-glossary +#import "styling/text.typ": text-styles +#import "styling/figures.typ": figure-styles +#import "styling/table.typ": table-styles +#import "styling/page-header.typ": page-header-styles +#import "styling/page-border.typ": page-border-styles +#import "translations.typ": languages, setup-language +#import "@preview/tieflang:0.1.0": tr + +#let zhaw-thesis( + language: languages.de, + cover: ( + school: none, + institute: none, + work-type: none, + title: none, + authors: none, + supervisors: none, + industry-partner: none, + study-program: none, + override: none, + ), + abstract: ( + keywords: none, + en: none, + de: none, + override: none, + ), + acknowledgements: ( + text: none, + override: none, + ), + declaration-of-originality: ( + location: none, + text: none, + override: none, + ), + glossary-entries: none, + /// Pass a bibliography() object directly, e.g. bibliography("refs.bib", style: "ieee"). + bibliography: none, + appendix: none, + page-border: true, + /// "thesis": show everything (default). "draft": hide all frontmatter. "exercise": show cover only. + preset: "thesis", + print-mode: false, + doc, +) = { + // Apply deep-merge to nested structures with defaults + let cover-defaults = ( + school: none, + institute: none, + work-type: none, + title: none, + authors: none, + supervisors: none, + co-supervisors: none, + industry-partner: none, + study-program: none, + override: none, + ) + let cover = deep-merge(cover-defaults, cover) + + let abstract-defaults = ( + keywords: none, + en: none, + de: none, + override: none, + ) + let abstract = deep-merge(abstract-defaults, abstract) + + let acknowledgements-defaults = ( + text: none, + override: none, + ) + let acknowledgements = deep-merge(acknowledgements-defaults, acknowledgements) + + let declaration-defaults = ( + location: none, + text: none, + override: none, + ) + + let declaration-of-originality = deep-merge(declaration-defaults, declaration-of-originality) + + set enum(numbering: "1.i.1.i.") + + show: setup-language.with(language) + show: doc => { + if glossary-entries != none { + init-glossary(glossary-entries, term-links: true, doc) + } else { + doc + } + } + + show outline.entry.where( + level: 1, + ): it => { + set block(above: 0.5cm) + set text(font: tokens.font-families.headers, weight: "bold") + it + } + show: text-styles + show: page-header-styles + show: figure-styles + show: table-styles + show: code-styles + show ref: set text(fill: tokens.colour.main) + show link: set text(fill: tokens.colour.main) + show: page-border-styles.with(page-border) // Keep after table styles!! + + // context is needed for the tr() calls inside the page functions + context { + if preset == "thesis" or preset == "exercise" { + zhaw-cover-page(cover, print-mode: print-mode) + } + + if preset == "thesis" or preset == "exercise" { + set page(numbering: "i") + counter(page).update(1) + + if preset == "thesis" { + zhaw-abstract-section(abstract, cover) + zhaw-acknowledgements-section(acknowledgements, cover) + zhaw-declaration-section(declaration-of-originality, cover) + } + + outline(title: tr().table_of_contents, depth: 3) + } + + set page(numbering: "1") + counter(page).update(1) + + doc + + if glossary-entries != none { + styled-glossary + } + + if bibliography != none { + bibliography + } + + if appendix != none { + set heading(numbering: none, supplement: tr().appendix) + counter(heading).update(1) + [ + #heading(level: 1)[#tr().appendix] + ] + + set heading(numbering: "1.1.1") + + appendix + } + } +} + + +#import "@preview/colorful-boxes:1.4.3": * + +#let callout(title, text) = colorbox( + title: title, + color: ( + fill: tokens.colour.lightest, + stroke: tokens.colour.main, + title: tokens.colour.main, + ), + radius: tokens.radius, + width: auto, + inset: 10pt, +)[ + #text +] diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/template/appendix.typ b/packages/preview/modern-zhaw-thesis/0.4.0/template/appendix.typ new file mode 100644 index 0000000000..d7fff876d1 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/template/appendix.typ @@ -0,0 +1,17 @@ +== AI Usage Declaration + +During the preparation of this thesis, AI tools were used for the following purposes: + +#figure( + table( + columns: (auto, auto), + [AI Tool], [Purpose], + [Gemini], [Text summarisation and formulation], + [GitHub Copilot], [Code completion and refactoring suggestions], + [Grammarly], [Grammar and spelling corrections in English text], + [Perplexity], [Literature search assistance], + ), + caption: "AI tools used during thesis preparation", +) + +All AI-generated content was thoroughly reviewed, validated, and modified as necessary. The core ideas, analysis, and conclusions are entirely the work of the authors. diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/template/biblio.bib b/packages/preview/modern-zhaw-thesis/0.4.0/template/biblio.bib new file mode 100644 index 0000000000..dadff0fe7f --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/template/biblio.bib @@ -0,0 +1,46 @@ +// Note: the Typst web app offers auto-sync from Zotero and Mendeley + +@article{chandola_2024, + author = {Chandola, Varun and Banerjee, Arindam and Kumar, Vipin}, + title = {Anomaly Detection: A Survey}, + journal = {ACM Computing Surveys}, + year = {2024}, + volume = {41}, + number = {3}, + pages = {1--58}, + doi = {10.1145/1541880.1541882}, +} + +@inproceedings{breunig2000lof, + author = {Breunig, Markus M. and Kriegel, Hans-Peter and Ng, Raymond T. and Sander, J{\"o}rg}, + title = {LOF: Identifying Density-Based Local Outliers}, + booktitle = {Proceedings of the 2000 ACM SIGMOD International Conference on Management of Data}, + year = {2000}, + pages = {93--104}, + publisher = {ACM}, +} + +@article{liu2008isolation, + author = {Liu, Fei Tony and Ting, Kai Ming and Zhou, Zhi-Hua}, + title = {Isolation Forest}, + journal = {IEEE Transactions on Knowledge and Data Engineering}, + year = {2008}, + volume = {20}, + number = {12}, + pages = {1618--1630}, +} + +@book{goodfellow2016deep, + author = {Goodfellow, Ian and Bengio, Yoshua and Courville, Aaron}, + title = {Deep Learning}, + publisher = {MIT Press}, + year = {2016}, +} + +@inproceedings{garcia2021microservices, + author = {Garcia, Alessandro and others}, + title = {Monitoring and Anomaly Detection in Microservices: A Systematic Literature Review}, + booktitle = {IEEE International Conference on Software Architecture}, + year = {2021}, + pages = {201--212}, +} \ No newline at end of file diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/template/glossary.typ b/packages/preview/modern-zhaw-thesis/0.4.0/template/glossary.typ new file mode 100644 index 0000000000..64d4682e44 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/template/glossary.typ @@ -0,0 +1,48 @@ +#let myGlossary = ( + "deployment": ( + short: "deployment", + description: [The process of making a software system operational in a production environment. In distributed systems, deployment involves orchestrating multiple services, configuring network connections, and ensuring system availability during transitions.], + group: "Terms", + ), + "iot": ( + short: "IoT", + long: "Internet of Things", + description: [A network of physical devices embedded with sensors, software, and connectivity that enables them to collect and exchange data. IoT systems often generate massive amounts of monitoring data that require automated analysis.], + group: "Acronyms", + ), + "ML": ( + short: "ML", + long: "machine learning", + description: [A subset of artificial intelligence focused on building systems that learn from data. ML algorithms identify patterns and make predictions without being explicitly programmed for specific tasks.], + group: "Acronyms", + ), + "API": ( + short: "API", + long: "Application Programming Interface", + description: [A set of protocols and tools for building software applications. APIs define how software components should interact, enabling integration between different systems.], + group: "Acronyms", + ), + "microservice": ( + short: "microservice", + description: [An architectural style that structures an application as a collection of loosely coupled, independently deployable services. Each microservice focuses on a specific business capability and communicates with others through well-defined interfaces.], + group: "Terms", + ), + "ntp": ( + short: "NTP", + long: "Network Time Protocol", + description: [A networking protocol for clock synchronization between computer systems over packet-switched networks. NTP is critical for distributed systems to maintain consistent timestamps across multiple machines.], + group: "Acronyms", + ), + "pca": ( + short: "PCA", + long: "Principal Component Analysis", + description: [A dimensionality reduction technique that transforms high-dimensional data into a lower-dimensional space while preserving as much variance as possible. PCA identifies the principal components that capture the most important patterns in the data.], + group: "Acronyms", + ), + "roc": ( + short: "ROC", + long: "Receiver Operating Characteristic", + description: [A graphical plot that illustrates the diagnostic ability of a binary classifier as its discrimination threshold is varied. The ROC curve plots the true positive rate against the false positive rate at various threshold settings.], + group: "Acronyms", + ), +) diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/template/main.typ b/packages/preview/modern-zhaw-thesis/0.4.0/template/main.typ new file mode 100644 index 0000000000..c9a6f077e1 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/template/main.typ @@ -0,0 +1,160 @@ +#import "@preview/modern-zhaw-thesis:0.4.0": * +#import "glossary.typ": myGlossary + +#show: zhaw-thesis.with( + language: languages.en, + cover: ( + school: "Engineering", + institute: "Institute of Computer Science", + work-type: "Bachelor thesis", + title: "ZHAW Thesis Template", + authors: ("Alice Müller", "Bob Schmidt"), + supervisors: ( + main: ("Alice Müller", "Dr. Eve Johnson"), + ), + study-program: "Computer Science B.Sc.", + ), + abstract: ( + keywords: ("template", "typst", "zhaw", "thesis"), + de: "Diese Vorlage dient als Demonstration der Funktionen von Typst und der Struktur einer Abschlussarbeit an der ZHAW. Sie umfasst Beispiele für Querverweise, ein Glossar, ein Literaturverzeichnis, mathematische Gleichungen und Codeausschnitte.", + en: "This template serves as a demonstration of Typst features and the structure of a thesis at ZHAW. It includes examples of cross-references, a glossary, bibliography, mathematical equations, and code snippets.", + ), + declaration-of-originality: ( + location: "Zürich", + ), + glossary-entries: myGlossary, + bibliography: bibliography("biblio.bib", style: "ieee"), + appendix: [#include "appendix.typ"], +) + +// ---------------------------------- +// Do not forget to IMPORT THE FONTS! Otherwise your document will LOOK WRONG. See: https://typst.app/universe/package/modern-zhaw-thesis +// ---------------------------------- + +// Below you can write your thesis. It's recommended to create separate files for each chapter and include them here using #include "path/to/chapter.typ". + += Introduction + +This document serves as a demo of both Typst features and the template itself. + +== What is Typst? + +#link("https://typst.app/", "Typst") is a modern typesetting system that combines the simplicity of Markdown with the power of LaTeX. It allows you to create beautiful documents with ease, keeping styling separate from content. + +== Is Typst better than Latex? + +That's subjective, but: +- it compiles much faster and provides real-time preview (no compilation delays) +- its syntax is much nicer to write and look at +- it has built-in support for features that require packages in LaTeX (bibliography, cross-references, glossary, code snippets, etc.) +- its web editor works better than Overleaf + += Features showcase + +== Glossary, references and labels + +Here we have a glossary reference: @iot:long. We can also use the acronym form: @iot:short. See the #link("https://typst.app/universe/package/glossy/", "Glossy") documentation for more details. Here we refer to a term, not an acronym: @deployment. + +Here we refer to a source from our bibliography @garcia2021microservices. Pass a #link("https://typst.app/docs/reference/model/bibliography/")[`bibliography()` object] to the `bibliography:` parameter to control the file and style. + +We can also refer to equations, such as @eq:weights, and figures, such as @tab:results. + +Finally, here we refer to a section: @intro. + +== Quotes, lists, and footnotes + +#quote(attribution: [Alan Kay, keynote address])[The best way to predict the future is to invent it.] + ++ Plan the structure of each chapter ++ Write and revise the main text + + Draft each section in a separate file and `#include` it from `main.typ` + + Keep figures and tables near their first reference + + Prefer vector graphics (SVG) for diagrams + + Export raster images at sufficient resolution for print ++ Proofread the bibliography and front matter + +Here is a footnote.#footnote[ + Footnote text is set at the bottom of the page; numbering restarts each page by default unless you change `footnote` rules globally. +] + +== Maths + +$ + f(x) & = sigma(W_L sigma(W_(L-1) dots sigma(W_1 x + b_1) dots + b_(L-1)) + b_L) \ + & = (sigma dot W_L dot sigma dot W_(L-1) dot dots dot sigma dot W_1)(x, b_1, dots, b_L) +$ + +The equation above (@eq:weights) shows the forward pass of a neural network with $L$ layers, weights $W_i$, biases $b_i$, and activation function $sigma$. + +Equations can also be inline: $V_k^* (s) = max_a sum_(s') underbrace(P(s' | s, a), "transition proba") lr((underbrace(r_(t+1), "reward s" arrow "s'") + gamma underbrace(V_(k-1)^* (s'), "precalc. val of s'")), size: #40%)$. + +== Figures + +Figures allow to wrap images, tables, code snippets, and more in captions and labels that you can refer to. + +=== Tables + +#figure( + table( + columns: 3, + align: (left, center, right), + [Metric], [Best Value], [Interpretation], + [Precision], [1.0], [All predictions are correct], + [Recall], [1.0], [All targets are found], + [$F_1$ Score], [1.0], [Perfect balance], + [False Positive Rate], [0.0], [No false alarms], + ), + caption: "Table with custom alignment", +) + +#figure( + { + show table.cell.where(y: 5): strong + table( + columns: (2fr, 1fr, 1fr, 1fr, 1fr), + align: (left, center, center, center, center), + [Approach], [Speed], [Beauty], [Ease], [Overall], + [Word], [0.2], [0.3], [0.7], [0.4], + [LaTeX], [0.3], [0.9], [0.3], [0.5], + [Markdown], [0.9], [0.4], [0.9], [0.7], + [Google Docs], [0.7], [0.2], [0.8], [0.6], + [Typst], [0.95], [0.98], [0.92], [0.95], + ) + }, + caption: "Table that spans full width", +) + +=== Code + +@code:detection below shows how code snippets look like. This is handled by the #link("https://typst.app/universe/package/codly/", "Codly") package, which provides many customisation options. + +#figure( + ```python + import numpy as np + from sklearn.preprocessing import StandardScaler + from sklearn.ensemble import IsolationForest + + class Model: + def __init__(self, temp=1): + self.scaler = StandardScaler() + self.excitement = IsolationForest( + temp=temp, + random_state=42 + ) + ```, + caption: "Sample code demonstrating syntax highlighting and professional formatting", +) + +#callout( + "In summary", +)[ + This template showcases: + + + Cross-references to sections, equations, tables, and figures + + Glossary integration with short (@ML:short) and long (@ntp:long) forms + + Bibliography citations @garcia2021microservices + + Mathematical equations with proper labeling + + Code listings with syntax highlighting + + Professional tables and figures + + Block quotes, nested numbered lists, and footnotes +] diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/thumbnail.png b/packages/preview/modern-zhaw-thesis/0.4.0/thumbnail.png new file mode 100644 index 0000000000..a4484ad457 Binary files /dev/null and b/packages/preview/modern-zhaw-thesis/0.4.0/thumbnail.png differ diff --git a/packages/preview/modern-zhaw-thesis/0.4.0/typst.toml b/packages/preview/modern-zhaw-thesis/0.4.0/typst.toml new file mode 100644 index 0000000000..4bb9410753 --- /dev/null +++ b/packages/preview/modern-zhaw-thesis/0.4.0/typst.toml @@ -0,0 +1,30 @@ +# for a description of available keys, see https://github.com/typst/packages/?tab=readme-ov-file#package-format + +[package] +name = "modern-zhaw-thesis" +version = "0.4.0" +entrypoint = "lib.typ" +authors = ["Stanislas Laurent "] +license = "MIT-0" +description = "Comprehensive template for ZHAW academic works (theses, exercises, reports...), adapted from the official branding and guidelines." +repository = "https://github.com/stanlrt/typst-zhaw-thesis" +keywords = ["thesis", "ZHAW", "Zürcher Hochschule für Angewandte Wissenschaften", "Zürich", "academic"] +categories = ["thesis", "report", "paper"] +compiler = "0.14.0" +exclude = [ + ".github", + "docs", + "scripts", + "tests", + ".typstignore", + "Justfile", + "showcase.pdf", +] + +[template] +path = "template/" # all files inside will be copied into the user's new project directory +entrypoint = "main.typ" # compilation target +thumbnail = "thumbnail.png" + +# [tool.mytool] +# foo = "bar"