diff --git a/packages/preview/kthesis/0.1.7/LICENSE-MIT b/packages/preview/kthesis/0.1.7/LICENSE-MIT new file mode 100644 index 0000000000..d026733c4c --- /dev/null +++ b/packages/preview/kthesis/0.1.7/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Rafael Oliveira + +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/kthesis/0.1.7/LICENSE-MIT-0 b/packages/preview/kthesis/0.1.7/LICENSE-MIT-0 new file mode 100644 index 0000000000..8c06f1c518 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/LICENSE-MIT-0 @@ -0,0 +1,18 @@ +MIT No Attribution + +Copyright (c) 2025 Rafael Oliveira + +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/kthesis/0.1.7/README.md b/packages/preview/kthesis/0.1.7/README.md new file mode 100644 index 0000000000..f1d07c693e --- /dev/null +++ b/packages/preview/kthesis/0.1.7/README.md @@ -0,0 +1,143 @@ +# KTHesis + +An unofficial, slightly opinionated, extensible [Typst](https://typst.app/home/) +template for writing a Degree Project thesis for KTH Royal Institute of +Technology in Stockholm, Sweden. + +Inspired by and partially adapted from Gerald Q. Maguire Jr.'s LaTeX template +and KTH's official degree project report covers as published on the +[institution's website](https://www.kth.se/en/omslag-till-ditt-exjobb-1.479838). + +## Overview + +This template is primarily targeted at Master's Degree theses, though it aims to +be sufficiently generic so to also be suitable for other kinds of reports. It +strives to simplify drafting and counts with the following features, among +others: + +- Supports both English and Swedish as primary language, with built-in + translations for template-managed headings and sections; +- Supports additional Abstracts in other languages; +- Supports arbitrary extra preamble sections, such as a Glossary / Table of + Acronyms - i.e., integrates well with + [glossarium](https://typst.app/universe/package/glossarium) or similar; +- Does not conflict with Typst's native + [bibliography](https://typst.app/docs/reference/model/bibliography/) mechanism + even without requiring any additional configuration - "plug and play"; +- Uses [hydra](https://typst.app/universe/package/hydra) to show the current + Chapter title in the page header; +- Uses [headcount](https://typst.app/universe/package/headcount) to make figure, + table, and listing numbers dependent on Chapter number; +- Includes built-in selective inclusion of indices: an index for figures, + tables, and listings is automatically added if needed and omitted if not; +- Can generate a "For DiVA" JSON-based trailing section for compatibility with + existing, school-prevalent automation scripts; and +- Provides a simple interface and tuning options. + +## Getting Started + +Visit the template's [homepage](https://typst.app/universe/package/kthesis/) and +click "Create project in app" to try it out in the Typst web app. + +Alternatively, you can also run `typst init @preview/kthesis` to bootstrap a new +project via the Typst CLI. + +## Usage + +The main entrypoint is the function `kth-thesis`, which should be invoked with +a `show` rule at the beginning of the document: + +```typ +#show: kth-thesis.with(primary-lang: "en") +``` + +Additional configuration options are passed as needed. After this rule has been +declared, you can write your thesis's content as normal. Level 1 headings (`=`) +mark Chapters, Level 2 headings (`==`) delimitate Sections, Level 3 headings +(`===`) indicate Subsections, and so on. + +The second and last point of contact with the template is the function +`setup-appendices`, which you may (if needed) opt to invoke in a `show` rule to +mark the subsequent sections as appendices and switch the numbering to letters: + +```typ +#show: setup-appendices +``` + +## Configuration + +There are a number of options that can be passed to the `kth-thesis` function to +customize how the final document looks. All of them are optional since they come +with default values, but in most cases you'll gradually end up having to set +all of them to get the behavior you want. Here's a description of what is +available: + +- `primary-lang`: Primary document language; either `en` or `sv` +- `localized-info`: Language-specific information, including title, subtitle, + abstract, and keywords +- `authors`: Information about who is conducting the degree project +- `supervisors`: Information about who is supervising the degree project +- `examiner`: Information about who is evaluating the degree project +- `course`: Degree project course of which this thesis is part +- `degree`: Degree within the scope of which this project is being conducted +- `national-subject-categories`: One or more mandatory classification codes, + from [SCB's list](https://www.scb.se/contentassets/10054f2ef27c437884e8cde0d38b9cc4/standard-for-svensk-indelning--av-forskningsamnen-2011-uppdaterad-aug-2016.pdf) +- `school`: KTH institution hosting the project +- `trita-number`: TRITA number assigned by the school upon project completion +- `host-company`: Company hosting the degree project, if any +- `host-org`: Organization hosting the degree project, if any +- `opponents`: Names of assigned opponents, if known +- `presentation`: Final presentation details, if known +- `cover-image`: Image to include on the front cover, if any +- `acknowledgements`: Body of acknowledgements section +- `extra-preambles`: Additional, arbitrary front-matter sections, if needed +- `doc-date`: Document authoring/submission date +- `doc-city`: Document city, for acknowledgments signature +- `doc-extra-keywords`: Additional keywords for document metadata (but not text) +- `with-for-diva`: Whether to include meta "For DiVA" section after back cover +- `style`: Miscellaneous settings affecting the document's appearance + +Exact syntax and semantics for each option are shown in the starter `thesis.typ` +main file provided by this template. + +**Note:** if `with-for-diva` is enabled, abstracts must use only very simple +Typst constructs since content must be converted to HTML (which is a very lossy +and naive process). + +## Future Work + +Feature requests (via issues) and patch submissions (via PRs) are very welcome. + +Among others, in the future it might be nice to support: + +- G5 size paper (traditional for theses in Sweden), instead of just A4; +- Alternative, shorter author names for acknowledgements signature; +- Multiple degrees, including the "Same"/"Both" mechanism for similar or + distinct subject areas, respectively; and +- Copyleft option, instead of just copyright; + +## Conformance + +This template is unofficial and has not been verified to fully conform to KTH's +requirements, therefore you should use it at your own risk. However, available +information appears to imply that the covers are the only standardized part of +the degree project report, with students having freedom to decide on all other +formatting, styling, and layout aspects (if accepted by the Examiner). + +Covers (June 2024 version) have been replicated as best as possible in Typst +from the provided DOTX templates, but future bids at refining fidelity may be +attempted in the future, especially if and when LaTeX versions are published. + +The covers use Arial, which is a proprietary font and may be difficult to get +access to. This template will use Arial if it is available on the system at +compile-time and `style.use-arial` is manually enabled (opt-in); otherwise, it +will be replaced by an open, metric-compatible substitute: Liberation Sans. + +## Licensing + +This project and all materials in this repository are made available under the +MIT License, except for the contents of the `/template` directory (i.e., the +files given for the thesis authors to edit), which are instead licensed under +MIT No Attribution. + +SPDX-License-Identifier: MIT AND MIT-0 diff --git a/packages/preview/kthesis/0.1.7/assets/KTH_logo_RGB_bla.svg b/packages/preview/kthesis/0.1.7/assets/KTH_logo_RGB_bla.svg new file mode 100644 index 0000000000..9b05b7074a --- /dev/null +++ b/packages/preview/kthesis/0.1.7/assets/KTH_logo_RGB_bla.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/preview/kthesis/0.1.7/shell.nix b/packages/preview/kthesis/0.1.7/shell.nix new file mode 100644 index 0000000000..79ef720e22 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/shell.nix @@ -0,0 +1,11 @@ +{ + pkgs ? (import { }), + unstable ? (import { }), +}: +pkgs.mkShellNoCC { + buildInputs = with pkgs; [ + unstable.typst + unstable.typstyle + poppler-utils # for pdfinfo, to see metadata + ]; +} diff --git a/packages/preview/kthesis/0.1.7/src/covers.typ b/packages/preview/kthesis/0.1.7/src/covers.typ new file mode 100644 index 0000000000..07e9d8e25c --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/covers.typ @@ -0,0 +1,80 @@ +#import "./utils.typ": sans-serif, t + +#let front-cover( + title: "Example Title in Primary Language", + subtitle: "Example Subtitle in Primary Language", + authors: ("Peter Grey", "Joan Yellow"), + subject-area: "Technology", + cycle: 2, + credits: 15, + cover-image: none, + style, +) = page( + margin: (top: 12.5mm, rest: 25mm), + { + set align(center) + set text(size: 12pt, font: sans-serif(style)) + + image("../assets/KTH_logo_RGB_bla.svg", width: 37.45mm) + + [ + \ + + \ + + #t("degree-project-in") #subject-area \ + + #set text(size: 10pt) + #t("cycle-" + str(cycle)), #credits #t("credits") \ + + \ + + #text(size: 26pt, strong(title)) \ + \ + #if subtitle != none [ + #text(size: 16pt, subtitle) + \ + ] + + \ + ] + + for author in authors { + strong(upper(author)) + linebreak() + } + + if cover-image != none { + // from official cover template: 120 twips after author + 680 twips before + // image = 800 twips = 40pt of vertical space + v(40pt) + + cover-image + } + }, +) + +#let back-cover( + trita-series: "EECS-EX", + trita-number: "2026:0000", + year: 2026, + style, +) = page( + margin: (top: 65mm, bottom: 30mm, left: 74pt, right: 35mm), + { + set text(size: 12pt, font: sans-serif(style)) + + v(1fr) + + set text(size: 10pt) + show link: it => text(fill: rgb("#1954A6"), it) // not an official color? + + // I don't know why they want an en-dash here... + [ + TRITA -- #trita-series #trita-number \ + #set text(size: 8pt) + #t("stockholm-sweden") #year \ + #link("https://www.kth.se", "www.kth.se") + ] + }, +) diff --git a/packages/preview/kthesis/0.1.7/src/for-diva.typ b/packages/preview/kthesis/0.1.7/src/for-diva.typ new file mode 100644 index 0000000000..a8aaba3035 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/for-diva.typ @@ -0,0 +1,197 @@ +#import "./utils.typ": content-to-html, content-to-string, omit-dict-none + +// what has humanity done to deserve this structure and its pervasiveness? + +#let serialize-org(school, dept) = { + let org = omit-dict-none(("L1": school, "L2": dept)) + + if org.len() > 0 { + org + } else { + none + } +} + +#let serialize-person(person) = { + omit-dict-none(( + "Last name": person.at("last-names"), + "First name": person.at("first-name"), + "Local User Id": person.at("user-id", default: none), + "E-mail": person.at("email", default: none), + "organisation": serialize-org( + person.at("school", default: none), + person.at("department", default: none), + ), + "Other organisation": person.at("external-org", default: none), + )) +} + +#let serialize-degree(degree) = { + ( + "Educational program": degree.at("name"), + "programcode": degree.at("code"), + "Degree": degree.at("kind"), + "subjectArea": degree.at("subject-area"), + ) +} + +#let serialize-lang(lang) = { + if lang.len() == 3 { + lang + } else if lang == "en" { + "eng" + } else if lang == "sv" { + "swe" + } else { + panic("Cannot serialize to alpha-3 language " + lang) + } +} + +#let serialize-title(lang, info) = { + ( + "Main title": content-to-string(info.at("title")), + "Subtitle": content-to-string(info.at("subtitle", default: "")), + "Language": info.at("alpha-3", default: serialize-lang(lang)), + ) +} + +#let serialize-cooperation(host-company, host-org) = { + if host-company != none { + ("Partner_name": host-company) + } else if host-org != none { + ("Partner_name": host-org) + } else { + none + } +} + +#let serialize-opponents(opponents) = { + if opponents != none and opponents.len() > 0 { + ("Name": opponents.join(" & ")) + } else { + none + } +} + +#let serialize-presentation(presentation) = { + if presentation != none { + let location = presentation.at( + "location", + default: ( + room: none, + address: none, + city: none, + ), + ) + + let online = presentation.at("online", default: none) + let online-room = if online != none { + "via " + online.at("service") + ": " + online.at("link") + } else { + none + } + + omit-dict-none(( + "Date": presentation + .at("slot") + .display("[year]-[month]-[day] [hour]:[minute]"), + "Language": serialize-lang(presentation.at("language")), + "Room": location.at("room", default: online-room), + "Address": location.at("address", default: none), + "City": location.at("city", default: none), + )) + } else { + none + } +} + +#let serialize-global( + primary-lang: "en", + alt-lang: "sv", + localized-info: (:), + authors: (), + supervisors: (), + examiner: (:), + course: (:), + degree: (:), + host-company: none, + host-org: none, + national-subject-categories: (), + trita-series: "TRITA-EECS-EX", + trita-number: "2026:0000", + opponents: none, + presentation: none, + doc-date: datetime.today(), + page-series-counts: (), +) = { + let struct = (:) + + for (n, author) in authors.enumerate(start: 1) { + struct.insert("Author" + str(n), serialize-person(author)) + // note: n > 2 might be ignored by consuming automation scripts + } + + struct.insert("Cycle", str(degree.at("cycle"))) + struct.insert("Course code", str(course.at("code"))) + struct.insert("Credits", str(course.at("credits"))) + + struct.insert("Degree1", serialize-degree(degree)) + // TODO: support for multiple degrees (including Same/Both mechanism) + + let primary-info = localized-info.at(primary-lang) + let alt-info = localized-info.at(alt-lang) + + struct.insert("Title", serialize-title(primary-lang, primary-info)) + struct.insert("Alternative title", serialize-title(alt-lang, alt-info)) + + for (n, supervisor) in supervisors.enumerate(start: 1) { + struct.insert("Supervisor" + str(n), serialize-person(supervisor)) + // note: n > 3 might be ignored by consuming automation scripts + } + + struct.insert("Examiner1", serialize-person(examiner)) + struct.insert("Cooperation", serialize-cooperation(host-company, host-org)) + struct.insert( + "National Subject Categories", + national-subject-categories.join(", "), + ) + struct.insert( + "Other information", + ( + "Year": str(doc-date.year()), + "Number of pages": page-series-counts.join(","), + ), + ) + struct.insert("Copyrightleft", "copyright") // TODO: support copyleft + struct.insert( + "Series", + ( + "Title of series": "TRITA-" + trita-series, + "No. in series": trita-number, + ), + ) + struct.insert("Opponents", serialize-opponents(opponents)) + struct.insert("Presentation", serialize-presentation(presentation)) + struct.insert("Number of lang instances", str(localized-info.len())) + + for (lang, info) in localized-info.pairs() { + let tag = serialize-lang(info.at("alpha-3", default: lang)) + struct.insert( + "Abstract[" + tag + "]", + content-to-html(info.at("abstract")), + ) + struct.insert("Keywords[" + tag + "]", info.at("keywords").join(", ")) + } + + omit-dict-none(struct) +} + +#let for-diva-json(..args) = { + let struct = serialize-global(..args) + + set text(font: "DejaVu Sans Mono", size: 10pt, ligatures: false) + + heading(outlined: false, level: 1, "€€€€ For DIVA €€€€") + + json.encode(struct) +} diff --git a/packages/preview/kthesis/0.1.7/src/front-matter.typ b/packages/preview/kthesis/0.1.7/src/front-matter.typ new file mode 100644 index 0000000000..4e9ba11113 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/front-matter.typ @@ -0,0 +1,170 @@ +#import "./utils.typ": join-names, t + +#let title-page( + title: "Primary Language Title Goes Here", + subtitle: "Primary Language Subtitle Goes Here", // may be none! + alt-title: "Alternative Language Title Goes Here", + alt-subtitle: "Alternative Language Subtitle Goes Here", // may be none! + alt-lang: "sv", // either "en" or "sv" + degree: "Master's Program, Computer Science", + date: datetime.today(), + authors: ("Newt Yellow", "Bellatrix Green"), + supervisors: ("Minerva Red", "Filius Blue"), + examiner-name: "Brian Gold", + examiner-school: "School of Electrical Engineering and Computer Science", + host-company: "Företaget AB", // may be none! + host-org: "CERN", // may be none! +) = page( + margin: (top: 65mm, bottom: 30mm, left: 74pt, right: 35mm), + { + text(size: 25pt, strong(title)) + + if subtitle != none { + v(10pt) + + text(size: 18pt, subtitle) + } + + v(10mm) + + for author in authors { + text(size: 14pt, upper(author)) + linebreak() + } + + v(1fr) + + [ + *#degree* \ + *#t("date"):* #date.display("[month repr:long] [day], [year]") + ] + + v(5mm) + + let super-label = if supervisors.len() == 1 { + t("supervisor") + } else { + t("supervisors") + } + + [ + *#super-label:* #join-names(supervisors) \ + *#t("examiner"):* #examiner-name \ + #hide[*#t("examiner"):*] #emph(examiner-school) \ + ] + + if host-company != none { + [*#t("host-company"):* #host-company] + linebreak() + } + + if host-org != none { + [*#t("host-org"):* #host-org] + linebreak() + } + + [ + *#t("alt-title"):* #text(lang: alt-lang, alt-title) \ + #if alt-subtitle != none { + [*#t("alt-subtitle"):* #text(lang: alt-lang, alt-subtitle)] + } + ] + }, +) + +#let copyright-page( + year: 2026, + authors: ("Astronaut Boulder", "Cat Dog"), +) = page( + margin: (top: 250mm, bottom: 30mm, left: 74pt, right: 35mm), + { + v(1fr) + + [#sym.copyright #year #sym.space.quad #join-names(authors)] + }, +) + +#let localized-abstract( + lang: "en", + abstract-heading: none, + keywords-heading: none, + keywords: ("Magic", "Wonder"), + body, +) = { + if abstract-heading == none { + abstract-heading = t("abstract-heading") + } + + if keywords-heading == none { + keywords-heading = t("keywords-heading") + } + + set text(lang: lang) + + // explicit lang is necessary for it to be shown correctly in header, + // since it's extracted without the above set rule's effects and so + // would otherwise be displayed in the document's primary language + heading(outlined: false, depth: 1, text(lang: lang, abstract-heading)) + + body + + heading(outlined: false, depth: 2, keywords-heading) + + keywords.join(", ") +} + +#let signed-acknowledgements( + city: "Stockholm", + date: datetime.today, + authors: ("Gary Lose", "Harriet Lung"), + body, +) = { + heading(outlined: false, depth: 1, t("ack-heading")) + + body + + v(5pt) + + [#city, #date.display("[month repr:long] [year]")] + for author in authors { + linebreak() + author + } +} + +#let indices = { + pagebreak(weak: true, to: "odd") + { + show outline.entry.where(level: 1): it => { + v(1em, weak: true) + strong(it) + } + + outline(title: t("table-of-contents"), indent: auto) + } + + let images-target = figure.where(kind: image, outlined: true) + context if (query(images-target).len() > 0) { + pagebreak(weak: true, to: "odd") + outline(title: t("list-of-figures"), target: images-target) + } + + let tables-target = figure.where(kind: table, outlined: true) + context if (query(tables-target).len() > 0) { + pagebreak(weak: true, to: "odd") + outline(title: t("list-of-tables"), target: tables-target) + } + + let code-target = figure.where(kind: raw, outlined: true) + context if (query(code-target).len() > 0) { + pagebreak(weak: true, to: "odd") + outline(title: t("list-of-listings"), target: code-target) + } +} + +#let extra-preamble(title: "Additional Preamble", body) = { + pagebreak(weak: true, to: "odd") + heading(outlined: false, depth: 1, title) + + body +} diff --git a/packages/preview/kthesis/0.1.7/src/lang.toml b/packages/preview/kthesis/0.1.7/src/lang.toml new file mode 100644 index 0000000000..e928d884c3 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/lang.toml @@ -0,0 +1,60 @@ +[conf] +default-lang = "en" + +[lang.en] +degree-project-in = "Degree Project in" +cycle-1 = "First cycle" +cycle-2 = "Second cycle" +credits = "credits" +stockholm-sweden = "Stockholm, Sweden" +date = "Date" +supervisor = "Supervisor" +supervisors = "Supervisors" +examiner = "Examiner" +host-company = "Host company" +host-org = "Host organization" +alt-title = "Swedish title" +alt-subtitle = "Swedish subtitle" +abstract-heading = "Abstract" +keywords-heading = "Keywords" +ack-heading = "Acknowledgments" +chapter = "Chapter" +section = "Section" +appendix = "Appendix" +table-of-contents = "Contents" +list-of-figures = "List of Figures" +list-of-tables = "List of Tables" +list-of-listings = "Listings" +figure = "Figure" +figure-table = "Table" +figure-code = "Listing" +separator-last = " and " # Oxford comma would be weird for n=2 + +[lang.sv] +degree-project-in = "Examensarbete inom" +cycle-1 = "Grundnivå" +cycle-2 = "Avancerad nivå" +credits = "hp" +stockholm-sweden = "Stockholm, Sverige" +date = "Datum" +supervisor = "Handledare" +supervisors = "Handledare" +examiner = "Examinator" +host-company = "Uppdragsgivare" +host-org = "Uppdragsgivare" +alt-title = "Engelsk titel" +alt-subtitle = "Engelsk undertitel" +abstract-heading = "Sammanfattning" +keywords-heading = "Nyckelord" +ack-heading = "Författarnas tack" +chapter = "Kapitel" +section = "Avsnitt" +appendix = "Bilaga" +table-of-contents = "Innehåll" +list-of-figures = "Figurförteckning" +list-of-tables = "Tabellförteckning" +list-of-listings = "Kodförteckning" +figure = "Figur" +figure-table = "Tabell" +figure-code = "Kod" +separator-last = " och " diff --git a/packages/preview/kthesis/0.1.7/src/lib.typ b/packages/preview/kthesis/0.1.7/src/lib.typ new file mode 100644 index 0000000000..39d3f68c10 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/lib.typ @@ -0,0 +1,463 @@ +#import "./covers.typ": * +#import "./front-matter.typ": * +#import "./styling-setup.typ": * +#import "./for-diva.typ": for-diva-json +#import "./utils.typ": ( + assert-arg-type, extract-name, get-one-liner, maybe-sans-serif, z, + z-arbitrarily-keyed-dict, z-matches-regex, +) + +#let kth-thesis( + // Primary document language; either "en" or "sv" + primary-lang: "en", + // Language-specific title, subtitle, abstract, and keywords. + // Grouped by language, with only values for "en" and "sv" being mandatory. + // Localized abstract/keywords headings may be omitted only for "en" and "sv". + // Field "alpha-3" is the language's ISO 639-3 code, for non-"en"/"sv" langs. + // If desired, any "subtitle" field may be set to none (to omit it entirely). + localized-info: ( + en: ( + title: "How to Abandon Dinosaur-Age TypeSetting Software", + subtitle: "A Modern Approach to Problem-Solving", + abstract: lorem(300), + keywords: ("Dogs", "Chicken nuggets"), + ), + sv: ( + title: "Svenska Översättningen av Titeln", + subtitle: "Svenska Översättningen av Undertiteln", + abstract: lorem(300), + keywords: ("Hundar", "Kycklingnuggets"), + ), + pt: ( + alpha-3: "por", + title: "Tradução em Português do Título", + subtitle: "Tradução em Português do Subtítulo", + abstract-heading: "Resumo", + keywords-heading: "Palavras-chave", + abstract: lorem(300), + keywords: ("Cães", "Nuggets de frango"), + ), + ), + // Ordered author information; only first and last names fields are mandatory + authors: ( + ( + first-name: "John", + last-names: "Doe", + email: "john.doe@example.com", + user-id: "jod", + school: "School of Electrical Engineering and Computer Science", + department: "Department of Typesetting Sanity", + ), + ( + first-name: "Jane", + last-names: "Doe", + ), + ), + // Ordered supervisor information; "external-org" replaces userid/school/dept + supervisors: ( + ( + first-name: "Alice", + last-names: "Smith", + email: "alice@example.com", + user-id: "alice", + school: "School of Electrical Engineering and Computer Science", + department: "Department of Loyal Supervision", + ), + ( + first-name: "Bob", + last-names: "Jones", + email: "bob@example.com", + external-org: "Företag AB", + ), + ), + // Thesis examiner; must be internal to the school so all fields are mandatory + examiner: ( + first-name: "Charlie", + last-names: "Johnson", + email: "charlie@example.com", + user-id: "chj", + school: "School of Electrical Engineering and Computer Science", + department: "Department of Fair Examination", + ), + // Degree project course within which the thesis is being conducted. + // All fields are mandatory; credits are the course's ECTS credits (hp). + course: ( + code: "DA237X", + credits: 30, + ), + // Degree as part of which the thesis is conducted; all fields are mandatory. + // Subject area is main field of study as listed in the second dropdown here: + // https://www.kth.se/en/student/studier/examen/examensregler-1.5685 + // Kind is the degree title conferred as listed in the third dropdown above. + // Cycle is either 1 (Bachelor's) or 2 (Master's), per Bologna. + degree: ( + code: "TCYSM", + name: "Master's Program, Cybersecurity", + subject-area: "Computer Science and Engineering", + kind: "Master of Science", + cycle: 2, + ), + // National subject category codes; mandatory for DiVA classification. + // One or more 3-to-5 digit codes, with preference for 5-digit codes, from: + // https://www.scb.se/contentassets/10054f2ef27c437884e8cde0d38b9cc4/standard-for-svensk-indelning--av-forskningsamnen-2011-uppdaterad-aug-2016.pdf + national-subject-categories: ("10201", "10206"), + // School that the thesis is part of (abbreviation) + school: "EECS", + // TRITA number assigned to thesis after final examiner approval + trita-number: "2026:0000", + // Host company collaborating for this thesis; may be none + host-company: "Företag AB", + // Host organization collaborating for this thesis; may be none + host-org: none, + // Names of opponents for this thesis; may be none until they're assigned + opponents: ("Mary Ignatia", "Alexander Smith"), + // Thesis presentation details; may be none until it's scheduled and set. + // Either "online" or "location" fields may be none, but not both. + presentation: ( + language: "en", + slot: datetime( + year: 2026, + month: 6, + day: 14, + hour: 13, + minute: 0, + second: 0, + ), + online: (service: "Zoom", link: "https://kth-se.zoom.us/j/111222333"), + location: ( + room: "F1 (Alfvénsalen)", + address: "Lindstedtsvägen 22", + city: "Stockholm", + ), + ), + // Optional image to show on the front cover. + // This should either be none, or an "image" element. For example, + // cover-image: image("./assets/cover.png", width: 100%) + // If provided, the image can be formatted arbitrarily to look however desired + // (especially its height, width, and fit mode). However, the recommended + // styles are (width: 100%) or (width: 16cm, height: 10cm, fit: "contain"). + cover-image: none, + // Acknowledgements body + acknowledgements: { + par(lorem(100)) + par(lorem(150)) + }, + // Additional front-matter sections, each with keys "heading" and "body". + // For example, ((heading: "Acronyms and Abbreviations", body: glossary),) + extra-preambles: (), + // Document date; hardcode for determinism/reproducibility + doc-date: datetime.today(), + // Document city (where it's being signed/authored/submitted) + doc-city: "Stockholm", + // Extra keywords, embedded in document metadata but not listed in text + doc-extra-keywords: ("master thesis",), + // Whether to include trailing "For DiVA" metadata structure section + with-for-diva: true, + // Miscellaneous settings affecting the document's appearance + style: (:), + // Document body + body, +) = context { + // manual type checking because typst sadly has no strong typing and sometimes + // incorrect arguments can lead to very strange errors that are hard to debug + // (especially when accidentally using `(x)` instead of `(x,)` to construct an + // array, leading to no array being constructed at all) + // note that this is not necessarily exhaustive and is intended just as a + // convenience, so that obvious problems surface immediately and clearly + + assert-arg-type("primary-lang", primary-lang, z.choice(("en", "sv"))) + assert-arg-type("localized-info", localized-info, z-arbitrarily-keyed-dict( + "localized-info", + z.string(assertions: (z.assert.length.equals(2),)), + z.dictionary( + ( + alpha-3: z.string(optional: true, assertions: ( + z.assert.length.equals(3), + )), + title: z.string(min: 1), + subtitle: z.string(optional: true, min: 1), + abstract: z.content(), + keywords: z.array(z.string(min: 1)), + ), + ), + min: 1, + require-keys: ("en", "sv"), + )) + assert-arg-type("authors", authors, z.array( + z.dictionary(( + first-name: z.string(min: 1), + last-names: z.string(min: 1), + email: z.email(optional: true), + user-id: z.string(optional: true, min: 1), + school: z.string(optional: true, min: 1), + department: z.string(optional: true, min: 1), + )), + min: 1, + )) + let internal-person = z.dictionary(( + first-name: z.string(min: 1), + last-names: z.string(min: 1), + email: z.email(), + user-id: z.string(min: 1), + school: z.string(min: 1), + department: z.string(min: 1), + )) + assert-arg-type("supervisors", supervisors, z.array( + z.either(internal-person, z.dictionary(( + first-name: z.string(min: 1), + last-names: z.string(min: 1), + email: z.email(), + external-org: z.string(min: 1), + ))), + min: 1, + )) + assert-arg-type("examiner", examiner, internal-person) + assert-arg-type("course", course, z.dictionary(( + code: z.string(min: 1), + credits: z.number(min: 1), + ))) + assert-arg-type("degree", degree, z.dictionary(( + code: z.string(min: 1), + name: z.string(min: 1), + subject-area: z.string(min: 1), + kind: z.string(min: 1), + cycle: z.number(min: 1, max: 2), // better error messages than z.choice + ))) + assert-arg-type( + "national-subject-categories", + national-subject-categories, + z.array( + z.string(min: 3, max: 5, assertions: z-matches-regex( + "^\d+$", + "All characters must be digits", + )), + min: 1, + ), + ) + assert-arg-type("school", school, z.choice(( + "ABE", + "EECS", + "ITM", + "CBH", + "SCI", + ))) + assert-arg-type("trita-number", trita-number, z.string( + assertions: z-matches-regex("\d{4}:\d+", "Must follow format `2026:0000`"), + )) + assert-arg-type("host-company", host-company, z.string( + optional: true, + min: 1, + )) + assert-arg-type("host-org", host-org, z.string(optional: true, min: 1)) + assert-arg-type("opponents", opponents, z.array( + z.string(min: 1), + optional: true, + min: 1, + )) + assert-arg-type("presentation", presentation, z.dictionary( + ( + language: z.choice(("en", "sv")), + slot: z.date(), + online: z.dictionary( + (service: z.string(min: 1), link: z.string(min: 1)), + optional: true, + ), + location: z.dictionary( + ( + room: z.string(min: 1), + address: z.string(min: 1), + city: z.string(min: 1), + ), + optional: true, + ), + ), + optional: true, + assertions: ( + ( + condition: (_, it) => ( + it.at("online", default: none) != none + or it.at("location", default: none) != none + ), + message: (_, it) => "Either \"online\" or \"location\" must be set", + ), + ), + )) + assert-arg-type("cover-image", cover-image, z.content(optional: true)) + assert-arg-type( + "acknowledgements", + acknowledgements, + z.content(optional: true), + ) + assert-arg-type("extra-preambles", extra-preambles, z.array(z.dictionary(( + heading: z.string(min: 1), + body: z.content(), + )))) + assert-arg-type("doc-date", doc-date, z.date()) + assert-arg-type("doc-city", doc-city, z.string(min: 1)) + assert-arg-type("doc-extra-keywords", doc-extra-keywords, z.array( + z.string(min: 1), + )) + assert-arg-type("with-for-diva", with-for-diva, z.boolean()) + assert-arg-type("style", style, z.dictionary( + ( + use-arial: z.boolean(optional: true), + more-sans-serif: z.boolean(optional: true), + fancy-chapters: z.boolean(optional: true), + ), + optional: true, + )) + + // ---------- END OF MANUAL TYPE CHECKING ---------- + + let style = ( + ( + more-sans-serif: false, + use-arial: false, + fancy-chapters: false, + ) + + style // provided values have higher precedence over default values + ) + + let alt-lang = if primary-lang == "en" { + "sv" + } else if primary-lang == "sv" { + "en" + } else { + panic("Invalid primary language " + primary-lang) + } + + let primary-info = localized-info.at(primary-lang) + let alt-info = localized-info.at(alt-lang) + + let author-names = authors.map(extract-name) + + set document( + title: get-one-liner(primary-lang, primary-info), + description: get-one-liner(alt-lang, alt-info), // Subject field + date: doc-date, + keywords: primary-info.at("keywords") + doc-extra-keywords, + author: author-names, + ) + set page("a4") + set text(lang: primary-lang, size: 12pt) + + front-cover( + title: primary-info.at("title"), + subtitle: primary-info.at("subtitle", default: none), + authors: author-names, + subject-area: degree.at("subject-area"), + cycle: degree.at("cycle"), + credits: course.at("credits"), + cover-image: cover-image, + style, + ) + + page[] // blank + + set text(font: maybe-sans-serif(style)) + + title-page( + title: primary-info.at("title"), + subtitle: primary-info.at("subtitle", default: none), + alt-title: alt-info.at("title"), + alt-subtitle: alt-info.at("subtitle", default: none), + alt-lang: alt-lang, + degree: degree.at("name"), + date: doc-date, + authors: author-names, + supervisors: supervisors.map(extract-name), + examiner-name: extract-name(examiner), + examiner-school: examiner.at("school"), + host-company: host-company, + host-org: host-org, + ) + + copyright-page(year: doc-date.year(), authors: author-names) + + global-setup(style, { + set page(numbering: "i") + counter(page).update(1) + + for (lang, info) in localized-info { + page( + localized-abstract( + lang: lang, + abstract-heading: info.at("abstract-heading", default: none), + keywords-heading: info.at("keywords-heading", default: none), + keywords: info.at("keywords"), + info.at("abstract"), + ), + ) + page(header: none, footer: none, []) // blank + } + + page( + signed-acknowledgements( + city: doc-city, + date: doc-date, + authors: author-names, + acknowledgements, + ), + ) + + page(indices) + + for extra in extra-preambles { + extra-preamble(title: extra.at("heading"), extra.at("body")) + } + + [#metadata(()) ] + pagebreak(to: "odd") + + // text.font reflects original font because of the `context` surrounding + // this entire function (prior to when the font was changed) + set text(font: text.font) + + set page(numbering: "1") + counter(page).update(1) + + styled-body(style, body) + }) + + let trita-series = school + "-EX" + + [#metadata(()) ] + pagebreak(to: "odd") + + page[] // empty + back-cover( + trita-series: trita-series, + trita-number: trita-number, + year: doc-date.year(), + style, + ) + + context if with-for-diva { + let page-series-counts = ( + numbering("i", ..counter(page).at()), + numbering("1", ..counter(page).at()), + ) + + page( + for-diva-json( + primary-lang: primary-lang, + alt-lang: alt-lang, + localized-info: localized-info, + authors: authors, + supervisors: supervisors, + examiner: examiner, + course: course, + degree: degree, + national-subject-categories: national-subject-categories, + trita-series: trita-series, + trita-number: trita-number, + host-company: host-company, + host-org: host-org, + opponents: opponents, + presentation: presentation, + doc-date: doc-date, + page-series-counts: page-series-counts, + ), + ) + } +} diff --git a/packages/preview/kthesis/0.1.7/src/styling-setup.typ b/packages/preview/kthesis/0.1.7/src/styling-setup.typ new file mode 100644 index 0000000000..58bcc8ff84 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/styling-setup.typ @@ -0,0 +1,117 @@ +#import "./utils.typ": kth-blue, maybe-sans-serif, t + +#import "@preview/headcount:0.1.1": dependent-numbering +#import "@preview/hydra:0.6.3": hydra + +#let header(style) = context { + set text(font: maybe-sans-serif(style)) + + let chapter = hydra(1, skip-starting: false, display: (ctx, h) => h.body) + + let number = counter(page).display(here().page-numbering()) + + if calc.odd(here().page()) { + align(right, [#chapter | #number]) + } else { + align(left, [#number | #chapter]) + } +} + +#let global-setup(style, body) = context { + set page( + // I don't like these numbers, especially the bottom margin... + margin: (top: 37mm, bottom: 50mm, inside: 45mm, outside: 35mm), + header-ascent: 15mm + 6mm, + footer-descent: 25mm, + header: header(style), + footer: none, + ) + + set par(justify: true) + + show heading: set text(font: maybe-sans-serif(style)) + + // front matter only; essentially styles [h1 as h2] and [h2 as h3] + show heading.where(level: 1): set text(size: 18pt) + show heading.where(level: 2): set text(size: 14pt) + + show figure: set figure(supplement: t("figure")) + show figure.where(kind: table): set figure(supplement: t("figure-table")) + show figure.where(kind: raw): set figure(supplement: t("figure-code")) + + set figure(numbering: dependent-numbering("1.1")) + + body +} + +#let styled-body(style, body) = { + set heading(numbering: "1.1.", supplement: t("section")) + + show heading: set text(size: 12pt) // for level > 3 + show heading.where(level: 1): set text(size: 25pt) + show heading.where(level: 2): set text(size: 18pt) + show heading.where(level: 3): set text(size: 14pt) + + // cannot merge these rules or the first one won't work + show heading.where(level: 1): set heading(supplement: t("chapter")) + show heading.where(level: 1): it => { + pagebreak(weak: true, to: "odd") + + counter(figure.where(kind: image)).update(0) + counter(figure.where(kind: table)).update(0) + counter(figure.where(kind: raw)).update(0) + + if it.numbering == none { + it.body + } else { + let numbering = it.numbering.slice(0, -1) // remove trailing . + let number = counter(heading).display(numbering) + + if style.fancy-chapters { + [ + #set align(end) + + #text(fill: rgb("#444"), [ + #upper(it.supplement) #box(rect( + fill: rgb("#444"), + outset: 2pt, + text( + size: 60pt, + fill: white, + align(center, number), + ), + )) + ]) \ + #text(size: 36pt, strong(it.body)) + ] + } else { + [ + #it.supplement #number \ + #it.body + ] + } + + v(1em) + } + } + + show link: it => if type(it.dest) == str { + // only affect external links, not e.g. glossary refs + underline( + stroke: 1pt + kth-blue, + text(fill: kth-blue, it), + ) + } else { + it + } + + body +} + +#let setup-appendices(body) = { + set heading(numbering: "A.1.") + counter(heading).update(0) + show heading.where(level: 1): set heading(supplement: t("appendix")) + + body +} diff --git a/packages/preview/kthesis/0.1.7/src/utils.typ b/packages/preview/kthesis/0.1.7/src/utils.typ new file mode 100644 index 0000000000..701e04a48f --- /dev/null +++ b/packages/preview/kthesis/0.1.7/src/utils.typ @@ -0,0 +1,212 @@ +#import "@preview/linguify:0.5.0": linguify +#import "@preview/valkyrie:0.2.2" as z + +#let kth-blue = rgb("#004791") +#let kth-navy = rgb("#000061") + +#let lang-db = toml("./lang.toml") + +#let t = key => linguify(key, from: lang-db) + +#let assert-arg-type(name, value, schema) = { + let _ = z.parse(value, schema, scope: ("kthesis argument " + name,)) +} + +#let z-arbitrarily-keyed-dict( + name, + k-schema, + v-schema, + require-keys: (), + assertions: (), + ..args, +) = { + // see: https://github.com/typst-community/valkyrie/issues/53#issuecomment-3297983717 + // this feature is missing from valkyrie so we have to implement it manually + // using transformations to validate as an array of pairs + + for req in require-keys { + assertions.push(( + condition: (_, it) => it.find(((k, _)) => k == req) != none, + message: (_, it) => "Must contain key `" + req + "`", + )) + } + + return z.array( + z.tuple(k-schema, v-schema), + pre-transform: (_, it) => { + assert.eq( + type(it), + dictionary, + message: "kthesis argument `" + name + "` must be a dictionary", + ) + + it.pairs() + }, + post-transform: (_, it) => it.fold((:), (acc, (k, v)) => acc + ((k): v)), + assertions: assertions, + ..args, + ) +} + +#let z-matches-regex(pattern, message) = { + return ( + ( + condition: (_, it) => it.match(regex(pattern)) != none, + message: (_, it) => message, + ), + ) +} + +#let sans-serif(style) = { + let fonts = ("Liberation Sans",) + + if style.use-arial { + fonts.insert(0, "Arial") + } + + return fonts +} + +// needs to be invoked from within a `context` block (to access `text.font`) +#let maybe-sans-serif(style) = { + if style.more-sans-serif { + sans-serif(style) + } else { + text.font + } +} + +#let get-one-liner(lang, info) = { + let title = info.at("title") + let subtitle = info.at("subtitle", default: none) + + if subtitle == none { + return title + } + + // I don't really understand why this is the intended behavior either... + if lang == "sv" { + title + " - " + subtitle + } else { + title + ": " + subtitle + } +} + +#let extract-name(person) = { + return person.at("first-name") + " " + person.at("last-names") +} + +#let join-names(names) = { + return names.join(", ", last: t("separator-last")) +} + +#let omit-dict-none(d) = { + return d.pairs().filter(((_, v)) => v != none).to-dict() +} + +// This function most definitely should not exist, but alas... +#let content-to-string(it, mode: "plain") = { + assert( + mode == "plain" or mode == "html", + message: "mode must be 'plain' or 'html'", + ) + let content-to-string = content-to-string.with(mode: mode) + + let escape-body = body => { + if mode == "html" { + body.replace("<", "<").replace(">", ">") + } else { + body + } + } + + let tag-or-plain = (tag, body, attrs: none) => { + if mode == "html" { + let attrs = if attrs != none { + " " + attrs + } else { + "" + } + "<" + tag + attrs + ">" + body + "" + } else { + body + } + } + + if type(it) == str { + escape-body(it) + } else if type(it) == content { + if it.func() == raw { + if it.block { + tag-or-plain("pre", it.text) + } else { + tag-or-plain("code", it.text) + } + } else if it == [ ] { + " " + } else if it.func() == linebreak { + if mode == "html" { + "
" + } else { + "\n" + } + } else if it.func() == parbreak { + if mode == "html" { + "

