diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 8987306..cbf517c 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Metropolis](./themes/gallery/metropolis.md) - [University](./themes/gallery/university.md) - [Bipartite](./themes/gallery/bipartite.md) + - [Estonia](./themes/gallery/estonia.md) - [Build your own](./themes/your-own.md) - [Utilities](./utils/utils.md) - [Side by side](./utils/side-by-side.md) diff --git a/book/src/themes/gallery/estonia.md b/book/src/themes/gallery/estonia.md new file mode 100644 index 0000000..1b833f1 --- /dev/null +++ b/book/src/themes/gallery/estonia.md @@ -0,0 +1,176 @@ +# Estonia theme + +![estonia](estonia.png) + +This theme is inspired by the +[estonia-beamer-theme](https://github.com/mpoquet/estonia-beamer-theme) +(both the polylux and beamer themes have been created by Millian Poquet). + +Use it via +```typ +{{#include ../../IMPORT.typ}} +#import themes.estonia: * + +#show: estonia-theme.with(...) +``` + +`estonia` uses polylux' section handling, the regular `#outline()` will not work +properly, use `#estonia-outline` instead. + +## Options for initialisation +`estonia-theme` accepts the following optional keyword arguments: + +- `aspect-ratio`: the aspect ratio of the slides, either `"16-9"` or `"4-3"`, + default is `"16-9"` + +## Slide functions +`estonia` does **not** provide a `title-slide` function. + +`estonia` provides the following custom slide function: + +```typ +#slide(...)[ + ... +] +``` + +This is the main function provided by `estonia`. +This function generates a slide (page) around the provided content. +It has been created as pure as possible to enable a high level of customization via its arguments. + +Here is the function definition with all its arguments. + +```typ +#let slide( + title: none, + title-bar: auto, + title-bar-args: ( + height-per-line: 1.5em, + func: est-title-bar, + func-args: (:), + ), + progress-bar: true, + progress-bar-args: ( + height: 1em, + func: est-progress-bar, + func-args: (:), + ), + bottom-bar: false, + bottom-bar-args: ( + height: 6mm, + func: est-speaker-slide-reminder-bar, + func-args: (:) + ), + body-args: ( + margin: 1cm, + alignment: left+horizon, + text-fill-color: black, + bg-color: white), + body +) +``` + +- set `progress-bar`, `title-bar`, or `bottom-bar` to `true` or `false` to always show or hide each bar + - `title-bar` can be `auto`. In this case the title bar will be shown if and only if a `title` is provided +- how your content is displayed can be customized via the `body-args` dictionary. + - `margin` defines the margin to put around your content. By default, `1cm` is used in all directions. + - you can use `margin: 0cm` to disable all margins. + - you can use `margin: (top: 0cm, rest: 1cm)` to disable the top margin. + - the values used in this dictionary are `top`, `bottom`, `left`, `right` and `rest`. + - other variables should be self-explanatory +- each bar has its own parameters, that are given in the `progress-bar-args`, `title-bar-args` or `bottom-bar-args` args + - bars have a `height`. the title bar has a `height-per-line`, which is multiplied by the number of lines in the title. + - each bar is generated by a function. a default function is provided for each bar, but this can be customized via the `func` arg. + - bar generation functions are called with some arguments set by the `slide` function: + - `width` is the total page width. this value is higher than `100%` because things other than your content are put in the page margins + - `height` is the bar height + - `title` is the title value (only used by the title bar) + - additional arguments can be set/overriden by giving a `func-args` dictionary to each bar. + +## Additional features + +The `#est-blue` variables defines the blue color used in the theme. + +The `#estonia-outline` customises `#polylux-outline` and displays a table of contents with all sections. + +--- + +The `est-progress-bar` is the default function to generate a progress bar. +It displays a content in an horizontal grid for each slide in your presentation. + +```typ +#let est-progress-bar( + width, + height, + inset: 1cm, + show-slide-number: true, + bg-color: black, + text-size: 0.8em, + alignment: horizon, + before-slide: loc => {circle(radius: .075em, fill: white.darken(50%))}, + current-slide: loc => {circle(radius: .15em, fill: white)}, + after-slide: loc => {circle(radius: .075em, fill: white.darken(50%))}, + slide-number: loc => {block(width: 1em, align(right, text(fill:white, logic.logical-slide.display())))} +) +``` + +- `before-slide`, `current-slide` and `after-slide` are functions that generate content. + these functions are called to generate the *dots* that represent each slide in the progress bar. +- `slide-number` is a function that generates a content that should display the current slide number. +- other arguments enable visual customization + +--- + +The `est-title-bar` is the default function to generate a title bar. +It is just meant to show the slide title. + +```typ +#let est-title-bar( + title, + width, + height, + bg-color: est-blue, + inset: 1cm, + alignment: left+horizon, + text-fill-color: white, + text-size: 1.2em, +) +``` + +Its arguments enable visual customization. + +--- + +The `est-speaker-slide-reminder-bar` is the default function to generate a bottom bar. +It is meant to show the speaker identity, the title presentation, and optionally the page number. + +```typ +#let est-speaker-slide-reminder-bar( + width, + height, + show-slide-number: false, + text-size: .8em, + alignment: center+horizon, + author-text-fill-color: white, + author-bg-color: black, + presentation-title-text-fill-color: white, + presentation-title-bg-color: est-blue, + slide-number-text-fill-color: black, + slide-number-bg-color: white, + author: [Speaker], + presentation-title: [Presentation title], + slide-number: {logic.logical-slide.display()} +) +``` + +- `show-slide-number` defines whether the slide number should be shown in the bar or not. +- `author` is the speaker identity +- `presentation-title` is the presentation title +- other arguments enable visual customization + +## Example code +The image at the top is created by the following code: +```typ +{{#include ../../IMPORT.typ}} +{{#include estonia.typ:3:}} +``` diff --git a/book/src/themes/gallery/estonia.typ b/book/src/themes/gallery/estonia.typ new file mode 100644 index 0000000..f5b82b8 --- /dev/null +++ b/book/src/themes/gallery/estonia.typ @@ -0,0 +1,748 @@ +#import "@local/polylux:0.3.0": * +#import themes.estonia: * + +#show: estonia-theme + +#set text(font: "Fira Sans", size: 20pt) +#show math.equation: set text(font: "Fira Math", weight: "regular") +#set strong(delta: 100) +#set par(justify: true) + +#let dslide(raw-font-size: 20pt, ..args) = { + show raw: set text(font: "Inconsolata", weight: "semibold", size: raw-font-size) + slide(..args) +} + +#set footnote.entry( + separator: rect(width:50%, height: .5mm, fill: black), + clearance: 1em, + indent: 0em, +) + +#show link: v => underline(text(v, fill: est-blue)) + +#let plain-slide = slide.with(body-args: (margin: 0mm)) +#let focus-slide(body) = { + let content = { + set align(horizon + center) + set text(size: 1.5em, fill: white) + body + } + slide(body-args: (bg-color: est-blue), content) +} +#let new-section-slide(name) = { + let content = { + utils.register-section(name) + strong(name) + } + focus-slide(content) +} + +#dslide({ + show: pad.with(2em) + grid(columns: 1, row-gutter: 1fr, + { + text(size: 1.3em, strong[The #text(fill: est-blue, [Estonia]) polylux theme]) + v(1mm) + text(size: 1em, [Simple and customizable]) + line(length: 100%, stroke: .13em + est-blue) + }, + align(center, { block(width:60%, { grid(columns: 3, column-gutter: 2fr, [Author A], [Author B], [Author C]) })}), + align(bottom+center)[Date] + ) +}) + +#dslide(title: "Table of contents")[ + #estonia-outline +] + +#new-section-slide("Showcase") + +#dslide(title: "Maths")[ + A slide with some equations + + #set align(center+horizon) + $ sum_(k=0)^n k + &= 1 + ... + n \ + &= (n(n+1)) / 2 $ + + #set align(left) + more equations + $ 7.32 beta + + sum_(i=0)^nabla + (Q_i (a_i - epsilon)) / 2 $ +] + +#dslide(title: "Footnotes")[ + Footnotes #footnote[https://typst.app/docs/reference/meta/footnote/] can be added to your Typst #footnote[Mädje, L. (2022). A Programmable Markup Language for Typesetting (Master's thesis, TU Berlin). #link("https://www.user.tu-berlin.de/laurmaedje/programmable-markup-language-for-typesetting.pdf", [pdf])] slides. + + #v(2cm) + #grid(columns: (10%, 1fr), + text(fill: red, size: 40pt, emoji.warning), + [ + Your slide content must have enough remaining vertical space. \ + Otherwise, footnotes will not be shown. + ] + ) +] + +#dslide(title:"Code blocks")[ + #set raw(theme: "mpoquet.tmTheme") + #let code = [ + ```c + // comment + int main() + { + printf("Hello, world!\n"); + if (42 == 6*7 && 0+0 == 0) + return 0; + return 1; + } + ``` + ] + + You can of course show code blocks too! + #v(2cm) + #code +] + +#dslide(title: "Titles can contain line breaks\nSuch as this one\nOr this one...")[ + Title bar height should be adjusted automatically. +] + +#new-section-slide("Usage & slide API") + +#dslide(title: "The slide function", raw-font-size: 18pt)[ + #set align(top+left) + This theme is essentially a `slide` function. + #uncover("2-")[It is called like this:] + + #uncover("2-")[ + ```typst + #slide(title: "The slide title")[ + The slide content + ] + ``` + ] + + #uncover("3-")[ + //#v(1cm) + `slide` has many parameters that enables customization. + For example to hide the progress bar, show the title bar and the bottom bar with a slide number: + + ```typst + #slide(title: "Title", progress-bar: false, title-bar: true, + bottom-bar: true, + bottom-bar-args: ( + func-args: ( + show-slide-number: true + ) + ) + ) + ``` + ] +] + +#dslide(title: "The slide function: custom call")[ + Typst makes it convenient to customize the function call for all your slides + + ```typst + #let my-slide = slide.with(/*args you want to customize*/) + ``` + + #uncover("2-")[ + #v(10mm) + You can also create your own wrapper around the function, + which is very convenient to change some parameter for specific slides only. + For example this wrapper that sets the font size of code blocks has been used heavily to generate the #text(fill:est-blue, [Estonia]) slides: + + ```typst + #let dslide(raw-font-size: 20pt, ..args) = { + show raw: set text(font: "Inconsolata", + weight: "semibold", + size: raw-font-size) + slide(..args) + } + ``` + ] +] + +#new-section-slide("Title bar customization") + +#dslide(title: "Title bar")[ + #grid(columns: (auto, auto), gutter: 3cm, + [ + `slide` API related with title (bar): + #v(5mm) + ```typst + #let slide( + title: none, + title-bar: auto, + title-bar-args: ( + height-per-line: 1.5em, + func: est-title-bar, + func-args: (:), + ), + // (other args hidden) + ) = { /* ... */ } + ``` + ], + [ + `title`: `none` or a string + #v(5mm) + `title-bar`: + - `auto`: shown iff `title` is set + - `true/false`: always shown/hidden + #v(5mm) + inside `title-bar-args` dict: + - `height-per-line` is the height of a line. This is multiplied by the number of lines in `title` + - `func` is the function that generates the title bar (default `func` is presented on next slide) + - `func-args` are the additional arguments given to `func` when it is called + + ] + ) +] + +#dslide(title: "Title bar: default function")[ + #grid(columns: (auto, auto), gutter: 3cm, + [ + ```typst + #let est-title-bar( + title, + width, + height, + bg-color: est-blue, + inset: 1cm, + alignment: left+horizon, + text-fill-color: white, + text-size: 1.2em, + ) + ``` + ], + [ + Arguments always set by `slide` at call time: + - `title`: the slide title (`string`) + - `width`: the page width (`length`) + - `height`: the title bar height (`length`) + + #v(1mm) + + The other arguments enable cutomization of the bar. + Their names should be enough to explain what they do ;). + ] + ) +] + +#let critical-slide = dslide.with( + title-bar: true, + title-bar-args: ( + height-per-line: 3cm, + func: est-title-bar, + func-args: ( + bg-color: red, + alignment: center+horizon, + text-fill-color: white, + text-size: 1.5em, + ) + ) +) + +#critical-slide(title: "Title bar: customize default function call", raw-font-size: 18pt)[ + #grid(columns: (auto, auto), gutter: 5cm, + [ + ```typst + #let critical-slide = slide.with( + title-bar: true, + title-bar-args: ( + height-per-line: 3cm, + func: est-title-bar, + func-args: ( + bg-color: red, + alignment: center+horizon, + text-fill-color: white, + text-size: 1.5em, + ) + ) + ) + ``` + ], + [ + #set align(top) + ```typst + #critical-slide(title: "...")[ + /* content */ + ] + ``` + ] + ) +] + +#let my-title-bar-func(title, width, height, inset: 3cm, nb-tilde: 10) = { + show: block.with( + width: width, height: height, + above: 0pt, below: 0pt, + breakable: false, + fill: est-blue, + inset: inset, + ) + set align(horizon+center) + set text(fill: white, size: 1.2em) + if title == none [] else { + for _ in range(nb-tilde) [\~] + h(3mm) + smallcaps(title) + h(3mm) + for _ in range(nb-tilde) [\~] + } +} + +#dslide(title: "Title bar: your own function", raw-font-size: 18pt, + title-bar-args: ( + func: my-title-bar-func, + func-args: ( + nb-tilde: 7, + ) + ) +)[ + #grid(columns: (auto, auto), gutter: 2cm, + [ + ```typst + #let my-title-bar-func(title, width, height, + inset: 3cm, nb-tilde: 10) = { + show: block.with( + width: width, height: height, + above: 0pt, below: 0pt, + breakable: false, + fill: est-blue, inset: inset, + ) + set align(horizon+center) + set text(fill: white, size: 1.2em) + if title == none [] else { + for _ in range(nb-tilde) [\~] + h(3mm) smallcaps(title) h(3mm) + for _ in range(nb-tilde) [\~] + } + } + ``` + ], + [ + ```typst + #slide(title: "...", + func: my-title-bar-func, + func-args: (nb-tilde: 7) + )[ + /* content */ + ] + ``` + ] + ) +] + +#new-section-slide("Progress bar customization") + +#dslide(title: "Progress bar")[ + #grid(columns: (auto, auto), gutter: 3cm, + [ + `slide` API related with progress bar: + #v(5mm) + ```typst + #let slide( + progress-bar: true, + progress-bar-args: ( + height: 1em, + func: est-progress-bar, + func-args: (:), + ), + // (other args hidden) + ) = { /* ... */ } + ``` + ], + [ + `progress-bar`: + - `true/false`: always shown/hidden + #v(5mm) + inside `progress-bar-args` dict: + - `height` is the height of the progress bar + - `func` is the function that generates the progress bar (default `func` is presented on next slide) + - `func-args` are the additional arguments given to `func` when it is called + + ] + ) +] + +#dslide(title: "Progress bar: default function", raw-font-size: 16pt)[ + #grid(columns: (auto, auto), gutter: 3cm, + [ + ```typst + #let est-progress-bar( + width, height, inset: 1cm, + show-slide-number: true, + bg-color: black, text-size: 0.8em, + alignment: horizon, + current-slide: loc => + {circle(radius: .15em, fill: white)}, + before-slide: loc => + {circle(radius: .075em, + fill: white.darken(50%))}, + after-slide: loc => + {circle(radius: .075em, + fill: white.darken(50%))}, + slide-number: loc => + {block(width: 1em, align(right, text( + fill:white, + logic.logical-slide.display())))} + ) + + ``` + ], + [ + Arguments always set by `slide` at call time: + - `width`: the page width (`length`) + - `height`: the progress bar height (`length`) + + #v(1mm) + + The other arguments enable cutomization of the bar. + - `before-slide`, `current-slide` and `after-slide` are functions that generate contents inside the bar. + By default they generate disks of various size/color. + - `slide-number` is a function that generate contents inside the bar. + By default it simply writes the current slide number. + ] + ) +] + +#let pacman-progress-slide = dslide.with(progress-bar-args: ( + func: est-progress-bar, + func-args: ( + show-slide-number: false, + inset: 5mm, + before-slide: loc => {circle(radius: .075em, fill: white.darken(60%))}, + after-slide: loc => {circle(radius: .075em, fill: white)}, + current-slide: loc => { text(size: 12pt, fill: yellow, weight: "bold")[<] } + ) +)) + +#pacman-progress-slide(title: "Progress bar: customize default function call")[ + ```typst + #let pacman-progress-slide = slide.with(progress-bar-args: ( + func: est-progress-bar, + func-args: ( + show-slide-number: false, + inset: 5mm, + before-slide: loc => {circle(radius: .075em, fill: white.darken(60%))}, + after-slide: loc => {circle(radius: .075em, fill: white)}, + current-slide: loc => { text(size: 12pt, fill: yellow, weight: "bold")[<] } + ) + )) + + #pacman-progress-slide(title: "...")[ + /* content */ + ] + ``` +] + +#let my-progress-bar(width, height) = { + locate(loc => { + let current_slide = logic.logical-slide.at(loc).first() - 1 + let nb_slides = logic.logical-slide.final(loc).first() + let ratio = current_slide / nb_slides + set align(left+horizon) + grid(columns: (width * ratio, width * (1 - ratio)), gutter: 0mm, + est-cell(fill: white, height: height, text(fill: est-blue, size: .8em, [#current_slide / #nb_slides])), + est-cell(fill: black, height: height) + ) + }) +} + +#dslide(title: "Progress bar: your own function", raw-font-size: 18pt, + progress-bar-args: ( + func: my-progress-bar + ))[ + ```typst + #let my-progress-bar(width, height) = { + locate(loc => { + let current_slide = logic.logical-slide.at(loc).first() - 1 + let nb_slides = logic.logical-slide.final(loc).first() + let ratio = current_slide / nb_slides + set align(left+horizon) + grid(columns: (width * ratio, width * (1 - ratio)), gutter: 0mm, + est-cell(fill: white, height: height, text(fill: est-blue, size: .8em, + [#current_slide / #nb_slides])), + est-cell(fill: black, height: height) + ) + }) + } + #slide(title: "...", progress-bar-args: (func: my-progress-bar))[ + /* content */ + ] + ``` +] + +#new-section-slide("Bottom bar customization") + +#dslide(title: "Bottom bar", raw-font-size: 18pt)[ + #grid(columns: (auto, auto), gutter: 1cm, + [ + `slide` API related with bottom bar: + #v(5mm) + ```typst + #let slide( + bottom-bar: false, + bottom-bar-args: ( + height: 6mm, + func: est-speaker-slide-reminder-bar, + func-args: (:) + ), + // (other args hidden) + ) = { /* ... */ } + ``` + ], + [ + `bottom-bar`: + - `true/false`: always shown/hidden + #v(5mm) + inside `title-bar-args` dict: + - `height` is the height of the bottom bar. \ + #text(fill: red, emoji.warning) + using `em` units here can be tedious + - `func` is the function that generates the title bar (default `func` is presented on next slide) + - `func-args` are the additional arguments given to `func` when it is called + ] + ) +] + +#dslide(title: "Bottom bar: default function", raw-font-size: 16pt)[ + #grid(columns: (auto, auto), gutter: 2cm, + [ + ```typst + #let est-speaker-slide-reminder-bar( + width, height, + show-slide-number: false, + text-size: .8em, + alignment: center+horizon, + author-text-fill-color: white, + author-bg-color: black, + presentation-title-text-fill-color: white, + presentation-title-bg-color: est-blue, + slide-number-text-fill-color: black, + slide-number-bg-color: white, + author: [Speaker], + presentation-title: [Presentation title], + slide-number: + {logic.logical-slide.display()} + ) = { /*...*/ } + ``` + ], + [ + Arguments always set by `slide` at call time: + - `width`: the page width (`length`) + - `height`: the bottom bar height (`length`) + + #v(1mm) + + The other arguments enable cutomization of the bar. + Their names should be enough to explain what they do ;). + ], + ) +] + +#let struts-slide = dslide.with( + bottom-bar: true, + bottom-bar-args: ( + func: est-speaker-slide-reminder-bar, + func-args: ( + author: [Jane Doe], + presentation-title: [Estonia], + show-slide-number: true, + ) + ) +) + +#struts-slide(title: "Bottom bar: customize default function call", raw-font-size: 18pt)[ + ```typst + #let struts-slide = slide.with( + bottom-bar: true, + bottom-bar-args: ( + func: est-speaker-slide-reminder-bar, + func-args: ( + author: [Jane Doe], + presentation-title: [Estonia], + show-slide-number: true, + ) + ) + ) + + #struts-slide(title: "...")[ + /* content */ + ] + ``` +] + +#let my-bottom-bar(width, height, font-size: .8em) = { + block(width: width, height: height, breakable:false, inset: 3mm, { + set align(horizon+right) + set text(size: font-size) + locate(loc => { + let current_slide = logic.logical-slide.at(loc).first() - 1 + let nb_slides = logic.logical-slide.final(loc).first() + [ #current_slide / #nb_slides ] + }) + }) +} + +#dslide(title: "Bottom bar: your own function", raw-font-size: 16pt, + bottom-bar: true, + bottom-bar-args: (func: my-bottom-bar) +)[ + ```typst + #let my-bottom-bar(width, height, font-size: .8em) = { + block(width: width, height: height, breakable:false, inset: 3mm, { + set align(horizon+right) + set text(size: font-size) + locate(loc => { + let current_slide = logic.logical-slide.at(loc).first() - 1 + let nb_slides = logic.logical-slide.final(loc).first() + [ #current_slide / #nb_slides ] + }) + }) + } + + #slide(title: "...", bottom-bar: true, bottom-bar-args: (func: my-bottom-bar))[ + /* content */ + ] + ``` +] + +#new-section-slide("Layout and margins") + +#let blank-progress-bar(width, height) = { + block(width: width, height: height, breakable: false, inset: 3mm, { + set align(center+horizon) + est-cell(fill:black, width:width, height: height, text(fill: white, size: .8em, [progress bar])) + }) +} + +#let blank-title-bar(title, width, height) = { + block(width: width, height: height, breakable: false, inset: 3mm, { + set align(center+horizon) + est-cell(fill: est-blue, width:width, height: height, text(fill: white, size: 1.2em, [title bar])) + }) +} + +#let blank-bottom-bar(width, height) = { + block(width: width, height: height, breakable: false, inset: 3mm, { + set align(center+horizon) + est-cell(fill:black, width:width, height: height, text(fill: white, size: .8em, [bottom bar])) + }) +} + +#let my-pink = rgb("#a0ffa0") + +#dslide(progress-bar: true, title-bar: true, bottom-bar: true, + progress-bar-args: (func: blank-progress-bar), + title-bar-args: (func: blank-title-bar), + bottom-bar-args: (func: blank-bottom-bar) +)[ + #show: rect.with(width: 100%, height: 100%, fill: my-pink) + #show: set align(center+horizon) + The layout of this theme is the one presented on this slide. \ + Bars can be disabled but cannot be placed at a different position for now. + + #v(5mm) + The green rectangle is your content bounding box. \ + Your content is by default surrounded by a 1cm border on all sides. + + #v(5mm) + From the Typst point of view, \ + everything around your content bounding box are page margins. +] + +#dslide(progress-bar: false, title-bar: false, bottom-bar: false)[ + #show: rect.with(width: 100%, height: 100%, fill: my-pink) + #set align(left+horizon) + + If you generate a slide without any bar, your content still has borders by default. + #v(1cm) + + ```typst + #slide(progress-bar: false, title-bar: false, bottom-bar: false)[ + /* content */ + ] + ``` +] + +#dslide(title: "slide body & margin customization")[ + #grid(columns: (auto, auto), gutter: 2cm, + [ + Here are the remaining `slide` arguments: + ```typst + #let slide( + body-args: ( + margin: 1cm, + alignment: left+horizon, + text-fill-color: black, + bg-color: white), + body + ) = { /* ... */ } + ``` + ], + [ + `margin`: + - if (`length`): all body margins set to the given value + - if (`dict`): specify the body margin value for each margin + - `top`, `bottom`, `left`, or `right` + - `rest` is used if a specific value is not set + ] + ) +] + +#dslide(title: "Disable all margins", body-args: (margin: 0cm))[ + #show: rect.with(width: 100%, height: 100%, fill: my-pink) + #show: set align(center) + ```typst + #slide(body-args: (margin: 0cm))[ + /* content */ + ] + ``` +] + +#dslide(title: "Disable a single margin", body-args: ( + margin: (top: 0cm, + rest: 1cm) +))[ + #show: rect.with(width: 100%, height: 100%, fill: my-pink) + #show: set align(center) + ```typst + #slide(body-args: (margin: (top: 0cm, + rest: 1cm)) + )[ + /* content */ + ] + ``` +] + +#dslide(title: "Customize all margins", bottom-bar:true, body-args: ( + margin: (top: 0cm, + bottom: 0cm, + left: 1cm, + right: 2cm), +))[ + #show: rect.with(width: 100%, height: 100%, fill: my-pink) + #show: set align(center) + ```typst + #slide(title: "...", + bottom-bar: true, + body-args: (margin: (top: 0cm, + bottom: 0cm, + left: 1cm, + right: 2cm)) + )[ + /* content */ + ] + ``` +] + +#focus-slide()[ + That's all folks! +] + diff --git a/themes/estonia.typ b/themes/estonia.typ new file mode 100644 index 0000000..75f7e7a --- /dev/null +++ b/themes/estonia.typ @@ -0,0 +1,251 @@ +// This theme is inspired by the flag of Estonia + +// Consider using: +// #set text(font: "Fira Sans", size: 20pt) +// #show math.equation: set text(font: "Fira Math", weight: "regular") +// #set strong(delta: 100) +// #set par(justify: true) +// show raw: set text(font: "Inconsolata", weight: "semibold", size: raw-font-size) + +#import "../logic.typ" +#import "../utils/utils.typ" + +#let est-blue = rgb("#0072ce") + +#let est-cell = block.with( + width: 100%, + height: 100%, + above: 0pt, + below: 0pt, + breakable: false +) + +#let est-progress-bar( + width, + height, + inset: 1cm, + show-slide-number: true, + bg-color: black, + text-size: 0.8em, + alignment: horizon, + before-slide: loc => {circle(radius: .075em, fill: white.darken(50%))}, + current-slide: loc => {circle(radius: .15em, fill: white)}, + after-slide: loc => {circle(radius: .075em, fill: white.darken(50%))}, + slide-number: loc => {block(width: 1em, align(right, text(fill:white, logic.logical-slide.display())))} +) = { + show: est-cell.with( + fill: bg-color, + width: width, + height: height, + inset: inset, + ) + + set text(size: text-size) + set align(alignment) + locate(loc => { + let current_circle = logic.logical-slide.at(loc).first() - 1 + let nb_circles = logic.logical-slide.final(loc).first() + + let circles = range(nb_circles).map(x => { + if x < current_circle { before-slide(loc) } + else if x == current_circle { current-slide(loc) } + else { after-slide(loc) } + }) + + if show-slide-number { grid( + columns: nb_circles + 1, + gutter: 1fr, + ..circles, + slide-number(loc) + )} else { grid( + columns: nb_circles, + gutter: 1fr, + ..circles + )} + }) +} + +#let est-title-bar( + title, + width, + height, + bg-color: est-blue, + inset: 1cm, + alignment: left+horizon, + text-fill-color: white, + text-size: 1.2em, +) = { + show: est-cell.with( + width: width, + height: height, + fill: bg-color, + inset: inset, + ) + + set align(alignment) + set text(fill: text-fill-color, size: text-size) + if title != none {title} else [] +} + +#let est-speaker-slide-reminder-bar( + width, + height, + show-slide-number: false, + text-size: .8em, + alignment: center+horizon, + author-text-fill-color: white, + author-bg-color: black, + presentation-title-text-fill-color: white, + presentation-title-bg-color: est-blue, + slide-number-text-fill-color: black, + slide-number-bg-color: white, + author: [Speaker], + presentation-title: [Presentation title], + slide-number: {logic.logical-slide.display()} +) = { + set text(size: text-size) + set align(alignment) + + let contents = ( + block(fill: author-bg-color, width: 100%, height: height, text(fill: author-text-fill-color, author)), + block(fill: presentation-title-bg-color, width: 100%, height: height, text(fill: presentation-title-text-fill-color, presentation-title)), + block(fill: slide-number-bg-color, width:100%, height: height, text(fill: slide-number-text-fill-color, slide-number)), + ) + + block(width:100%, height: height, breakable:false, { + if show-slide-number { + grid(columns: (width * 30%, width * 65%, width * 5%), gutter: 0cm, + ..contents + ) + } else { + grid(columns: (width * 30%, width * 70%), gutter: 0cm, + ..contents.slice(0, 2) + ) + } + }) +} + +#let estonia-theme( + aspect-ratio: "16-9", + body +) = { + set page( + paper: "presentation-" + aspect-ratio, + margin: 0cm, + header: none, + footer: none, + ) + + body +} + +#let slide( + title: none, + title-bar: auto, + title-bar-args: ( + height-per-line: 1.5em, + func: est-title-bar, + func-args: (:), + ), + progress-bar: true, + progress-bar-args: ( + height: 1em, + func: est-progress-bar, + func-args: (:), + ), + bottom-bar: false, + bottom-bar-args: ( + height: 6mm, + func: est-speaker-slide-reminder-bar, + func-args: (:) + ), + body-args: ( + margin: 1cm, + alignment: left+horizon, + text-fill-color: black, + bg-color: white), + body +) = { + let show_progress_bar = progress-bar + let show_title_bar = if title-bar == auto {title != none} else { title-bar } + let show_bottom_bar = bottom-bar + + let margin-value(name, margin) = { + if type(margin) == "length" { margin } + else if type(margin) == "dictionary" { + let val = margin.at(name, default: "unset") + if val != "unset" { val } else { + let rest = margin.at("rest", default: "unset") + if rest != "unset" { rest } else { panic("bad body-margin: should define all top/bottom/left/right keys or define a rest key") } + } + } else { panic("body-margin should be a length or a dictionary") } + } + + let body-margin = body-args.at("margin", default: 1cm) + let body-margin-top = margin-value("top", body-margin) + let body-margin-bottom = margin-value("bottom", body-margin) + let body-margin-left = margin-value("left", body-margin) + let body-margin-right = margin-value("right", body-margin) + + let titles_nb_lines = if title == none {1} else { title.split("\n").len()} + let progress_bar_height = if progress-bar {progress-bar-args.at("height", default: 1em)} else {0em} + let title_bar_height = if show_title_bar {titles_nb_lines * title-bar-args.at("height-per-line", default: 1.5em)} else {0em} + + let header_bars_height = progress_bar_height + title_bar_height + let top_margin = header_bars_height + body-margin-top + + let bottom_bar_height = if bottom-bar != false {bottom-bar-args.at("height", default: 6mm)} else {0em} + let footer_bars_height = bottom_bar_height + let bottom_margin = footer_bars_height + body-margin-bottom + + set page( + header: none, + footer: none, + margin: ( + top: top_margin, + bottom: bottom_margin, + left: body-margin-left, + right: body-margin-right, + ), + fill: body-args.at("bg-color", default: white), + ) + + let page_width = body-margin-left + 100% + body-margin-right + + let content = { + if show_progress_bar { place(top+left, dx: -body-margin-left, dy: -top_margin, { + progress-bar-args.at("func", default: est-progress-bar)( + page_width, + progress_bar_height, + ..progress-bar-args.at("func-args", default: (:)) + ) + })} + if show_title_bar { place(top+left, dx: -body-margin-left, dy: -top_margin + progress_bar_height, { + title-bar-args.at("func", default: est-title-bar)( + title, + page_width, + title_bar_height, + ..title-bar-args.at("func-args", default: (:)) + ) + })} + if show_bottom_bar { place(top+left, + dx: -body-margin-left, + dy: -top_margin + header_bars_height + body-margin-top + 100% + body-margin-bottom, { + bottom-bar-args.at("func", default: est-speaker-slide-reminder-bar)( + page_width, + bottom_bar_height, + ..bottom-bar-args.at("func-args", default: (:)) + ) + })} + + set align(body-args.at("alignment", default: left+horizon)) + set text(fill: body-args.at("text-fill-color", default: black)) + body + } + + logic.polylux-slide(content) +} + +#let estonia-outline = { + utils.polylux-outline(enum-args: (tight: false,)) +} diff --git a/themes/themes.typ b/themes/themes.typ index c3ef0ed..1e81dbb 100644 --- a/themes/themes.typ +++ b/themes/themes.typ @@ -3,3 +3,4 @@ #import "bipartite.typ" #import "university.typ" #import "metropolis.typ" +#import "estonia.typ"