├── book ├── .gitignore ├── src │ ├── dynamic │ │ ├── .gitignore │ │ ├── cover.png │ │ ├── pause.png │ │ ├── handout.png │ │ ├── one-by-one.png │ │ ├── rule-array.png │ │ ├── alternatives.png │ │ ├── line-by-line.png │ │ ├── only-uncover.png │ │ ├── rule-string.png │ │ ├── alternatives-fn.png │ │ ├── enum-one-by-one.png │ │ ├── list-one-by-one.png │ │ ├── rule-interval.png │ │ ├── one-by-one-start.png │ │ ├── poor-alternatives.png │ │ ├── terms-one-by-one.png │ │ ├── alternatives-cases.png │ │ ├── alternatives-match.png │ │ ├── alternatives-position.png │ │ ├── alternatives-repeat-last.png │ │ ├── rule-string.typ │ │ ├── one-by-one-start.typ │ │ ├── pause.typ │ │ ├── one-by-one.typ │ │ ├── rule-array.typ │ │ ├── list-one-by-one.typ │ │ ├── line-by-line.typ │ │ ├── enum-one-by-one.typ │ │ ├── terms-one-by-one.typ │ │ ├── alternatives-fn.typ │ │ ├── alternatives-match.typ │ │ ├── only-uncover.typ │ │ ├── alternatives-repeat-last.typ │ │ ├── alternatives-position.typ │ │ ├── alternatives.typ │ │ ├── handout.typ │ │ ├── poor-alternatives.typ │ │ ├── cover.typ │ │ ├── alternatives-cases.typ │ │ ├── rule-interval.typ │ │ ├── helper.md │ │ ├── handout.md │ │ ├── reserve.md │ │ ├── pause.md │ │ ├── syntax.md │ │ ├── cover.md │ │ ├── complex.md │ │ ├── dynamic.md │ │ ├── internals.md │ │ ├── obo-lbl.md │ │ └── alternatives.md │ ├── themes │ │ ├── .gitignore │ │ ├── gallery │ │ │ ├── .gitignore │ │ │ ├── clean.png │ │ │ ├── simple.png │ │ │ ├── bipartite.png │ │ │ ├── dummy-logo.png │ │ │ ├── metropolis.png │ │ │ ├── university.png │ │ │ ├── dummy-watermark.png │ │ │ ├── bipartite.typ │ │ │ ├── index.md │ │ │ ├── simple.typ │ │ │ ├── clean.typ │ │ │ ├── university.typ │ │ │ ├── metropolis.typ │ │ │ ├── simple.md │ │ │ ├── bipartite.md │ │ │ ├── metropolis.md │ │ │ ├── clean.md │ │ │ ├── background.svg │ │ │ └── university.md │ │ ├── helpers.md │ │ ├── science-slam.png │ │ ├── science-slam.typ │ │ ├── themes.md │ │ └── your-own.md │ ├── IMPORT.typ │ ├── logo.png │ ├── logo2.png │ ├── diy │ │ ├── quiz.png │ │ ├── hello-world.png │ │ ├── slide-title.png │ │ ├── title-slide.png │ │ ├── hello-world.typ │ │ ├── title-slide.typ │ │ ├── slide-title.typ │ │ ├── quiz.typ │ │ └── diy.md │ ├── minimal.png │ ├── utils │ │ ├── side-by-side.png │ │ ├── fill-remaining.png │ │ ├── fit-to-height-width.png │ │ ├── side-by-side-kwargs.png │ │ ├── fill-remaining.typ │ │ ├── side-by-side.typ │ │ ├── fit-to-height-width.typ │ │ ├── side-by-side-kwargs.typ │ │ ├── utils.md │ │ ├── progress.md │ │ ├── fit-to-height.md │ │ ├── side-by-side.md │ │ └── sections.md │ ├── external │ │ ├── external.md │ │ └── pdfpc.md │ ├── getting-started.md │ ├── SUMMARY.md │ ├── changelog.md │ ├── polylux.md │ └── themes.md └── book.toml ├── tests ├── .gitignore ├── first-slide.typ ├── subslides.typ ├── alternatives.typ └── pause.typ ├── examples ├── .gitignore ├── literature.bib ├── flipbook.typ ├── minimal.typ ├── gauss.pdfpc ├── gauss.typ ├── demo.pdfpc └── demo.typ ├── pdfpc-extractor ├── .gitignore ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── assets ├── logo.png ├── logo2.png ├── minimal.png └── simple.png ├── themes ├── themes.typ ├── simple.typ ├── bipartite.typ ├── metropolis.typ ├── clean.typ └── university.typ ├── typst.toml ├── polylux.typ ├── scripts └── extract-package.fish ├── LICENSE ├── .github └── workflows │ └── mdbook.yml ├── utils ├── pdfpc.typ └── utils.typ ├── README.md └── logic.typ /book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | -------------------------------------------------------------------------------- /book/src/dynamic/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | -------------------------------------------------------------------------------- /book/src/themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | -------------------------------------------------------------------------------- /pdfpc-extractor/.gitignore: -------------------------------------------------------------------------------- 1 | target/* 2 | -------------------------------------------------------------------------------- /book/src/themes/gallery/.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | -------------------------------------------------------------------------------- /book/src/themes/helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers for theme authors 2 | -------------------------------------------------------------------------------- /book/src/IMPORT.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/polylux:0.3.1": * 2 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/assets/logo.png -------------------------------------------------------------------------------- /assets/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/assets/logo2.png -------------------------------------------------------------------------------- /assets/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/assets/minimal.png -------------------------------------------------------------------------------- /assets/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/assets/simple.png -------------------------------------------------------------------------------- /book/src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/logo.png -------------------------------------------------------------------------------- /book/src/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/logo2.png -------------------------------------------------------------------------------- /book/src/diy/quiz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/diy/quiz.png -------------------------------------------------------------------------------- /book/src/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/minimal.png -------------------------------------------------------------------------------- /book/src/dynamic/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/cover.png -------------------------------------------------------------------------------- /book/src/dynamic/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/pause.png -------------------------------------------------------------------------------- /book/src/diy/hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/diy/hello-world.png -------------------------------------------------------------------------------- /book/src/diy/slide-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/diy/slide-title.png -------------------------------------------------------------------------------- /book/src/diy/title-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/diy/title-slide.png -------------------------------------------------------------------------------- /book/src/dynamic/handout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/handout.png -------------------------------------------------------------------------------- /book/src/dynamic/one-by-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/one-by-one.png -------------------------------------------------------------------------------- /book/src/dynamic/rule-array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/rule-array.png -------------------------------------------------------------------------------- /book/src/utils/side-by-side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/utils/side-by-side.png -------------------------------------------------------------------------------- /book/src/dynamic/alternatives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/alternatives.png -------------------------------------------------------------------------------- /book/src/dynamic/line-by-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/line-by-line.png -------------------------------------------------------------------------------- /book/src/dynamic/only-uncover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/only-uncover.png -------------------------------------------------------------------------------- /book/src/dynamic/rule-string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/rule-string.png -------------------------------------------------------------------------------- /book/src/themes/gallery/clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/clean.png -------------------------------------------------------------------------------- /book/src/themes/science-slam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/science-slam.png -------------------------------------------------------------------------------- /book/src/utils/fill-remaining.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/utils/fill-remaining.png -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-fn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/alternatives-fn.png -------------------------------------------------------------------------------- /book/src/dynamic/enum-one-by-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/enum-one-by-one.png -------------------------------------------------------------------------------- /book/src/dynamic/list-one-by-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/list-one-by-one.png -------------------------------------------------------------------------------- /book/src/dynamic/rule-interval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/rule-interval.png -------------------------------------------------------------------------------- /book/src/themes/gallery/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/simple.png -------------------------------------------------------------------------------- /book/src/dynamic/one-by-one-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/one-by-one-start.png -------------------------------------------------------------------------------- /book/src/dynamic/poor-alternatives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/poor-alternatives.png -------------------------------------------------------------------------------- /book/src/dynamic/terms-one-by-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/terms-one-by-one.png -------------------------------------------------------------------------------- /book/src/themes/gallery/bipartite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/bipartite.png -------------------------------------------------------------------------------- /book/src/themes/gallery/dummy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/dummy-logo.png -------------------------------------------------------------------------------- /book/src/themes/gallery/metropolis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/metropolis.png -------------------------------------------------------------------------------- /book/src/themes/gallery/university.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/university.png -------------------------------------------------------------------------------- /book/src/utils/fit-to-height-width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/utils/fit-to-height-width.png -------------------------------------------------------------------------------- /book/src/utils/side-by-side-kwargs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/utils/side-by-side-kwargs.png -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-cases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/alternatives-cases.png -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/alternatives-match.png -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/alternatives-position.png -------------------------------------------------------------------------------- /book/src/themes/gallery/dummy-watermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/themes/gallery/dummy-watermark.png -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-repeat-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/polylux/main/book/src/dynamic/alternatives-repeat-last.png -------------------------------------------------------------------------------- /themes/themes.typ: -------------------------------------------------------------------------------- 1 | #import "simple.typ" 2 | #import "clean.typ" 3 | #import "bipartite.typ" 4 | #import "university.typ" 5 | #import "metropolis.typ" 6 | -------------------------------------------------------------------------------- /book/src/diy/hello-world.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9") 4 | #set text(size: 25pt) 5 | 6 | #polylux-slide[ 7 | Hello, world! 8 | ] 9 | -------------------------------------------------------------------------------- /book/src/utils/fill-remaining.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #fit-to-height(1fr)[BIG] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/rule-string.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #uncover("-2, 4, 6-8, 10-")[polylux] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/one-by-one-start.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #one-by-one(start: 3)[This ][came ][pretty late.] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/pause.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | first #pause 7 | 8 | second #pause 9 | 10 | third 11 | ] 12 | -------------------------------------------------------------------------------- /book/src/dynamic/one-by-one.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #one-by-one[Do you know ][$pi$ ][to a thousand decimal places?] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/rule-array.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 30pt) 4 | 5 | #polylux-slide[ 6 | #uncover((1, 2, 4))[uncovered only on subslides 1, 2, and 4] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/list-one-by-one.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #list-one-by-one(marker: [--], tight: false)[first][second][third] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/line-by-line.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #line-by-line[ 7 | - first 8 | - second 9 | - third 10 | ] 11 | ] 12 | -------------------------------------------------------------------------------- /book/src/dynamic/enum-one-by-one.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #enum-one-by-one(numbering: "i)", number-align: start)[first][second][third] 7 | ] 8 | -------------------------------------------------------------------------------- /book/src/dynamic/terms-one-by-one.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #terms-one-by-one(separator: [~---~])[/ first: 1st][/ second: 2nd][/ third: 3rd] 7 | ] 8 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Andreas Kröpelin"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Polylux" 7 | 8 | [output.html] 9 | site-url = "/polylux/book/" 10 | 11 | [output.html.code.hidelines] 12 | typ = "~" 13 | 14 | -------------------------------------------------------------------------------- /book/src/utils/side-by-side.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #side-by-side[ 7 | #lorem(7) 8 | ][ 9 | #lorem(10) 10 | ][ 11 | #lorem(5) 12 | ] 13 | ] 14 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-fn.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 60pt) 4 | 5 | #polylux-slide[ 6 | #alternatives-fn(start: 2, count: 7, subslide => { 7 | numbering("(i)", subslide) 8 | }) 9 | ] 10 | -------------------------------------------------------------------------------- /book/src/utils/fit-to-height-width.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #fit-to-height(5cm, prescale-width: 300%, width: 50%)[ 7 | #set par(justify: true) 8 | #lorem(200) 9 | ] 10 | ] 11 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-match.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #alternatives-match(( 7 | "1, 3-5": [this text has the majority], 8 | "2, 6": [this is shown less often] 9 | )) 10 | ] 11 | -------------------------------------------------------------------------------- /book/src/dynamic/only-uncover.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 30pt) 4 | 5 | #polylux-slide[ 6 | before #only(2)[*displayed only on subslide 2*] after 7 | 8 | before #uncover(2)[*uncovered only on subslide 2*] after 9 | ] 10 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-repeat-last.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | #alternatives(repeat-last: true)[temporary][transitory][ephemeral][permanent!] 7 | 8 | #uncover(5)[Did I miss something?] 9 | ] 10 | -------------------------------------------------------------------------------- /examples/literature.bib: -------------------------------------------------------------------------------- 1 | @misc{A, 2 | title={A} 3 | } 4 | @misc{B, 5 | title={B} 6 | } 7 | @misc{C, 8 | title={C} 9 | } 10 | @misc{D, 11 | title={D} 12 | } 13 | @misc{E, 14 | title={E} 15 | } 16 | @misc{F, 17 | title={F} 18 | } 19 | @misc{G, 20 | title={G} 21 | } 22 | @misc{H, 23 | title={H} 24 | } 25 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-position.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | We know that 7 | #alternatives(position: center + horizon)[$pi$][$sqrt(2)^2 + 1/3$] 8 | is 9 | #alternatives[irrational][rational]. 10 | ] 11 | -------------------------------------------------------------------------------- /book/src/external/external.md: -------------------------------------------------------------------------------- 1 | # External tools 2 | Most users will only come across Typst itself and some off-the-shelf PDF viewer. 3 | However, there are some additional tools that might come in handy and Polylux 4 | supports some of their special needs. 5 | 6 | So far, this support is limited to the pdfpc presentation tool. 7 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polylux" 3 | version = "0.3.1" 4 | entrypoint = "polylux.typ" 5 | authors = ["Andreas Kröpelin", "contributors"] 6 | license = "MIT" 7 | description = "Presentation slides creation with Typst" 8 | repository = "https://github.com/andreasKroepelin/polylux" 9 | exclude = ["README.md"] 10 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #alternatives[Ann][Bob][Christopher] 7 | likes 8 | #alternatives[chocolate][strawberry][vanilla] 9 | ice cream. 10 | 11 | This sentence is a visual reference. 12 | ] 13 | -------------------------------------------------------------------------------- /book/src/dynamic/handout.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 30pt) 4 | 5 | #enable-handout-mode(true) 6 | // ... 7 | #polylux-slide[ 8 | Some text. 9 | #uncover("3-")[You cannot always see this.] 10 | ...Or can you? 11 | #pause 12 | 13 | And what about this? 14 | ] 15 | -------------------------------------------------------------------------------- /tests/first-slide.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9", height: auto) 4 | #set text(size: 25pt) 5 | 6 | #let slide = polylux-slide 7 | 8 | #slide[ 9 | == First slide 10 | 11 | This is supposed to appear on the first PDF page. 12 | ] 13 | 14 | #slide[ 15 | == Second slide 16 | #lorem(10) 17 | ] -------------------------------------------------------------------------------- /book/src/dynamic/poor-alternatives.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #only(1)[Ann] #only(2)[Bob] #only(3)[Christopher] 7 | likes 8 | #only(1)[chocolate] #only(2)[strawberry] #only(3)[vanilla] 9 | ice cream. 10 | 11 | This sentence is a visual reference. 12 | ] 13 | -------------------------------------------------------------------------------- /polylux.typ: -------------------------------------------------------------------------------- 1 | #import "themes/themes.typ" 2 | #import "logic.typ" 3 | #import "logic.typ": polylux-slide, uncover, only, alternatives, alternatives-match, alternatives-fn, alternatives-cases, one-by-one, line-by-line, list-one-by-one, enum-one-by-one, terms-one-by-one, pause, enable-handout-mode 4 | #import "utils/utils.typ" 5 | #import "utils/utils.typ": polylux-outline, fit-to-height, side-by-side, pdfpc 6 | -------------------------------------------------------------------------------- /book/src/diy/title-slide.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9", fill: teal.lighten(90%)) 4 | #set text(size: 25pt, font: "Blogger Sans") 5 | 6 | #polylux-slide[ 7 | #set align(horizon + center) 8 | = My fabulous talk 9 | 10 | Jane Doe 11 | 12 | Conference on Advances in Slide Making 13 | ] 14 | 15 | #polylux-slide[ 16 | Hello, world! 17 | ] 18 | -------------------------------------------------------------------------------- /book/src/utils/side-by-side-kwargs.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 40pt) 4 | 5 | #polylux-slide[ 6 | #side-by-side(gutter: 3mm, columns: (1fr, 2fr, 1fr))[ 7 | #rect(width: 100%, stroke: none, fill: aqua) 8 | ][ 9 | #rect(width: 100%, stroke: none, fill: teal) 10 | ][ 11 | #rect(width: 100%, stroke: none, fill: eastern) 12 | ] 13 | ] 14 | -------------------------------------------------------------------------------- /book/src/dynamic/cover.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 30pt) 4 | 5 | #polylux-slide[ 6 | #uncover(3, mode: "transparent")[abc] 7 | 8 | #one-by-one(start: 2, mode: "transparent")[def ][ghi] 9 | 10 | #line-by-line(mode: "transparent")[ 11 | - jkl 12 | - mno 13 | ] 14 | 15 | #enum-one-by-one(mode: "transparent", tight: false)[pqr][stu][vwx] 16 | ] 17 | -------------------------------------------------------------------------------- /book/src/diy/slide-title.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9", fill: teal.lighten(90%)) 4 | #set text(size: 25pt, font: "Blogger Sans") 5 | 6 | #polylux-slide[ 7 | #set align(horizon + center) 8 | = My fabulous talk 9 | 10 | Jane Doe 11 | 12 | Conference on Advances in Slide Making 13 | ] 14 | 15 | #polylux-slide[ 16 | == My slide title 17 | Hello, world! 18 | ] 19 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives-cases.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | /* 6 | #alternatives-match(( 7 | "1, 3" : [ 8 | Some text 9 | ], 10 | "2" : [ 11 | #set text(fill: teal) 12 | Some text 13 | ], 14 | )) 15 | */ 16 | 17 | #polylux-slide[ 18 | #alternatives-cases(("1, 3", "2"), case => [ 19 | #set text(fill: teal) if case == 1 20 | Some text 21 | ]) 22 | ] 23 | -------------------------------------------------------------------------------- /book/src/dynamic/rule-interval.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 30pt) 4 | 5 | #polylux-slide[ 6 | #only((beginning: 1, until: 5))[Content displayed on subslides 1, 2, 3, 4, and 5 \ ] 7 | #only((beginning: 2))[Content displayed on subslide 2 and every following one \ ] 8 | #only((until: 3))[Content displayed on subslides 1, 2, and 3 \ ] 9 | #only((:))[Content that is always displayed] 10 | ] 11 | -------------------------------------------------------------------------------- /pdfpc-extractor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "polylux2pdfpc" 3 | license = "MIT" 4 | description = "A tool to make pdfpc interpret slides created by polylux correctly" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | miette = { version = "5.10.0", features = ["fancy"] } 12 | serde = { version = "1.0.186", features = ["derive"] } 13 | serde_json = "1.0.105" 14 | -------------------------------------------------------------------------------- /tests/subslides.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | #set page(paper: "presentation-16-9") 3 | #set text(size: 50pt) 4 | 5 | #polylux-slide[ 6 | == `alternatives-fn` 7 | #alternatives-fn(start: 1, end: 8, subslide => [#subslide]) 8 | 9 | #uncover(3)[only on 3] 10 | 11 | #uncover("10,11-")[from 10 on] 12 | ] 13 | 14 | #polylux-slide[ 15 | == `pause` 16 | a 17 | #pause 18 | 19 | a 20 | #pause 21 | 22 | a 23 | ] 24 | 25 | #polylux-slide[ 26 | Can you see me? 27 | ] 28 | -------------------------------------------------------------------------------- /examples/flipbook.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | #import "@preview/cetz:0.1.0" 3 | 4 | #set page(paper: "presentation-16-9", margin: 2em) 5 | #set text(size: 30pt, font: "Kalam") 6 | 7 | #polylux-slide[ 8 | == A sunrise on Capri 9 | #alternatives-fn(count: 30, position: top+left, subslide => 10 | cetz.canvas({ 11 | import cetz.draw: * 12 | stroke(none) 13 | circle((0, 7), radius: 1pt) 14 | fill(orange) 15 | circle((3, subslide * 3. / 30), radius: 3) 16 | }) 17 | ) 18 | ] 19 | -------------------------------------------------------------------------------- /book/src/themes/gallery/bipartite.typ: -------------------------------------------------------------------------------- 1 | #import "../../../../polylux.typ": * 2 | 3 | #import themes.bipartite: * 4 | 5 | #show: bipartite-theme 6 | 7 | #set text(size: 25pt) 8 | 9 | #title-slide( 10 | author: [Author], 11 | title: [Title], 12 | subtitle: [Subtitle], 13 | date: [Date], 14 | ) 15 | 16 | #west-slide(title: "A longer slide title")[ 17 | #lorem(40) 18 | ] 19 | 20 | #east-slide(title: "On the right!")[ 21 | #lorem(40) 22 | ] 23 | 24 | #split-slide[ 25 | #lorem(40) 26 | ][ 27 | #lorem(40) 28 | ] 29 | -------------------------------------------------------------------------------- /examples/minimal.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9") 4 | #set text(size: 25pt) 5 | 6 | #polylux-slide[ 7 | #align(horizon + center)[ 8 | = Very minimalist slides 9 | 10 | A lazy author 11 | 12 | July 23, 2023 13 | ] 14 | ] 15 | 16 | #polylux-slide[ 17 | == First slide 18 | 19 | Some static text on this slide. 20 | ] 21 | 22 | #polylux-slide[ 23 | == This slide changes! 24 | 25 | You can always see this. 26 | #uncover(2)[But this appears later!] 27 | ] 28 | -------------------------------------------------------------------------------- /book/src/themes/gallery/index.md: -------------------------------------------------------------------------------- 1 | # Theme gallery 2 | 3 | If you decide to use one of the themes shipped with polylux, you can use this 4 | gallery to get to know how each theme works and if you like how it looks. 5 | All themes follow the convention introduced on the previous page if not stated 6 | otherwise. 7 | For many things, most themes are interchangeable, meaning that you can simply 8 | change what theme you import and its initialisation and everything still works. 9 | However, this is not guaranteed and might break at any point. 10 | -------------------------------------------------------------------------------- /book/src/diy/quiz.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9", fill: teal.lighten(90%)) 4 | #set text(size: 25pt, font: "Blogger Sans") 5 | 6 | #polylux-slide[ 7 | #set align(horizon + center) 8 | = My fabulous talk 9 | 10 | Jane Doe 11 | 12 | Conference on Advances in Slide Making 13 | ] 14 | 15 | #polylux-slide[ 16 | == My slide title 17 | Hello, world! 18 | ] 19 | 20 | #polylux-slide[ 21 | == A quiz 22 | 23 | What is the capital of the Republic of Benin? 24 | 25 | #uncover(2)[Porto-Novo] 26 | ] 27 | -------------------------------------------------------------------------------- /scripts/extract-package.fish: -------------------------------------------------------------------------------- 1 | function extract-package 2 | if test (git branch --show-current) != "release" 3 | echo "You are not on the release branch!" 4 | return 1 5 | end 6 | 7 | set target $argv[1] 8 | pwd 9 | mkdir -p $target 10 | cp README.md $target 11 | cp LICENSE $target 12 | cp typst.toml $target 13 | cp polylux.typ $target 14 | cp logic.typ $target 15 | mkdir -p $target/themes 16 | cp themes/* $target/themes 17 | mkdir -p $target/utils 18 | cp utils/* $target/utils 19 | echo "Done" 20 | end 21 | -------------------------------------------------------------------------------- /book/src/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | You can find this package in the 4 | [official Typst package repository](https://github.com/typst/packages). 5 | To use it, start your document with 6 | ```typ 7 | {{#include IMPORT.typ}} 8 | ``` 9 | You now have two options: 10 | 1. use the features of polylux but define every visual aspect yourself, 11 | 2. use one of the provided themes. 12 | 13 | We will first explore how you can get started without the support of themes. 14 | Later, we will see how you can employ them to get beautiful slides without having 15 | to do the design work yourself. -------------------------------------------------------------------------------- /book/src/dynamic/helper.md: -------------------------------------------------------------------------------- 1 | # Higher level helper functions 2 | With `#only` and `#uncover` you can come a long way but there are some reoccurring 3 | situations for which helper functions are provided. 4 | We call them "higher level" because they use `#only` and `#uncover` under the 5 | hood and operate on larger pieces of content. 6 | 7 | For the common case of succesively revealing content, there are `#pause` and 8 | `#one-by-one` and its friends. 9 | For substituting content, we have `#alternatives` in different variants. 10 | The following sections will describe these functions in detail. 11 | 12 | -------------------------------------------------------------------------------- /book/src/utils/utils.md: -------------------------------------------------------------------------------- 1 | # Utility features 2 | 3 | Let us have a look at some common use cases you run into as either a theme 4 | author or as an end user producing slides and what solutions are provided by 5 | Polylux. 6 | 7 | Specifically, the functions discussed here reside in the `utils` and `logic` 8 | modules. 9 | They are exported by Polylux so if you (as someone making slides) imported it 10 | using 11 | ```typ 12 | {{#include ../IMPORT.typ}} 13 | ``` 14 | you can directly invoke them using `utils` and `logic`. 15 | As a theme author, you have access to them due to the imports 16 | ```typ 17 | #import "../logic.typ" 18 | #import "../utils/utils.typ" 19 | ``` 20 | (see previous page). 21 | 22 | -------------------------------------------------------------------------------- /book/src/dynamic/handout.md: -------------------------------------------------------------------------------- 1 | # Handout mode 2 | If you distribute your slides after your talk for further reference, you might 3 | not want to keep in all the dynamic content. 4 | Imagine using `one-by-one` on a bullet point list and readers having to scroll 5 | through endless pages when they just want to see the full list. 6 | 7 | You can use `#enable-handout-mode(true)` at the top of your code to achieve this: 8 | 9 | It has the effect that all dynamic visibility of elements _that reserve space_ 10 | is switched off. 11 | For example, 12 | ```typ 13 | {{#include handout.typ:5:14}} 14 | ``` 15 | becomes: 16 | 17 | ![handout](handout.png) 18 | 19 | Note that `only` and `alternatives` are **not** affected as there is no obvious 20 | way to unify their content to one slide. 21 | -------------------------------------------------------------------------------- /book/src/dynamic/reserve.md: -------------------------------------------------------------------------------- 1 | # Reserve space or not? 2 | When you want to specify that a certain piece of content should be displayed on 3 | some subslides but not on others, the first question should be what should happen 4 | on the subslides it is _not_ displayed on. 5 | You could either want 6 | - that it is completely not existing there, or 7 | - that it is invisible but it still occupies the space it would need otherwise 8 | (see [the docs of the `#hide` function](https://typst.app/docs/reference/layout/hide/)) 9 | 10 | The two different behaviours can be achieved using either `#only` or `#uncover`, 11 | respectively. 12 | The intuition behind it is that, in one case, content is _only_ existing on some 13 | slides, and, in the other case, it is merely _covered_ when not displayed. 14 | 15 | -------------------------------------------------------------------------------- /book/src/themes/gallery/simple.typ: -------------------------------------------------------------------------------- 1 | #import "../../../../polylux.typ": * 2 | 3 | #import themes.simple: * 4 | 5 | #set text(font: "Inria Sans") 6 | 7 | #show: simple-theme.with( 8 | footer: [Simple slides], 9 | ) 10 | 11 | #title-slide[ 12 | = Keep it simple! 13 | #v(2em) 14 | 15 | Alpha #footnote[Uni Augsburg] #h(1em) 16 | Bravo #footnote[Uni Bayreuth] #h(1em) 17 | Charlie #footnote[Uni Chemnitz] #h(1em) 18 | 19 | July 23 20 | ] 21 | 22 | #slide[ 23 | == First slide 24 | 25 | #lorem(20) 26 | ] 27 | 28 | #focus-slide[ 29 | _Focus!_ 30 | 31 | This is very important. 32 | ] 33 | 34 | #centered-slide[ 35 | = Let's start a new section! 36 | ] 37 | 38 | #slide[ 39 | == Dynamic slide 40 | Did you know that... 41 | 42 | #pause 43 | ...you can see the current section at the top of the slide? 44 | ] 45 | -------------------------------------------------------------------------------- /book/src/themes/gallery/clean.typ: -------------------------------------------------------------------------------- 1 | #import "../../../../polylux.typ": * 2 | 3 | #import themes.clean: * 4 | 5 | #set text(font: "Inria Sans") 6 | 7 | #show: clean-theme.with( 8 | footer: [Author, institution], 9 | short-title: [Short title], 10 | logo: image("dummy-logo.png"), 11 | ) 12 | 13 | #title-slide( 14 | title: [Presentation title], 15 | subtitle: [Presentation subtitle], 16 | authors: ([Author A], [Author B], [Author C]), 17 | date: [January 1, 1970], 18 | watermark: image("dummy-watermark.png"), 19 | ) 20 | 21 | #slide(title: [First slide title])[ 22 | #lorem(20) 23 | ] 24 | 25 | #new-section-slide("The new section") 26 | 27 | #slide(title: "Another slide")[ 28 | Note that you can see the section title at the top. 29 | 30 | The rest of this slide will fill more than one page! 31 | 32 | #lorem(100) 33 | ] 34 | 35 | #focus-slide[ 36 | _Focus!_ 37 | 38 | This is very important. 39 | ] 40 | -------------------------------------------------------------------------------- /book/src/themes/gallery/university.typ: -------------------------------------------------------------------------------- 1 | #import "../../../../polylux.typ": * 2 | 3 | #import themes.university: * 4 | 5 | #show: university-theme.with( 6 | short-author: "Short author", 7 | short-title: "Short title", 8 | short-date: "Short date", 9 | ) 10 | 11 | #title-slide( 12 | authors: ("Author A", "Author B"), 13 | title: "Title", 14 | subtitle: "Subtitle", 15 | date: "Date", 16 | institution-name: "University Name", 17 | logo: image("dummy-logo.png", width: 60mm) 18 | ) 19 | 20 | #slide(title: [Slide title], new-section: [The section])[ 21 | #lorem(40) 22 | ] 23 | 24 | #focus-slide(background-img: image("background.svg"))[ 25 | *Another variant with an image in background...* 26 | ] 27 | 28 | #matrix-slide[ 29 | left 30 | ][ 31 | middle 32 | ][ 33 | right 34 | ] 35 | 36 | #matrix-slide(columns: 1)[ 37 | top 38 | ][ 39 | bottom 40 | ] 41 | 42 | #matrix-slide(columns: (1fr, 2fr, 1fr), ..(lorem(8),) * 9) 43 | -------------------------------------------------------------------------------- /book/src/themes/gallery/metropolis.typ: -------------------------------------------------------------------------------- 1 | #import "../../../../polylux.typ": * 2 | 3 | #import themes.metropolis: * 4 | 5 | #show: metropolis-theme.with( 6 | footer: [Custom footer] 7 | ) 8 | 9 | #set text(font: "Fira Sans", weight: "light", size: 20pt) 10 | #show math.equation: set text(font: "Fira Math") 11 | #set strong(delta: 100) 12 | #set par(justify: true) 13 | 14 | #title-slide( 15 | author: [Authors], 16 | title: "Title", 17 | subtitle: "Subtitle", 18 | date: "Date", 19 | extra: "Extra" 20 | ) 21 | 22 | #slide(title: "Table of contents")[ 23 | #metropolis-outline 24 | ] 25 | 26 | #slide(title: "Slide title")[ 27 | A slide with some maths: 28 | $ x_(n+1) = (x_n + a/x_n) / 2 $ 29 | 30 | #lorem(200) 31 | ] 32 | 33 | #new-section-slide("First section") 34 | 35 | #slide[ 36 | A slide without a title but with #alert[important] infos 37 | ] 38 | 39 | #new-section-slide([Second section]) 40 | 41 | #focus-slide[ 42 | Wake up! 43 | ] 44 | -------------------------------------------------------------------------------- /tests/alternatives.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9") 4 | 5 | #polylux-slide[ 6 | == Test that `repeat-last` works 7 | 8 | #alternatives[abc ][def ][ghi ] 9 | 10 | #alternatives(repeat-last: true)[jkl ][mno ][this stays ] 11 | 12 | #uncover(5)[You can go now.] 13 | ] 14 | 15 | #polylux-slide[ 16 | == Test that `alternatives-match` works 17 | 18 | #alternatives-match(position: center, ( 19 | "-2": [beginning], 20 | "3, 5": [main part], 21 | "4": [short break], 22 | "6-" : [end] 23 | )) 24 | 25 | #uncover("1-8")[I am always here, for technical reasons.] 26 | ] 27 | 28 | #polylux-slide[ 29 | == Test that `alternatives-cases` works 30 | 31 | #alternatives-cases(("1,3,4", "2,5", "6"), case => { 32 | set text(fill: lime) if case == 1 33 | lorem(10) 34 | }) 35 | ] 36 | 37 | #polylux-slide[ 38 | == Test that `alternatives-fn` works 39 | 40 | #alternatives-fn(count: 5, subslide => numbering("(i)", subslide)) 41 | ] 42 | -------------------------------------------------------------------------------- /book/src/dynamic/pause.md: -------------------------------------------------------------------------------- 1 | # `pause` to reveal content piece by piece 2 | Consider some code like the following: 3 | ```typ 4 | #uncover("1-")[first] 5 | 6 | #uncover("2-")[second] 7 | 8 | #uncover("3-")[third] 9 | ``` 10 | The goal here is to uncover parts of the slide one by one, so that an increasing 11 | amount of content is shown, but we don't want to specify all subslide indices 12 | manually, ideally. 13 | 14 | If you have used the LaTeX beamer package before, you might be familiar with the 15 | `\pause` command. 16 | It makes everything after it on that slide appear on the next subslide. 17 | In Polylux, this works very similar with `#pause`, so we can equivalently write 18 | the above code as: 19 | ```typ 20 | {{#include pause.typ:6:10}} 21 | ``` 22 | This results in 23 | 24 | ![pause](pause.png) 25 | 26 | `#pause` should mainly be used when you want to distribute a lot of code onto 27 | different subslides. 28 | Note that it does not affect content in the same paragraph as itself, for example. 29 | For smaller pieces of code, consider one of the functions described next. 30 | -------------------------------------------------------------------------------- /book/src/dynamic/syntax.md: -------------------------------------------------------------------------------- 1 | # General syntax for `#only` and `#uncover` 2 | Both functions are used in the same way. 3 | They each take two positional arguments, the first is a description of the 4 | subslides the content is supposed to be shown on, the second is the content itself. 5 | Note that Typst provides some syntactic sugar for trailing content arguments, 6 | namely putting the content block _behind_ the function call. 7 | 8 | You could therefore write: 9 | ```typ 10 | {{#include only-uncover.typ:5:}} 11 | ``` 12 | ...resulting in 13 | 14 | ![only-uncover](only-uncover.png) 15 | 16 | (Note again that the gray border is not part of the slides and that the labels 17 | indicating the index of the subslide are also added afterwards.) 18 | 19 | You can clearly see the difference in behaviour between `only` and `uncover`. 20 | In the first line, "after" moves but not in the second line. 21 | 22 | In this example, we specified only a single subslide index, resulting in content 23 | that is shown on that exact subslide and at no other one. 24 | Let's explore more complex rules next. 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Andreas Kröpelin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /book/src/dynamic/cover.md: -------------------------------------------------------------------------------- 1 | # Cover mode 2 | Covered content (using `#uncover`, `#one-by-one`, `#line-by-line`, or 3 | `#{list|enum|terms}-one-by-one`) is completely invisible, by default. 4 | You can decide to make it visible but less prominent using the optional `mode` 5 | argument to each of those functions. 6 | The `mode` argument takes two different values: `"invisible"` (the default) and 7 | `"transparent"`. 8 | (This terminology is taken from LaTeX beamer as well.) 9 | With `mode: "transparent"`, text is printed in a light gray. 10 | 11 | Use it as follows: 12 | ```typ 13 | {{#include cover.typ:6:15}} 14 | ``` 15 | resulting in 16 | 17 | ![cover](cover.png) 18 | 19 | **Warning!** 20 | The transparent mode really only wraps the covered content in a 21 | ```typ 22 | #text(fill: gray.lighten(50%)[...] 23 | ``` 24 | so it has only limited control over the actual display. 25 | Especially 26 | - text that defines its own color (e.g. syntax highlighting), 27 | - visualisations, 28 | - images 29 | 30 | will not be affected by that. 31 | This makes the transparent mode only somewhat useful today. 32 | ([Relevant GitHub issue](https://github.com/andreasKroepelin/polylux/issues/17)) 33 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Polylux](./polylux.md) 4 | - [Getting started](./getting-started.md) 5 | - [Do it yourself](./diy/diy.md) 6 | - [Dynamic slides](./dynamic/dynamic.md) 7 | - [Reserving space: only and uncover](./dynamic/reserve.md) 8 | - [General syntax](./dynamic/syntax.md) 9 | - [Complex rules](./dynamic/complex.md) 10 | - [Helper functions](./dynamic/helper.md) 11 | - [pause](./dynamic/pause.md) 12 | - [one-by-one and friends](./dynamic/obo-lbl.md) 13 | - [alternatives](./dynamic/alternatives.md) 14 | - [Cover mode](./dynamic/cover.md) 15 | - [Handout mode](./dynamic/handout.md) 16 | - [Internals](./dynamic/internals.md) 17 | - [Themes](./themes/themes.md) 18 | - [Gallery](./themes/gallery/index.md) 19 | - [Simple](./themes/gallery/simple.md) 20 | - [Clean](./themes/gallery/clean.md) 21 | - [Metropolis](./themes/gallery/metropolis.md) 22 | - [University](./themes/gallery/university.md) 23 | - [Bipartite](./themes/gallery/bipartite.md) 24 | - [Build your own](./themes/your-own.md) 25 | - [Utilities](./utils/utils.md) 26 | - [Side by side](./utils/side-by-side.md) 27 | - [Fit to height](./utils/fit-to-height.md) 28 | - [Progress](./utils/progress.md) 29 | - [Sections](./utils/sections.md) 30 | - [External tools](./external/external.md) 31 | - [pdfpc](./external/pdfpc.md) 32 | - [Changelog](./changelog.md) 33 | -------------------------------------------------------------------------------- /book/src/utils/progress.md: -------------------------------------------------------------------------------- 1 | # How much longer? 🥱 2 | 3 | There are a handful of features that let you display the progress of the 4 | presentation. 5 | 6 | The most simple one is directly displaying the current slide number. 7 | Remember that each slide might produce an arbitrary amount of subslides, i.e. 8 | PDF pages, so we cannot rely on the builtin page counter. 9 | Instead, there is the `logical-slide` counter in the `logic` module. 10 | Therefore, you can use 11 | ```typ 12 | #logic.logical-slide.display() 13 | ``` 14 | to see what the current slide number is. 15 | 16 | If you want to put that into relation to how many slides there are in total, 17 | you can also display 18 | ```typ 19 | #utils.last-slide-number 20 | ``` 21 | which is a short-hand way of getting the final value of `logic.logical-slide`. 22 | 23 | Note that both these things are content, though, so you can only display them 24 | and not calculate with the numbers. 25 | A common calculation you might want do to is finding their ratio, i.e. current 26 | slide number divided by total number of slides. 27 | To that end, you can use the function `utils.polylux-progress`. 28 | You can pass a function to it that turns the current ratio into some content. 29 | For example: 30 | ```typ 31 | #utils.polylux-progress( ratio => [ 32 | You already made it through #calc.round(ratio * 100) #sym.percent of the presentation! 33 | ]) 34 | ``` 35 | Some themes utilise this to display a little progress bar, for example. 36 | -------------------------------------------------------------------------------- /tests/pause.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | 3 | #set page(paper: "presentation-16-9") 4 | 5 | #polylux-slide[ 6 | == Text like content 7 | 8 | Hello 9 | #pause 10 | $a + b$ 11 | #pause 12 | $ integral f(x) dif x $ 13 | #pause 14 | 15 | - *item1* 16 | - _item2_ 17 | - `item3` 18 | 19 | #pause 20 | 21 | + #underline[item1] 22 | + #strike[item2] 23 | + #overline[item3] 24 | 25 | #pause 26 | 27 | / def1: abc 28 | / def2: ghi 29 | 30 | #pause 31 | 32 | #box(stroke: 2pt + aqua, inset: 2pt)[boxed!] 33 | 34 | #pause 35 | 36 | #block(stroke: 2pt + lime, inset: 2pt)[blocked!] 37 | ] 38 | 39 | #polylux-slide[ 40 | == Inside grid 41 | 42 | #grid(columns: 4 * (1fr,))[ 43 | abc 44 | #pause 45 | ][ 46 | def 47 | #pause 48 | ][ 49 | ghi 50 | #pause 51 | ][ 52 | jkl 53 | ] 54 | ] 55 | 56 | #polylux-slide[ 57 | == Visuals 58 | 59 | #pause 60 | 61 | // Fails to be hidden as of Typst 0.7.0 62 | #path( 63 | fill: teal.lighten(50%), stroke: teal, closed: true, 64 | (0cm, 0cm), (1cm, 0cm), (1cm, 1cm) 65 | ) 66 | 67 | #rect() 68 | 69 | #square() 70 | 71 | #circle() 72 | 73 | #ellipse() 74 | 75 | // Fails to be hidden as of Typst 0.7.0 76 | #line() 77 | 78 | // Fails to be hidden as of Typst 0.7.0 79 | #polygon( 80 | fill: teal.lighten(50%), stroke: teal, 81 | (0cm, 0cm), (1cm, 0cm), (1cm, 1cm) 82 | ) 83 | 84 | #image("../assets/logo.png", width: 3em) 85 | ] 86 | 87 | -------------------------------------------------------------------------------- /book/src/themes/science-slam.typ: -------------------------------------------------------------------------------- 1 | #import "../../../polylux.typ": * 2 | 3 | // ANCHOR: init 4 | #let science-slam-theme(aspect-ratio: "16-9", darkness: "dark", body) = { 5 | let background-color = if darkness == "dark" { 6 | navy 7 | } else if darkness == "very dark" { 8 | navy.darken(50%) 9 | } else if darkness == "ultra dark" { 10 | black 11 | } else { 12 | panic("illegal darkness, must be one of dark, very dark, ultra dark") 13 | } 14 | 15 | set page( 16 | paper: "presentation-" + aspect-ratio, 17 | fill: background-color, 18 | ) 19 | set text(fill: white.darken(10%), size: 40pt, font: "Fira Sans") 20 | 21 | body 22 | } 23 | // ANCHOR_END: init 24 | 25 | // ANCHOR: title-slide 26 | #let title-slide(title: [], author: []) = { 27 | polylux-slide({ 28 | set align(center + horizon) 29 | smallcaps(strong(title)) 30 | parbreak() 31 | text(size: .7em, author) 32 | }) 33 | } 34 | // ANCHOR_END: title-slide 35 | 36 | // ANCHOR: slide 37 | #let slide(title: [], body) = { 38 | polylux-slide({ 39 | strong(title) 40 | set align(horizon) 41 | body 42 | }) 43 | } 44 | // ANCHOR_END: slide 45 | 46 | // ANCHOR: use-init 47 | #show: science-slam-theme.with(darkness: "very dark") 48 | // ANCHOR_END: use-init 49 | 50 | // ANCHOR: use-title-slide 51 | #title-slide(title: [My awesome topic], author: [A really funny guy]) 52 | // ANCHOR_END: use-title-slide 53 | 54 | // ANCHOR: use-slide 55 | #slide(title: "A hilarious slide")[ 56 | You didn't expect that! 57 | ] 58 | // ANCHOR_END: use-slide 59 | 60 | -------------------------------------------------------------------------------- /book/src/dynamic/complex.md: -------------------------------------------------------------------------------- 1 | # Complex display rules 2 | There are multiple options to define more complex display rules than a single 3 | number. 4 | 5 | ### Array 6 | The simplest extension is to use an array. 7 | For example 8 | ```typ 9 | {{#include rule-array.typ:5:}} 10 | ``` 11 | results in: 12 | 13 | ![rule-array](rule-array.png) 14 | 15 | The array elements can actually themselves be any kind of rule that is explained 16 | on this page. 17 | 18 | ### Interval 19 | You can also provide a (bounded or half-bounded) interval in the form of a 20 | dictionary with a `beginning` and/or an `until` key: 21 | ```typ 22 | {{#include rule-interval.typ:5:}} 23 | ``` 24 | results in: 25 | 26 | ![rule-interval](rule-interval.png) 27 | 28 | In the last case, you would not need to use `#only` anyways, obviously. 29 | 30 | ### Convenient syntax as strings 31 | In principle, you can specify every rule using numbers, arrays, and intervals. 32 | However, consider having to write 33 | ```typ 34 | #uncover(((until: 2), 4, (beginning: 6, until: 8), (beginning: 10)))[polylux] 35 | ``` 36 | That's only fun the first time. 37 | Therefore, we provide a convenient alternative. 38 | You can equivalently write: 39 | ```typ 40 | {{#include rule-string.typ:6}} 41 | ``` 42 | which results in: 43 | 44 | ![rule-string](rule-string.png) 45 | 46 | Much better, right? 47 | The spaces are optional, so just use them if you find it more readable. 48 | 49 | Unless you are creating those function calls programmaticly, it is a good 50 | recommendation to use the single-number syntax (`#only(1)[...]`) if that 51 | suffices and the string syntax for any more complex use case. 52 | 53 | -------------------------------------------------------------------------------- /examples/gauss.pdfpc: -------------------------------------------------------------------------------- 1 | {"pdfpcFormat":2,"duration":15,"startTime":"08:15","disableMarkdown":false,"noteFontSize":5,"defaultTransition":"push:0.3:180:horizontal:outward","endSlide":9,"savedSlide":3,"pages":[{"idx":0,"label":1,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":1,"label":2,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":2,"label":3,"overlay":0,"forcedOverlay":false,"hidden":false,"note":"Remember to explain Sigma notation!"},{"idx":3,"label":4,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":4,"label":5,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":5,"label":6,"overlay":0,"forcedOverlay":false,"hidden":true},{"idx":6,"label":7,"overlay":0,"forcedOverlay":false,"hidden":false,"note":"# How the last steps work\nWe use _basic algebra_ rules for the last steps."},{"idx":7,"label":7,"overlay":1,"forcedOverlay":true,"hidden":false,"note":"# How the last steps work\nWe use _basic algebra_ rules for the last steps."},{"idx":8,"label":7,"overlay":2,"forcedOverlay":true,"hidden":false,"note":"# How the last steps work\nWe use _basic algebra_ rules for the last steps."},{"idx":9,"label":7,"overlay":3,"forcedOverlay":true,"hidden":false,"note":"# How the last steps work\nWe use _basic algebra_ rules for the last steps."},{"idx":10,"label":7,"overlay":4,"forcedOverlay":true,"hidden":false,"note":"# How the last steps work\nWe use _basic algebra_ rules for the last steps."},{"idx":11,"label":8,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":12,"label":9,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":13,"label":10,"overlay":0,"forcedOverlay":false,"hidden":false},{"idx":14,"label":11,"overlay":0,"forcedOverlay":false,"hidden":false}]} -------------------------------------------------------------------------------- /book/src/dynamic/dynamic.md: -------------------------------------------------------------------------------- 1 | # Dynamic slides 2 | 3 | The PDF format does not (trivially) allow to include animations, as one would be 4 | used to from, say, PowerPoint. 5 | The solution PDF-based presentation slides use is to create multiple PDF pages 6 | for one slide, each with slightly different content. 7 | This enables us to have some basic dynamic elements on our slides. 8 | 9 | In this book, we will use the term _logical slide_ for a section of content that 10 | was created by one call to `#polylux-slide`, and _subslide_ for a resulting PDF 11 | page. 12 | Each logical side can have an arbitrary amount of subslides and every subslide 13 | is part of exactly one logical slide. 14 | Note that the same page number is displayed for all subslides of a logical slide. 15 | 16 | As you will see soon, the commands for creating dynamic content all have some way 17 | of specifying on what subslides some content is supposed to be shown. 18 | One of those subslides has the highest index, of course. 19 | **Of all those commands with their respective highest subslide to show something, 20 | the maximum is take again and that defines the number of PDF pages produced for 21 | one logical slide.** 22 | For example, suppose we have a slide with the following commands: 23 | - show something on subslides 1 and 3 24 | - show something from subslide 2 to subslide 4 25 | - show something until subslide 6 26 | 27 | This results in 6 PDF pages for this logical slide. 28 | 29 | 30 | In the LaTeX beamer package, the functionalities described in this part are 31 | called "overlays". 32 | 33 | Everything discussed here works just as well when you use themes. 34 | For simplicity, we will work through the material without them, though. 35 | -------------------------------------------------------------------------------- /book/src/utils/fit-to-height.md: -------------------------------------------------------------------------------- 1 | # Fit to height 2 | 3 | Suppose you have some content and some size constraints for it but the two don't 4 | match, i.e. the content does not have the size that you want. 5 | The function `#fit-to-height` can help you with that. 6 | 7 | It expects a height and some content and will try to scale the content such that 8 | it takes on the given height: 9 | 10 | ```typ 11 | {{#include fill-remaining.typ:6}} 12 | ``` 13 | 14 | resulting in 15 | 16 | ![fill-remaining](fill-remaining.png) 17 | 18 | Using `1fr` as the height is probably also the prime use case for this function, 19 | as it fills the remaining space of the slide with the given content. 20 | Anything else (like `5pt`, `3cm`, `4em` etc.) is possible as well, of course. 21 | 22 | ## Adjusting the width 23 | To finetune the result of `#fit-to-height`, you have two optional parameters: 24 | - `width`: If specified, this determines the width of the content _after_ scaling. 25 | So, if you want the scaled content to fill half the slide's width for example, 26 | you can use `width: 50%`. 27 | By default, the scaled content will be constrained by the slide's width and 28 | will have less than the requested height if necessary. 29 | - `prescale-width`: This parameter allows you to make Typst's layouting assume 30 | the given content is to be layouted in a container of a certain width _before_ 31 | scaling. 32 | You can pretend the slide is twice as wide using `prescale-width: 200%`, for 33 | example. 34 | 35 | We can illustrate that using the following example: 36 | 37 | ```typ 38 | {{#include fit-to-height-width.typ:6:9}} 39 | ``` 40 | 41 | resulting in 42 | 43 | ![fit-to-height-width](fit-to-height-width.png) 44 | -------------------------------------------------------------------------------- /book/src/utils/side-by-side.md: -------------------------------------------------------------------------------- 1 | # Side by side 2 | To make good use of the space on a slide, you will often want to place content 3 | next to each other. 4 | For convenience, Polylux provides the function `#side-by-side` for this purpose. 5 | If you used 6 | ```typ 7 | {{#include ../IMPORT.typ}} 8 | ``` 9 | you have it directly available. 10 | Otherwise you can get if from the `utils` module. 11 | 12 | It is basically a thin wrapper around the Typst function 13 | [`#grid`](https://typst.app/docs/reference/layout/grid/) but tailored 14 | towards this specific usecase. 15 | In its simplest form, you can use it as 16 | ```typ 17 | {{#include side-by-side.typ:6:12}} 18 | ``` 19 | 20 | resulting in 21 | 22 | ![side-by-side](side-by-side.png) 23 | 24 | As you can see, the content arguments you provide will be placed next to each 25 | other with equal proportions of width. 26 | A spacing (gutter) of `1em` will also be put between them. 27 | 28 | The widths and gutter can be configured using the `columns` and `gutter` optional 29 | arguments, respectively. 30 | They are propagated to `#grid` directly so you can look up possible values in 31 | its documentation 32 | ([`gutter`](https://typst.app/docs/reference/layout/grid/#parameters-gutter) 33 | and [`columns`](https://typst.app/docs/reference/layout/grid/#parameters-columns) 34 | arguments). 35 | If not specified, they fall back to these defaults: 36 | - `gutter`: `1em` 37 | - `columns`: `(1fr,) * n` if you provided `n` content arguments, that means an 38 | array with the value `1fr` repeated `n` times. 39 | 40 | A more complex example would therefore be: 41 | ```typ 42 | {{#include side-by-side-kwargs.typ:6:12}} 43 | ``` 44 | 45 | resulting in 46 | 47 | ![side-by-side-kwargs](side-by-side-kwargs.png) 48 | -------------------------------------------------------------------------------- /themes/simple.typ: -------------------------------------------------------------------------------- 1 | #import "../logic.typ" 2 | 3 | #let simple-footer = state("simple-footer", []) 4 | 5 | #let simple-theme( 6 | aspect-ratio: "16-9", 7 | footer: [], 8 | background: white, 9 | foreground: black, 10 | body 11 | ) = { 12 | set page( 13 | paper: "presentation-" + aspect-ratio, 14 | margin: 2em, 15 | header: none, 16 | footer: none, 17 | fill: background, 18 | ) 19 | set text(fill: foreground, size: 25pt) 20 | show footnote.entry: set text(size: .6em) 21 | show heading.where(level: 2): set block(below: 2em) 22 | set outline(target: heading.where(level: 1), title: none, fill: none) 23 | show outline.entry: it => it.body 24 | show outline: it => block(inset: (x: 1em), it) 25 | 26 | simple-footer.update(footer) 27 | 28 | body 29 | } 30 | 31 | #let centered-slide(body) = { 32 | logic.polylux-slide(align(center + horizon, body)) 33 | } 34 | 35 | #let title-slide(body) = { 36 | set heading(outlined: false) 37 | centered-slide(body) 38 | } 39 | 40 | #let focus-slide(background: aqua.darken(50%), foreground: white, body) = { 41 | set page(fill: background) 42 | set text(fill: foreground, size: 1.5em) 43 | logic.polylux-slide(align(center + horizon, body)) 44 | } 45 | 46 | #let slide(body) = { 47 | let deco-format(it) = text(size: .6em, fill: gray, it) 48 | set page( 49 | header: locate( loc => { 50 | let sections = query(heading.where(level: 1, outlined: true).before(loc), loc) 51 | if sections == () [] else { deco-format(sections.last().body) } 52 | }), 53 | footer: deco-format({ 54 | simple-footer.display(); h(1fr); logic.logical-slide.display() 55 | }), 56 | footer-descent: 1em, 57 | header-ascent: 1em, 58 | ) 59 | logic.polylux-slide(body) 60 | } 61 | -------------------------------------------------------------------------------- /book/src/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.3.0 4 | 5 | - The previously existing module `helpers` was transformed to `utils` and now 6 | contains many more useful features. 7 | - The modules `logic` and `utils` are now directly accessible when importing 8 | Polylux (it was a bug that it did not work previously). 9 | - We finally have an ergonomic `#pause` function that does not expect the user 10 | to keep track of some counter themselves. 11 | - The `#alternatives` function has gained lots of friends that make specific 12 | situations a bit more convenient, namely `#alternatives-match`, 13 | `alternatives-cases`, and `alternatives-fn`. 14 | Also, there is a parameter `repeat-last` for `#alternatives` now. 15 | - Bullet lists, enumerations, and term lists now have custom functions to display 16 | them dynamically: `#list-one-by-one`, `#enum-one-by-one`, and `#terms-one-by-one`. 17 | - There is a new function `#fit-to-height` that allows you to resize content to 18 | a given height (especially make it fill the remaining space on a slide!) 19 | Thank you to [@ntjess](https://github.com/ntjess) for the initial implementation! 20 | - Previously, certain themes allowed you to easily put multiple content elements 21 | next to each other. 22 | This is now a commonly available function: `#side-by-side`. 23 | You can use it regardless of any theme and the functionality was removed from 24 | the previously implementing themes. 25 | - Polylux now has special support for the pdfpc presentation viewer. 26 | You can add speaker notes, hide slides, configure the timer, and more all from 27 | within your Typst source file. 28 | Thank you to [@JuliusFreudenberger](https://github.com/JuliusFreudenberger) 29 | for the inspiration and for creating the `polylux2pdfpc` AUR package. 30 | -------------------------------------------------------------------------------- /book/src/utils/sections.md: -------------------------------------------------------------------------------- 1 | # Sections 2 | Another way of expressing where we are in a presentation is working with sections. 3 | Usually, this is a topic that a theme will/should handle so **this page is 4 | addressed more towards theme authors**. 5 | 6 | In your theme, you can incorporate the following features from the `utils` 7 | module: 8 | 9 | First, whenever a user wants to start a new section, you can call 10 | ```typ 11 | #utils.register-section(the-section-name) 12 | ``` 13 | with whatever name they specify. 14 | It is up to you to decide what kind of interface you provide for the user and 15 | how/if you visualise a new section, of course. 16 | 17 | Based on that, you can then display what section the presenter is currently in 18 | by using: 19 | ```typ 20 | #utils.current-section 21 | ``` 22 | If no section has been registered so far, this is empty content (`[]`). 23 | 24 | And finally, you might want to display some kind of overview over all the sections. 25 | This is achieved by: 26 | ```typ 27 | #utils.polylux-outline() 28 | ``` 29 | Unfortunately, it is hard to get the Typst-builtin `#outline` to work properly 30 | with slides, partly again due to how page numbers are kind of meaningless. 31 | `polylux-outline` is a good alternative to that and will return an `enum` with 32 | all the registered sections (ever, not only so far, so you can safely use it 33 | at the beginning of a presentation). 34 | 35 | `polylux-outline` has two optional keyword arguments: 36 | - `enum-args`: pass a dictionary that is propagated to 37 | [`enum` as keyword arguments](https://typst.app/docs/reference/layout/enum#parameters), 38 | for example `enum-args: (tight: false)`, default: `(:)` 39 | - `padding`: pass [something that `pad` accepts](https://typst.app/docs/reference/layout/pad#parameters), 40 | will be used to pad the `enum`, default: `0pt` 41 | -------------------------------------------------------------------------------- /.github/workflows/mdbook.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a mdBook site to GitHub Pages 2 | # 3 | # To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html 4 | # 5 | name: Deploy mdBook site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["release"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | env: 32 | MDBOOK_VERSION: 0.4.21 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Install mdBook 36 | run: | 37 | mkdir bin 38 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin 39 | - name: Setup Pages 40 | id: pages 41 | uses: actions/configure-pages@v3 42 | - name: Build with mdBook 43 | run: bin/mdbook build book 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v1 46 | with: 47 | path: ./book 48 | 49 | # Deployment job 50 | deploy: 51 | environment: 52 | name: github-pages 53 | url: ${{ steps.deployment.outputs.page_url }} 54 | runs-on: ubuntu-latest 55 | needs: build 56 | steps: 57 | - name: Deploy to GitHub Pages 58 | id: deployment 59 | uses: actions/deploy-pages@v1 60 | -------------------------------------------------------------------------------- /book/src/polylux.md: -------------------------------------------------------------------------------- 1 | ![logo](logo2.png) 2 | 3 | **Polylux** is a package for the typesetting system [*Typst*](https://typst.app) 4 | to create presentation slides, just like you would use the _beamer_ package in LaTeX. 5 | (So far, it is much less advanced than beamer, obviously.) 6 | 7 | If you haven't heard of things like LaTeX's beamer before, here is how this works: 8 | As a rule of thumb, one slide becomes one PDF page, and most PDF viewers can 9 | display PDFs in the form of a slide show (usually by hitting the F5-key). 10 | 11 | 12 | Polylux gives you: 13 | - Elegant yet powerful typesetting by the ever-improving Typst. 14 | - Fully customisable slides. 15 | - Dynamic slides (or *overlays* or (dis-)appearing content, or however you want 16 | to call it). 17 | - Decently looking themes. 18 | 19 | If you like it, consider [giving a star on GitHub](https://github.com/andreasKroepelin/polylux)! 20 | 21 | 22 | ## Why the name? 23 | A [*polylux*](https://en.wikipedia.org/wiki/Polylux_(overhead_projector)) is 24 | a brand of overhead projectors very common in Eastern German schools (where the 25 | main author of this package grew up). 26 | It fulfils a similar function to a projector, namely projecting visuals to a 27 | wall to aid a presentation. 28 | The German term for projector is *beamer*, and now you might understand how it 29 | all comes together. 30 | (The original author of the aforementioned LaTeX package is German as well.) 31 | 32 | ## About this book 33 | This book documents all features currently implemented in Polylux. 34 | Specifically, it describes the state of the package as it is pulished to the 35 | Typst package registry. 36 | The `main` branch of the Polylux repository may contain features not documented 37 | here. 38 | 39 | ## Contributing 40 | This package is free and open source. 41 | You can find the code on [GitHub](https://github.com/andreasKroepelin/polylux) 42 | where you can also create issues or pull requests. 43 | 44 | ## License 45 | Polylux is released under the 46 | [MIT license](https://github.com/andreasKroepelin/polylux/blob/main/LICENSE). 47 | -------------------------------------------------------------------------------- /book/src/themes/gallery/simple.md: -------------------------------------------------------------------------------- 1 | # Simple theme 2 | 3 | ![simple](simple.png) 4 | 5 | This theme is rather unobtrusive and might still be considered bare-bones. 6 | It uses a minimal amount of colour and lets you define your slides' content very 7 | freely. 8 | 9 | Use it via 10 | ```typ 11 | {{#include ../../IMPORT.typ}} 12 | #import themes.simple: * 13 | 14 | #show: simple-theme.with(...) 15 | ``` 16 | 17 | `simple` uses regular headings for sections. 18 | Unless specified otherwise, first level headings create new sections. 19 | The regular `#outline` is configured such that it lists the names of all sections. 20 | 21 | Second level headings are supposed to be used as slide titles and introduce some 22 | spacing below them. 23 | 24 | Text is configured to have a base font size of 25 pt. 25 | 26 | ## Options for initialisation 27 | `simple-theme` accepts the following optional keyword arguments: 28 | 29 | - `aspect-ratio`: the aspect ratio of the slides, either `"16-9"` or `"4-3"`, 30 | default is `"16-9"` 31 | - `footer`: text to display in the footer of every slide, default is `[]` 32 | - `background`: background colour, default is `white` 33 | - `foreground`: text colour, default is `black` 34 | 35 | ## Slide functions 36 | `simple` provides the following custom slide functions: 37 | 38 | ```typ 39 | #centered-slide[ 40 | ... 41 | ] 42 | ``` 43 | A slide where the content is positioned in the center of the slide. 44 | Not suitable for content that exceeds one page. 45 | 46 | --- 47 | 48 | ```typ 49 | #title-slide[ 50 | ... 51 | ] 52 | ``` 53 | Same as `centered-slide` but makes heading of level 1 not outlined, so that the 54 | presentation title does not show up in the outline. 55 | Not suitable for content that exceeds one page. 56 | 57 | --- 58 | 59 | ```typ 60 | #slide[ 61 | ... 62 | ] 63 | ``` 64 | Decorates the provided content with a header containing the current section (if 65 | any) and a footer containing some custom text and the slide number. 66 | 67 | --- 68 | 69 | ```typ 70 | #focus-slide(foreground: ..., background: ...)[ 71 | ... 72 | ] 73 | ``` 74 | Draw attention with this variant where the content is displayed centered and text 75 | is enlarged. 76 | Optionally accepts a foreground colour (default: `white`) and background color 77 | (default: `aqua.darken(50%)`). 78 | Not suitable for content that exceeds one page. 79 | 80 | 81 | ## Example code 82 | The image at the top is created by the following code: 83 | ```typ 84 | {{#include ../../IMPORT.typ}} 85 | {{#include simple.typ:3:}} 86 | ``` 87 | -------------------------------------------------------------------------------- /examples/gauss.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | 3 | #import themes.clean: * 4 | 5 | #set text(font: "Source Sans 3") 6 | #show math.equation: set text(font: "GFS Neohellenic Math") 7 | 8 | #show: clean-theme.with( 9 | footer: [Sum of natural numbers, CF Gauß], 10 | ) 11 | 12 | #pdfpc.config( 13 | duration-minutes: 15, 14 | start-time: datetime(hour: 8, minute: 15, second: 0), 15 | note-font-size: 5, 16 | default-transition: (type: "push", duration-seconds: 0.3), 17 | ) 18 | 19 | #title-slide( 20 | authors: "Carl Friedrich Gauß", 21 | title: [On a revolutionary way to \ sum up natural numbers], 22 | subtitle: "What they won't teach you in school", 23 | date: "1784", 24 | ) 25 | 26 | #new-section-slide("Introduction") 27 | 28 | #slide(title: "Problem statement")[ 29 | Let $n in NN$. 30 | We are interested in sums of the form 31 | $ 1 + ... + n = sum_(i=1)^n i $ 32 | #pdfpc.speaker-note("Remember to explain Sigma notation!") 33 | ] 34 | 35 | #slide(title: "The theorem")[ 36 | 37 | I discovered that 38 | $ sum_(i=1)^n i = n(n+1)/2 $ 39 | 40 | Let's prove that! 41 | #pdfpc.save-slide 42 | ] 43 | 44 | #new-section-slide("Proof") 45 | 46 | #slide(title: "Method of proof")[ 47 | We will prove the theorem by induction, following these steps: 48 | 49 | + base case 50 | + induction hypothesis 51 | + induction step 52 | 53 | #pdfpc.hidden-slide 54 | ] 55 | 56 | #slide(title: "Proof")[ 57 | #set text(.7em) 58 | 59 | #one-by-one[ 60 | *base case:* Let $n = 1$. Then $sum_(i=1)^1 i = (1 dot.c 2)/2 = 1$ $checkmark$ 61 | 62 | ][ 63 | *ind. hypothesis:* Let $sum_(i=1)^k i = k(k+1)/2$ for some $k >= 1$. 64 | 65 | ][ 66 | *ind. step:* Show that 67 | $sum_(i=1)^(k+1) i = ((k+1)(k+2))/2$ 68 | 69 | $sum_(i=1)^(k+1) i = sum_(i=1)^k i quad + quad (k+1)$ 70 | ][ 71 | $= k(k+1)/2 + (k+1)$ 72 | ][ 73 | $= (k+1) dot.c (k/2 + 1) 74 | = (k+1) dot.c (k/2 + 2/2) 75 | = ((k+1)(k+2))/2 76 | #h(1em) checkmark$ 77 | ] 78 | 79 | #pdfpc.speaker-note(```md 80 | # How the last steps work 81 | We use _basic algebra_ rules for the last steps. 82 | ```) 83 | ] 84 | 85 | #focus-slide[ 86 | Proof is over, wake up! 87 | ] 88 | 89 | #new-section-slide("Conclusion") 90 | 91 | #slide(title: "That's it!")[ 92 | 93 | Now you know how to calculate those sums more quickly. Nice! 94 | 95 | #pdfpc.end-slide 96 | ] 97 | 98 | #slide(title: [Further references])[ 99 | If you want to learn more about this cool kind of math, you can start your 100 | investigation here: https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss 101 | ] 102 | -------------------------------------------------------------------------------- /themes/bipartite.typ: -------------------------------------------------------------------------------- 1 | // This theme is inspired by https://slidesgo.com/theme/modern-annual-report 2 | 3 | #import "../logic.typ" 4 | 5 | 6 | #let bipartite-dark = rgb("#192e41") 7 | #let bipartite-bright = rgb("#fafafa") 8 | #let bipartite-accent = rgb("#fc9278") 9 | 10 | #let bipartite-theme( 11 | aspect-ratio: "16-9", 12 | body 13 | ) = { 14 | set page( 15 | paper: "presentation-" + aspect-ratio, 16 | margin: 0pt, 17 | ) 18 | 19 | body 20 | } 21 | 22 | #let title-slide(title: [], subtitle: none, author: [], date: none) = { 23 | let title-block(fg, bg, height, body) = block( 24 | width: 100%, height: height, outset: 0em, inset: 0em, breakable: false, 25 | stroke: none, spacing: 0em, fill: bg, 26 | align(center + horizon, text(fill: fg, body)) 27 | ) 28 | let content = { 29 | title-block(bipartite-dark, bipartite-bright, 60%, text(1.7em, title)) 30 | title-block(bipartite-bright, bipartite-dark, 40%, { 31 | if subtitle != none { 32 | text(size: 1.2em, subtitle) 33 | parbreak() 34 | } 35 | 36 | text(size: .9em, { author; if date != none { h(1em); sym.dot.c; h(1em); date } }) 37 | }) 38 | place( 39 | center + horizon, dy: 10%, 40 | rect(width: 6em, height: .5em, radius: .25em, fill: bipartite-accent) 41 | ) 42 | } 43 | logic.polylux-slide(content) 44 | } 45 | 46 | #let _bipartite-content-box(fg, bg, width, alignment, body) = box( 47 | width: width, height: 100%, outset: 0em, inset: (x: 1em), baseline: 0em, 48 | stroke: none, fill: bg, 49 | align(alignment + horizon, text(fill: fg, body)) 50 | ) 51 | 52 | #let west-slide(title: none, body) = { 53 | let content = { 54 | _bipartite-content-box( 55 | bipartite-bright, bipartite-dark, 30%, left, 56 | if title != none { heading(level: 2, title) } else { [] } 57 | ) 58 | _bipartite-content-box(bipartite-dark, bipartite-bright, 70%, left, body) 59 | } 60 | logic.polylux-slide(content) 61 | } 62 | 63 | #let east-slide(title: none, body) = { 64 | let content = { 65 | _bipartite-content-box(bipartite-dark, bipartite-bright, 70%, right, body) 66 | _bipartite-content-box( 67 | bipartite-bright, bipartite-dark, 30%, right, 68 | if title != none { heading(level: 2, title) } else { [] } 69 | ) 70 | } 71 | logic.polylux-slide(content) 72 | } 73 | 74 | #let split-slide(body-left, body-right) = { 75 | let content = { 76 | _bipartite-content-box(bipartite-dark, bipartite-bright, 50%, right, body-left) 77 | _bipartite-content-box(bipartite-bright, bipartite-dark, 50%, left, body-right) 78 | } 79 | logic.polylux-slide(content) 80 | } 81 | -------------------------------------------------------------------------------- /book/src/themes/themes.md: -------------------------------------------------------------------------------- 1 | # Themes 2 | 3 | As we have already discussed, you can use polylux completely without using 4 | themes. 5 | For most users, themes will simplify their preparation of decent slides quite a 6 | bit, however. 7 | So let's take a look at how they work. 8 | 9 | It is important to note that polylux does not define a specific way a theme *has* 10 | to work. 11 | Similarly, if you define your own slide layout, don't feel obliged to do it in 12 | any way related to how the provided themes do it. 13 | To improve the user experience and avoid having to learn everything anew when 14 | switching to another theme, the existing themes all follow a certain convention, 15 | though. 16 | 17 | ## The theme convention 18 | 19 | First of all, all themes reside in the `themes` module inside polylux. 20 | That means, if you want to employ, say, the `simple` theme, you add the following 21 | to your regular `#import` line at the top: 22 | ```typ 23 | {{#include ../IMPORT.typ}} 24 | #import themes.simple: * 25 | ``` 26 | 27 | Next, a theme usually provides some **initialisation function** that has a name 28 | ending in `-theme`. 29 | It is supposed to be used in a `#show: ...` rule, i.e. (again for the `simple` 30 | theme): 31 | ```typ 32 | #show: simple-theme.with(...) 33 | ``` 34 | Inside the `with()`, you can set some theme-specific configuration. 35 | Here you find options that concern the whole presentation, such as the aspect 36 | ratio of your slides. 37 | 38 | Speaking of which, when you use a theme, you do not have to set the paper size 39 | yourself anymore, such things are handled by the theme (the convention is that 40 | every theme has an `aspect-ratio` keyword for its initialisation function that 41 | can be set to `"16-9"` or `"4-3"`). 42 | 43 | The other major feature of themes is that they usually come with **custom slide 44 | functions**. 45 | That means that you will *not* use the `#polylux-slide` function! 46 | It is called under the hood by the wrapper functions from the theme. 47 | 48 | To be more accurate: 49 | Nothing stops you from still calling `#polylux-slide` and you can always build 50 | something custom along the "regular" theme-slides if you are not satisfied with 51 | what a theme offers you. 52 | It's just that you usually will not have to do this. 53 | 54 | The range of theme-specific slide functions varies from theme to theme but there 55 | is again one convention: 56 | A theme usually has a `#title-slide` function for, well, the title slide and a 57 | `#slide` function that you will use for "normal" slides. 58 | 59 | Each of these functions might accept some keyword arguments and/or one or 60 | multiple content blocks to define what is supposed to be seen on the slide. 61 | -------------------------------------------------------------------------------- /utils/pdfpc.typ: -------------------------------------------------------------------------------- 1 | #let speaker-note(text) = { 2 | let text = if type(text) == "string" { 3 | text 4 | } else if type(text) == "content" and text.func() == raw { 5 | text.text.trim() 6 | } else { 7 | panic("A note must either be a string or a raw block") 8 | } 9 | [ #metadata((t: "Note", v: text)) ] 10 | } 11 | 12 | #let end-slide = [ 13 | #metadata((t: "EndSlide")) 14 | ] 15 | 16 | #let save-slide = [ 17 | #metadata((t: "SaveSlide")) 18 | ] 19 | 20 | #let hidden-slide = [ 21 | #metadata((t: "HiddenSlide")) 22 | ] 23 | 24 | #let config( 25 | duration-minutes: none, 26 | start-time: none, 27 | end-time: none, 28 | last-minutes: none, 29 | note-font-size: none, 30 | disable-markdown: false, 31 | default-transition: none, 32 | ) = { 33 | if duration-minutes != none { 34 | [ #metadata((t: "Duration", v: duration-minutes)) ] 35 | } 36 | 37 | let _time-config(time, msg-name, tag-name) = { 38 | let time = if type(time) == "datetime" { 39 | time.display("[hour padding:zero repr:24]:[minute padding:zero]") 40 | } else if type(time) == "string" { 41 | time 42 | } else { 43 | panic(msg-name + " must be either a datetime or a string in the HH:MM format.") 44 | } 45 | 46 | [ #metadata((t: tag-name, v: time)) ] 47 | } 48 | 49 | if start-time != none { 50 | _time-config(start-time, "Start time", "StartTime") 51 | } 52 | 53 | if end-time != none { 54 | _time-config(end-time, "End time", "EndTime") 55 | } 56 | 57 | if last-minutes != none { 58 | [ #metadata((t: "LastMinutes", v: last-minutes)) ] 59 | } 60 | 61 | if note-font-size != none { 62 | [ #metadata((t: "NoteFontSize", v: note-font-size)) ] 63 | } 64 | 65 | [ #metadata((t: "DisableMarkdown", v: disable-markdown)) ] 66 | 67 | if default-transition != none { 68 | let dir-to-angle(dir) = if dir == ltr { 69 | "0" 70 | } else if dir == rtl { 71 | "180" 72 | } else if dir == ttb { 73 | "90" 74 | } else if dir == btt { 75 | "270" 76 | } else { 77 | panic("angle must be a direction (ltr, rtl, ttb, or btt)") 78 | } 79 | 80 | let transition-str = ( 81 | default-transition.at("type", default: "replace") 82 | + ":" + 83 | str(default-transition.at("duration-seconds", default: 1)) 84 | + ":" + 85 | dir-to-angle(default-transition.at("angle", default: rtl)) 86 | + ":" + 87 | default-transition.at("alignment", default: "horizontal") 88 | + ":" + 89 | default-transition.at("direction", default: "outward") 90 | ) 91 | 92 | [ #metadata((t: "DefaultTransition", v: transition-str)) ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /book/src/themes/gallery/bipartite.md: -------------------------------------------------------------------------------- 1 | # Bipartite theme 2 | 3 | ![bipartite](bipartite.png) 4 | 5 | This theme is inspired by 6 | [Modern Annual Report](https://slidesgo.com/theme/modern-annual-report). 7 | and a bit more opinionated. 8 | It features a dominant partition of space into a bright and a dark side and is 9 | rather on the "artsy" than functional side. 10 | 11 | Use it via 12 | ```typ 13 | {{#include ../../IMPORT.typ}} 14 | #import themes.bipartite: * 15 | 16 | #show: bipartite-theme.with(...) 17 | ``` 18 | 19 | The `bipartite` theme cannot display content that exceeds one page, in general. 20 | Note that, against the convention, `bipartite` offers no `#slide` function. 21 | Use either `#west-slide` or #`east-slide` for regular content. 22 | Also, this theme features no sections or slide numbers. 23 | 24 | ## Options for initialisation 25 | `bipartite-theme` accepts the following optional keyword arguments: 26 | 27 | - `aspect-ratio`: the aspect ratio of the slides, either `"16-9"` or `"4-3"`, 28 | default is `"16-9"` 29 | 30 | ## Slide functions 31 | `bipartite` provides the following custom slide functions: 32 | 33 | ```typ 34 | #title-slide(...) 35 | ``` 36 | Displays presentation title on a large bright portion above the subtitle, the 37 | author and the date. 38 | If a date was given, separates it from the author by a central dot. 39 | Accepts the following keyword arguments: 40 | - `title`: title of the presentation, default: `[]` 41 | - `subtitle`: subtitle of the presentation, default: `none` 42 | - `author`: author of presentation, arbitrary content, default: `[]` 43 | - `date`: date of the presentation, default: `none` 44 | 45 | Does not accept additional content. 46 | 47 | --- 48 | 49 | ```typ 50 | #west-slide(title: ...)[ 51 | ... 52 | ] 53 | ``` 54 | Splits the slide into a larger bright section on the right where the content 55 | goes and a smaller, darker, left section where the title is displayed. 56 | Everything is left aligned. 57 | 58 | --- 59 | 60 | ```typ 61 | #east-slide(title: ...)[ 62 | ... 63 | ] 64 | ``` 65 | Same as `#west-slide` but with the title and the content switching places, and 66 | everything being right aligned. 67 | 68 | --- 69 | 70 | ```typ 71 | #split-slide[ 72 | ... 73 | ][ 74 | ... 75 | ] 76 | ``` 77 | Splits the slide into two equal sections on the left and the right that both 78 | contain content (`#split-slide` requires exactly two content blocks to be passed). 79 | The left half is dark text on a bright background and right aligned, the right 80 | half is bright text on dark background and left aligned. 81 | Does not display a slide title. 82 | 83 | 84 | ## Example code 85 | The image at the top is created by the following code: 86 | ```typ 87 | {{#include ../../IMPORT.typ}} 88 | {{#include bipartite.typ:3:}} 89 | ``` 90 | -------------------------------------------------------------------------------- /book/src/themes/gallery/metropolis.md: -------------------------------------------------------------------------------- 1 | # Metropolis theme 2 | 3 | ![metropolis](metropolis.png) 4 | 5 | This theme is inspired by the 6 | [Metropolis beamer theme](https://github.com/matze/mtheme), 7 | created by Matthias Vogelgesang. 8 | 9 | Use it via 10 | ```typ 11 | {{#include ../../IMPORT.typ}} 12 | #import themes.metropolis: * 13 | 14 | #show: metropolis-theme.with(...) 15 | ``` 16 | 17 | `metropolis` uses polylux' section handling, the regular `#outline()` will not work 18 | properly, use `#metropolis-outline` instead. 19 | 20 | ## Options for initialisation 21 | `metropolis-theme` accepts the following optional keyword arguments: 22 | 23 | - `aspect-ratio`: the aspect ratio of the slides, either `"16-9"` or `"4-3"`, 24 | default is `"16-9"` 25 | - `footer`: text to display in the footer of every slide, default is `[]` 26 | 27 | ## Slide functions 28 | `metropolis` provides the following custom slide functions: 29 | 30 | ```typ 31 | #title-slide(...) 32 | ``` 33 | Creates a title slide where title and subtitle are separated by additional 34 | information by a bright line. 35 | Accepts the following keyword arguments: 36 | - `title`: title of the presentation, default: `[]` 37 | - `subtitle`: subtitle of the presentation, default: `none` 38 | - `author`: author of presentation, can be arbitrary content, default: `none` 39 | - `date`: date of the presentation, default: `none` 40 | - `extra`: some extra information, can be used for affiliation, instiution, 41 | logos, etc., default: `none` 42 | 43 | Does not accept additional content. 44 | 45 | --- 46 | 47 | ```typ 48 | #slide(...)[ 49 | ... 50 | ] 51 | ``` 52 | Decorates the provided content with a header containing the slide title and a 53 | footer containing some custom text and the slide number. 54 | 55 | Pass the slide title as a keyword argument `title` (default: `none`). 56 | 57 | --- 58 | 59 | ```typ 60 | #focus-slide[ 61 | ... 62 | ] 63 | ``` 64 | Draw attention with this variant where the content is displayed centered and text 65 | is enlarged and bright. 66 | Uses the background colour of the title on regular slides as the background 67 | colour for the whole slide. 68 | Not suitable for content that exceeds one page. 69 | 70 | --- 71 | 72 | ```typ 73 | #new-section-slide(name) 74 | ``` 75 | Start a new section with the given `name` (string or content, positional argument). 76 | Creates a slide with this name in the center, a progress bar indicating the 77 | current progress of the presentation below, and nothing else. 78 | Use `#metropolis-outline` to display all sections, similarly to how you would use 79 | `#outline()` otherwise. 80 | 81 | ## Additional features 82 | `metropolis` provides a further way to highlight text besids `#emph` and `#strong`, 83 | namely `#alert` (as known from LaTex's beamer). 84 | ```typ 85 | #alert[very important] 86 | ``` 87 | prints its content in a bright colour. 88 | 89 | There is also `#metropolis-outline` which customises `#polylux-outline` and 90 | displays a table of contents with all sections. 91 | 92 | 93 | ## Example code 94 | The image at the top is created by the following code: 95 | ```typ 96 | {{#include ../../IMPORT.typ}} 97 | {{#include metropolis.typ:3:}} 98 | ``` 99 | -------------------------------------------------------------------------------- /book/src/dynamic/internals.md: -------------------------------------------------------------------------------- 1 | # Internals 2 | Here, topics regarding the internal implementation of dynamic content in polylux 3 | is discussed. 4 | Usually, you can completely ignore this section. 5 | 6 | ## Internal number of repetitions 7 | **TL;DR:** 8 | For slides with more than ten subslides, you need to set the `max-repetitions` 9 | argument of the `#polylux-slide` function to a higher number. 10 | 11 | For technical reasons (this might change in the future when we find a better 12 | solution), producing PDF pages for subslides is implemented in the following way: 13 | Each dynamic element, such as `#only` or `#beginning` "knows" how many subslides a 14 | logical slide must have for it to "make sense". 15 | For example, a `#beginning(5)[...]` only makes sense if at least 5 subslides are 16 | produced. 17 | 18 | Internally, when typesetting a slide, we now look at all the dynamic elements in 19 | it and find the maximum of those individual "required" subslide counts. 20 | So if a slide contains a `#only(2)[...]`, a `#until(4)[...]`, and nothing else, 21 | we know that exactly 4 subslides are necessary. 22 | 23 | However, we only acquire this knowledge _after_ the first subslide has been 24 | produced, i.e. when all of the slide's content has been "looked at" once. 25 | This is why we cannot simply implement something like "produce 4 pages by 26 | iterating this loop 4 times". 27 | Instead, the (admittedly hacky) solution is to iterate "very often" and check in 28 | each iteration if we still need to produce another page. 29 | This works because we always need to produce at least one page for a slide, so 30 | we can unhurriedly inspect all dynamic elements and find the maximum subslide 31 | count at the first iteration. 32 | After that, we have the information we need. 33 | 34 | Now, the question arises how often "very often" should be. 35 | This requires a trade-off: 36 | Iterating too few times (say, twice) will lead to frequent situations where we 37 | ignore dynamic behaviour that was supposed to happen in later subslides (say, in 38 | the third). 39 | Iterating, say, a thousand times means that we will practically never encounter 40 | such situations but we now perform a thousand iterations _per slide_. 41 | Especially when you want to see a live update of your produced PDF as you type, 42 | this leads to severe lagging that somewhat defeats the purpose of Typst's speed. 43 | (Still faster than LaTeX, though...) 44 | 45 | It appears reasonable that occasions are rare where one needs more than ten 46 | subslides for one slide. 47 | Therefore, ten is the default value for how often we try to create a new subslide. 48 | This should not produce noticeable lag. 49 | (If it does for you, consider 50 | [creating an issue](https://github.com/andreasKroepelin/polylux/issues) 51 | so we can discuss this.) 52 | 53 | For those hopefully rare occasions where you do, in fact, need more than ten 54 | subslides, you can manually increase this number using the `max-repetitions` 55 | argument of the `#slide` function: 56 | ```typ 57 | #polylux-slide(max-repetitions: 20)[ 58 | This is gonna take a while: 59 | #uncover(20)[I can wait...] 60 | ] 61 | ``` 62 | 63 | Again, use this feature sparingly, as it decreases typesetting performance. 64 | 65 | -------------------------------------------------------------------------------- /book/src/dynamic/obo-lbl.md: -------------------------------------------------------------------------------- 1 | # More sophisticated piecewise revealing 2 | ## `#one-by-one` 3 | `#pause` may be considered syntactically a bit surprising by some although (or 4 | because) it is very convenient to use. 5 | If you prefer to signal the grouping of content appearing together syntactically 6 | by using scopes, you can use `#one-by-one`: 7 | ```typ 8 | {{#include one-by-one.typ:6}} 9 | ``` 10 | resulting in 11 | 12 | ![one-by-one](one-by-one.png) 13 | 14 | If we still want to uncover certain elements one after the other but starting 15 | on a later subslide, we can use the optional `start` argument of `#one-by-one`: 16 | ```typ 17 | {{#include one-by-one-start.typ:6}} 18 | ``` 19 | resulting in 20 | 21 | ![one-by-one-start](one-by-one-start.png) 22 | 23 | This optional `start` argument exists for all functions displayed on this page. 24 | 25 | 26 | ## `#line-by-line` 27 | `#one-by-one` is especially useful for arbitrary contents that you want to display 28 | in that manner. 29 | Sometimes, it produces a bit too much syntactical noise again with 30 | all the brackets between content, though. 31 | That is especially true if each piece fits into a single line, as for example 32 | for a simple bullet list. 33 | Instead of 34 | ```typ 35 | #one-by-one[ 36 | - first 37 | ][ 38 | - second 39 | ][ 40 | - third 41 | ] 42 | ``` 43 | you can also write 44 | ```typ 45 | {{#include line-by-line.typ:6:10}} 46 | ``` 47 | resulting in 48 | 49 | ![line-by-line](line-by-line.png) 50 | 51 | The content provided as an argument to `#line-by-line` is parsed as a `sequence` 52 | by Typst with one element per line (hence the name of this function). 53 | We then simply iterate over that `sequence` as if it were given to `#one-by-one`. 54 | 55 | ## `#list-one-by-one` 56 | 57 | What if you want a more customized bullet list, though? 58 | The code above produces a tight list, for example, and maybe you do not want that. 59 | All your needs are covered by the `#list-one-by-one` function: 60 | ```typ 61 | {{#include list-one-by-one.typ:6}} 62 | ``` 63 | resulting in 64 | 65 | ![list-one-by-one](list-one-by-one.png) 66 | 67 | As you can see, you can provide any arguments that the 68 | [`list`](https://typst.app/docs/reference/layout/list/) function accepts. 69 | 70 | ## `#enum-one-by-one` 71 | 72 | Analogously, there is the same thing for enums, accepting the same arguments as 73 | [`enum`](https://typst.app/docs/reference/layout/enum/): 74 | ```typ 75 | {{#include enum-one-by-one.typ:6}} 76 | ``` 77 | resulting in 78 | 79 | ![enum-one-by-one](enum-one-by-one.png) 80 | 81 | ## `#terms-one-by-one` 82 | 83 | And finally we have a function to produce a 84 | [term list](https://typst.app/docs/reference/layout/terms/): 85 | 86 | ```typ 87 | {{#include terms-one-by-one.typ:6}} 88 | ``` 89 | resulting in 90 | 91 | ![terms-one-by-one](terms-one-by-one.png) 92 | 93 | **Note** that `#list-one-by-one` and `#enum-one-by-one` expect only the body of 94 | the individual items while you need to provide an actual term item (using the 95 | `/ term: description` syntax) to `#terms-one-by-one`. 96 | 97 | Also, you will realise that the bullet markers, the numbers, and the terms in 98 | the lists, enums, and term lists are not hidden for technical reasons, respectively. 99 | You can truly consider this either a bug or a feature... 100 | (This could be "fixed" for enums and term lists, so file an issue on GitHub if 101 | this bothers you a lot!) 102 | -------------------------------------------------------------------------------- /book/src/themes/gallery/clean.md: -------------------------------------------------------------------------------- 1 | # Clean theme 2 | 3 | ![clean](clean.png) 4 | 5 | This theme is a bit more opinionated than the `simple` theme but still supposed 6 | to be an off-the-shelf solution that fits many use cases. 7 | 8 | Use it via 9 | ```typ 10 | {{#include ../../IMPORT.typ}} 11 | #import themes.clean: * 12 | 13 | #show: clean-theme.with(...) 14 | ``` 15 | 16 | `clean` uses polylux' section handling, the regular `#outline()` will not work 17 | properly, use `#polylux-outline()` instead. 18 | 19 | Text is configured to have a base font size of 25 pt. 20 | 21 | ## Options for initialisation 22 | `clean-theme` accepts the following optional keyword arguments: 23 | 24 | - `aspect-ratio`: the aspect ratio of the slides, either `"16-9"` or `"4-3"`, 25 | default is `"16-9"` 26 | - `footer`: text to display in the footer of every slide, default is `[]` 27 | - `short-title`: short form of the presentation title, to be displayed on every 28 | slide, default: `none` 29 | - `logo`: some content (most likely an image) used as a logo on every slide, 30 | default: `none` 31 | - `color`: colour of decorative lines, default: `teal` 32 | 33 | ## Slide functions 34 | `clean` provides the following custom slide functions: 35 | 36 | ```typ 37 | #title-slide(...) 38 | ``` 39 | Creates a title slide where title and subtitle are put between decorative lines, 40 | along with logos and author and date infos. 41 | Accepts the following keyword arguments: 42 | - `title`: title of the presentation, default: `none` 43 | - `subtitle`: subtitle of the presentation, default: `none` 44 | - `authors`: authors of presentation, can be an array of contents or a single 45 | content, will be displayed in a grid, default: `()` 46 | - `date`: date of the presentation, default: `none` 47 | - `watermark`: some content (most likely an image) used as a watermark behind 48 | the titlepage, default: `none` 49 | - `secondlogo`: some content (most likely an image) used as a second logo opposite 50 | to the regular logo on the title page, default: `none` 51 | 52 | Does not accept additional content. 53 | 54 | --- 55 | 56 | ```typ 57 | #slide(...)[ 58 | ... 59 | ] 60 | ``` 61 | Decorates the provided content with a header containing the current section (if 62 | any), the short title of the presentation, and the logo; and a footer containing 63 | some custom text and the slide number. 64 | 65 | Pass the slide title as a keyword argument `title`. 66 | 67 | Accepts the following keyword arguments: 68 | - `title`: title of the slide, default: `none`, 69 | 70 | --- 71 | 72 | ```typ 73 | #focus-slide(foreground: ..., background: ...)[ 74 | ... 75 | ] 76 | ``` 77 | Draw attention with this variant where the content is displayed centered and text 78 | is enlarged. 79 | Optionally accepts a foreground colour (default: `white`) and background color 80 | (default: `teal`). 81 | Not suitable for content that exceeds one page. 82 | 83 | --- 84 | 85 | ```typ 86 | #new-section-slide(name) 87 | ``` 88 | Start a new section with the given `name` (string or content, positional argument). 89 | Creates a slide with this name in the center, a decorative line below, and 90 | nothing else. 91 | Use `#polylux-outline()` to display all sections, similarly to how you would use 92 | `#outline()` otherwise. 93 | 94 | 95 | ## Example code 96 | The image at the top is created by the following code: 97 | ```typ 98 | {{#include ../../IMPORT.typ}} 99 | {{#include clean.typ:3:}} 100 | ``` 101 | -------------------------------------------------------------------------------- /themes/metropolis.typ: -------------------------------------------------------------------------------- 1 | // This theme is inspired by https://github.com/matze/mtheme 2 | // The polylux-port was performed by https://github.com/Enivex 3 | 4 | // Consider using: 5 | // #set text(font: "Fira Sans", weight: "light", size: 20pt) 6 | // #show math.equation: set text(font: "Fira Math") 7 | // #set strong(delta: 100) 8 | // #set par(justify: true) 9 | 10 | #import "../logic.typ" 11 | #import "../utils/utils.typ" 12 | 13 | #let m-dark-teal = rgb("#23373b") 14 | #let m-light-brown = rgb("#eb811b") 15 | #let m-lighter-brown = rgb("#d6c6b7") 16 | #let m-extra-light-gray = white.darken(2%) 17 | 18 | #let m-footer = state("m-footer", []) 19 | 20 | #let m-cell = block.with( 21 | width: 100%, 22 | height: 100%, 23 | above: 0pt, 24 | below: 0pt, 25 | breakable: false 26 | ) 27 | 28 | #let m-progress-bar = utils.polylux-progress( ratio => { 29 | grid( 30 | columns: (ratio * 100%, 1fr), 31 | m-cell(fill: m-light-brown), 32 | m-cell(fill: m-lighter-brown) 33 | ) 34 | }) 35 | 36 | #let metropolis-theme( 37 | aspect-ratio: "16-9", 38 | footer: [], 39 | body 40 | ) = { 41 | set page( 42 | paper: "presentation-" + aspect-ratio, 43 | fill: m-extra-light-gray, 44 | margin: 0em, 45 | header: none, 46 | footer: none, 47 | ) 48 | 49 | m-footer.update(footer) 50 | 51 | body 52 | } 53 | 54 | #let title-slide( 55 | title: [], 56 | subtitle: none, 57 | author: none, 58 | date: none, 59 | extra: none, 60 | ) = { 61 | let content = { 62 | set text(fill: m-dark-teal) 63 | set align(horizon) 64 | block(width: 100%, inset: 2em, { 65 | text(size: 1.3em, strong(title)) 66 | if subtitle != none { 67 | linebreak() 68 | text(size: 0.9em, subtitle) 69 | } 70 | line(length: 100%, stroke: .05em + m-light-brown) 71 | set text(size: .8em) 72 | if author != none { 73 | block(spacing: 1em, author) 74 | } 75 | if date != none { 76 | block(spacing: 1em, date) 77 | } 78 | set text(size: .8em) 79 | if extra != none { 80 | block(spacing: 1em, extra) 81 | } 82 | 83 | }) 84 | } 85 | 86 | logic.polylux-slide(content) 87 | } 88 | 89 | #let slide(title: none, body) = { 90 | let header = { 91 | set align(top) 92 | if title != none { 93 | show: m-cell.with(fill: m-dark-teal, inset: 1em) 94 | set align(horizon) 95 | set text(fill: m-extra-light-gray, size: 1.2em) 96 | strong(title) 97 | } else { [] } 98 | } 99 | 100 | let footer = { 101 | set text(size: 0.8em) 102 | show: pad.with(.5em) 103 | set align(bottom) 104 | text(fill: m-dark-teal.lighten(40%), m-footer.display()) 105 | h(1fr) 106 | text(fill: m-dark-teal, logic.logical-slide.display()) 107 | } 108 | 109 | set page( 110 | header: header, 111 | footer: footer, 112 | margin: (top: 3em, bottom: 1em), 113 | fill: m-extra-light-gray, 114 | ) 115 | 116 | let content = { 117 | show: align.with(horizon) 118 | show: pad.with(2em) 119 | set text(fill: m-dark-teal) 120 | body 121 | } 122 | 123 | logic.polylux-slide(content) 124 | } 125 | 126 | #let new-section-slide(name) = { 127 | let content = { 128 | utils.register-section(name) 129 | set align(horizon) 130 | show: pad.with(20%) 131 | set text(size: 1.5em) 132 | name 133 | block(height: 2pt, width: 100%, spacing: 0pt, m-progress-bar) 134 | } 135 | logic.polylux-slide(content) 136 | } 137 | 138 | #let focus-slide(body) = { 139 | set page(fill: m-dark-teal, margin: 2em) 140 | set text(fill: m-extra-light-gray, size: 1.5em) 141 | logic.polylux-slide(align(horizon + center, body)) 142 | } 143 | 144 | #let alert = text.with(fill: m-light-brown) 145 | 146 | #let metropolis-outline = utils.polylux-outline(enum-args: (tight: false,)) 147 | -------------------------------------------------------------------------------- /book/src/themes/gallery/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | image/svg+xml 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /book/src/diy/diy.md: -------------------------------------------------------------------------------- 1 | # Do it yourself 2 | 3 | Let's start with the absolute minimal effort. 4 | What characterises a set of slides? 5 | Well, each slide (or PDF page, as we already established) has specific dimensions. 6 | Some time ago, a 4:3 format was common, nowadays 16:9 is used more often. 7 | Typst has those built in: 8 | ```typ 9 | #set page(paper: "presentation-16-9") 10 | ``` 11 | You probably don't want your audience to carry magnifying glasses, so let's set 12 | the font size to something readable from the last row: 13 | ```typ 14 | #set text(size: 25pt) 15 | ``` 16 | We should be ready do go to create some actual slides now. 17 | We will use the function `polylux-slide` for this, which is kind of at the core 18 | of this package. 19 | ```typ 20 | // Remember to actually import polylux before this! 21 | 22 | #polylux-slide[ 23 | Hello, world! 24 | ] 25 | ``` 26 | And here is the result (the gray border is not part of the output but it makes 27 | the slide easier to see here): 28 | ![helloworld](hello-world.png) 29 | Already kinda looks like a slide, but also a bit boring, maybe. 30 | We should add a title slide before that so that our audience actually knows what 31 | talk they are attending. 32 | Also, let us choose a nicer font and maybe add some colour? 33 | We modify the `#set page` and `#set text` commands for that: 34 | ```typ 35 | #set page(paper: "presentation-16-9", fill: teal.lighten(90%)) 36 | #set text(size: 25pt, font: "Blogger Sans") 37 | 38 | #polylux-slide[ 39 | #set align(horizon + center) 40 | = My fabulous talk 41 | 42 | Jane Doe 43 | 44 | Conference on Advances in Slide Making 45 | ] 46 | ``` 47 | ![titleslide](title-slide.png) 48 | Not bad, right? 49 | Another thing that is usually a good idea is to have a title on each slide. 50 | That is also no big deal by using off-the-shelf Typst features, so let's modify 51 | our first slide: 52 | ```typ 53 | #polylux-slide[ 54 | == My slide title 55 | Hello, world! 56 | ] 57 | ``` 58 | This is starting to look like a real presentation: 59 | ![slidetitle](slide-title.png) 60 | 61 | ## So what? 62 | To be honest, everything we did so far would have been just as easy without 63 | using polylux at all. 64 | So why should you care about it? 65 | 66 | Consider the following situation: 67 | You have a slide where parts of the content appear or disappear, or the colour 68 | of some text changes, or some other small-sized change. 69 | Would you like to duplicate the whole slide just so to create this affect? 70 | And then maintain multiple copies of the same content, making sure never to 71 | forget updating all copies when your content evolves? 72 | Of course you wouldn't and, gladly, polylux can handle this for you. 73 | 74 | This kind of feature is called **dynamic content** or **overlays** (loosely 75 | speaking, you might also say **animations** but that might be a bit of a stretch, 76 | nothing actually "moves" on PDF pages). 77 | 78 | So how does that work in polylux? 79 | As a quick example, let's add a little quiz to our slides: 80 | ```typ 81 | #polylux-slide[ 82 | == A quiz 83 | 84 | What is the capital of the Republic of Benin? 85 | 86 | #uncover(2)[Porto-Novo] 87 | ] 88 | ``` 89 | ![quiz](quiz.png) 90 | Note how two more slides have been created even though we declared only one. 91 | 92 | The next sections will explain dynamic content in polylux in all its details. 93 | 94 | For reference, here is the full source code for the slides we developed in this 95 | section: 96 | ```typ 97 | {{#include ../IMPORT.typ}} 98 | 99 | #set page(paper: "presentation-16-9", fill: teal.lighten(90%)) 100 | #set text(size: 25pt, font: "Blogger Sans") 101 | 102 | #polylux-slide[ 103 | #set align(horizon + center) 104 | = My fabulous talk 105 | 106 | Jane Doe 107 | 108 | Conference on Advances in Slide Making 109 | ] 110 | 111 | #polylux-slide[ 112 | == My slide title 113 | Hello, world! 114 | ] 115 | 116 | #polylux-slide[ 117 | == A quiz 118 | 119 | What is the capital of the Republic of Benin? 120 | 121 | #uncover(2)[Porto-Novo] 122 | ] 123 | ``` 124 | 125 | -------------------------------------------------------------------------------- /book/src/dynamic/alternatives.md: -------------------------------------------------------------------------------- 1 | # `#alternatives` to substitute content 2 | The so far discussed helpers `#pause`, `#one-by-one` etc. all build upon `#uncover`. 3 | There is an analogon to `#one-by-one` that is based on `#only`, namely 4 | `#alternatives`. 5 | You can use it to show some content on one subslide, then substitute it by 6 | something else, then by something else, etc. 7 | 8 | Consider this example: 9 | ```typ 10 | {{#include poor-alternatives.typ:6:11}} 11 | ``` 12 | Here, we want to display three different sentences with the same structure: 13 | Some person likes some sort of ice cream. 14 | 15 | ![poor-alternatives](poor-alternatives.png) 16 | 17 | As you can see, the positioning of `likes` and `ice cream` moves 18 | around in the produced slide because, for example, `Ann` takes much less space 19 | than `Christopher` when using `#only` for that job. 20 | This somewhat disturbs the perception of the constant structure of the sentence 21 | and that only the names and kinds of ice cream change. 22 | 23 | To avoid such movement and only substitute certain parts of content, you can use 24 | the `#alternatives` function. 25 | With it, our example becomes: 26 | ```typ 27 | {{#include alternatives.typ:6:11}} 28 | ``` 29 | resulting in 30 | 31 | ![alternatives](alternatives.png) 32 | 33 | `#alternatives` will put enough empty space around, for example, `Ann` such that 34 | it uses the same amount of space as `Christopher`. 35 | In a sense, it is like a mix of `#only` and `#uncover` with some reserving of 36 | space. 37 | 38 | ### Repeat last content 39 | In case you have other dynamic content on a slide that happens after the contents 40 | of `#alternatives` are exhausted, you might want to not have the `#alternatives` 41 | element disappear but instead continue to show its last content argument. 42 | To achieve this, you can use the `repeat-last` parameter: 43 | ```typ 44 | {{#include alternatives-repeat-last.typ:6:9}} 45 | ``` 46 | resulting in 47 | 48 | ![alternatives-repeat-last](alternatives-repeat-last.png) 49 | 50 | ### Positioning 51 | By default, all elements that enter an `#alternatives` command are aligned at 52 | the bottom left corner. 53 | This might not always be the desired or the most pleasant way to position it, so 54 | you can provide an optional `position` argument to `#alternatives` that takes an 55 | [`alignment` or `2d alignment`](https://typst.app/docs/reference/layout/align/#parameters--alignment). 56 | For example: 57 | ```typ 58 | {{#include alternatives-position.typ:6:9}} 59 | ``` 60 | makes the mathematical terms look better positioned: 61 | 62 | ![alternatives-position](alternatives-position.png) 63 | 64 | All functions described on this page have such a `position` argument. 65 | 66 | Similar to `#one-by-one`, `#alternatives` also has an optional `start` argument 67 | that works just the same. 68 | 69 | ## `#alternatives-match` 70 | `#alternatives` has a couple of "cousins" that might be more convenient in some 71 | situations. 72 | The first one is `#alternatives-match` that has a name inspired by match-statements 73 | in many functional programming languages. 74 | The idea is that you give it a dictionary mapping from subslides to content: 75 | ```typ 76 | {{#include alternatives-match.typ:6:9}} 77 | ``` 78 | resulting in 79 | 80 | ![alternatives-match](alternatives-match.png) 81 | 82 | **Note** that it is your responsibility to make sure that the subslide sets are 83 | mutually disjoint. 84 | 85 | ## `#alternatives-cases` 86 | You can use this function if you want to have one piece of content that changes 87 | only slightly depending of what "case" of subslides you are in. 88 | So instead of 89 | ```typ 90 | {{#include alternatives-cases.typ:6:14}} 91 | ``` 92 | you can avoid duplication and write 93 | ```typ 94 | {{#include alternatives-cases.typ:18:21}} 95 | ``` 96 | using a function that maps the current "case" to content, resulting in 97 | 98 | ![alternatives-cases](alternatives-cases.png) 99 | 100 | **Note** that the cases are 0-indexed (as are Typst arrays). 101 | 102 | 103 | ## `#alternatives-fn` 104 | Finally, you can have very fine-grained control over the content depending on 105 | the current subslide by using `#alternatives-fn`. 106 | It accepts a function (hence the name) that maps the current subslide index to 107 | some content. 108 | 109 | Similar to `#alternatives`, it accepts an optional `start` parameter that has a 110 | default of `1`. 111 | `#alternatives-fn` only knows for how long to display something, though, if you 112 | provide either the number of subslides (`count` parameter) or the last subslide 113 | index (`end` parameter). 114 | So exactly one of them is necessary. 115 | 116 | For example: 117 | ```typ 118 | {{#include alternatives-fn.typ:6:8}} 119 | ``` 120 | resulting in 121 | 122 | ![alternatives-fn](alternatives-fn.png) 123 | -------------------------------------------------------------------------------- /themes/clean.typ: -------------------------------------------------------------------------------- 1 | // This theme contains ideas from the former "bristol" theme, contributed by 2 | // https://github.com/MarkBlyth 3 | 4 | #import "../logic.typ" 5 | #import "../utils/utils.typ" 6 | 7 | #let clean-footer = state("clean-footer", []) 8 | #let clean-short-title = state("clean-short-title", none) 9 | #let clean-color = state("clean-color", teal) 10 | #let clean-logo = state("clean-logo", none) 11 | 12 | 13 | #let clean-theme( 14 | aspect-ratio: "16-9", 15 | footer: [], 16 | short-title: none, 17 | logo: none, 18 | color: teal, 19 | body 20 | ) = { 21 | set page( 22 | paper: "presentation-" + aspect-ratio, 23 | margin: 0em, 24 | header: none, 25 | footer: none, 26 | ) 27 | set text(size: 25pt) 28 | show footnote.entry: set text(size: .6em) 29 | 30 | clean-footer.update(footer) 31 | clean-color.update(color) 32 | clean-short-title.update(short-title) 33 | clean-logo.update(logo) 34 | 35 | body 36 | } 37 | 38 | 39 | #let title-slide( 40 | title: none, 41 | subtitle: none, 42 | authors: (), 43 | date: none, 44 | watermark: none, 45 | secondlogo: none, 46 | ) = { 47 | let content = locate( loc => { 48 | let color = clean-color.at(loc) 49 | let logo = clean-logo.at(loc) 50 | let authors = if type(authors) in ("string", "content") { 51 | ( authors, ) 52 | } else { 53 | authors 54 | } 55 | 56 | if watermark != none { 57 | set image(width: 100%) 58 | place(watermark) 59 | } 60 | 61 | v(5%) 62 | grid(columns: (5%, 1fr, 1fr, 5%), 63 | [], 64 | if logo != none { 65 | set align(bottom + left) 66 | set image(height: 3em) 67 | logo 68 | } else { [] }, 69 | if secondlogo != none { 70 | set align(bottom + right) 71 | set image(height: 3em) 72 | secondlogo 73 | } else { [] }, 74 | [] 75 | ) 76 | 77 | v(-10%) 78 | align(center + horizon)[ 79 | #block( 80 | stroke: ( y: 1mm + color ), 81 | inset: 1em, 82 | breakable: false, 83 | [ 84 | #text(1.3em)[*#title*] \ 85 | #{ 86 | if subtitle != none { 87 | parbreak() 88 | text(.9em)[#subtitle] 89 | } 90 | } 91 | ] 92 | ) 93 | #set text(size: .8em) 94 | #grid( 95 | columns: (1fr,) * calc.min(authors.len(), 3), 96 | column-gutter: 1em, 97 | row-gutter: 1em, 98 | ..authors 99 | ) 100 | #v(1em) 101 | #date 102 | ] 103 | }) 104 | logic.polylux-slide(content) 105 | } 106 | 107 | #let slide(title: none, body) = { 108 | let header = align(top, locate( loc => { 109 | let color = clean-color.at(loc) 110 | let logo = clean-logo.at(loc) 111 | let short-title = clean-short-title.at(loc) 112 | 113 | show: block.with(stroke: (bottom: 1mm + color), width: 100%, inset: (y: .3em)) 114 | set text(size: .5em) 115 | 116 | grid( 117 | columns: (1fr, 1fr), 118 | if logo != none { 119 | set align(left) 120 | set image(height: 4em) 121 | logo 122 | } else { [] }, 123 | if short-title != none { 124 | align(horizon + right, grid( 125 | columns: 1, rows: 1em, gutter: .5em, 126 | short-title, 127 | utils.current-section 128 | )) 129 | } else { 130 | align(horizon + right, utils.current-section) 131 | } 132 | ) 133 | })) 134 | 135 | let footer = locate( loc => { 136 | let color = clean-color.at(loc) 137 | 138 | block( 139 | stroke: ( top: 1mm + color ), width: 100%, inset: ( y: .3em ), 140 | text(.5em, { 141 | clean-footer.display() 142 | h(1fr) 143 | logic.logical-slide.display() 144 | }) 145 | ) 146 | }) 147 | 148 | set page( 149 | margin: ( top: 4em, bottom: 2em, x: 1em ), 150 | header: header, 151 | footer: footer, 152 | footer-descent: 1em, 153 | header-ascent: 1.5em, 154 | ) 155 | 156 | let body = pad(x: .0em, y: .5em, body) 157 | 158 | 159 | let content = { 160 | if title != none { 161 | heading(level: 2, title) 162 | } 163 | body 164 | } 165 | 166 | logic.polylux-slide(content) 167 | } 168 | 169 | #let focus-slide(background: teal, foreground: white, body) = { 170 | set page(fill: background, margin: 2em) 171 | set text(fill: foreground, size: 1.5em) 172 | let content = { v(.1fr); body; v(.1fr) } 173 | // logic.polylux-slide(align(horizon, body)) 174 | logic.polylux-slide(content) 175 | } 176 | 177 | #let new-section-slide(name) = { 178 | set page(margin: 2em) 179 | let content = locate( loc => { 180 | let color = clean-color.at(loc) 181 | set align(center + horizon) 182 | show: block.with(stroke: ( bottom: 1mm + color ), inset: 1em,) 183 | set text(size: 1.5em) 184 | strong(name) 185 | utils.register-section(name) 186 | }) 187 | logic.polylux-slide(content) 188 | } 189 | -------------------------------------------------------------------------------- /utils/utils.typ: -------------------------------------------------------------------------------- 1 | #import "../logic.typ" 2 | 3 | #import "pdfpc.typ" 4 | 5 | // SECTIONS 6 | 7 | #let sections-state = state("polylux-sections", ()) 8 | #let register-section(name) = locate( loc => { 9 | sections-state.update(sections => { 10 | sections.push((body: name, loc: loc)) 11 | sections 12 | }) 13 | }) 14 | 15 | #let current-section = locate( loc => { 16 | let sections = sections-state.at(loc) 17 | if sections.len() > 0 { 18 | sections.last().body 19 | } else { 20 | [] 21 | } 22 | }) 23 | 24 | #let polylux-outline(enum-args: (:), padding: 0pt) = locate( loc => { 25 | let sections = sections-state.final(loc) 26 | pad(padding, enum( 27 | ..enum-args, 28 | ..sections.map(section => link(section.loc, section.body)) 29 | )) 30 | }) 31 | 32 | 33 | // PROGRESS 34 | 35 | #let polylux-progress(ratio-to-content) = locate( loc => { 36 | let ratio = logic.logical-slide.at(loc).first() / logic.logical-slide.final(loc).first() 37 | ratio-to-content(ratio) 38 | }) 39 | 40 | #let last-slide-number = locate(loc => logic.logical-slide.final(loc).first()) 41 | 42 | 43 | // HEIGHT FITTING 44 | 45 | #let _size-to-pt(size, styles, container-dimension) = { 46 | let to-convert = size 47 | if type(size) == "ratio" { 48 | to-convert = container-dimension * size 49 | } 50 | measure(v(to-convert), styles).height 51 | } 52 | 53 | #let _limit-content-width(width: none, body, container-size, styles) = { 54 | let mutable-width = width 55 | if width == none { 56 | mutable-width = calc.min(container-size.width, measure(body, styles).width) 57 | } else { 58 | mutable-width = _size-to-pt(width, styles, container-size.width) 59 | } 60 | box(width: mutable-width, body) 61 | } 62 | 63 | #let fit-to-height(height, width: none, prescale-width: none, body) = { 64 | // Place two labels with the requested vertical separation to be able to 65 | // measure their vertical distance in pt. 66 | // Using this approach instead of using `measure` allows us to accept fractions 67 | // like `1fr` as well. 68 | // The label must be attached to content, so we use a show rule that doesn't 69 | // display anything as the anchor. 70 | let before-label = label("polylux-fit-height-before") 71 | let after-label = label("polylux-fit-height-after") 72 | [ 73 | #show before-label: none 74 | #show after-label: none 75 | #v(1em) 76 | hidden#before-label 77 | #v(height) 78 | hidden#after-label 79 | ] 80 | 81 | locate(loc => { 82 | let before = query(selector(before-label).before(loc), loc) 83 | let before-pos = before.last().location().position() 84 | let after = query(selector(after-label).before(loc), loc) 85 | let after-pos = after.last().location().position() 86 | 87 | let available-height = after-pos.y - before-pos.y 88 | 89 | style(styles => { 90 | layout(container-size => { 91 | // Helper function to more easily grab absolute units 92 | let get-pts(body, w-or-h) = { 93 | let dim = if w-or-h == "w" {container-size.width} else {container-size.height} 94 | _size-to-pt(body, styles, dim) 95 | } 96 | 97 | // Provide a sensible initial width, which will define initial scale parameters. 98 | // Note this is different from the post-scale width, which is a limiting factor 99 | // on the allowable scaling ratio 100 | let boxed-content = _limit-content-width( 101 | width: prescale-width, body, container-size, styles 102 | ) 103 | 104 | // post-scaling width 105 | let mutable-width = width 106 | if width == none { 107 | mutable-width = container-size.width 108 | } 109 | mutable-width = get-pts(mutable-width, "w") 110 | 111 | let size = measure(boxed-content, styles) 112 | let h-ratio = available-height / size.height 113 | let w-ratio = mutable-width / size.width 114 | let ratio = calc.min(h-ratio, w-ratio) * 100% 115 | 116 | let new-width = size.width * ratio 117 | v(-available-height) 118 | // If not boxed, the content can overflow to the next page even though it will fit. 119 | // This is because scale doesn't update the layout information. 120 | // Boxing in a container without clipping will inform typst that content 121 | // will indeed fit in the remaining space 122 | box( 123 | width: new-width, 124 | height: available-height, 125 | scale(x: ratio, y: ratio, origin: top + left, boxed-content) 126 | ) 127 | }) 128 | }) 129 | }) 130 | } 131 | 132 | // SIDE BY SIDE 133 | 134 | #let side-by-side(columns: none, gutter: 1em, ..bodies) = { 135 | let bodies = bodies.pos() 136 | let columns = if columns == none { (1fr,) * bodies.len() } else { columns } 137 | if columns.len() != bodies.len() { 138 | panic("number of columns must match number of content arguments") 139 | } 140 | 141 | grid(columns: columns, gutter: gutter, ..bodies) 142 | } 143 | -------------------------------------------------------------------------------- /book/src/themes/your-own.md: -------------------------------------------------------------------------------- 1 | # Build your own theme 2 | 3 | Again, there is no right or wrong when it comes to how a Polylux theme works. 4 | If you consider building a theme that you would like to contribute to the 5 | package ([which you are cordially invited to do!](https://github.com/andreasKroepelin/polylux/pulls)), 6 | we kindly ask you to follow the convention presented before. 7 | In any case, it is probably a good idea to take that as an orientation. 8 | 9 | To demonstrate how one would go about defining a custom theme, let us create one 10 | together! 11 | How about we make one for science slams? 12 | If you have ever been to one, you might have noticed that the presenters there 13 | love sparse dark-mode slides with huge font sizes. 14 | 15 | ## Imports 16 | Depending on whether this is a theme for yourself or supposed to be part of 17 | polylux, you do one of two things: 18 | For yourself, you simply import polylux as always: 19 | ```typ 20 | {{#include ../IMPORT.typ}} 21 | ``` 22 | 23 | A theme that is shipped with polylux doesn't have to do that, and it shouldn't! 24 | Otherwise circular imports can occur. 25 | Instead, you depend on the two files `logic.typ` and `utils/utils.typ`. 26 | As your theme file `science-slam.typ` will be inside the `themes` directory, the 27 | imports will be: 28 | ```typ 29 | #import "../logic.typ" 30 | #import "../utils/utils.typ" 31 | ``` 32 | Additionally, you have to make polylux know about your theme which you do by 33 | adding 34 | ```typ 35 | #import "science-slam.typ" 36 | ``` 37 | to `themes/themes.typ`. 38 | 39 | ## The initialisation function 40 | 41 | With that out of the way, we start with the function that sets the scene for 42 | everything else to come. 43 | By convention, we call it `science-slam-theme` and it can accept some keyword 44 | arguments along a single content argument. 45 | The keyword arguments are for configuration options, the content is for the rest 46 | of the document (read [here](https://typst.app/docs/tutorial/making-a-template/) 47 | if you are unfamiliar with this kind of function, this feature is rather unique 48 | to Typst). 49 | ```typ 50 | {{#include science-slam.typ:init}} 51 | ``` 52 | As you can see, we have two configuration options: 53 | One for the aspect ratio (as is convention) and one to determine the background 54 | colour — the more serious you are, the darker your background colour is, of course. 55 | After we have set the `page` parameters accordingly, we also define the text to 56 | be huge, bright and in a sans serif font. 57 | 58 | Using it looks like this: 59 | ```typ 60 | {{#include science-slam.typ:use-init}} 61 | ``` 62 | 63 | ## Title slide 64 | Next up, let us define a cool title slide. 65 | The only thing you have to keep in mind when defining your own slide functions 66 | is that **you need to put the content you produce into the `#polylux-slide` 67 | function in the end**. 68 | It might look as if it works without that as well but it actually breaks when you 69 | use `#uncover` or similar polylux features. 70 | If you build a theme as part of polylux and you have followed the import 71 | instructions from above, you will qualify the function as `logic.polylux-slide`. 72 | 73 | Our title slide here is very simple, it just makes very sure to let the audience 74 | know what the topic is and who is speaking: 75 | ```typ 76 | {{#include science-slam.typ:title-slide}} 77 | ``` 78 | You can use it like this: 79 | ```typ 80 | {{#include science-slam.typ:use-title-slide}} 81 | ``` 82 | 83 | Note that the user does not actually provide any content themselves to this function. 84 | That is a common thing for title slides because their structure is so well-defined 85 | that a theme can produce all the content by just asking for a few pieces of 86 | information (like the title etc.). 87 | 88 | ## Regular slides 89 | The principle is the same as with the title slide. 90 | Define a function, create some content, pass it to `polylux-slide`. 91 | By convention, you should name the function for regular slides `slide` because 92 | it will be used most often. 93 | Here you will typically accept arbitrary content as a positional parameter that 94 | will make up the main content of the slide. 95 | 96 | For example: 97 | ```typ 98 | {{#include science-slam.typ:slide}} 99 | ``` 100 | And you can use it like this: 101 | ```typ 102 | {{#include science-slam.typ:use-slide}} 103 | ``` 104 | 105 | In case you wondered, this is how the theme and the slides we just put together 106 | look like: 107 | 108 | ![science-slam](science-slam.png) 109 | 110 | ## Any number of further variants 111 | Be creative! 112 | There are no limits but your own to the slide functions your theme can contain 113 | once you grasped the simple structure. 114 | For "serious" themes (other than this demo) you will probably want to think 115 | about adding headers, footers, slide numbers etc. 116 | Why not look into the source code of existing themes to get some inspiration? 117 | 118 | The next page also lists some small tools that polylux provides to make common 119 | tasks simpler when creating a slide. 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polylux 2 | This is a package for creating presentation slides in [Typst](https://typst.app/). 3 | Read the [book](https://polylux.dev/book) to learn all 4 | about it and click [here](https://polylux.dev/book/changelog.html) 5 | to see what's new! 6 | 7 | If you like it, consider [giving a star on GitHub](https://github.com/andreasKroepelin/polylux)! 8 | 9 | [![Book badge](https://img.shields.io/badge/docs-book-green)](https://polylux.dev/book) 10 | ![GitHub](https://img.shields.io/github/license/andreasKroepelin/polylux) 11 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/andreasKroepelin/polylux) 12 | ![GitHub Repo stars](https://img.shields.io/github/stars/andreasKroepelin/polylux) 13 | [![Demo badge](https://img.shields.io/badge/demo-pdf-blue)](https://github.com/andreasKroepelin/polylux/releases/latest/download/demo.pdf) 14 | ![Themes badge](https://img.shields.io/badge/themes-5-aqua) 15 | 16 | 17 | ## Quickstart 18 | For the bare-bones, do-it-yourself experience, all you need is: 19 | ```typ 20 | // Get Polylux from the official package repository 21 | #import "@preview/polylux:0.3.1": * 22 | 23 | // Make the paper dimensions fit for a presentation and the text larger 24 | #set page(paper: "presentation-16-9") 25 | #set text(size: 25pt) 26 | 27 | // Use #polylux-slide to create a slide and style it using your favourite Typst functions 28 | #polylux-slide[ 29 | #align(horizon + center)[ 30 | = Very minimalist slides 31 | 32 | A lazy author 33 | 34 | July 23, 2023 35 | ] 36 | ] 37 | 38 | #polylux-slide[ 39 | == First slide 40 | 41 | Some static text on this slide. 42 | ] 43 | 44 | #polylux-slide[ 45 | == This slide changes! 46 | 47 | You can always see this. 48 | // Make use of features like #uncover, #only, and others to create dynamic content 49 | #uncover(2)[But this appears later!] 50 | ] 51 | ``` 52 | This code produces these PDF pages: 53 | ![minimal example](https://polylux.dev/book/minimal.png) 54 | 55 | From there, you can either start creatively adapting the looks to your likings 56 | or you can use one of the provided themes. 57 | The simplest one of them is called `simple` (what a coincidence!). 58 | It is still very unintrusive but gives you some sensible defaults: 59 | ```typ 60 | #import "@preview/polylux:0.3.1": * 61 | 62 | #import themes.simple: * 63 | 64 | #set text(font: "Inria Sans") 65 | 66 | #show: simple-theme.with( 67 | footer: [Simple slides], 68 | ) 69 | 70 | #title-slide[ 71 | = Keep it simple! 72 | #v(2em) 73 | 74 | Alpha #footnote[Uni Augsburg] #h(1em) 75 | Bravo #footnote[Uni Bayreuth] #h(1em) 76 | Charlie #footnote[Uni Chemnitz] #h(1em) 77 | 78 | July 23 79 | ] 80 | 81 | #slide[ 82 | == First slide 83 | 84 | #lorem(20) 85 | ] 86 | 87 | #focus-slide[ 88 | _Focus!_ 89 | 90 | This is very important. 91 | ] 92 | 93 | #centered-slide[ 94 | = Let's start a new section! 95 | ] 96 | 97 | #slide[ 98 | == Dynamic slide 99 | Did you know that... 100 | 101 | #pause 102 | ...you can see the current section at the top of the slide? 103 | ] 104 | ``` 105 | This time, we obtain these PDF pages: 106 | ![simple example](https://polylux.dev/book/themes/gallery/simple.png) 107 | 108 | As you can see, a theme can introduce its own types of slides (here: `title-slide`, 109 | `slide`, `focus-slide`, `centered-slide`) to let you quickly switch between 110 | different layouts. 111 | The book 112 | [has more infos](https://polylux.dev/book/themes/themes.html) 113 | on how to use (and create your own) themes. 114 | 115 | 116 | For dynamic content, Polylux also provides [a convenient API for complex 117 | overlays](https://polylux.dev/book/dynamic/dynamic.html). 118 | 119 | If you use [pdfpc](https://pdfpc.github.io/) to display your slides, you can rely 120 | on [Polylux' support for it](https://polylux.dev/book/external/pdfpc.html) 121 | and create speaker notes, hide slides, configure the timer and more! 122 | 123 | Visit the 124 | [book](https://polylux.dev/book) 125 | for more details or take a look at the 126 | [demo PDF](https://github.com/andreasKroepelin/polylux/releases/latest/download/demo.pdf) 127 | where you can see the features of this template in action. 128 | 129 | **⚠ This package is under active development and there are no backwards 130 | compatibility guarantees!** 131 | 132 | ## Acknowledgements 133 | Thank you to... 134 | - [@drupol](https://github.com/drupol) for the `university` theme 135 | - [@Enivex](https://github.com/Enivex) for the `metropolis` theme 136 | - [@MarkBlyth](https://github.com/MarkBlyth) for contributing to the `clean` theme 137 | - [@ntjess](https://github.com/ntjess) for contributing to the height fitting 138 | feature 139 | - [@JuliusFreudenberger](https://github.com/JuliusFreudenberger) for maintaining 140 | the `polylux2pdfpc` AUR package 141 | - [@fncnt](https://github.com/fncnt) for coming up with the name "Polylux" 142 | - the Typst authors and contributors for this refreshing piece of software 143 | -------------------------------------------------------------------------------- /book/src/themes/gallery/university.md: -------------------------------------------------------------------------------- 1 | # University theme 2 | 3 | ![university](university.png) 4 | 5 | This theme offers a simple yet versatile design, allowing for easy customization 6 | and flexibility. Additionally, it incorporates a progress bar at the top, which 7 | displays the current status of the presentation. 8 | 9 | Use it via 10 | ```typ 11 | {{#include ../../IMPORT.typ}} 12 | #import themes.university: * 13 | 14 | #show: university-theme.with(...) 15 | ``` 16 | 17 | `university` uses polylux' section handling, the regular `#outline()` will not work 18 | properly, use `#polylux-outline` instead. 19 | Starting a new section is done by the corresponding keyword argument of `#slide`. 20 | 21 | Text is configured to have a base font size of 25 pt. 22 | 23 | ## Options for initialisation 24 | `university-theme` accepts the following optional keyword arguments: 25 | 26 | - `aspect-ratio`: the aspect ratio of the slides, either `"16-9"` or `"4-3"`, 27 | default is `"16-9"` 28 | - `short-title`: short title of the presentation to display on every slide, 29 | default: `none` 30 | - `short-author`: short author of the presentation to display on every slide, 31 | default: `none` 32 | - `short-date`: short date of presentation to display on every slide, default: 33 | `none` 34 | - `color-a`: main colour of decorations, default: `rgb("#0C6291")` 35 | - `color-b`: accent colour, default: `rgb("#A63446")` 36 | - `color-c`: second accent colour, default: `rgb("#FBFEF9")` 37 | - `progress-bar`: boolean value whether or not to display a progress bar on 38 | regular sides, default: `true` 39 | 40 | ## Slide functions 41 | `university` provides the following custom slide functions: 42 | 43 | ```typ 44 | #title-slide(...) 45 | ``` 46 | Creates a title slide where title and subtitle are separated by additional 47 | information by a bright line. 48 | Accepts the following keyword arguments: 49 | - `title`: title of the presentation, default: `[]` 50 | - `subtitle`: subtitle of the presentation, default: `none` 51 | - `authors`: authors of presentation, can be an array of contents or a single 52 | content, will be displayed in a grid, default: `()` 53 | - `date`: date of the presentation, default: `none` 54 | - `institution-name`: name of the institution, default: `"University"` 55 | - `logo`: some content (most likely an image) used as a logo on the title slide, 56 | default: `none` 57 | 58 | Does not accept additional content. 59 | 60 | --- 61 | 62 | ```typ 63 | #slide(...)[ 64 | ... 65 | ] 66 | ``` 67 | Decorates the provided content with a header containing a progress bar (optionally), 68 | the slide title, and the current section (if any); and a footer containing short 69 | forms of authors, title, and date, and the slide number. 70 | Header and footer can also be overwritten by respective keyword arguments. 71 | 72 | Pass the slide title as a keyword argument `title`. 73 | 74 | Accepts the following keyword arguments: 75 | - `title`: title of the slide, default: `none`, 76 | - `header`: custom content to overwrite default header 77 | - `footer`: custom content to overwrite default footer 78 | - `new-section`: name of the new section that starts here if not `none`, default: 79 | `none` 80 | 81 | --- 82 | 83 | ```typ 84 | #focus-slide(background-img: ..., background-color: ...)[ 85 | ... 86 | ] 87 | ``` 88 | Draw attention with this variant where the content is displayed centered and text 89 | is enlarged and bright. 90 | You can either specify a background image or a background colour as keyword 91 | arguments. 92 | If you specify none of them, a background colour of `rgb("#0C6291")` is used as 93 | a default. 94 | 95 | Not suitable for content that exceeds one page. 96 | 97 | --- 98 | 99 | ```typ 100 | #matrix-slide(columns: ..., rows: ...)[ 101 | ... 102 | ][ 103 | ... 104 | ] 105 | ``` 106 | Create a slide where the provided content blocks are displayed in a grid and 107 | coloured in a checkerboard pattern without further decoration. 108 | You can configure the grid using the `rows` and `columns` keyword arguments 109 | (both default to `none`). 110 | It is determined in the following way: 111 | 1. If `colmuns` is an integer, create that many columns of width `1fr`. 112 | 2. If `columns` is `none`, create as many columns of width `1fr` as there are 113 | content blocks. 114 | 3. Otherwise assume that `columns` is an array of widths already, use that. 115 | 4. If `rows` is an integer, create that many rows of height `1fr`. 116 | 5. If `rows` is `none` create that many rows of height `1fr` as are needed 117 | given the number of content blocks and columns. 118 | 6. Otherwise assume that `rows` is an array of heights already, use that. 119 | 7. Check that there are enough rows and columns to fit in all the content blocks. 120 | 121 | That means that `#matrix-slide[...][...]` stacks horizontally and 122 | `#matrix-slide(columns: 1)[...][...]` stacks vertically. 123 | 124 | Not suitable for content that exceeds one page. 125 | 126 | 127 | ## Example code 128 | The image at the top is created by the following code: 129 | ```typ 130 | {{#include ../../IMPORT.typ}} 131 | {{#include university.typ:3:}} 132 | ``` 133 | -------------------------------------------------------------------------------- /book/src/external/pdfpc.md: -------------------------------------------------------------------------------- 1 | # pdfpc 2 | 3 | [pdfpc](https://pdfpc.github.io/) is a "presenter console with multi-monitor 4 | support for PDF-files". 5 | That means, you can use it to display slides in the form of PDF-pages and also 6 | have some of the nice features known from, for example, PowerPoint. 7 | Check out their website to learn more. 8 | 9 | When pdfpc is provided a special `.pdfpc` file containing some JSON data, it can 10 | use that to enhance the user experience by correctly handling overlay slides, 11 | displaying speaker notes, setting up a specific timer, and more. 12 | While you can write this file by hand or use the pdfpc-internal features to edit 13 | it, some might find it more convenient to have all data about their presentation 14 | in one place, i.e. the Typst source file. 15 | Polylux allows you to do that. 16 | 17 | ## Adding metadata to the Typst source 18 | 19 | Polylux exports the `pdfpc` module that comes with a bunch of useful functions 20 | that do not actually add any content to the produced PDF but instead insert 21 | metadata that can later be extracted from the document. 22 | 23 | ### Speaker notes 24 | This is possibly the most useful feature of pdfpc. 25 | Using the function `#pdfpc.speaker-note` inside a slide, you can add a note to 26 | that slide that will only be visible to the speaker in pdfpc. 27 | It accepts either a string: 28 | ```typ 29 | #pdfpc.speaker-note("This is a note that only the speaker will see.") 30 | ``` 31 | or a `raw` block: 32 | ````typ 33 | #pdfpc.speaker-note( 34 | ```md 35 | # My notes 36 | Did you know that pdfpc supports Markdown notes? _So cool!_ 37 | ``` 38 | ) 39 | ```` 40 | Note that you can only specify one note per slide (only the first one will 41 | survive if you use more than one.) 42 | 43 | ### End slide 44 | Sometimes the last slide in your presentation is not really the one you want to 45 | end with. 46 | Say, you have some bibliography or appendix for the sake of completeness after 47 | your "I thank my mom and everyone who believed in me"-slide. 48 | 49 | With a simple `pdfpc.end-slide` inside any slide you can tell pdfpc that this is 50 | the last slide you usually want to show and hitting the `End` key will jump there. 51 | 52 | ### Save a slide 53 | Similarly, there is a feature in pdfpc to bookmark a specific slide (and you can 54 | jump to it using `Shift + M`). 55 | In your Typst source, you can choose that slide by putting `pdfpc.save-slide` 56 | inside it. 57 | 58 | ### Hide slides 59 | If you want to keep a certain slide in your presentation (just in case) but don't 60 | normally intend to show it, you can hide it inside pdfpc. 61 | It will be skipped during the presentation but it is still available in the 62 | overview. 63 | You can use `pdfpc.hidden-slide` in your Typst source to mark a slide as hidden. 64 | 65 | ### Configure pdfpc 66 | The previous commands are all supposed to be used _inside_ a slide. 67 | To perform some additional global configuration, you can use `pdfpc.config()` 68 | _before_ any of the slides (it will not be recognised otherwise). 69 | 70 | It accepts the following optional keyword arguments: 71 | 72 | - `duration-minutes`: how many minutes (a number) the presentation is supposed 73 | to take, affects the timer in pdfpc 74 | - `start-time`: wall-clock time when the presentation is supposed to start, either 75 | as a `datetime(hour: ..., minute: ..., second: ...)` or as a string in the 76 | `HH:MM` format 77 | - `end-time`: same as `start-time` but when the presentation is supposed to end 78 | - `last-minutes`: how many minutes (a number) before the time runs out the timer 79 | is supposed to change its colour as a warning 80 | - `note-font-size`: the font size (a number) the speaker notes are displayed in 81 | - `disable-markdown`: whether or not to disable rendering the notes as markdown 82 | (a bool), default `false` 83 | - `default-transition`: the transition to use between subsequent slides, must be 84 | given as a dictionary with (potentially) the following keys: 85 | - `type`: one of `"replace"` (default), `"push"`, `"blinds"`, `"box"`, 86 | `"cover"`, `"dissolve"`, `"fade"`, `"glitter"`, `"split"`, `"uncover"`, 87 | `"wipe"` 88 | - `duration-seconds`: the duration of the transition in seconds (a number) 89 | - `angle`: in which angle the transition moves, one of `ltr`, `rtl`, `ttb`, 90 | and `btt` (see [the `#stack` function](https://typst.app/docs/reference/layout/stack/#parameters-dir)) 91 | - `alignment`: whether the transition is performed horizontally or vertically, 92 | one of `"horizontal"` and `"vertical"` 93 | - `direction`: whether the transition is performed inward or outward, one of 94 | `"inward"` and `"outward"` 95 | 96 | Not all combinations of values are necessary or make sense for all transitions, 97 | of course. 98 | 99 | ## Extracting the data: `polylux2pdfpc` 100 | As mentioned above, the functions from the `pdfpc` module don't alter the produced 101 | PDF itself. 102 | Instead, we need some other way to extract their data. 103 | You could, in principle, do that by hand using the `typst query` CLI and then 104 | assemble the correct `.pdfpc` file yourself. 105 | However, this tedious task is better solved by the `polylux2pdfpc` tool. 106 | 107 | ### Installation 108 | If you have [Rust](https://www.rust-lang.org/tools/install) installed, you can 109 | simply run 110 | ```sh 111 | cargo install --git https://github.com/andreasKroepelin/polylux/ --branch release 112 | ``` 113 | If you use Arch Linux btw, you can also install `polylux2pdfpc` from the AUR 114 | package [polylux2pdfpc-git](https://aur.archlinux.org/packages/polylux2pdfpc-git) 115 | (thank you to Julius Freudenberger!) 116 | 117 | ### Usage 118 | You invoke `polylux2pdfpc` with the same arguments you would also give to `typst 119 | compile` when you wanted to build your slides. 120 | For example, say you have a file called `talk.typ` in the folder `thesis` that 121 | has some global utility files or so, you 122 | would compile it using 123 | ```sh 124 | typst compile --root .. thesis/talk.typ 125 | ``` 126 | and extract the pdfpc data using 127 | ```sh 128 | polylux2pdfpc --root .. thesis/talk.typ 129 | ``` 130 | 131 | Internally, `polylux2pdfpc` runs `typst query`, collects all the pdfpc-related 132 | metadata and then writes a `.pdfpc` file that equals the input file up to the 133 | suffix. 134 | In our example with `thesis/talk.typ`, we obtain `thesis/talk.pdfpc`. 135 | Since `typst compile` produced `thesis/talk.pdf`, you can now simply open the PDF 136 | in pdpfc: 137 | ```sh 138 | pdfpc thesis/talk.pdf 139 | ``` 140 | and it will automatically recognise the `.pdfpc` file. 141 | -------------------------------------------------------------------------------- /examples/demo.pdfpc: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | { 4 | "label": "1", 5 | "idx": 0, 6 | "overlay": 0 7 | }, 8 | { 9 | "label": "2", 10 | "idx": 1, 11 | "overlay": 0 12 | }, 13 | { 14 | "label": "3", 15 | "overlay": 0, 16 | "idx": 2 17 | }, 18 | { 19 | "idx": 3, 20 | "label": "4", 21 | "overlay": 0 22 | }, 23 | { 24 | "idx": 4, 25 | "overlay": 0, 26 | "label": "5" 27 | }, 28 | { 29 | "idx": 5, 30 | "overlay": 0, 31 | "label": "6" 32 | }, 33 | { 34 | "idx": 6, 35 | "label": "7", 36 | "overlay": 0 37 | }, 38 | { 39 | "idx": 7, 40 | "label": "8", 41 | "overlay": 0 42 | }, 43 | { 44 | "overlay": 1, 45 | "label": "8", 46 | "idx": 8, 47 | "forcedOverlay": true 48 | }, 49 | { 50 | "label": "8", 51 | "idx": 9, 52 | "forcedOverlay": true, 53 | "overlay": 2 54 | }, 55 | { 56 | "idx": 10, 57 | "overlay": 0, 58 | "label": "9" 59 | }, 60 | { 61 | "idx": 11, 62 | "overlay": 0, 63 | "label": "10" 64 | }, 65 | { 66 | "idx": 12, 67 | "overlay": 1, 68 | "forcedOverlay": true, 69 | "label": "10" 70 | }, 71 | { 72 | "forcedOverlay": true, 73 | "overlay": 2, 74 | "idx": 13, 75 | "label": "10" 76 | }, 77 | { 78 | "overlay": 0, 79 | "idx": 14, 80 | "label": "11" 81 | }, 82 | { 83 | "idx": 15, 84 | "overlay": 1, 85 | "label": "11", 86 | "forcedOverlay": true 87 | }, 88 | { 89 | "overlay": 2, 90 | "label": "11", 91 | "forcedOverlay": true, 92 | "idx": 16 93 | }, 94 | { 95 | "forcedOverlay": true, 96 | "label": "11", 97 | "overlay": 3, 98 | "idx": 17 99 | }, 100 | { 101 | "overlay": 0, 102 | "idx": 18, 103 | "label": "12" 104 | }, 105 | { 106 | "forcedOverlay": true, 107 | "overlay": 1, 108 | "label": "12", 109 | "idx": 19 110 | }, 111 | { 112 | "idx": 20, 113 | "label": "12", 114 | "forcedOverlay": true, 115 | "overlay": 2 116 | }, 117 | { 118 | "forcedOverlay": true, 119 | "overlay": 3, 120 | "idx": 21, 121 | "label": "12" 122 | }, 123 | { 124 | "overlay": 4, 125 | "label": "12", 126 | "forcedOverlay": true, 127 | "idx": 22 128 | }, 129 | { 130 | "idx": 23, 131 | "label": "12", 132 | "overlay": 5, 133 | "forcedOverlay": true 134 | }, 135 | { 136 | "forcedOverlay": true, 137 | "idx": 24, 138 | "overlay": 6, 139 | "label": "12" 140 | }, 141 | { 142 | "label": "12", 143 | "overlay": 7, 144 | "idx": 25, 145 | "forcedOverlay": true 146 | }, 147 | { 148 | "overlay": 0, 149 | "idx": 26, 150 | "label": "13" 151 | }, 152 | { 153 | "idx": 27, 154 | "overlay": 1, 155 | "forcedOverlay": true, 156 | "label": "13" 157 | }, 158 | { 159 | "idx": 28, 160 | "forcedOverlay": true, 161 | "label": "13", 162 | "overlay": 2 163 | }, 164 | { 165 | "forcedOverlay": true, 166 | "idx": 29, 167 | "overlay": 3, 168 | "label": "13" 169 | }, 170 | { 171 | "label": "13", 172 | "overlay": 4, 173 | "forcedOverlay": true, 174 | "idx": 30 175 | }, 176 | { 177 | "idx": 31, 178 | "forcedOverlay": true, 179 | "label": "13", 180 | "overlay": 5 181 | }, 182 | { 183 | "idx": 32, 184 | "overlay": 0, 185 | "label": "14" 186 | }, 187 | { 188 | "overlay": 1, 189 | "label": "14", 190 | "forcedOverlay": true, 191 | "idx": 33 192 | }, 193 | { 194 | "forcedOverlay": true, 195 | "label": "14", 196 | "overlay": 2, 197 | "idx": 34 198 | }, 199 | { 200 | "label": "15", 201 | "idx": 35, 202 | "overlay": 0 203 | }, 204 | { 205 | "overlay": 1, 206 | "forcedOverlay": true, 207 | "label": "15", 208 | "idx": 36 209 | }, 210 | { 211 | "forcedOverlay": true, 212 | "overlay": 2, 213 | "idx": 37, 214 | "label": "15" 215 | }, 216 | { 217 | "idx": 38, 218 | "forcedOverlay": true, 219 | "overlay": 3, 220 | "label": "15" 221 | }, 222 | { 223 | "label": "16", 224 | "idx": 39, 225 | "overlay": 0 226 | }, 227 | { 228 | "idx": 40, 229 | "forcedOverlay": true, 230 | "label": "16", 231 | "overlay": 1 232 | }, 233 | { 234 | "idx": 41, 235 | "label": "16", 236 | "forcedOverlay": true, 237 | "overlay": 2 238 | }, 239 | { 240 | "label": "16", 241 | "forcedOverlay": true, 242 | "overlay": 3, 243 | "idx": 42 244 | }, 245 | { 246 | "label": "17", 247 | "overlay": 0, 248 | "idx": 43 249 | }, 250 | { 251 | "forcedOverlay": true, 252 | "idx": 44, 253 | "overlay": 1, 254 | "label": "17" 255 | }, 256 | { 257 | "idx": 45, 258 | "overlay": 2, 259 | "forcedOverlay": true, 260 | "label": "17" 261 | }, 262 | { 263 | "overlay": 0, 264 | "label": "18", 265 | "idx": 46 266 | }, 267 | { 268 | "overlay": 0, 269 | "label": "19", 270 | "idx": 47 271 | }, 272 | { 273 | "label": "20", 274 | "overlay": 0, 275 | "idx": 48 276 | }, 277 | { 278 | "idx": 49, 279 | "overlay": 0, 280 | "label": "21" 281 | }, 282 | { 283 | "overlay": 0, 284 | "idx": 50, 285 | "label": "22" 286 | }, 287 | { 288 | "overlay": 0, 289 | "label": "23", 290 | "idx": 51 291 | }, 292 | { 293 | "label": "24", 294 | "idx": 52, 295 | "overlay": 0 296 | }, 297 | { 298 | "overlay": 0, 299 | "idx": 53, 300 | "label": "25" 301 | }, 302 | { 303 | "overlay": 0, 304 | "label": "26", 305 | "idx": 54 306 | }, 307 | { 308 | "overlay": 0, 309 | "idx": 55, 310 | "label": "27" 311 | }, 312 | { 313 | "idx": 56, 314 | "label": "28", 315 | "overlay": 0 316 | }, 317 | { 318 | "idx": 57, 319 | "overlay": 0, 320 | "label": "29" 321 | }, 322 | { 323 | "idx": 58, 324 | "label": "30", 325 | "overlay": 0 326 | }, 327 | { 328 | "label": "31", 329 | "idx": 59, 330 | "overlay": 0 331 | }, 332 | { 333 | "idx": 60, 334 | "label": "32", 335 | "overlay": 0 336 | } 337 | ], 338 | "pdfpcFormat": 2 339 | } -------------------------------------------------------------------------------- /themes/university.typ: -------------------------------------------------------------------------------- 1 | #import "../logic.typ" 2 | #import "../utils/utils.typ" 3 | 4 | // University theme 5 | // 6 | // Originally contributed by Pol Dellaiera - https://github.com/drupol 7 | // 8 | // Please feel free to improve this theme 9 | // by submitting a PR in https://github.com/andreasKroepelin/typst-slides 10 | 11 | #let uni-colors = state("uni-colors", (:)) 12 | #let uni-short-title = state("uni-short-title", none) 13 | #let uni-short-author = state("uni-short-author", none) 14 | #let uni-short-date = state("uni-short-date", none) 15 | #let uni-progress-bar = state("uni-progress-bar", true) 16 | 17 | #let university-theme( 18 | aspect-ratio: "16-9", 19 | short-title: none, 20 | short-author: none, 21 | short-date: none, 22 | color-a: rgb("#0C6291"), 23 | color-b: rgb("#A63446"), 24 | color-c: rgb("#FBFEF9"), 25 | progress-bar: true, 26 | body 27 | ) = { 28 | set page( 29 | paper: "presentation-" + aspect-ratio, 30 | margin: 0em, 31 | header: none, 32 | footer: none, 33 | ) 34 | set text(size: 25pt) 35 | show footnote.entry: set text(size: .6em) 36 | 37 | uni-progress-bar.update(progress-bar) 38 | uni-colors.update((a: color-a, b: color-b, c: color-c)) 39 | uni-short-title.update(short-title) 40 | uni-short-author.update(short-author) 41 | uni-short-date.update(short-date) 42 | 43 | body 44 | } 45 | 46 | #let title-slide( 47 | title: [], 48 | subtitle: none, 49 | authors: (), 50 | institution-name: "University", 51 | date: none, 52 | logo: none, 53 | ) = { 54 | let authors = if type(authors) == "array" { authors } else { (authors,) } 55 | 56 | let content = locate( loc => { 57 | let colors = uni-colors.at(loc) 58 | 59 | if logo != none { 60 | align(right, logo) 61 | } 62 | 63 | align(center + horizon, { 64 | block( 65 | inset: 0em, 66 | breakable: false, 67 | { 68 | text(size: 2em, fill: colors.a, strong(title)) 69 | if subtitle != none { 70 | parbreak() 71 | text(size: 1.2em, fill: colors.a, subtitle) 72 | } 73 | } 74 | ) 75 | set text(size: .8em) 76 | grid( 77 | columns: (1fr,) * calc.min(authors.len(), 3), 78 | column-gutter: 1em, 79 | row-gutter: 1em, 80 | ..authors.map(author => text(fill: black, author)) 81 | ) 82 | v(1em) 83 | if institution-name != none { 84 | parbreak() 85 | text(size: .9em, institution-name) 86 | } 87 | if date != none { 88 | parbreak() 89 | text(size: .8em, date) 90 | } 91 | }) 92 | }) 93 | 94 | logic.polylux-slide(content) 95 | } 96 | 97 | 98 | #let slide( 99 | title: none, 100 | header: none, 101 | footer: none, 102 | new-section: none, 103 | body 104 | ) = { 105 | 106 | let body = pad(x: 2em, y: .5em, body) 107 | 108 | let progress-barline = locate( loc => { 109 | if uni-progress-bar.at(loc) { 110 | let cell = block.with( width: 100%, height: 100%, above: 0pt, below: 0pt, breakable: false ) 111 | let colors = uni-colors.at(loc) 112 | 113 | utils.polylux-progress( ratio => { 114 | grid( 115 | rows: 2pt, columns: (ratio * 100%, 1fr), 116 | cell(fill: colors.a), 117 | cell(fill: colors.b) 118 | ) 119 | }) 120 | } else { [] } 121 | }) 122 | 123 | let header-text = { 124 | if header != none { 125 | header 126 | } else if title != none { 127 | if new-section != none { 128 | utils.register-section(new-section) 129 | } 130 | locate( loc => { 131 | let colors = uni-colors.at(loc) 132 | block(fill: colors.c, inset: (x: .5em), grid( 133 | columns: (60%, 40%), 134 | align(top + left, heading(level: 2, text(fill: colors.a, title))), 135 | align(top + right, text(fill: colors.a.lighten(65%), utils.current-section)) 136 | )) 137 | }) 138 | } else { [] } 139 | } 140 | 141 | let header = { 142 | set align(top) 143 | grid(rows: (auto, auto), row-gutter: 3mm, progress-barline, header-text) 144 | } 145 | 146 | let footer = { 147 | set text(size: 10pt) 148 | set align(center + bottom) 149 | let cell(fill: none, it) = rect( 150 | width: 100%, height: 100%, inset: 1mm, outset: 0mm, fill: fill, stroke: none, 151 | align(horizon, text(fill: white, it)) 152 | ) 153 | if footer != none { 154 | footer 155 | } else { 156 | locate( loc => { 157 | let colors = uni-colors.at(loc) 158 | 159 | show: block.with(width: 100%, height: auto, fill: colors.b) 160 | grid( 161 | columns: (25%, 1fr, 15%, 10%), 162 | rows: (1.5em, auto), 163 | cell(fill: colors.a, uni-short-author.display()), 164 | cell(uni-short-title.display()), 165 | cell(uni-short-date.display()), 166 | cell(logic.logical-slide.display() + [~/~] + utils.last-slide-number) 167 | ) 168 | }) 169 | } 170 | } 171 | 172 | 173 | set page( 174 | margin: ( top: 2em, bottom: 1em, x: 0em ), 175 | header: header, 176 | footer: footer, 177 | footer-descent: 0em, 178 | header-ascent: .6em, 179 | ) 180 | 181 | logic.polylux-slide(body) 182 | } 183 | 184 | #let focus-slide(background-color: none, background-img: none, body) = { 185 | let background-color = if background-img == none and background-color == none { 186 | rgb("#0C6291") 187 | } else { 188 | background-color 189 | } 190 | 191 | set page(fill: background-color, margin: 1em) if background-color != none 192 | set page( 193 | background: { 194 | set image(fit: "stretch", width: 100%, height: 100%) 195 | background-img 196 | }, 197 | margin: 1em, 198 | ) if background-img != none 199 | 200 | set text(fill: white, size: 2em) 201 | 202 | logic.polylux-slide(align(horizon, body)) 203 | } 204 | 205 | #let matrix-slide(columns: none, rows: none, ..bodies) = { 206 | let bodies = bodies.pos() 207 | 208 | let columns = if type(columns) == "integer" { 209 | (1fr,) * columns 210 | } else if columns == none { 211 | (1fr,) * bodies.len() 212 | } else { 213 | columns 214 | } 215 | let num-cols = columns.len() 216 | 217 | let rows = if type(rows) == "integer" { 218 | (1fr,) * rows 219 | } else if rows == none { 220 | let quotient = calc.quo(bodies.len(), num-cols) 221 | let correction = if calc.rem(bodies.len(), num-cols) == 0 { 0 } else { 1 } 222 | (1fr,) * (quotient + correction) 223 | } else { 224 | rows 225 | } 226 | let num-rows = rows.len() 227 | 228 | if num-rows * num-cols < bodies.len() { 229 | panic("number of rows (" + str(num-rows) + ") * number of columns (" + str(num-cols) + ") must at least be number of content arguments (" + str(bodies.len()) + ")") 230 | } 231 | 232 | let cart-idx(i) = (calc.quo(i, num-cols), calc.rem(i, num-cols)) 233 | let color-body(idx-body) = { 234 | let (idx, body) = idx-body 235 | let (row, col) = cart-idx(idx) 236 | let color = if calc.even(row + col) { white } else { silver } 237 | set align(center + horizon) 238 | rect(inset: .5em, width: 100%, height: 100%, fill: color, body) 239 | } 240 | 241 | let content = grid( 242 | columns: columns, rows: rows, 243 | gutter: 0pt, 244 | ..bodies.enumerate().map(color-body) 245 | ) 246 | 247 | logic.polylux-slide(content) 248 | } 249 | -------------------------------------------------------------------------------- /book/src/themes.md: -------------------------------------------------------------------------------- 1 | # Themes 2 | The `polylux` template also supports theming. 3 | By default, the theme `slides-default-theme` is used. 4 | You can specify the use of another one in the initial `slides` statement: 5 | ```typ 6 | #show: slides( 7 | authors: "...", 8 | title: "...", 9 | // ... 10 | theme: your-fancy-theme(with, some, options), 11 | ) 12 | ``` 13 | 14 | ## Theme variants per slide 15 | The `#slide` function has an optional argument `theme-variant`. 16 | You can use it to select one of the variants your current theme provides, so 17 | that this specific slide will look different. 18 | ```typ 19 | #slide(theme-variant: "variant name")[ 20 | ... 21 | ] 22 | ``` 23 | 24 | ## Create your own theme 25 | On a high level, a theme is nothing more than a piece of code that defines how 26 | the slides look. 27 | 28 | On one level deeper, a theme is a function that takes a `data` dictionary and 29 | returns a dictionary with one key for each _variant_. 30 | Let us build a very simple one for demonstration purposes: 31 | ```typ 32 | #let dumb-theme = data => { 33 | ( 34 | "variant 1": ..., 35 | "variant 2": ..., 36 | ) 37 | } 38 | ``` 39 | Note that you can easily introduce parameters to your theme: 40 | ```typ 41 | #let dumb-theme(parameter) = data => { 42 | ( 43 | "variant 1": ..., 44 | "variant 2": ..., 45 | ) 46 | } 47 | ``` 48 | 49 | The `data` dictionary provides you with all the information that the user has 50 | set in the initial `slides` function, i.e. it has the following structure: 51 | ```typ 52 | ( 53 | author: ..., 54 | title: ..., 55 | subtitle: ..., 56 | short-author: ..., 57 | short-title: ..., 58 | date: ..., 59 | ) 60 | ``` 61 | Use these information however you please. 62 | 63 | On to the actual slides. 64 | Their appearance is defined by functions that accept some meta data, some 65 | array of contents (the last argument(s) to `#slide`, essentially) and return 66 | some richer content, somehow styling that slide: 67 | ```typ 68 | (dictionary, array of content) => content 69 | ``` 70 | We call these functions the _variants_ of the theme. 71 | You can define an arbitrary amount of them but one must be stored under the key 72 | `"default"` in your theme. 73 | 74 | The meta data referred to above are a dictionary containing any extra keyword 75 | arguments that the user has specified in their call to `#slide`. 76 | As a theme author, you should inform your users what kind of arguments you will 77 | respect. 78 | You should access that data defensively and not expect that users provide any 79 | of the extra arguments you can consume. 80 | Similarly, you should not expect that the meta data exclusively contains keys 81 | you respect. 82 | For example, if a user states 83 | ```typ 84 | #slide(funny-kwarg: "some value", theme-variant: "a variant", something-else: "wohoo")[ 85 | ... 86 | ] 87 | ``` 88 | then your styling function will be provided the following dictionary: 89 | ```typ 90 | ( 91 | funny-kwarg: "some value", 92 | something-else: "wohoo", 93 | ) 94 | ``` 95 | especially not containing a `theme-variant` item! 96 | 97 | The `#slide` function accepts an arbitrary amount of positional arguments that 98 | are interpreted as content for the slide. 99 | The user of this template can provide them by simply juxtapositioning multiple 100 | content blocks after `#slide` or `#slide(...)`, see 101 | [this section](./slide.html#slides-with-multiple-content-bodies). 102 | Often, it will make sense to work with only a single piece of content, 103 | however. 104 | In any case, your variant function will always be provided an array of contents 105 | as the second argument, so you will usually have to extract that single entry 106 | you are interested in (using `.at(0)` or `.first()`). 107 | It might also make sense to `panic` with an informative error message if the user 108 | provided another number of content blocks than you expected. 109 | 110 | 111 | To make use of some of the functionality this template offers, you can access 112 | - the counter `logical-slide`, telling you the "page number" of a slide, but 113 | without counting up on dynamical slides that produce multiple PDF pages; 114 | - the state `section`, telling you what the user has currently set as the 115 | section name. 116 | 117 | Let us define a few variants variants. 118 | You should probably add a variant for a title slide to your theme. 119 | By convention, this variant is called `"title slide"`. 120 | ```typ 121 | #let dumb-theme = data => { 122 | ( 123 | "title slide": (slide-info, bodies) => align(center + horizon, data.title), 124 | "variant 2": ..., 125 | ) 126 | } 127 | ``` 128 | As noted above, one of the variants must have the name `"default"`. 129 | 130 | ```typ 131 | #let dumb-theme = data => { 132 | ( 133 | "title slide": (slide-info, bodies) => align(center + horizon, data.title), 134 | "default": (slide-info, bodies) => { 135 | text(.7em, [#slide-info.title (current section: #section.display())]) 136 | v(1fr) 137 | for body in bodies { 138 | body 139 | v(1em) 140 | } 141 | v(1fr) 142 | text(.7em, [#h(1fr) #logical-slide.display()]) 143 | }, 144 | "australia": (slide-info, bodies) => { 145 | if bodies.len() != 1 { 146 | panic("australia variant expected exactly one body") 147 | } 148 | text(.7em, [current section: #section.display()]) 149 | v(1fr) 150 | scale(y: -100%, bodies.first()) 151 | v(1fr) 152 | text(.7em, [#h(1fr) #logical-slide.display()]) 153 | }, 154 | ) 155 | } 156 | ``` 157 | We can see that the `"default"` variant expects that users provide a `title` 158 | keyword argument to `#slide`. 159 | (Note again that this is bad practise and you should provide some fallback 160 | behaviour if some arguments you expect are not provided.) 161 | The `"australia"` variant does not make use of the `slide-info` argument at all. 162 | That is completely fine, it must have this argument for formal reasons, anyway, 163 | though. 164 | 165 | Also, the `"default"` variant displays all the content blocks provided to `#slide` 166 | while the `"australia"` variant errors if more or less than one such block is 167 | given. 168 | 169 | The `"title slide"` variant uses neither the `slide-info` nor the `bodies`. 170 | That is a usual behaviour, as title slides normally only depend on the general 171 | information like author, presentation title, etc. 172 | 173 | And that's it already! 174 | Have fun creating your own theme and maybe consider opening a pull request 175 | at [the GitHub repository](https://github.com/andreasKroepelin/polylux) 176 | to share your creations! 177 | 178 | ## Per-slide escape hatch 179 | In some exceptional situations, the machinery of themes and theme variants might 180 | be too limited for what you need. 181 | Sometimes, you might find yourself needing a very special purpose design _just_ 182 | for a couple of slides. 183 | For that case, you can use the `override-theme` argument of the `#slide` function. 184 | It accepts a function of the form `(slide-info, bodies) => [slide content]`, just 185 | as if you would define a theme variant (see details above). 186 | 187 | For example, you might write something like this: 188 | ```typ 189 | #let special-purpose-theme(slide-info, bodies) = align(horizon)[ 190 | #rotate(45deg, heading(level: 2, slide-info.title)) 191 | #scale(x: -100%, bodies.first()) 192 | ] 193 | #slide(override-theme: special-purpose-theme, title: "This is rotated")[ 194 | #lorem(40) 195 | ] 196 | ``` 197 | 198 | If you feel like your use of this feature becomes more than exceptional, consider 199 | contributing a variant to the theme you use, or a new theme entirely. -------------------------------------------------------------------------------- /logic.typ: -------------------------------------------------------------------------------- 1 | #let subslide = counter("subslide") 2 | #let pause-counter = counter("pause-counter") 3 | #let logical-slide = counter("logical-slide") 4 | #let repetitions = counter("repetitions") 5 | #let handout-mode = state("handout-mode", false) 6 | 7 | #let enable-handout-mode(flag) = handout-mode.update(flag) 8 | 9 | #let _slides-cover(mode, body) = { 10 | if mode == "invisible" { 11 | hide(body) 12 | } else if mode == "transparent" { 13 | text(gray.lighten(50%), body) 14 | } else { 15 | panic("Illegal cover mode: " + mode) 16 | } 17 | } 18 | 19 | #let _parse-subslide-indices(s) = { 20 | let parts = s.split(",").map(p => p.trim()) 21 | let parse-part(part) = { 22 | let match-until = part.match(regex("^-([[:digit:]]+)$")) 23 | let match-beginning = part.match(regex("^([[:digit:]]+)-$")) 24 | let match-range = part.match(regex("^([[:digit:]]+)-([[:digit:]]+)$")) 25 | let match-single = part.match(regex("^([[:digit:]]+)$")) 26 | if match-until != none { 27 | let parsed = int(match-until.captures.first()) 28 | // assert(parsed > 0, "parsed idx is non-positive") 29 | ( until: parsed ) 30 | } else if match-beginning != none { 31 | let parsed = int(match-beginning.captures.first()) 32 | // assert(parsed > 0, "parsed idx is non-positive") 33 | ( beginning: parsed ) 34 | } else if match-range != none { 35 | let parsed-first = int(match-range.captures.first()) 36 | let parsed-last = int(match-range.captures.last()) 37 | // assert(parsed-first > 0, "parsed idx is non-positive") 38 | // assert(parsed-last > 0, "parsed idx is non-positive") 39 | ( beginning: parsed-first, until: parsed-last ) 40 | } else if match-single != none { 41 | let parsed = int(match-single.captures.first()) 42 | // assert(parsed > 0, "parsed idx is non-positive") 43 | parsed 44 | } else { 45 | panic("failed to parse visible slide idx:" + part) 46 | } 47 | } 48 | parts.map(parse-part) 49 | } 50 | 51 | #let _check-visible(idx, visible-subslides) = { 52 | if type(visible-subslides) == "integer" { 53 | idx == visible-subslides 54 | } else if type(visible-subslides) == "array" { 55 | visible-subslides.any(s => _check-visible(idx, s)) 56 | } else if type(visible-subslides) == "string" { 57 | let parts = _parse-subslide-indices(visible-subslides) 58 | _check-visible(idx, parts) 59 | } else if type(visible-subslides) == "dictionary" { 60 | let lower-okay = if "beginning" in visible-subslides { 61 | visible-subslides.beginning <= idx 62 | } else { 63 | true 64 | } 65 | 66 | let upper-okay = if "until" in visible-subslides { 67 | visible-subslides.until >= idx 68 | } else { 69 | true 70 | } 71 | 72 | lower-okay and upper-okay 73 | } else { 74 | panic("you may only provide a single integer, an array of integers, or a string") 75 | } 76 | } 77 | 78 | #let _last-required-subslide(visible-subslides) = { 79 | if type(visible-subslides) == "integer" { 80 | visible-subslides 81 | } else if type(visible-subslides) == "array" { 82 | calc.max(..visible-subslides.map(s => _last-required-subslide(s))) 83 | } else if type(visible-subslides) == "string" { 84 | let parts = _parse-subslide-indices(visible-subslides) 85 | _last-required-subslide(parts) 86 | } else if type(visible-subslides) == "dictionary" { 87 | let last = 0 88 | if "beginning" in visible-subslides { 89 | last = calc.max(last, visible-subslides.beginning) 90 | } 91 | if "until" in visible-subslides { 92 | last = calc.max(last, visible-subslides.until) 93 | } 94 | last 95 | } else { 96 | panic("you may only provide a single integer, an array of integers, or a string") 97 | } 98 | } 99 | 100 | #let _conditional-display(visible-subslides, reserve-space, mode, body) = { 101 | locate( loc => { 102 | let vs = if reserve-space and handout-mode.at(loc) { 103 | (:) 104 | } else { 105 | visible-subslides 106 | } 107 | repetitions.update(rep => calc.max(rep, _last-required-subslide(vs))) 108 | if _check-visible(subslide.at(loc).first(), vs) { 109 | body 110 | } else if reserve-space { 111 | _slides-cover(mode, body) 112 | } 113 | }) 114 | } 115 | 116 | #let uncover(visible-subslides, mode: "invisible", body) = { 117 | _conditional-display(visible-subslides, true, mode, body) 118 | } 119 | 120 | #let only(visible-subslides, body) = { 121 | _conditional-display(visible-subslides, false, "doesn't even matter", body) 122 | } 123 | 124 | #let one-by-one(start: 1, mode: "invisible", ..children) = { 125 | for (idx, child) in children.pos().enumerate() { 126 | uncover((beginning: start + idx), mode: mode, child) 127 | } 128 | } 129 | 130 | #let alternatives-match(subslides-contents, position: bottom + left) = { 131 | let subslides-contents = if type(subslides-contents) == "dictionary" { 132 | subslides-contents.pairs() 133 | } else { 134 | subslides-contents 135 | } 136 | 137 | let subslides = subslides-contents.map(it => it.first()) 138 | let contents = subslides-contents.map(it => it.last()) 139 | style(styles => { 140 | let sizes = contents.map(c => measure(c, styles)) 141 | let max-width = calc.max(..sizes.map(sz => sz.width)) 142 | let max-height = calc.max(..sizes.map(sz => sz.height)) 143 | for (subslides, content) in subslides-contents { 144 | only(subslides, box( 145 | width: max-width, 146 | height: max-height, 147 | align(position, content) 148 | )) 149 | } 150 | }) 151 | } 152 | 153 | #let alternatives( 154 | start: 1, 155 | repeat-last: false, 156 | ..args 157 | ) = { 158 | let contents = args.pos() 159 | let kwargs = args.named() 160 | let subslides = range(start, start + contents.len()) 161 | if repeat-last { 162 | subslides.last() = (beginning: subslides.last()) 163 | } 164 | alternatives-match(subslides.zip(contents), ..kwargs) 165 | } 166 | 167 | #let alternatives-fn( 168 | start: 1, 169 | end: none, 170 | count: none, 171 | ..kwargs, 172 | fn 173 | ) = { 174 | let end = if end == none { 175 | if count == none { 176 | panic("You must specify either end or count.") 177 | } else { 178 | start + count 179 | } 180 | } else { 181 | end 182 | } 183 | 184 | let subslides = range(start, end) 185 | let contents = subslides.map(fn) 186 | alternatives-match(subslides.zip(contents), ..kwargs.named()) 187 | } 188 | 189 | #let alternatives-cases(cases, fn, ..kwargs) = { 190 | let idcs = range(cases.len()) 191 | let contents = idcs.map(fn) 192 | alternatives-match(cases.zip(contents), ..kwargs.named()) 193 | } 194 | 195 | #let line-by-line(start: 1, mode: "invisible", body) = { 196 | let items = if repr(body.func()) == "sequence" { 197 | body.children 198 | } else { 199 | ( body, ) 200 | } 201 | 202 | let idx = start 203 | for item in items { 204 | if repr(item.func()) != "space" { 205 | uncover((beginning: idx), mode: mode, item) 206 | idx += 1 207 | } else { 208 | item 209 | } 210 | } 211 | } 212 | 213 | 214 | #let _items-one-by-one(fn, start: 1, mode: "invisible", ..args) = { 215 | let kwargs = args.named() 216 | let items = args.pos() 217 | let covered-items = items.enumerate().map( 218 | ((idx, item)) => uncover((beginning: idx + start), mode: mode, item) 219 | ) 220 | fn( 221 | ..kwargs, 222 | ..covered-items 223 | ) 224 | } 225 | 226 | #let list-one-by-one(start: 1, mode: "invisible", ..args) = { 227 | _items-one-by-one(list, start: start, mode: mode, ..args) 228 | } 229 | 230 | #let enum-one-by-one(start: 1, mode: "invisible", ..args) = { 231 | _items-one-by-one(enum, start: start, mode: mode, ..args) 232 | } 233 | 234 | #let terms-one-by-one(start: 1, mode: "invisible", ..args) = { 235 | let kwargs = args.named() 236 | let items = args.pos() 237 | let covered-items = items.enumerate().map( 238 | ((idx, item)) => terms.item( 239 | item.term, 240 | uncover((beginning: idx + start), mode: mode, item.description) 241 | ) 242 | ) 243 | terms( 244 | ..kwargs, 245 | ..covered-items 246 | ) 247 | } 248 | 249 | #let pause = { 250 | // We need two separate `locate`s because `repetitions` needs to be updated 251 | // using the new value of `pause-counter`. 252 | locate( loc => { 253 | if not handout-mode.at(loc) { 254 | pause-counter.step() 255 | } 256 | }) 257 | locate( loc => { 258 | repetitions.update(rep => calc.max(rep, pause-counter.at(loc).first() + 1)) 259 | }) 260 | } 261 | 262 | #let paused-content(body) = locate( loc => { 263 | let current-subslide = subslide.at(loc).first() 264 | let current-pause-counter = pause-counter.at(loc).first() 265 | 266 | if current-subslide > current-pause-counter { 267 | body 268 | } else { 269 | hide(body) 270 | } 271 | }) 272 | 273 | #let polylux-slide(body) = { 274 | locate( loc => { 275 | if logical-slide.at(loc).first() > 0 { 276 | pagebreak(weak: true) 277 | } 278 | }) 279 | logical-slide.step() 280 | subslide.update(1) 281 | repetitions.update(1) 282 | pause-counter.update(0) 283 | 284 | // Having this here is a bit unfortunate concerning separation of concerns 285 | // but I'm not comfortable with logic depending on pdfpc... 286 | let pdfpc-slide-markers(curr-subslide) = locate( loc => [ 287 | #metadata((t: "NewSlide")) 288 | #metadata((t: "Idx", v: counter(page).at(loc).first() - 1)) 289 | #metadata((t: "Overlay", v: curr-subslide - 1)) 290 | #metadata((t: "LogicalSlide", v: logical-slide.at(loc).first())) 291 | ]) 292 | 293 | pdfpc-slide-markers(1) 294 | 295 | body 296 | 297 | subslide.step() 298 | set heading(outlined: false) 299 | 300 | locate( loc => { 301 | let reps = repetitions.at(loc).first() 302 | for curr-subslide in range(2, reps + 1) { 303 | pause-counter.update(0) 304 | pagebreak(weak: true) 305 | 306 | pdfpc-slide-markers(curr-subslide) 307 | 308 | body 309 | subslide.step() 310 | } 311 | }) 312 | } 313 | -------------------------------------------------------------------------------- /pdfpc-extractor/src/main.rs: -------------------------------------------------------------------------------- 1 | use miette::{miette, IntoDiagnostic, WrapErr}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::{convert::TryFrom, process::Command}; 4 | 5 | #[derive(Deserialize)] 6 | #[serde(transparent)] 7 | struct TypstQueryOutput(Vec); 8 | 9 | #[derive(Debug, Deserialize)] 10 | #[serde(tag = "t", content = "v")] 11 | enum QueryItem { 12 | Duration(u32), 13 | StartTime(String), 14 | EndTime(String), 15 | LastMinutes(u32), 16 | DisableMarkdown(bool), 17 | NoteFontSize(u32), 18 | DefaultTransition(String), 19 | 20 | NewSlide, 21 | Idx(u32), 22 | LogicalSlide(u32), 23 | Overlay(u32), 24 | Note(String), 25 | EndSlide, 26 | SaveSlide, 27 | HiddenSlide, 28 | } 29 | 30 | #[derive(Serialize)] 31 | #[serde(rename_all = "camelCase")] 32 | struct Page { 33 | idx: u32, 34 | #[serde(rename = "label")] 35 | logical_slide: u32, 36 | overlay: u32, 37 | forced_overlay: bool, 38 | hidden: bool, 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | note: Option, 41 | #[serde(skip)] 42 | end: bool, 43 | #[serde(skip)] 44 | saved: bool, 45 | } 46 | 47 | impl<'a> TryFrom<&'a [QueryItem]> for Page { 48 | type Error = miette::Report; 49 | 50 | fn try_from(items: &'a [QueryItem]) -> miette::Result { 51 | use QueryItem::*; 52 | Ok(Page { 53 | idx: items 54 | .iter() 55 | .find_map(|item| if let Idx(idx) = item { Some(idx) } else { None }) 56 | .cloned() 57 | .ok_or_else(|| miette!("Page has no idx."))?, 58 | logical_slide: items 59 | .iter() 60 | .find_map(|item| { 61 | if let LogicalSlide(logical_slide) = item { 62 | Some(logical_slide) 63 | } else { 64 | None 65 | } 66 | }) 67 | .cloned() 68 | .ok_or_else(|| miette!("Page has no label."))?, 69 | overlay: items 70 | .iter() 71 | .find_map(|item| { 72 | if let Overlay(overlay) = item { 73 | Some(overlay) 74 | } else { 75 | None 76 | } 77 | }) 78 | .cloned() 79 | .ok_or_else(|| miette!("Page has no overlay."))?, 80 | note: items 81 | .iter() 82 | .find_map(|item| { 83 | if let Note(note) = item { 84 | Some(note) 85 | } else { 86 | None 87 | } 88 | }) 89 | .cloned(), 90 | forced_overlay: items.iter().any(|item| match item { 91 | &Overlay(overlay) if overlay > 0 => true, 92 | _ => false, 93 | }), 94 | hidden: items.iter().any(|item| matches!(item, HiddenSlide)), 95 | end: items.iter().any(|item| matches!(item, EndSlide)), 96 | saved: items.iter().any(|item| matches!(item, SaveSlide)), 97 | }) 98 | } 99 | } 100 | 101 | #[derive(Serialize)] 102 | #[serde(rename_all = "camelCase")] 103 | struct PdfpcConfig { 104 | pdfpc_format: u32, 105 | /// Duration in minutes 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | duration: Option, 108 | #[serde(skip_serializing_if = "Option::is_none")] 109 | start_time: Option, 110 | #[serde(skip_serializing_if = "Option::is_none")] 111 | end_time: Option, 112 | #[serde(skip_serializing_if = "Option::is_none")] 113 | last_minutes: Option, 114 | disable_markdown: bool, 115 | #[serde(skip_serializing_if = "Option::is_none")] 116 | note_font_size: Option, 117 | #[serde(skip_serializing_if = "Option::is_none")] 118 | default_transition: Option, 119 | } 120 | 121 | impl<'a> TryFrom<&'a [QueryItem]> for PdfpcConfig { 122 | type Error = miette::Report; 123 | 124 | fn try_from(items: &'a [QueryItem]) -> miette::Result { 125 | use QueryItem::*; 126 | Ok(PdfpcConfig { 127 | pdfpc_format: 2, 128 | duration: items 129 | .iter() 130 | .find_map(|item| { 131 | if let Duration(duration) = item { 132 | Some(duration) 133 | } else { 134 | None 135 | } 136 | }) 137 | .cloned(), 138 | start_time: items 139 | .iter() 140 | .find_map(|item| { 141 | if let StartTime(start_time) = item { 142 | Some(start_time) 143 | } else { 144 | None 145 | } 146 | }) 147 | .cloned(), 148 | end_time: items 149 | .iter() 150 | .find_map(|item| { 151 | if let EndTime(end_time) = item { 152 | Some(end_time) 153 | } else { 154 | None 155 | } 156 | }) 157 | .cloned(), 158 | last_minutes: items 159 | .iter() 160 | .find_map(|item| { 161 | if let LastMinutes(last_minutes) = item { 162 | Some(last_minutes) 163 | } else { 164 | None 165 | } 166 | }) 167 | .cloned(), 168 | note_font_size: items 169 | .iter() 170 | .find_map(|item| { 171 | if let NoteFontSize(note_font_size) = item { 172 | Some(note_font_size) 173 | } else { 174 | None 175 | } 176 | }) 177 | .cloned(), 178 | default_transition: items 179 | .iter() 180 | .find_map(|item| { 181 | if let DefaultTransition(default_transition) = item { 182 | Some(default_transition) 183 | } else { 184 | None 185 | } 186 | }) 187 | .cloned(), 188 | disable_markdown: items 189 | .iter() 190 | .any(|item| matches!(item, DisableMarkdown(true))), 191 | }) 192 | } 193 | } 194 | 195 | #[derive(Serialize)] 196 | #[serde(rename_all = "camelCase")] 197 | struct Pdfpc { 198 | #[serde(flatten)] 199 | config: PdfpcConfig, 200 | #[serde(skip_serializing_if = "Option::is_none")] 201 | end_slide: Option, 202 | #[serde(skip_serializing_if = "Option::is_none")] 203 | saved_slide: Option, 204 | pages: Vec, 205 | } 206 | 207 | impl TryFrom for Pdfpc { 208 | type Error = miette::Report; 209 | 210 | fn try_from(output: TypstQueryOutput) -> miette::Result { 211 | let mut split_output = output.0.split(|item| matches!(item, QueryItem::NewSlide)); 212 | let config_items = split_output 213 | .next() 214 | .ok_or_else(|| miette!("There are no slides."))?; 215 | let config = PdfpcConfig::try_from(config_items) 216 | .wrap_err("Failed to aggregate pdfpc configuration data.")?; 217 | let pages = split_output 218 | .map(Page::try_from) 219 | .collect::, _>>() 220 | .wrap_err("Failed to aggregate page information.")?; 221 | 222 | Ok(Pdfpc { 223 | config, 224 | end_slide: pages.iter().find_map(|page| { 225 | if page.end { 226 | Some(page.logical_slide - 1) 227 | } else { 228 | None 229 | } 230 | }), 231 | saved_slide: pages.iter().find_map(|page| { 232 | if page.saved { 233 | Some(page.logical_slide - 1) 234 | } else { 235 | None 236 | } 237 | }), 238 | pages, 239 | }) 240 | } 241 | } 242 | 243 | fn main() -> miette::Result<()> { 244 | let args: Vec<_> = std::env::args().skip(1).collect(); 245 | let filename = args 246 | .iter() 247 | .find(|arg| arg.ends_with(".typ")) 248 | .cloned() 249 | .ok_or_else(|| miette!("No .typ file provided."))?; 250 | 251 | // dbg!(filename); 252 | 253 | let query_output = Command::new("typst") 254 | .arg("query") 255 | .args(args) 256 | .arg("--field") 257 | .arg("value") 258 | .arg("") 259 | .output() 260 | .into_diagnostic() 261 | .wrap_err("typst query failed.")?; 262 | let query_str = String::from_utf8(query_output.stdout) 263 | .into_diagnostic() 264 | .wrap_err("typst query produced invalid UTF-8 data.")?; 265 | 266 | if query_str.is_empty() { 267 | let query_errstr = String::from_utf8(query_output.stderr) 268 | .into_diagnostic() 269 | .wrap_err("typst query produced invalid UTF-8 on stderr.")?; 270 | miette::bail!(miette::diagnostic!( 271 | help = query_errstr, 272 | "typst query did not produce any output." 273 | )) 274 | } 275 | 276 | let query: TypstQueryOutput = serde_json::from_str(&query_str) 277 | .into_diagnostic() 278 | .wrap_err("Failed to parse JSON produced by typst query.")?; 279 | 280 | let pdfpc = Pdfpc::try_from(query).wrap_err("Failed to construct pdfpc data")?; 281 | 282 | let output = serde_json::to_string(&pdfpc) 283 | .into_diagnostic() 284 | .wrap_err("Failed to create pdfpc JSON.")?; 285 | let outfile = std::path::Path::new(&filename).with_extension("pdfpc"); 286 | std::fs::write(outfile, output) 287 | .into_diagnostic() 288 | .wrap_err("Failed to write pdfpc JSON to file.")?; 289 | Ok(()) 290 | } 291 | -------------------------------------------------------------------------------- /examples/demo.typ: -------------------------------------------------------------------------------- 1 | #import "../polylux.typ": * 2 | #import themes.clean: * 3 | 4 | #show link: set text(blue) 5 | #set text(font: "Inria Sans") 6 | // #show heading: set text(font: "Vollkorn") 7 | #show raw: set text(font: "JuliaMono") 8 | 9 | #show: clean-theme.with( 10 | footer: [Andreas Kröpelin, July 2023], 11 | short-title: [Polylux demo], 12 | logo: image("../assets/logo.png"), 13 | ) 14 | 15 | #set text(size: 20pt) 16 | 17 | #title-slide( 18 | title: [`Polylux`: Easily creating slides in Typst], 19 | subtitle: "An overview over all the features", 20 | authors: "Andreas Kröpelin", 21 | date: "April 2023", 22 | ) 23 | 24 | #new-section-slide("Introduction") 25 | 26 | #slide(title: "About this presentation")[ 27 | This presentation is supposed to briefly showcase what you can do with this 28 | package. 29 | 30 | For a full documentation, read the 31 | #link("https://polylux.dev/book/")[online book]. 32 | ] 33 | 34 | #slide(title: "A title")[ 35 | Let's explore what we have here. 36 | 37 | On the top of this slide, you can see the slide title. 38 | 39 | We used the `title` argument of the `#slide` function for that: 40 | ```typ 41 | #slide(title: "First slide")[ 42 | ... 43 | ] 44 | ``` 45 | (This works because we utilise the `clean` theme; more on that later.) 46 | ] 47 | 48 | #slide[ 49 | Titles are not mandatory, this slide doesn't have one. 50 | 51 | But did you notice that the current section name is displayed above that 52 | top line? 53 | 54 | We defined it using 55 | #raw("#new-section-slide(\"Introduction\")", lang: "typst", block: false). 56 | 57 | This helps our audience with not getting lost after a microsleep. 58 | 59 | You can also spot a short title above that. 60 | ] 61 | 62 | #slide(title: "The bottom of the slide")[ 63 | Now, look down! 64 | 65 | There we have some general info for the audience about what talk they are 66 | actually attending right now. 67 | 68 | You can also see the slide number there. 69 | ] 70 | 71 | 72 | #new-section-slide("Dynamic content") 73 | 74 | 75 | #slide(title: [A dynamic slide with `pause`s])[ 76 | Sometimes we don't want to display everything at once. 77 | #pause 78 | 79 | That's what the `#pause` function is there for! 80 | #pause 81 | 82 | It makes everything after it appear at the next subslide. 83 | 84 | #text(.6em)[(Also note that the slide number does not change while we are here.)] 85 | ] 86 | 87 | #slide(title: "Fine-grained control")[ 88 | When `#pause` does not suffice, you can use more advanced commands to show 89 | or hide content. 90 | 91 | These are some of your options: 92 | - `#uncover` 93 | - `#only` 94 | - `#alternatives` 95 | - `#one-by-one` 96 | - `#line-by-line` 97 | 98 | Let's explore them in more detail! 99 | ] 100 | 101 | #let example(body) = block( 102 | width: 100%, 103 | inset: .5em, 104 | fill: aqua.lighten(80%), 105 | radius: .5em, 106 | text(size: .8em, body) 107 | ) 108 | 109 | #slide(title: [`#uncover`: Reserving space])[ 110 | With `#uncover`, content still occupies space, even when it is not displayed. 111 | 112 | For example, #uncover(2)[these words] are only visible on the second "subslide". 113 | 114 | In `()` behind `#uncover`, you specify _when_ to show the content, and in 115 | `[]` you then say _what_ to show: 116 | #example[ 117 | ```typ 118 | #uncover(3)[Only visible on the third "subslide"] 119 | ``` 120 | #uncover(3)[Only visible on the third "subslide"] 121 | ] 122 | ] 123 | 124 | #slide(title: "Complex display rules")[ 125 | So far, we only used single subslide indices to define when to show something. 126 | 127 | We can also use arrays of numbers... 128 | #example[ 129 | ```typ 130 | #uncover((1, 3, 4))[Visible on subslides 1, 3, and 4] 131 | ``` 132 | #uncover((1, 3, 4))[Visible on subslides 1, 3, and 4] 133 | ] 134 | 135 | ...or a dictionary with `beginning` and/or `until` keys: 136 | #example[ 137 | ```typ 138 | #uncover((beginning: 2, until: 4))[Visible on subslides 2, 3, and 4] 139 | ``` 140 | #uncover((beginning: 2, until: 4))[Visible on subslides 2, 3, and 4] 141 | ] 142 | ] 143 | 144 | #slide(title: "Convenient rules as strings")[ 145 | As as short hand option, you can also specify rules as strings in a special 146 | syntax. 147 | 148 | Comma separated, you can use rules of the form 149 | #table( 150 | columns: (auto, auto), 151 | column-gutter: 1em, 152 | stroke: none, 153 | align: (x, y) => (right, left).at(x), 154 | [`1-3`], [from subslide 1 to 3 (inclusive)], 155 | [`-4`], [all the time until subslide 4 (inclusive)], 156 | [`2-`], [from subslide 2 onwards], 157 | [`3`], [only on subslide 3], 158 | ) 159 | #example[ 160 | ```typ 161 | #uncover("-2, 4-6, 8-")[Visible on subslides 1, 2, 4, 5, 6, and from 8 onwards] 162 | ``` 163 | #uncover("-2, 4-6, 8-")[Visible on subslides 1, 2, 4, 5, 6, and from 8 onwards] 164 | ] 165 | ] 166 | 167 | #slide(title: [`#only`: Reserving no space])[ 168 | Everything that works with `#uncover` also works with `#only`. 169 | 170 | However, content is completely gone when it is not displayed. 171 | 172 | For example, #only(2)[#text(red)[see how]] the rest of this sentence moves. 173 | 174 | Again, you can use complex string rules, if you want. 175 | #example[ 176 | ```typ 177 | #only("2-4, 6")[Visible on subslides 2, 3, 4, and 6] 178 | ``` 179 | #only("2-4, 6")[Visible on subslides 2, 3, 4, and 6] 180 | ] 181 | ] 182 | 183 | #slide(title: [`#alternatives`: Substituting content])[ 184 | You might be tempted to try 185 | #example[ 186 | ```typ 187 | #only(1)[Ann] #only(2)[Bob] #only(3)[Christopher] likes #only(1)[chocolate] #only(2)[strawberry] #only(3)[vanilla] ice cream. 188 | ``` 189 | #only(1)[Ann] #only(2)[Bob] #only(3)[Christopher] 190 | likes 191 | #only(1)[chocolate] #only(2)[strawberry] #only(3)[vanilla] 192 | ice cream. 193 | ] 194 | 195 | But it is hard to see what piece of text actually changes because everything 196 | moves around. 197 | Better: 198 | #example[ 199 | ```typ 200 | #alternatives[Ann][Bob][Christopher] likes #alternatives[chocolate][strawberry][vanilla] ice cream. 201 | ``` 202 | #alternatives[Ann][Bob][Christopher] likes #alternatives[chocolate][strawberry][vanilla] ice cream. 203 | ] 204 | ] 205 | 206 | #slide(title: [`#one-by-one`: An alternative for `#pause`])[ 207 | `#alternatives` is to `#only` what `#one-by-one` is to `#uncover`. 208 | 209 | `#one-by-one` behaves similar to using `#pause` but you can additionally 210 | state when uncovering should start. 211 | #example[ 212 | ```typ 213 | #one-by-one(start: 2)[one ][by ][one] 214 | ``` 215 | #one-by-one(start: 2)[one ][by ][one] 216 | ] 217 | 218 | `start` can also be omitted, then it starts with the first subside: 219 | #example[ 220 | ```typ 221 | #one-by-one[one ][by ][one] 222 | ``` 223 | #one-by-one[one ][by ][one] 224 | ] 225 | ] 226 | 227 | #slide(title: [`#line-by-line`: syntactic sugar for `#one-by-one`])[ 228 | Sometimes it is convenient to write the different contents to uncover one 229 | at a time in subsequent lines. 230 | 231 | This comes in especially handy for bullet lists, enumerations, and term lists. 232 | #example[ 233 | #grid( 234 | columns: (1fr, 1fr), 235 | gutter: 1em, 236 | ```typ 237 | #line-by-line(start: 2)[ 238 | - first 239 | - second 240 | - third 241 | ] 242 | ```, 243 | line-by-line(start: 2)[ 244 | - first 245 | - second 246 | - third 247 | ] 248 | ) 249 | ] 250 | 251 | `start` is again optional and defaults to `1`. 252 | ] 253 | 254 | #slide(title: [`#list-one-by-one` and Co: when `#line-by-line` doesn't suffice])[ 255 | While `#line-by-line` is very convenient syntax-wise, it fails to produce 256 | more sophisticated bullet lists, enumerations or term lists. 257 | For example, non-tight lists are out of reach. 258 | 259 | For that reason, there are `#list-one-by-one`, `#enum-one-by-one`, and 260 | `#terms-one-by-one`, respectively. 261 | #example[ 262 | #grid( 263 | columns: (1fr, 1fr), 264 | gutter: 1em, 265 | ```typ 266 | #enum-one-by-one(start: 2, tight: false, numbering: "i)")[first][second][third] 267 | ```, 268 | enum-one-by-one(start: 2, tight: false, numbering: "i)")[first][second][third] 269 | ) 270 | ] 271 | 272 | Note that, for technical reasons, the bullet points, numbers, or terms are 273 | never covered. 274 | 275 | `start` is again optional and defaults to `1`. 276 | ] 277 | 278 | 279 | /* 280 | #slide(title: "Different ways of covering content")[ 281 | When content is covered, it is completely invisible by default. 282 | 283 | However, you can also just display it in light gray by using the 284 | `mode` argument with the value `"transparent"`: 285 | #let pc = 1 286 | #{ pc += 1 } #show: pause(pc, mode: "transparent") 287 | 288 | Covered content is then displayed differently. 289 | #{ pc += 1 } #show: pause(pc, mode: "transparent") 290 | 291 | Every `uncover`-based function has an optional `mode` argument: 292 | - `#show: pause(...)` 293 | - `#uncover(...)[...]` 294 | - `#one-by-one(...)[...][...]` 295 | - `#line-by-line(...)[...][...]` 296 | ] 297 | */ 298 | 299 | #new-section-slide("Themes") 300 | 301 | 302 | #slide(title: "How a slide looks...")[ 303 | ... is defined by the _theme_ of the presentation. 304 | 305 | This demo uses the `clean` theme. 306 | 307 | Because of it, the title slide and the decoration on each slide (with 308 | section name, short title, slide number etc.) look the way they do. 309 | 310 | Themes can also provide variants, for example ... 311 | ] 312 | 313 | #focus-slide[ 314 | ... this one! 315 | 316 | It's very minimalist and helps the audience focus on an important point. 317 | ] 318 | 319 | #slide(title: "Your own theme?")[ 320 | If you want to create your own design for slides, you can define custom 321 | themes! 322 | 323 | #link("https://polylux.dev/book/themes/your-own.html")[The book] 324 | explains how to do so. 325 | ] 326 | 327 | #new-section-slide("Utilities") 328 | 329 | #slide(title: [The `utils` module])[ 330 | Polylux ships a `utils` module with solutions for common tasks in slide 331 | building. 332 | ] 333 | 334 | #slide(title: [Fit to height])[ 335 | You can scale content such that it has a certain height using 336 | `#fit-to-height(height, content)`: 337 | 338 | #fit-to-height(2.5cm)[Height is `2.5cm`] 339 | ] 340 | 341 | #slide(title: "Fill remaining space")[ 342 | This function also allows you to fill the remaining space by using fractions 343 | as heights, i.e. `fit-to-height(1fr)[...]`: 344 | 345 | #fit-to-height(1fr)[Wow!] 346 | ] 347 | 348 | #slide(title: "Side by side content")[ 349 | Often you want to put different content next to each other. 350 | We have the function `#side-by-side` for that: 351 | 352 | #side-by-side(lorem(10), lorem(20), lorem(15)) 353 | ] 354 | 355 | #slide(title: "Outline")[ 356 | Why not include an outline? 357 | #polylux-outline(padding: 1em, enum-args: (tight: false)) 358 | ] 359 | 360 | #new-section-slide("Typst features") 361 | 362 | #slide(title: "Use Typst!")[ 363 | Typst gives us so many cool things #footnote[For example footnotes!]. 364 | Use them! 365 | ] 366 | 367 | #slide(title: "Bibliography")[ 368 | Let us cite something so we can have a bibliography: @A @B @C 369 | #bibliography(title: none, "literature.bib") 370 | ] 371 | 372 | #new-section-slide("Conclusion") 373 | 374 | #slide(title: "That's it!")[ 375 | Hopefully you now have some kind of idea what you can do with this template. 376 | 377 | Consider giving it 378 | #link("https://github.com/andreasKroepelin/polylux")[a GitHub star #text(font: "OpenMoji")[#emoji.star]] 379 | or open an issue if you run into bugs or have feature requests. 380 | ] -------------------------------------------------------------------------------- /pdfpc-extractor/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "backtrace" 22 | version = "0.3.69" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 25 | dependencies = [ 26 | "addr2line", 27 | "cc", 28 | "cfg-if", 29 | "libc", 30 | "miniz_oxide", 31 | "object", 32 | "rustc-demangle", 33 | ] 34 | 35 | [[package]] 36 | name = "backtrace-ext" 37 | version = "0.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" 40 | dependencies = [ 41 | "backtrace", 42 | ] 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "2.4.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 49 | 50 | [[package]] 51 | name = "cc" 52 | version = "1.0.83" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 55 | dependencies = [ 56 | "libc", 57 | ] 58 | 59 | [[package]] 60 | name = "cfg-if" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 64 | 65 | [[package]] 66 | name = "errno" 67 | version = "0.3.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" 70 | dependencies = [ 71 | "errno-dragonfly", 72 | "libc", 73 | "windows-sys", 74 | ] 75 | 76 | [[package]] 77 | name = "errno-dragonfly" 78 | version = "0.1.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 81 | dependencies = [ 82 | "cc", 83 | "libc", 84 | ] 85 | 86 | [[package]] 87 | name = "gimli" 88 | version = "0.28.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 91 | 92 | [[package]] 93 | name = "hermit-abi" 94 | version = "0.3.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 97 | 98 | [[package]] 99 | name = "is-terminal" 100 | version = "0.4.9" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 103 | dependencies = [ 104 | "hermit-abi", 105 | "rustix", 106 | "windows-sys", 107 | ] 108 | 109 | [[package]] 110 | name = "is_ci" 111 | version = "1.1.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" 114 | 115 | [[package]] 116 | name = "itoa" 117 | version = "1.0.9" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 120 | 121 | [[package]] 122 | name = "libc" 123 | version = "0.2.147" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 126 | 127 | [[package]] 128 | name = "linux-raw-sys" 129 | version = "0.4.5" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" 132 | 133 | [[package]] 134 | name = "memchr" 135 | version = "2.5.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 138 | 139 | [[package]] 140 | name = "miette" 141 | version = "5.10.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" 144 | dependencies = [ 145 | "backtrace", 146 | "backtrace-ext", 147 | "is-terminal", 148 | "miette-derive", 149 | "once_cell", 150 | "owo-colors", 151 | "supports-color", 152 | "supports-hyperlinks", 153 | "supports-unicode", 154 | "terminal_size", 155 | "textwrap", 156 | "thiserror", 157 | "unicode-width", 158 | ] 159 | 160 | [[package]] 161 | name = "miette-derive" 162 | version = "5.10.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" 165 | dependencies = [ 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "miniz_oxide" 173 | version = "0.7.1" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 176 | dependencies = [ 177 | "adler", 178 | ] 179 | 180 | [[package]] 181 | name = "object" 182 | version = "0.32.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" 185 | dependencies = [ 186 | "memchr", 187 | ] 188 | 189 | [[package]] 190 | name = "once_cell" 191 | version = "1.18.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 194 | 195 | [[package]] 196 | name = "owo-colors" 197 | version = "3.5.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 200 | 201 | [[package]] 202 | name = "polylux2pdfpc" 203 | version = "0.1.0" 204 | dependencies = [ 205 | "miette", 206 | "serde", 207 | "serde_json", 208 | ] 209 | 210 | [[package]] 211 | name = "proc-macro2" 212 | version = "1.0.66" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 215 | dependencies = [ 216 | "unicode-ident", 217 | ] 218 | 219 | [[package]] 220 | name = "quote" 221 | version = "1.0.33" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 224 | dependencies = [ 225 | "proc-macro2", 226 | ] 227 | 228 | [[package]] 229 | name = "rustc-demangle" 230 | version = "0.1.23" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 233 | 234 | [[package]] 235 | name = "rustix" 236 | version = "0.38.8" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" 239 | dependencies = [ 240 | "bitflags", 241 | "errno", 242 | "libc", 243 | "linux-raw-sys", 244 | "windows-sys", 245 | ] 246 | 247 | [[package]] 248 | name = "ryu" 249 | version = "1.0.15" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 252 | 253 | [[package]] 254 | name = "serde" 255 | version = "1.0.186" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" 258 | dependencies = [ 259 | "serde_derive", 260 | ] 261 | 262 | [[package]] 263 | name = "serde_derive" 264 | version = "1.0.186" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" 267 | dependencies = [ 268 | "proc-macro2", 269 | "quote", 270 | "syn", 271 | ] 272 | 273 | [[package]] 274 | name = "serde_json" 275 | version = "1.0.105" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 278 | dependencies = [ 279 | "itoa", 280 | "ryu", 281 | "serde", 282 | ] 283 | 284 | [[package]] 285 | name = "smawk" 286 | version = "0.3.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" 289 | 290 | [[package]] 291 | name = "supports-color" 292 | version = "2.0.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" 295 | dependencies = [ 296 | "is-terminal", 297 | "is_ci", 298 | ] 299 | 300 | [[package]] 301 | name = "supports-hyperlinks" 302 | version = "2.1.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" 305 | dependencies = [ 306 | "is-terminal", 307 | ] 308 | 309 | [[package]] 310 | name = "supports-unicode" 311 | version = "2.0.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7" 314 | dependencies = [ 315 | "is-terminal", 316 | ] 317 | 318 | [[package]] 319 | name = "syn" 320 | version = "2.0.29" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 323 | dependencies = [ 324 | "proc-macro2", 325 | "quote", 326 | "unicode-ident", 327 | ] 328 | 329 | [[package]] 330 | name = "terminal_size" 331 | version = "0.1.17" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 334 | dependencies = [ 335 | "libc", 336 | "winapi", 337 | ] 338 | 339 | [[package]] 340 | name = "textwrap" 341 | version = "0.15.2" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" 344 | dependencies = [ 345 | "smawk", 346 | "unicode-linebreak", 347 | "unicode-width", 348 | ] 349 | 350 | [[package]] 351 | name = "thiserror" 352 | version = "1.0.47" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 355 | dependencies = [ 356 | "thiserror-impl", 357 | ] 358 | 359 | [[package]] 360 | name = "thiserror-impl" 361 | version = "1.0.47" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 364 | dependencies = [ 365 | "proc-macro2", 366 | "quote", 367 | "syn", 368 | ] 369 | 370 | [[package]] 371 | name = "unicode-ident" 372 | version = "1.0.11" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 375 | 376 | [[package]] 377 | name = "unicode-linebreak" 378 | version = "0.1.5" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 381 | 382 | [[package]] 383 | name = "unicode-width" 384 | version = "0.1.10" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 387 | 388 | [[package]] 389 | name = "winapi" 390 | version = "0.3.9" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 393 | dependencies = [ 394 | "winapi-i686-pc-windows-gnu", 395 | "winapi-x86_64-pc-windows-gnu", 396 | ] 397 | 398 | [[package]] 399 | name = "winapi-i686-pc-windows-gnu" 400 | version = "0.4.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 403 | 404 | [[package]] 405 | name = "winapi-x86_64-pc-windows-gnu" 406 | version = "0.4.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 409 | 410 | [[package]] 411 | name = "windows-sys" 412 | version = "0.48.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 415 | dependencies = [ 416 | "windows-targets", 417 | ] 418 | 419 | [[package]] 420 | name = "windows-targets" 421 | version = "0.48.5" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 424 | dependencies = [ 425 | "windows_aarch64_gnullvm", 426 | "windows_aarch64_msvc", 427 | "windows_i686_gnu", 428 | "windows_i686_msvc", 429 | "windows_x86_64_gnu", 430 | "windows_x86_64_gnullvm", 431 | "windows_x86_64_msvc", 432 | ] 433 | 434 | [[package]] 435 | name = "windows_aarch64_gnullvm" 436 | version = "0.48.5" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 439 | 440 | [[package]] 441 | name = "windows_aarch64_msvc" 442 | version = "0.48.5" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 445 | 446 | [[package]] 447 | name = "windows_i686_gnu" 448 | version = "0.48.5" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 451 | 452 | [[package]] 453 | name = "windows_i686_msvc" 454 | version = "0.48.5" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 457 | 458 | [[package]] 459 | name = "windows_x86_64_gnu" 460 | version = "0.48.5" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 463 | 464 | [[package]] 465 | name = "windows_x86_64_gnullvm" 466 | version = "0.48.5" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 469 | 470 | [[package]] 471 | name = "windows_x86_64_msvc" 472 | version = "0.48.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 475 | --------------------------------------------------------------------------------