" + } else { + "\n\n" + } + } else if it.func() == smartquote { + if it.double { + "\"" + } else { + "'" + } + } else if it.func() == strong { + tag-or-plain("strong", content-to-string(it.body)) + } else if it.func() == emph { + tag-or-plain("i", content-to-string(it.body)) + } else if it.func() == super { + tag-or-plain("sup", content-to-string(it.body)) + } else if it.func() == sub { + tag-or-plain("sub", content-to-string(it.body)) + } else if it.func() == link and type(it.dest) == str { + if mode == "html" { + tag-or-plain( + "a", + content-to-string(it.body), + attrs: "href='" + it.dest + "'", + ) + } else { + it.body + } + } else if it.func() == heading { + tag-or-plain("h" + str(it.depth), content-to-string(it.body)) + } else if it.has("child") { + content-to-string(it.child) + } else if it.has("children") { + it.children.map(content-to-string).join() + } else if it.has("body") { + content-to-string(it.body) + } else if it.has("text") { + if type(it.text) == str { + escape-body(it.text) + } else { + content-to-string(it.text) + } + } else { + panic("Cannot serialize content: " + json.encode(it)) + } + } else { + panic("Cannot serialize object: " + json.encode(it)) + } +} + +#let content-to-html(content) = { + // trim empty paragraphs + let html = content-to-string(content, mode: "html") + .replace(regex("^(

)+"), "") + .replace(regex("(

)+$"), "") + + "

