1
0
dfa-for-co-slr/fancyuulm.typ
2025-07-22 15:20:41 +02:00

282 lines
11 KiB
Typst

// This theme is adapted from https://github.com/touying-typ/touying/blob/main/themes/simple.typ by OrangeX4
#import "@preview/touying:0.6.1": *
/// Default slide function for the presentation.
///
/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For more several configurations, you can use `utils.merge-dicts` to merge them.
///
/// - repeat (int, auto): The number of subslides. Default is `auto`, which means touying will automatically calculate the number of subslides.
///
//// The `repeat` argument is necessary when you use `#slide(repeat: 3, self => [ .. ])` style code to create a slide. The callback-style `uncover` and `only` cannot be detected by touying automatically.
///
/// - setting (function): The setting of the slide. You can use it to add some set/show rules for the slide.
///
/// - composer (function): The composer of the slide. You can use it to set the layout of the slide.
///
/// For example, `#slide(composer: (1fr, 2fr, 1fr))[A][B][C]` to split the slide into three parts. The first and the last parts will take 1/4 of the slide, and the second part will take 1/2 of the slide.
///
/// If you pass a non-function value like `(1fr, 2fr, 1fr)`, it will be assumed to be the first argument of the `components.side-by-side` function.
///
/// The `components.side-by-side` function is a simple wrapper of the `grid` function. It means you can use the `grid.cell(colspan: 2, ..)` to make the cell take 2 columns.
///
/// For example, `#slide(composer: 2)[A][B][#grid.cell(colspan: 2)[Footer]]` will make the `Footer` cell take 2 columns.
///
/// If you want to customize the composer, you can pass a function to the `composer` argument. The function should receive the contents of the slide and return the content of the slide, like `#slide(composer: grid.with(columns: 2))[A][B]`.
///
/// - bodies (array): The contents of the slide. You can call the `slide` function with syntax like `#slide[A][B][C]` to create a slide.
#let slide(
config: (:),
repeat: auto,
setting: body => body,
composer: auto,
..bodies,
) = touying-slide-wrapper(self => {
let header(self) = utils.call-or-display(self, self.store.header)
let footer(self) = {
text(size: 0.5em, fill: self.colors.neutral-lightest, grid(
align: (left + horizon, center + horizon, right + horizon),
fill: self.colors.secondary,
columns: (1fr, 4.5fr, 1fr),
// gutter: 0em,
// stroke: (x: self.colors.secondary + 0.5em),
rows: 100%,
box(inset: (left: self.page.margin.x / 2), utils.call-or-display(self, self.store.footer-left)),
utils.call-or-display(self, self.store.footer),
box(fill: self.colors.primary, height: 100%, inset: (x: self.page.margin.x / 2), align(center, utils.call-or-display(self, self.store.footer-right))),
)
)
}
let self = utils.merge-dicts(
self,
config-page(
header: header,
footer: footer,
),
config-common(subslide-preamble: self.store.subslide-preamble),
)
touying-slide(self: self, config: config, repeat: repeat, setting: setting, composer: composer, ..bodies)
})
/// Centered slide for the presentation.
///
/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For several configurations, you can use `utils.merge-dicts` to merge them.
#let centered-slide(config: (:), ..args) = touying-slide-wrapper(self => {
touying-slide(self: self, ..args.named(), config: config, align(center + horizon, args.pos().sum(default: none)))
})
/// Title slide for the presentation.
///
/// Example: `#title-slide(title-image: "assets/title-image.png")`
///
/// - content (content | none): The content of the title slide, typically an image. The content will be displayed centered above the title and subtitle.
///
/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For several configurations, you can use `utils.merge-dicts` to merge them.
#let title-slide(content, config: (:), ..args) = touying-slide-wrapper(self => {
let body = {
set block(spacing: 0em)
// show text: it => align(right + horizon, it)
set text(fill: self.colors.neutral-lightest)
block(
inset: (x: self.page.margin.x * 2/3, y: 0.5cm),
width: 100%,
height: 1fr,
content
)
block(
fill: self.colors.primary,
width: 100%,
height: 2.2cm,
inset: (x: self.page.margin.x * 2/3),
align(right + horizon, text(size: 1.4em, weight: "bold", self.info.title))
)
let secondary-info = (self.info.subtitle, self.info.author, self.info.date.display("[month repr:long] [day], [year]")).filter(it => it != none).join(" | ")
block(
fill: self.colors.secondary,
width: 100%,
height: 1.1cm,
inset: (x: self.page.margin.x * 2/3),
align(right + horizon, text(size: 0.9em, secondary-info))
)
block(
width: 100%,
height: 2.2cm,
inset: (x: self.page.margin.x * 2/3, y: 0.35cm),
box(image(self.info.institute-logo, height: 1fr)) + h(1fr) + box(image(self.info.university-logo, height: 1fr))
)
}
let self = utils.merge-dicts(
self,
config-common(freeze-slide-counter: true),
config-page(margin: 0em)
)
touying-slide(self: self, ..args.named(), config: config, body)
})
/// New section slide for the presentation. You can update it by updating the `new-section-slide-fn` argument for `config-common` function.
///
/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For more several configurations, you can use `utils.merge-dicts` to merge them.
#let new-section-slide(config: (:), body) = centered-slide(config: config, [
#text(1.2em, weight: "bold", utils.display-current-heading(level: 1))
#body
])
/// Focus on some content.
///
/// Example: `#focus-slide[Wake up!]`
///
/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For more several configurations, you can use `utils.merge-dicts` to merge them.
///
/// - background (color, auto): The background color of the slide. Default is `auto`, which means the primary color of the slides.
///
/// - foreground (color): The foreground color of the slide. Default is `white`.
#let focus-slide(config: (:), background: auto, foreground: white, body) = touying-slide-wrapper(self => {
self = utils.merge-dicts(
self,
config-common(freeze-slide-counter: true),
config-page(fill: if background == auto {
self.colors.primary
} else {
background
}),
)
set text(fill: foreground, size: 1.5em)
touying-slide(self: self, config: config, align(center + horizon, body))
})
/// Fancy Ulm University theme (Institute of Software Engineering and Programming Languages).
///
/// Example:
///
/// ```typst
/// #show: fancyuulm.with(aspect-ratio: "16-9", config-colors(primary: blue))`
/// ```
///
/// The default colors:
///
/// ```typst
/// config-colors(
/// neutral-light: gray,
/// neutral-lightest: rgb("#ffffff"),
/// neutral-darkest: rgb("#000000"),
/// primary: rgb("#A32638"),
/// secondary: rgb("#A9A28D"),
/// )
/// ```
///
/// - aspect-ratio (string): The aspect ratio of the slides. Default is `16-9`.
///
/// - header (function): The header of the slides. Default is `self => utils.display-current-heading(setting: utils.fit-to-width.with(grow: false, 100%), depth: self.slide-level)`.
///
/// - footer-left (function): The left part of the footer. Default is `self => self.info.author`.
///
/// - footer (function): The footer of the slides. Default is `self => self.info.title`.
///
/// - footer-right (content): The right part of the footer. Default is `context utils.slide-counter.display()`.
///
/// - primary (color): The primary color of the slides. Default is `rgb("#A32638")`.
///
/// - secondary (color): The secondary color of the slides. Default is `rgb("#A9A28D")`.
///
/// - subslide-preamble (content): The preamble of the subslides. Default is `block(below: 1.5em, text(1.2em, weight: "bold", utils.display-current-heading(level: 2)))`.
///
/// - include-new-section-slides (bool): Whether to include slides for introducing new sections. Default is `false`.
#let fancyuulm(
aspect-ratio: "16-9",
header: self => [],//utils.display-current-heading(
// setting: utils.fit-to-width.with(grow: false, 100%),
// level: 1,
// depth: self.slide-level,
// ),
footer-left: self => self.info.author,
footer: self => [#self.info.title -- #self.info.subtitle -- #utils.display-current-heading(level: 1)],
footer-right: context utils.slide-counter.display(),
primary: rgb("#A32638"),
secondary: rgb("#A9A28D"),
subslide-preamble: block(
below: 1.3cm,
text(1.5em, weight: "bold", utils.display-current-heading(level: 2)),
),
include-new-section-slides: false,
..args,
body,
) = {
show: touying-slides.with(
config-page(
paper: "presentation-" + aspect-ratio,
margin: (x: 2cm, top: 1.5cm, bottom: 2cm),
footer-descent: 1.2cm,
),
config-common(
slide-fn: slide,
new-section-slide-fn: if include-new-section-slides { new-section-slide },
zero-margin-header: false,
zero-margin-footer: true,
),
config-methods(
init: (self: none, body) => {
set text(size: 18pt, number-width: "tabular", font: "Fira Sans")
show raw: set text(font: "Fira Code")
show math.equation: set text(font: "Noto Sans Math")
set par(spacing: 1cm)
set footnote.entry(indent: 0cm, clearance: 0.3cm)
show footnote.entry: set text(size: 8pt)
show footnote.entry: set cite(form: "full")
show footnote.entry: box.with(width: 62.5%)
// set list(spacing: 1cm)
// show list: it => {
// show list: set list(spacing: 0.75cm)
// it
// }
// show raw.where(block: false): box
// show raw.where(block: false): set box(
// fill: self.colors.neutral-lighter,
// inset: (x: 5pt, y: 0pt),
// outset: (y: 5pt),
// radius: 3pt,
// )
// set table(inset: 9pt, stroke: (0.5pt + self.colors.neutral-light))
// show table: table => {
// show raw.where(block: false): set box(
// fill: none,
// inset: (x: 1pt),
// outset: 0pt
// )
// table
// }
body
},
alert: utils.alert-with-primary-color,
),
config-colors(
neutral-light: luma(200),
neutral-lighter: luma(250),
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
primary: primary,
secondary: secondary,
),
// Save the variables for later use
config-store(
header: header,
footer-left: footer-left,
footer: footer,
footer-right: footer-right,
subslide-preamble: subslide-preamble,
),
..args,
)
body
}