diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/LICENSE b/packages/preview/cleanified-hpi-thesis/0.2.0/LICENSE new file mode 100644 index 0000000000..28f1e098e7 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 Robert Richter and Felix Hoffmann + +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/cleanified-hpi-thesis/0.2.0/README.md b/packages/preview/cleanified-hpi-thesis/0.2.0/README.md new file mode 100644 index 0000000000..afd7f6efdb --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/README.md @@ -0,0 +1,71 @@ +# HPI Thesis Template + +This template is for HPI students writing their Bachelor's or Master's thesis. + +## Disclaimer + +- This template is not official. +- Official university guidelines may differ from the ones used in this template. + +## Getting Started + +```bash +typst init @preview/cleanified-hpi-thesis +``` + +## Configuration + +An example configuration is located in [`example/`](./example/main.typ). + +```typst +#import "@preview/cleanified-hpi-thesis:0.2.0": * + +#show: project.with( + title: "My Very Long, Informative, Expressive, and Definitely Fancy Title", + translation: "Eine adäquate Übersetzung meines Titels", + name: "Max Mustermann", + date: "17. Juli, 2025", + study-program: "IT-Systems Engineering", + chair: "Data-Intensive Internet Computing", + professors: ("Prof. Dr. Rosseforp Renttalp", "Prof. Dr. Erika Mustermann"), + advisors: ("Dr. Karla Musterfrau",), + abstract: "Some abstract", + abstract-de: "Der deutsche Abstract...", + acknowledgements: "Thanks to ...", + // acronyms: [API -- Application Programming Interface], + // ai-declaration: [Describe any use of generative AI tools here.], + type: "Master", + bibliography: bibliography("references.bib"), + // lang: "de", + // typography: (font: "STIX Two Text", body-text-size: 12pt), + // layout: (for-print: true, toc-depth: 2), + // appearance: (accent-color: rgb("#B1063A")), + // labels: (declaration-city: "Berlin"), +) + +... your content ... +``` + +`professors` and `advisors` are ordered tuples. Their entries appear in the +given order, one name per line, on the title page. A tuple containing one person +requires a trailing comma, for example `("Dr. Karla Musterfrau",)`. + +Set `acronyms` to optional content that should appear after the acknowledgements +and before the table of contents. The template does not prescribe how that +content is generated, so it can be supplied by a glossary package or written +directly. + +Set `ai-declaration` to disclose the use of generative AI tools in the final, +signed declaration of authorship. The disclosure is placed after the authorship +statement and before the place, date, and signature. When the option is left +empty, the regular declaration of authorship is rendered unchanged. The +combined title is localized through `lang` and can be overridden with the +`ai-declaration-title` label. + +Keep the declaration concise. If detailed documentation of tools, prompts, or +affected passages is required, place it in an appendix and refer to it from the +declaration. + +## Logo Usage + +Please note the logo usage guidelines of University of Potsdam ([UP Logo Usage Guidelines](https://www.uni-potsdam.de/fileadmin/projects/zim/files/MMP/PDF_Dateien_MMP/250509-Leitfaden_DigitalPrint-web.pdf)) and Hasso Plattner Institute ([HPI Logo Usage Guidelines](https://hpi.de/en/imprint/)). The logos are subject to copyright of the respective institutions. diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/example/main.typ b/packages/preview/cleanified-hpi-thesis/0.2.0/example/main.typ new file mode 100644 index 0000000000..590d9924f7 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/example/main.typ @@ -0,0 +1,55 @@ +// For local development, replace the import below with: +// #import "../template/lib.typ": * +#import "@preview/cleanified-hpi-thesis:0.2.0": * + +#let abstract = [ + This is a very good abstract. +] + +#let abstract-de = [ + Die ist eine wirklich gute Zusammenfassung. +] + +#let acknowledgements = [ + Thanks to ... +] + +#show: project.with( + title: "My Very Long, Informative, Expressive, and Definitely Fancy Title", + translation: "Eine adäquate Übersetzung meines Titels", + name: "Max Mustermann", + date: "17. Juli, 2025", + study-program: "IT-Systems Engineering", + chair: "Data-Intensive Internet Computing", + professors: ("Prof. Dr. Rosseforp Renttalp", "Prof. Dr. Erika Mustermann"), + advisors: ("Dr. Karla Musterfrau",), + abstract: abstract, + abstract-de: abstract-de, + acknowledgements: acknowledgements, + // acronyms: [API -- Application Programming Interface], + // ai-declaration: [Describe any use of generative AI tools here.], + type: "Master", + bibliography: bibliography("references.bib"), + // lang: "de", // Switch all labels to German defaults + // typography: (font: "STIX Two Text", body-text-size: 12pt), + // layout: (for-print: true, toc-depth: 2), + // appearance: (accent-color: rgb("#B1063A")), + // labels: (declaration-city: "Berlin"), +) + += Introduction +#lorem(80) + +As shown by Doe and Smith @example2025, this approach is effective. + +== In this paper +#lorem(20) + +=== Contributions +#lorem(40) + +==== Really Small Stuff +#lorem(20) + += Related Work +#lorem(500) diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/example/references.bib b/packages/preview/cleanified-hpi-thesis/0.2.0/example/references.bib new file mode 100644 index 0000000000..f8752897a3 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/example/references.bib @@ -0,0 +1,9 @@ +@article{example2025, + author = {Jane Doe and John Smith}, + title = {An Example Article for Demonstration}, + journal = {Journal of Examples}, + year = {2025}, + volume = {1}, + number = {1}, + pages = {1--10}, +} diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/template/hpi-logo.svg b/packages/preview/cleanified-hpi-thesis/0.2.0/template/hpi-logo.svg new file mode 100644 index 0000000000..b0e0504426 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/template/hpi-logo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/template/hpi-title-page.typ b/packages/preview/cleanified-hpi-thesis/0.2.0/template/hpi-title-page.typ new file mode 100644 index 0000000000..710160a9b4 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/template/hpi-title-page.typ @@ -0,0 +1,102 @@ +#let value-type = type + +#let render-people(people, field) = { + assert( + value-type(people) == array, + message: field + " must be an ordered tuple, for example (\"Name\",)", + ) + people.map(person => [#person]).join(linebreak()) +} + +#let hpi-title-page( + professors: (), + advisors: (), + chair: "", + name: "", + title: "", + translation: "", + study-program: "", + date: none, + type: "", + accent-color: rgb("#4f5358"), + university-logo: "up-logo.svg", + institute-logo: "hpi-logo.svg", + labels: (:), +) = { + assert( + type in ("Bachelor", "Master", ""), + message: "type must be 'Bachelor' or 'Master'", + ) + + let rendered-professors = render-people(professors, "professors") + let rendered-advisors = render-people(advisors, "advisors") + + let (thesis-kind, degree, abbreviation) = if type == "Master" { + ( + labels.at("master-thesis-kind"), + labels.at("master-degree"), + labels.at("master-abbreviation"), + ) + } else { + ( + labels.at("bachelor-thesis-kind"), + labels.at("bachelor-degree"), + labels.at("bachelor-abbreviation"), + ) + } + + page(footer: [])[ + // Title page + #grid( + columns: (1fr, 1fr), + rows: (80pt, 80pt), + grid.cell(image(university-logo, alt: "University logo")), + grid.cell(align(right, image( + institute-logo, + alt: "Institute logo", + ))), + ) + + #align(center, block[ + #line(length: 100%, stroke: 0.75pt + accent-color)\ + #text(2em, weight: "bold", title) \ \ + #text(1.5em, translation) \ \ + #line(length: 100%, stroke: 0.75pt + accent-color) + ]) + + #align(center, text(1.5em, weight: "bold", name)) + + #align(center, block[ + #thesis-kind\ + #labels.at("thesis-purpose") + ]) + + #align(center, text(1.5em, block[ + #degree \ + #text(style: "italic", "(" + abbreviation + ")") + ])) + + #align(center, block[ + #labels.at("study-program-label") \ + #study-program + ]) + + #align(center, block[ + #labels.at("submitted-on") #date #labels.at("submitted-on-suffix") \ + #labels.at("chair-label") #chair #labels.at("chair-suffix") \ + #labels.at("faculty") \ + #labels.at("university") + ]) + + #v(1cm) + #align(center, grid( + columns: (1fr, 1.8fr), + rows: (auto, auto), + row-gutter: 6pt, + grid.cell(align(left + top, text(weight: "bold", labels.at("examiner")))), + grid.cell(align(left + top, rendered-professors)), + grid.cell(align(left + top, text(weight: "bold", labels.at("advisor")))), + grid.cell(align(left + top, rendered-advisors)), + )) + ] +} diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/template/lib.typ b/packages/preview/cleanified-hpi-thesis/0.2.0/template/lib.typ new file mode 100644 index 0000000000..75be4541cb --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/template/lib.typ @@ -0,0 +1,399 @@ +#let std-bibliography = bibliography + +#import "hpi-title-page.typ": hpi-title-page + +// Default label sets for English and German. +// English labels use German strings for the title page because HPI requires +// a German title page regardless of thesis language. Only structural labels +// (abstract, contents, declaration, etc.) are translated to English. +#let default-labels-en = ( + abstract: "Abstract", + abstract-de: "Zusammenfassung", + acknowledgements: "Acknowledgements", + acronyms: "List of Acronyms", + ai-declaration-title: "Declaration of Authorship and Use of Generative AI", + contents: "Contents", + bibliography: "Bibliography", + declaration-title: "Declaration of Authorship", + declaration-text: [ + I hereby declare that this thesis is my own unaided work. All direct or + indirect sources used are acknowledged as references. + ], + declaration-city: "Potsdam", + thesis-purpose: "zur Erlangung des akademischen Grades", + study-program-label: "im Studiengang", + submitted-on: "eingereicht am", + submitted-on-suffix: "am", + chair-label: "Fachgebiet", + chair-suffix: "der", + faculty: "Digital-Engineering-Fakultät", + university: "der Universität Potsdam", + examiner: "Gutachter", + advisor: "Betreuer", + bachelor-thesis-kind: "Universitätsbachelorarbeit", + bachelor-degree: "Bachelor of Science", + bachelor-abbreviation: "B.Sc.", + master-thesis-kind: "Universitätsmasterarbeit", + master-degree: "Master of Science", + master-abbreviation: "M.Sc.", +) + +#let default-labels-de = ( + abstract: "Zusammenfassung (Englisch)", + abstract-de: "Zusammenfassung", + acknowledgements: "Danksagung", + acronyms: "Abkürzungsverzeichnis", + ai-declaration-title: [ + Eigenständigkeitserklärung mit Angaben zur Nutzung von KI-Werkzeugen + ], + contents: "Inhaltsverzeichnis", + bibliography: "Literaturverzeichnis", + declaration-title: "Eigenständigkeitserklärung", + declaration-text: [ + Ich erkläre hiermit, dass ich die vorliegende Arbeit selbstständig verfasst + und keine anderen als die angegebenen Quellen und Hilfsmittel verwendet + habe. + ], + declaration-city: "Potsdam", + thesis-purpose: "zur Erlangung des akademischen Grades", + study-program-label: "im Studiengang", + submitted-on: "eingereicht am", + submitted-on-suffix: "am", + chair-label: "Fachgebiet", + chair-suffix: "der", + faculty: "Digital-Engineering-Fakultät", + university: "der Universität Potsdam", + examiner: "Gutachter", + advisor: "Betreuer", + bachelor-thesis-kind: "Universitätsbachelorarbeit", + bachelor-degree: "Bachelor of Science", + bachelor-abbreviation: "B.Sc.", + master-thesis-kind: "Universitätsmasterarbeit", + master-degree: "Master of Science", + master-abbreviation: "M.Sc.", +) + +// Default typography settings (font, sizes, spacing). +#let default-typography = ( + font: "Libertinus Serif", + body-text-size: 11pt, + line-spacing: 0.65em, + justify: true, + heading-sizes: (h1: 20pt, h2: 16pt, h3: 14pt, h4: 12pt, fallback: 11pt), +) + +// Default layout settings (margins, print mode, ToC depth). +#let default-layout = ( + margin: (left: 35mm, right: 35mm, top: 30mm, bottom: 30mm), + for-print: false, + chapter-pagebreak: false, + toc-depth: 4, +) + +// Default appearance settings (colors, logos). +#let default-appearance = ( + accent-color: rgb("#4f5358"), + university-logo: "up-logo.svg", + institute-logo: "hpi-logo.svg", +) + +// The project function defines how your document looks. +// It takes your content and some metadata and formats it. +// Go ahead and customize it to your liking! +#let project( + // The title of the thesis + title: "", + // The translated title of the thesis + translation: "", + // The name of the student writing the thesis + name: "", + // Date of handing in the thesis + date: none, + // "Bachelor" or "Master" + type: "", + // Study Program of the student + study-program: "", + // Chair where the thesis is written + chair: "", + // Ordered tuple of examining professors + professors: (), + // Ordered tuple of advisors + advisors: (), + // The abstract of the thesis + abstract: "", + // The German translation of the abstract + // If not given, the page for German translation of abstract will not appear + abstract-de: "", + // The student may want to add acknowledgements + // If not given, the page for acknowledgements will not appear + acknowledgements: "", + // Optional acronym-list content + // If not given, the page for the acronym list will not appear + acronyms: none, + // Optional declaration describing the use of generative AI tools + // If given, it is included in the final declaration of authorship + ai-declaration: "", + // Optional bibliography content (e.g., bibliography("references.bib")). + // If provided, a bibliography section will be added at the end. + bibliography: none, + // Document language (e.g., "en", "de"). Affects label defaults. + lang: "en", + // Typography settings (font, sizes, spacing). Merged with defaults. + typography: (:), + // Layout settings (margins, print mode, ToC depth). Merged with defaults. + layout: (:), + // Appearance settings (colors, logos). Merged with defaults. + appearance: (:), + // Override any translatable string. Merged on top of the language defaults. + labels: (:), + body, +) = { + // Merge group defaults with user overrides. + let typo = default-typography + typography + let typo = ( + typo + + ( + heading-sizes: default-typography.heading-sizes + + typography.at("heading-sizes", default: (:)), + ) + ) + let lay = default-layout + layout + let app = default-appearance + appearance + + // Merge label defaults with user overrides. + let base-labels = if lang == "de" { default-labels-de } else { + default-labels-en + } + let valid-keys = base-labels.keys() + for key in labels.keys() { + assert(key in valid-keys, message: "Unknown label key: " + key) + } + let l = base-labels + labels + + // Set the document's basic properties. + set document(author: name, title: title) + set page( + margin: lay.margin, + numbering: "1", + number-align: end, + ) + set text(font: typo.font, size: typo.body-text-size, lang: lang) + set par(leading: typo.line-spacing, justify: typo.justify) + show math.equation: set text(weight: 400) + + hpi-title-page( + professors: professors, + name: name, + advisors: advisors, + title: title, + translation: translation, + study-program: study-program, + chair: chair, + type: type, + date: date, + accent-color: app.accent-color, + university-logo: app.university-logo, + institute-logo: app.institute-logo, + labels: l, + ) + + // Helper to render a heading with its numbering. + let styled-heading( + it, + size, + fill, + spacing-before, + spacing-after, + underline: false, + ) = { + let number = if it.numbering != none { + counter(heading).display(it.numbering) + h(7pt, weak: true) + } + + v(spacing-before) + text(size: size, fill: fill, weight: "bold", block([#number #it.body])) + if underline { line(length: 100%, stroke: 2pt + fill) } + v(spacing-after) + } + + // Configure chapter headings (level 1). + show heading.where(level: 1): it => { + if lay.chapter-pagebreak { pagebreak(weak: true) } + styled-heading(it, typo.heading-sizes.at("h1"), app.accent-color, 5%, 1.5em) + } + + // Configure section headings (levels 2-4). + show heading.where(level: 2): it => styled-heading( + it, + typo.heading-sizes.at("h2"), + app.accent-color, + 2%, + 0.75em, + ) + show heading.where(level: 3): it => styled-heading( + it, + typo.heading-sizes.at("h3"), + app.accent-color, + 2%, + 0pt, + ) + show heading.where(level: 4): it => styled-heading( + it, + typo.heading-sizes.at("h4"), + app.accent-color, + 2%, + 0pt, + ) + + // Fallback for deeper heading levels. + show heading: set text(typo.heading-sizes.at("fallback"), weight: 400) + + // Helper: insert a front matter section followed by a page break. + let front-section(title-text, content) = { + heading(level: 1, numbering: none, title-text) + v(0.5cm) + content + pagebreak() + if lay.for-print { pagebreak() } + } + + // Front matter (unnumbered pages). + set page(numbering: none) + pagebreak() + if lay.for-print { pagebreak() } + + // Roman-numbered front matter. + counter(page).update(1) + set page(numbering: "i") + + front-section(l.at("abstract"), abstract) + + if abstract-de != "" { + front-section(l.at("abstract-de"), abstract-de) + } + + if acknowledgements != "" { + front-section(l.at("acknowledgements"), acknowledgements) + } + + if acronyms != none { + front-section(l.at("acronyms"), acronyms) + } + + // Table of contents. + outline( + title: [ + #text( + size: typo.heading-sizes.at("h1"), + fill: app.accent-color, + l.at("contents"), + ) + ], + depth: lay.toc-depth, + ) + pagebreak() + if lay.for-print { pagebreak() } + + // Main body. + + // Creates a pagebreak to the given parity where empty pages + // can be detected via `is-page-empty`. + let detectable-pagebreak(to: "odd") = { + [#metadata(none) ] + pagebreak(to: to) + [#metadata(none) ] + } + + // Workaround for https://github.com/typst/typst/issues/2722 + let is-page-empty() = { + let page-num = here().page() + query() + .zip(query()) + .any(((start, end)) => { + ( + start.location().page() < page-num + and page-num < end.location().page() + ) + }) + } + + // Mark the start of the main body for header page number calculation. + [#metadata(none) ] + + // Configure page properties. + set page( + numbering: "1", + header: context { + if is-page-empty() { + return + } + + // Calculate the body page number relative to the start of the main body. + let body-start-page = query().first().location().page() + let i = here().page() - body-start-page + 1 + + // Skip headers on pages that start a chapter heading (level 1 only). + if query(heading.where(level: 1)).any(it => ( + it.location().page() == here().page() + )) { + return + } + + // Find the heading of the section we are currently in. + let before = query(selector(heading).before(here())) + if before != () { + set text(0.95em) + let author = text(style: "italic", name) + grid( + columns: (1fr, 10fr, 1fr), + align: (left, center, right), + if calc.even(i) [#i], + if calc.even(i) { author } else { title }, + if calc.odd(i) [#i], + ) + } + align(center, line(length: 100%, stroke: 0.5pt + app.accent-color)) + }, + ) + set heading(numbering: "1.1.1.1") + + counter(page).update(1) + body + + // Bibliography. + if bibliography != none { + pagebreak() + set std-bibliography(title: l.at("bibliography")) + bibliography + } + + pagebreak() + if lay.for-print { pagebreak() } + let declaration-heading = if ai-declaration == "" { + l.at("declaration-title") + } else { + l.at("ai-declaration-title") + } + heading(level: 1, numbering: none, declaration-heading) + + v(0.5cm) + block(l.at("declaration-text")) + + if ai-declaration != "" { + v(1cm) + block(ai-declaration) + } + + v(1.5cm) + [#l.at("declaration-city"), #date] + v(1cm) + grid( + columns: (1fr, 1fr), + [], + [ + #line(length: 100%, stroke: 0.5pt) + #align(center, name) + ], + ) +} diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/template/up-logo.svg b/packages/preview/cleanified-hpi-thesis/0.2.0/template/up-logo.svg new file mode 100644 index 0000000000..4ca5edc922 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/template/up-logo.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/thumbnail.png b/packages/preview/cleanified-hpi-thesis/0.2.0/thumbnail.png new file mode 100644 index 0000000000..e23c2c290c Binary files /dev/null and b/packages/preview/cleanified-hpi-thesis/0.2.0/thumbnail.png differ diff --git a/packages/preview/cleanified-hpi-thesis/0.2.0/typst.toml b/packages/preview/cleanified-hpi-thesis/0.2.0/typst.toml new file mode 100644 index 0000000000..114b7c3da9 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.2.0/typst.toml @@ -0,0 +1,37 @@ +[package] +name = "cleanified-hpi-thesis" +version = "0.2.0" +entrypoint = "template/lib.typ" +authors = [ + "Robert Richter ", + "Felix Hoffmann ", +] +license = "MIT" +disciplines = [ + "computer-science", + "engineering", + "mathematics", + "medicine", + "business", +] +description = "A clean thesis template for Bachelor's and Master's theses at HPI, University of Potsdam." +repository = "https://github.com/felixhoffmnn/hpi-thesis-template" +keywords = [ + "HPI", + "Hasso-Plattner-Institut", + "Hasso Plattner Institute", + "University of Potsdam", + "Bachelorthesis", + "Masterthesis", + "Bachelorarbeit", + "Masterarbeit", + "thesis", +] +categories = ["thesis"] +compiler = "0.14.0" +exclude = ["example/main.pdf"] + +[template] +path = "example" +entrypoint = "main.typ" +thumbnail = "thumbnail.png" diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/LICENSE b/packages/preview/cleanified-hpi-thesis/0.3.0/LICENSE new file mode 100644 index 0000000000..28f1e098e7 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 Robert Richter and Felix Hoffmann + +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/cleanified-hpi-thesis/0.3.0/README.md b/packages/preview/cleanified-hpi-thesis/0.3.0/README.md new file mode 100644 index 0000000000..798fa4eb6c --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/README.md @@ -0,0 +1,60 @@ +# HPI Thesis Template + +This template is for HPI students writing their Bachelor's or Master's thesis. + +**Note:** This is an unofficial template; current university guidelines take precedence. + +## Getting Started + +```bash +typst init @preview/cleanified-hpi-thesis +``` + +## Configuration + +An example configuration is located in [`example/`](./example/main.typ). + +```typst +#import "@preview/cleanified-hpi-thesis:0.3.0": * + +#show: project.with( + title: "My Thesis", + translation: "Meine Abschlussarbeit", + name: "Max Mustermann", date: "17. Juli 2026", type: "Master", + study-program: "IT-Systems Engineering", chair: "Data-Intensive Internet Computing", + professors: ("Prof. Erika Mustermann",), advisors: ("Dr. Karla Musterfrau",), + // lang: "de", + // typography: (font: "STIX Two Text", body-text-size: 12pt), + // layout: (for-print: true, toc-depth: 2), + // appearance: (accent-color: rgb("#B1063A")), + // labels: (declaration-city: "Berlin"), +) + +#abstract[English abstract.] +#abstract-de[Deutsche Zusammenfassung.] +#acknowledgements[Thanks to ...] +#acronyms[API -- Application Programming Interface] +#ai-declaration[Describe your use of generative AI tools.] + += Introduction +Write your thesis here. + +#bibliography("references.bib") + +#appendix[ + == Additional Results +] +``` + +## Logo Usage + +Please note the logo usage guidelines of University of Potsdam ([UP Logo Usage Guidelines](https://www.uni-potsdam.de/fileadmin/projects/zim/files/MMP/PDF_Dateien_MMP/250509-Leitfaden_DigitalPrint-web.pdf)) and Hasso Plattner Institute ([HPI Logo Usage Guidelines](https://hpi.de/en/imprint/)). The logos are subject to copyright of the respective institutions. + +## Changelog + +| Version tag | Breaking | Core changes | +| --- | --- | --- | +| `0.3.0` | Yes | On-demand section markers, native bibliography, appendices, and layout refinements. | +| `0.2.0` | Yes | Tuple-based professors and advisors, acronyms, and AI declarations. | +| `0.1.0` | Yes | Package rename and grouped configuration API. | +| `0.0.1` | No | Initial HPI thesis template. | diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/example/main.typ b/packages/preview/cleanified-hpi-thesis/0.3.0/example/main.typ new file mode 100644 index 0000000000..89ad350eb2 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/example/main.typ @@ -0,0 +1,64 @@ +// For local development, replace the import below with: +// #import "../template/lib.typ": * +#import "@preview/cleanified-hpi-thesis:0.3.0": * + +#show: project.with( + title: "My Very Long, Informative, Expressive, and Definitely Fancy Title", + translation: "Eine adäquate Übersetzung meines Titels", + name: "Max Mustermann", + date: "17. Juli, 2025", + study-program: "IT-Systems Engineering", + chair: "Data-Intensive Internet Computing", + professors: ("Prof. Dr. Rosseforp Renttalp", "Prof. Dr. Erika Mustermann"), + advisors: ("Dr. Karla Musterfrau",), + type: "Master", + // lang: "de", // Switch all labels to German defaults + // typography: (font: "STIX Two Text", body-text-size: 12pt), + // layout: (for-print: true, toc-depth: 2), + // appearance: (accent-color: rgb("#B1063A")), + // labels: (declaration-city: "Berlin"), +) + +#abstract[ + This is a very good abstract. +] + +#abstract-de[ + Dies ist eine wirklich gute Zusammenfassung. +] + +#acknowledgements[ + Thanks to ... +] + +// #acronyms[ +// API -- Application Programming Interface +// ] + +// #ai-declaration[ +// Describe any use of generative AI tools here. +// ] + += Introduction +#lorem(80) + +As shown by Doe and Smith @example2025, this approach is effective. + +== In this paper +#lorem(20) + +=== Contributions +#lorem(40) + +==== Really Small Stuff +#lorem(20) + += Related Work +#lorem(500) + +#bibliography("references.bib") + +#appendix[ + == Additional Results + #lorem(80) +] diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/example/references.bib b/packages/preview/cleanified-hpi-thesis/0.3.0/example/references.bib new file mode 100644 index 0000000000..f8752897a3 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/example/references.bib @@ -0,0 +1,9 @@ +@article{example2025, + author = {Jane Doe and John Smith}, + title = {An Example Article for Demonstration}, + journal = {Journal of Examples}, + year = {2025}, + volume = {1}, + number = {1}, + pages = {1--10}, +} diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/template/hpi-logo.svg b/packages/preview/cleanified-hpi-thesis/0.3.0/template/hpi-logo.svg new file mode 100644 index 0000000000..b0e0504426 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/template/hpi-logo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/template/hpi-title-page.typ b/packages/preview/cleanified-hpi-thesis/0.3.0/template/hpi-title-page.typ new file mode 100644 index 0000000000..77da299f31 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/template/hpi-title-page.typ @@ -0,0 +1,113 @@ +#let value-type = type + +#let render-people(people, field) = { + assert( + value-type(people) == array, + message: field + " must be an ordered tuple, for example (\"Name\",)", + ) + people.map(person => [#person]).join(linebreak()) +} + +#let hpi-title-page( + professors: (), + advisors: (), + chair: "", + name: "", + title: "", + translation: "", + study-program: "", + date: none, + type: "", + accent-color: rgb("#4f5358"), + university-logo: "up-logo.svg", + institute-logo: "hpi-logo.svg", + labels: (:), +) = { + assert( + type in ("Bachelor", "Master", ""), + message: "type must be 'Bachelor' or 'Master'", + ) + + let rendered-professors = render-people(professors, "professors") + let rendered-advisors = render-people(advisors, "advisors") + let examiner-label = labels.at( + if professors.len() == 1 { "examiner" } else { "examiners" }, + ) + let advisor-label = labels.at( + if advisors.len() == 1 { "advisor" } else { "advisors" }, + ) + + let (thesis-kind, degree, abbreviation) = if type == "Master" { + ( + labels.at("master-thesis-kind"), + labels.at("master-degree"), + labels.at("master-abbreviation"), + ) + } else { + ( + labels.at("bachelor-thesis-kind"), + labels.at("bachelor-degree"), + labels.at("bachelor-abbreviation"), + ) + } + + page(footer: [])[ + // Title page + #grid( + columns: (1fr, 1fr), + rows: (80pt, 80pt), + grid.cell(image(university-logo, alt: "University logo")), + grid.cell(align(right, image( + institute-logo, + alt: "Institute logo", + ))), + ) + + #align(center, block( + width: 100%, + inset: (x: 1em, y: 2em), + stroke: ( + top: 0.75pt + accent-color, + bottom: 0.75pt + accent-color, + ), + )[ + #text(2em, weight: "bold", title) + #if translation != "" [\ #v(0.5em) #text(1.5em, translation)] + ]) + + #align(center, text(1.5em, weight: "bold", name)) + + #align(center, block[ + #thesis-kind\ + #labels.at("thesis-purpose") + ]) + + #align(center, text(1.5em, block[ + #degree \ + #text(style: "italic", "(" + abbreviation + ")") + ])) + + #align(center, block[ + #labels.at("study-program-label") \ + #study-program + ]) + + #align(center, block[ + #labels.at("submitted-on") #date #labels.at("submitted-on-suffix") \ + #labels.at("chair-label") #chair #labels.at("chair-suffix") \ + #labels.at("faculty") \ + #labels.at("university") + ]) + + #v(1cm) + #align(center, grid( + columns: (1fr, 1.8fr), + rows: (auto, auto), + row-gutter: 6pt, + grid.cell(align(left + top, text(weight: "bold", examiner-label))), + grid.cell(align(left + top, rendered-professors)), + grid.cell(align(left + top, text(weight: "bold", advisor-label))), + grid.cell(align(left + top, rendered-advisors)), + )) + ] +} diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/template/lib.typ b/packages/preview/cleanified-hpi-thesis/0.3.0/template/lib.typ new file mode 100644 index 0000000000..5d3de052fa --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/template/lib.typ @@ -0,0 +1,390 @@ +#import "hpi-title-page.typ": hpi-title-page + +// On-demand document sections. Their content is collected and rendered by +// `project`, so calls can stay next to the source they describe. +#let abstract(body) = [#metadata(body) ] +#let abstract-de(body) = [#metadata(body) ] +#let acknowledgements(body) = [#metadata(body) ] +#let acronyms(body) = [#metadata(body) ] +#let ai-declaration(body) = [#metadata(body) ] +#let appendix(body) = [#metadata(body) ] + +// Default label sets for English and German. +// English labels use German strings for the title page because HPI requires +// a German title page regardless of thesis language. Only structural labels +// (abstract, contents, declaration, etc.) are translated to English. +#let default-labels-en = ( + abstract: "Abstract", + abstract-de: "Zusammenfassung", + acknowledgements: "Acknowledgements", + acronyms: "List of Acronyms", + chapter: "Chapter", + appendix: "Appendix", + ai-declaration-title: "Declaration of Authorship and Use of Generative AI", + contents: "Contents", + bibliography: "Bibliography", + declaration-title: "Declaration of Authorship", + declaration-text: [ + I hereby declare that this thesis is my own unaided work. All direct or + indirect sources used are acknowledged as references. + ], + declaration-city: "Potsdam", + thesis-purpose: "zur Erlangung des akademischen Grades", + study-program-label: "im Studiengang", + submitted-on: "eingereicht am", + submitted-on-suffix: "am", + chair-label: "Fachgebiet", + chair-suffix: "der", + faculty: "Digital-Engineering-Fakultät", + university: "der Universität Potsdam", + examiner: "Gutachter", + examiners: "Gutachter", + advisor: "Betreuer", + advisors: "Betreuer", + bachelor-thesis-kind: "Universitätsbachelorarbeit", + bachelor-degree: "Bachelor of Science", + bachelor-abbreviation: "B.Sc.", + master-thesis-kind: "Universitätsmasterarbeit", + master-degree: "Master of Science", + master-abbreviation: "M.Sc.", +) + +#let default-labels-de = default-labels-en + ( + abstract: "Zusammenfassung (Englisch)", + abstract-de: "Zusammenfassung", + acknowledgements: "Danksagung", + acronyms: "Abkürzungsverzeichnis", + chapter: "Kapitel", + appendix: "Anhang", + ai-declaration-title: [ + Eigenständigkeitserklärung mit Angaben zur Nutzung von KI-Werkzeugen + ], + contents: "Inhaltsverzeichnis", + bibliography: "Literaturverzeichnis", + declaration-title: "Eigenständigkeitserklärung", + declaration-text: [ + Ich erkläre hiermit, dass ich die vorliegende Arbeit selbstständig verfasst + und keine anderen als die angegebenen Quellen und Hilfsmittel verwendet + habe. + ], +) + +// Default typography settings (font, sizes, spacing). +#let default-typography = ( + font: "Libertinus Serif", + body-text-size: 11pt, + line-spacing: 0.65em, + justify: true, + table-text-size: 9pt, + caption-text-size: 9pt, + heading-sizes: (h1: 20pt, h2: 16pt, h3: 14pt, h4: 12pt, fallback: 11pt), +) + +// Default layout settings (margins, print mode, ToC depth). +#let default-layout = ( + margin: (left: 35mm, right: 35mm, top: 30mm, bottom: 30mm), + for-print: false, + chapter-pagebreak: true, + toc-depth: 4, +) + +// Default appearance settings (colors, logos). +#let default-appearance = ( + accent-color: rgb("#4f5358"), + university-logo: "up-logo.svg", + institute-logo: "hpi-logo.svg", +) + +// The project function defines how your document looks. +// It takes your content and some metadata and formats it. +// Go ahead and customize it to your liking! +#let project( + // The title of the thesis + title: "", + // The translated title of the thesis + translation: "", + // The name of the student writing the thesis + name: "", + // Date of handing in the thesis + date: none, + // "Bachelor" or "Master" + type: "", + // Study Program of the student + study-program: "", + // Chair where the thesis is written + chair: "", + // Ordered tuple of examining professors + professors: (), + // Ordered tuple of advisors + advisors: (), + // Document language (e.g., "en", "de"). Affects label defaults. + lang: "en", + // Typography settings (font, sizes, spacing). Merged with defaults. + typography: (:), + // Layout settings (margins, print mode, chapter breaks, ToC depth). + layout: (:), + // Appearance settings (colors, logos). Merged with defaults. + appearance: (:), + // Override any translatable string. Merged on top of the language defaults. + labels: (:), + body, +) = { + // Merge group defaults with user overrides. + let typo = default-typography + typography + let typo = ( + typo + + ( + heading-sizes: default-typography.heading-sizes + + typography.at("heading-sizes", default: (:)), + ) + ) + let lay = default-layout + layout + let app = default-appearance + appearance + + // Merge label defaults with user overrides. + let base-labels = if lang == "de" { default-labels-de } else { + default-labels-en + } + let valid-keys = base-labels.keys() + for key in labels.keys() { + assert(key in valid-keys, message: "Unknown label key: " + key) + } + let l = base-labels + labels + + // Set the document's basic properties. + set document(author: name, title: title) + set page(margin: lay.margin) + set text(font: typo.font, size: typo.body-text-size, lang: lang) + set par(leading: typo.line-spacing, justify: typo.justify) + show table: set text(size: typo.table-text-size) + show figure.caption: set text(size: typo.caption-text-size) + show figure.where(kind: table): set figure.caption(position: top) + show math.equation: set text(weight: 400) + + // Workaround for https://github.com/typst/typst/issues/2722 + let is-page-empty() = { + let page-num = here().page() + query() + .zip(query()) + .any(((start, end)) => ( + start.location().page() < page-num and page-num < end.location().page() + )) + } + + // Mark inserted print pages so their headers and footers can be suppressed. + let section-pagebreak(weak: false) = if lay.for-print { + [#metadata(none) ] + pagebreak(to: "odd", weak: weak) + [#metadata(none) ] + } else { + pagebreak(weak: weak) + } + + let page-footer = context { + if not is-page-empty() { + align( + if lay.for-print and calc.even(here().page()) { start } else { end }, + counter(page).display(), + ) + } + } + + hpi-title-page( + professors: professors, + name: name, + advisors: advisors, + title: title, + translation: translation, + study-program: study-program, + chair: chair, + type: type, + date: date, + accent-color: app.accent-color, + university-logo: app.university-logo, + institute-logo: app.institute-logo, + labels: l, + ) + + // Helper to render a heading with its numbering. + let styled-heading(it, size, spacing-before, spacing-after) = { + let number = if it.numbering != none { + counter(heading).display(it.numbering) + h(7pt, weak: true) + } + + v(spacing-before) + text( + size: size, + fill: app.accent-color, + weight: "bold", + block([#number #it.body]), + ) + v(spacing-after) + } + + // Configure chapter headings (level 1). + show heading.where(level: 1): set heading(supplement: l.at("chapter")) + show heading.where(level: 1): it => { + if lay.chapter-pagebreak { section-pagebreak(weak: true) } + styled-heading(it, typo.heading-sizes.at("h1"), 5%, 1.5em) + } + + // Configure section headings (levels 2-4). + show heading.where(level: 2): it => styled-heading( + it, + typo.heading-sizes.at("h2"), + 2%, + 0.75em, + ) + show heading.where(level: 3): it => styled-heading( + it, + typo.heading-sizes.at("h3"), + 2%, + 0pt, + ) + show heading.where(level: 4): it => styled-heading( + it, + typo.heading-sizes.at("h4"), + 2%, + 0pt, + ) + + // Fallback for deeper heading levels. + show heading: set text(typo.heading-sizes.at("fallback"), weight: 400) + + // Helper: insert a front matter section followed by a page break. + let front-section(title-text, content) = { + heading(level: 1, numbering: none, title-text) + v(0.5cm) + content + section-pagebreak() + } + + let marker-content(label) = query(label).map(it => it.value) + + // Front matter (unnumbered pages). + set page(numbering: none) + section-pagebreak() + + // Roman-numbered front matter. + counter(page).update(1) + set page(numbering: "i", footer: page-footer) + + context { + for (label, title) in ( + (, l.at("abstract")), + (, l.at("abstract-de")), + (, l.at("acknowledgements")), + (, l.at("acronyms")), + ) { + let content = marker-content(label) + if content != () { front-section(title, content.join(parbreak())) } + } + + // Table of contents. + outline( + title: [ + #text( + size: typo.heading-sizes.at("h1"), + fill: app.accent-color, + l.at("contents"), + ) + ], + depth: lay.toc-depth, + ) + section-pagebreak() + } + + // Main body. + + // Configure page properties. + set page( + numbering: "1", + footer: page-footer, + header: context { + if is-page-empty() { + return + } + + // Skip headers on pages that start a chapter heading. + if query(heading.where(level: 1)).any(it => ( + it.location().page() == here().page() + )) { + return + } + + // Find the heading of the section we are currently in. + let before = query(selector(heading).before(here())) + if before != () { + set text(0.95em) + align( + center, + if calc.even(here().page()) { + text(style: "italic", name) + } else { + title + }, + ) + } + align(center, line(length: 100%, stroke: 0.5pt + app.accent-color)) + }, + ) + set heading(numbering: "1.1.1.1") + // Native bibliographies keep their source position while sharing the + // template's localized title and page-break behavior. + set bibliography(title: l.at("bibliography")) + show bibliography: it => { + section-pagebreak(weak: true) + it + } + + counter(page).update(1) + body + + context { + let appendices = marker-content() + if appendices != () { + section-pagebreak() + heading(level: 1, numbering: none, l.at("appendix")) + counter(heading).update(0) + set heading(numbering: (..numbers) => numbering( + "A.1.1", + ..numbers.pos().slice(1), + )) + show heading.where(level: 2): set heading(supplement: l.at("appendix")) + appendices.join() + } + + section-pagebreak() + let disclosures = marker-content() + heading( + level: 1, + numbering: none, + if disclosures == () { + l.at("declaration-title") + } else { + l.at("ai-declaration-title") + }, + ) + + v(0.5cm) + block(l.at("declaration-text")) + + for disclosure in disclosures { + v(1cm) + block(disclosure) + } + + v(1.5cm) + [#l.at("declaration-city"), #date] + v(1cm) + grid( + columns: (1fr, 1fr), + [], + [ + #line(length: 100%, stroke: 0.5pt) + #align(center, name) + ], + ) + } +} diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/template/up-logo.svg b/packages/preview/cleanified-hpi-thesis/0.3.0/template/up-logo.svg new file mode 100644 index 0000000000..4ca5edc922 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/template/up-logo.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/thumbnail.png b/packages/preview/cleanified-hpi-thesis/0.3.0/thumbnail.png new file mode 100644 index 0000000000..c0774db83f Binary files /dev/null and b/packages/preview/cleanified-hpi-thesis/0.3.0/thumbnail.png differ diff --git a/packages/preview/cleanified-hpi-thesis/0.3.0/typst.toml b/packages/preview/cleanified-hpi-thesis/0.3.0/typst.toml new file mode 100644 index 0000000000..c04db88261 --- /dev/null +++ b/packages/preview/cleanified-hpi-thesis/0.3.0/typst.toml @@ -0,0 +1,37 @@ +[package] +name = "cleanified-hpi-thesis" +version = "0.3.0" +entrypoint = "template/lib.typ" +authors = [ + "Robert Richter ", + "Felix Hoffmann ", +] +license = "MIT" +disciplines = [ + "computer-science", + "engineering", + "mathematics", + "medicine", + "business", +] +description = "A clean thesis template for Bachelor's and Master's theses at HPI, University of Potsdam." +repository = "https://github.com/felixhoffmnn/hpi-thesis-template" +keywords = [ + "HPI", + "Hasso-Plattner-Institut", + "Hasso Plattner Institute", + "University of Potsdam", + "Bachelorthesis", + "Masterthesis", + "Bachelorarbeit", + "Masterarbeit", + "thesis", +] +categories = ["thesis"] +compiler = "0.14.0" +exclude = ["example/main.pdf"] + +[template] +path = "example" +entrypoint = "main.typ" +thumbnail = "thumbnail.png"