" + html.trim() + "

" +} diff --git a/packages/preview/kthesis/0.1.7/template/acronyms.typ b/packages/preview/kthesis/0.1.7/template/acronyms.typ new file mode 100644 index 0000000000..33a69aa45f --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/acronyms.typ @@ -0,0 +1,15 @@ +#let acronyms = ( + ( + key: "kth", + short: "KTH", + long: "KTH Royal Institute of Technology", + description: "A university in Stockholm", + ), + ( + key: "os", + short: "OS", + plural: "OSes", + long: "Operating System", + longplural: "Operating Systems", + ), +) diff --git a/packages/preview/kthesis/0.1.7/template/content/abstract-1-en.typ b/packages/preview/kthesis/0.1.7/template/content/abstract-1-en.typ new file mode 100644 index 0000000000..d83d8aaab9 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/abstract-1-en.typ @@ -0,0 +1 @@ +#lorem(300) diff --git a/packages/preview/kthesis/0.1.7/template/content/abstract-2-sv.typ b/packages/preview/kthesis/0.1.7/template/content/abstract-2-sv.typ new file mode 100644 index 0000000000..d83d8aaab9 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/abstract-2-sv.typ @@ -0,0 +1 @@ +#lorem(300) diff --git a/packages/preview/kthesis/0.1.7/template/content/abstract-3-pt.typ b/packages/preview/kthesis/0.1.7/template/content/abstract-3-pt.typ new file mode 100644 index 0000000000..d83d8aaab9 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/abstract-3-pt.typ @@ -0,0 +1 @@ +#lorem(300) diff --git a/packages/preview/kthesis/0.1.7/template/content/acknowledgements.typ b/packages/preview/kthesis/0.1.7/template/content/acknowledgements.typ new file mode 100644 index 0000000000..f31891a38e --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/acknowledgements.typ @@ -0,0 +1,5 @@ +#lorem(100) + +#lorem(150) + +#lorem(35) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch01-introduction.typ b/packages/preview/kthesis/0.1.7/template/content/ch01-introduction.typ new file mode 100644 index 0000000000..fda3c2031d --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch01-introduction.typ @@ -0,0 +1,21 @@ += Introduction + +See @bg:a1 for more information. + +#lorem(50) + +== Problem + +#lorem(100) + +== Purpose + +#lorem(50) + +== Goals + +#lorem(100) + +== Structure of the Thesis + +#lorem(50) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch02-background.typ b/packages/preview/kthesis/0.1.7/template/content/ch02-background.typ new file mode 100644 index 0000000000..b9a4cf9b6a --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch02-background.typ @@ -0,0 +1,25 @@ += Background + +#lorem(50) + +== Major Background Area 1 + +=== Subarea 1.1 + +As mentioned in @intro, ... + +=== Subarea 1.2 + +This is explained in @usage. + +== Major Background Area 2 + +#lorem(20) + +== Related Work Area + +#lorem(50) + +== Summary + +#lorem(20) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch03-method.typ b/packages/preview/kthesis/0.1.7/template/content/ch03-method.typ new file mode 100644 index 0000000000..13d7df05b8 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch03-method.typ @@ -0,0 +1,27 @@ += Method or Methods + +#lorem(50) + +== Research Process + +#lorem(30) + +== Data Collection + +#lorem(10) + +=== Sampling + +#lorem(20) + +=== Sample Size + +#lorem(25) + +=== Target Population + +#lorem(30) + +== Evaluation Framework + +#lorem(20) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch04-the-thing.typ b/packages/preview/kthesis/0.1.7/template/content/ch04-the-thing.typ new file mode 100644 index 0000000000..5bf0ba9337 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch04-the-thing.typ @@ -0,0 +1,15 @@ += The Thing + +== Software Design + +#lorem(50) + +== Implementation + +#lorem(1250) + +=== An Example Listing + +#figure(caption: [An innocuous command], raw("rm -rf /")) + +#lorem(30) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch05-results.typ b/packages/preview/kthesis/0.1.7/template/content/ch05-results.typ new file mode 100644 index 0000000000..83b5eb009b --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch05-results.typ @@ -0,0 +1,5 @@ += Results and Analysis + +As supported by @bevet, ... + +#lorem(100) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch06-discussion.typ b/packages/preview/kthesis/0.1.7/template/content/ch06-discussion.typ new file mode 100644 index 0000000000..e7731e7ad8 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch06-discussion.typ @@ -0,0 +1,9 @@ += Discussion + +Let's talk about acronyms: when referring to a single @os, the first time we mention it the @os gets expanded, but not any further. + +We can also discuss multiple @os:pl at once, in plural. + +#line(length: 50%) + +#lorem(200) diff --git a/packages/preview/kthesis/0.1.7/template/content/ch07-conclusion.typ b/packages/preview/kthesis/0.1.7/template/content/ch07-conclusion.typ new file mode 100644 index 0000000000..35515cdcc8 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/ch07-conclusion.typ @@ -0,0 +1,5 @@ += Conclusions and Future Work + +In conclusion, execute @thing:impl:cmd. + +This is another reference to @os:pl. diff --git a/packages/preview/kthesis/0.1.7/template/content/zz-a-usage.typ b/packages/preview/kthesis/0.1.7/template/content/zz-a-usage.typ new file mode 100644 index 0000000000..53f9a92f89 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/zz-a-usage.typ @@ -0,0 +1,5 @@ += Usage Instructions + +Hmm... + +#lorem(1000) diff --git a/packages/preview/kthesis/0.1.7/template/content/zz-b-else.typ b/packages/preview/kthesis/0.1.7/template/content/zz-b-else.typ new file mode 100644 index 0000000000..f26d61f45f --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/content/zz-b-else.typ @@ -0,0 +1,3 @@ += Something Else + +Whoa! diff --git a/packages/preview/kthesis/0.1.7/template/references.yaml b/packages/preview/kthesis/0.1.7/template/references.yaml new file mode 100644 index 0000000000..c094f8bc16 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/references.yaml @@ -0,0 +1,18 @@ +# Hayagriva: https://github.com/typst/hayagriva/blob/main/docs/file-format.md + +bevet: + type: article + title: 'You can''t "nudge" nuggets: An investigation of college late-night dining with behavioral economics interventions' + author: + - Samuel Bevet + - Meredith T. Niles + - Lizzy Pope + date: 2018-05-31 + serial-number: + doi: "10.1371/journal.pone.0198162" + parent: + type: periodical + title: "PLoS ONE" + volume: 13 + issue: 5 + publisher: Public Library of Science diff --git a/packages/preview/kthesis/0.1.7/template/thesis.typ b/packages/preview/kthesis/0.1.7/template/thesis.typ new file mode 100644 index 0000000000..cd790e1726 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/template/thesis.typ @@ -0,0 +1,195 @@ +#import "@preview/kthesis:0.1.7": kth-thesis, setup-appendices + +// The template is extensible and plays well with other dependencies; +// For example, a table of acronyms can be generated using glossarium +#import "@preview/glossarium:0.5.10": ( + make-glossary, print-glossary, register-glossary, +) +#import "./acronyms.typ": acronyms +#show: make-glossary +#register-glossary(acronyms) + +// Configure formatting options before invoking the template; +// For example, uncomment below to set another font (except for covers) +// #set text(font: "New Computer Modern") + +// --------------------------------------------------------------------- // +// ---------- MAIN THESIS TEMPLATE ENTRYPOINT & CONFIGURATION ---------- // +// --------------------------------------------------------------------- // +#show: kth-thesis.with( + // Primary document language; either "en" or "sv" + primary-lang: "en", + // Language-specific title, subtitle, abstract, and keywords. + // Grouped by language, with only values for "en" and "sv" being mandatory. + // Localized abstract/keywords headings may be omitted only for "en" and "sv". + // Field "alpha-3" is the language's ISO 639-3 code, for non-"en"/"sv" langs. + // If desired, any "subtitle" field may be set to none (to omit it entirely). + localized-info: ( + en: ( + title: "How to Abandon Dinosaur-Age TypeSetting Software", + subtitle: "A Modern Approach to Problem-Solving", + abstract: include "./content/abstract-1-en.typ", + keywords: ("Dogs", "Chicken nuggets"), + ), + sv: ( + title: "Svenska Översättningen av Titeln", + subtitle: "Svenska Översättningen av Undertiteln", + abstract: include "./content/abstract-2-sv.typ", + keywords: ("Hundar", "Kycklingnuggets"), + ), + pt: ( + alpha-3: "por", + title: "Tradução em Português do Título", + subtitle: "Tradução em Português do Subtítulo", + abstract-heading: "Resumo", + keywords-heading: "Palavras-chave", + abstract: include "./content/abstract-3-pt.typ", + keywords: ("Cães", "Nuggets de frango"), + ), + ), + // Ordered author information; only first and last names fields are mandatory + authors: ( + ( + first-name: "John", + last-names: "Doe", + email: "john.doe@example.com", + user-id: "jod", + school: "School of Electrical Engineering and Computer Science", + department: "Department of Typesetting Sanity", + ), + ( + first-name: "Jane", + last-names: "Doe", + ), + ), + // Ordered supervisor information; "external-org" replaces userid/school/dept + supervisors: ( + ( + first-name: "Alice", + last-names: "Smith", + email: "alice@example.com", + user-id: "alice", + school: "School of Electrical Engineering and Computer Science", + department: "Department of Loyal Supervision", + ), + ( + first-name: "Bob", + last-names: "Jones", + email: "bob@example.com", + external-org: "Företag AB", + ), + ), + // Thesis examiner; must be internal to the school so all fields are mandatory + examiner: ( + first-name: "Charlie", + last-names: "Johnson", + email: "charlie@example.com", + user-id: "chj", + school: "School of Electrical Engineering and Computer Science", + department: "Department of Fair Examination", + ), + // Degree project course within which the thesis is being conducted. + // All fields are mandatory; credits are the course's ECTS credits (hp). + course: ( + code: "DA237X", + credits: 30, + ), + // Degree as part of which the thesis is conducted; all fields are mandatory. + // Subject area is main field of study as listed in the second dropdown here: + // https://www.kth.se/en/student/studier/examen/examensregler-1.5685 + // Kind is the degree title conferred as listed in the third dropdown above. + // Cycle is either 1 (Bachelor's) or 2 (Master's), per Bologna. + degree: ( + code: "TCYSM", + name: "Master's Program, Cybersecurity", + subject-area: "Computer Science and Engineering", + kind: "Master of Science", + cycle: 2, + ), + // National subject category codes; mandatory for DiVA classification. + // One or more 3-to-5 digit codes, with preference for 5-digit codes, from: + // https://www.scb.se/dokumentation/klassifikationer-och-standarder/standard-for-svensk-indelning-av-forskningsamnen/ + // ^ (select from that page the most recent PDF) + national-subject-categories: ("10201", "10206"), + // School that the thesis is part of (abbreviation) + school: "EECS", + // TRITA number assigned to thesis after final examiner approval + trita-number: "2026:0000", + // Host company collaborating for this thesis; may be none + host-company: "Företag AB", + // Host organization collaborating for this thesis; may be none + host-org: none, + // Names of opponents for this thesis; may be none until they're assigned + opponents: ("Mary Ignatia", "Alexander Smith"), + // Thesis presentation details; may be none until it's scheduled and set. + // Either "online" or "location" fields may be none, but not both. + presentation: ( + language: "en", + slot: datetime( + year: 2026, + month: 6, + day: 14, + hour: 13, + minute: 0, + second: 0, + ), + online: (service: "Zoom", link: "https://kth-se.zoom.us/j/111222333"), + location: ( + room: "F1 (Alfvénsalen)", + address: "Lindstedtsvägen 22", + city: "Stockholm", + ), + ), + // Optional image to show on the front cover. + // This should either be none, or an "image" element. For example, + // cover-image: image("./assets/cover.png", width: 100%) + // If provided, the image can be formatted arbitrarily to look however desired + // (especially its height, width, and fit mode). However, the recommended + // styles are (width: 100%) or (width: 16cm, height: 10cm, fit: "contain"). + cover-image: none, + // Acknowledgements body + acknowledgements: include "content/acknowledgements.typ", + // Additional front-matter sections, each with keys "heading" and "body" + extra-preambles: ( + (heading: "Acronyms and Abbreviations", body: print-glossary(acronyms)), + ), + // Document date; hardcode for determinism/reproducibility + doc-date: datetime.today(), + // Document city (where it's being signed/authored/submitted) + doc-city: "Stockholm", + // Extra keywords, embedded in document metadata but not listed in text + doc-extra-keywords: ("master thesis",), + // Whether to include trailing "For DiVA" metadata structure section + with-for-diva: true, + // Miscellaneous settings affecting the document's appearance + style: ( + // Whether the proprietary Arial font should be used in Sans-Serif contexts. + // While this is the font prescribed by the official KTH covers, it is often + // preferable to use an open, metric-compatible alternative. If this is set + // to `false`, Liberation Sans will be used instead of Arial. Otherwise, if + // this is set to `true`, Typst will issue a warning if Arial is not found + // on the system at compile-time. + // Graceful font fallback is not possible until issue typst#6010 is fixed. + use-arial: false, + // Whether front matter, headings, and headings should use a Sans-Serif font + more-sans-serif: false, + // Whether to make top-level headings stand out more and look less plain + fancy-chapters: false, + ), +) + +// Tip: when tagging elements, scope labels like + +#include "./content/ch01-introduction.typ" +#include "./content/ch02-background.typ" +#include "./content/ch03-method.typ" +#include "./content/ch04-the-thing.typ" +#include "./content/ch05-results.typ" +#include "./content/ch06-discussion.typ" +#include "./content/ch07-conclusion.typ" + +#bibliography("references.yaml", title: "References") + +#show: setup-appendices +#include "./content/zz-a-usage.typ" +#include "./content/zz-b-else.typ" diff --git a/packages/preview/kthesis/0.1.7/thumbnail.png b/packages/preview/kthesis/0.1.7/thumbnail.png new file mode 100644 index 0000000000..fa50361f1a Binary files /dev/null and b/packages/preview/kthesis/0.1.7/thumbnail.png differ diff --git a/packages/preview/kthesis/0.1.7/typst.toml b/packages/preview/kthesis/0.1.7/typst.toml new file mode 100644 index 0000000000..136a995269 --- /dev/null +++ b/packages/preview/kthesis/0.1.7/typst.toml @@ -0,0 +1,17 @@ +[package] +name = "kthesis" +version = "0.1.7" +entrypoint = "src/lib.typ" +authors = ["Rafael Oliveira <@RafDevX>"] +license = "MIT AND MIT-0" +description = "Unofficial thesis template for KTH Royal Institute of Technology" +repository = "https://github.com/RafDevX/kthesis-typst" +categories = ["thesis", "layout"] +keywords = ["KTH", "Kungliga Tekniska högskolan", "exjobb", "master", "thesis"] +compiler = "0.13.0" +exclude = [".gitignore", "*.sh", "shell.nix", ".envrc"] + +[template] +path = "template" +entrypoint = "thesis.typ" +thumbnail = "thumbnail.png"