diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index 3840a3b..5b52d8c 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -7,7 +7,7 @@ name: Deploy mdBook site to Pages on: # Runs on pushes targeting the default branch push: - branches: ["main"] + branches: ["release"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/README.md b/README.md index 571f83d..62faa45 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Polylux This is a package for creating presentation slides in [Typst](https://typst.app/). +Read the [book](https://andreaskroepelin.github.io/polylux/book) to learn all +about it and click [here](https://andreaskroepelin.github.io/polylux/book/changelog.html) +to see what's new! If you like it, consider [giving a star on GitHub](https://github.com/andreasKroepelin/polylux)! @@ -14,8 +17,8 @@ If you like it, consider [giving a star on GitHub](https://github.com/andreasKro ## Quickstart For the bare-bones, do-it-yourself experience, all you need is: ```typ -// Get polylux from the official package repository -#import "@preview/polylux:0.2.0": * +// Get Polylux from the official package repository +#import "@preview/polylux:0.3.0": * // Make the paper dimensions fit for a presentation and the text larger #set page(paper: "presentation-16-9") @@ -54,7 +57,7 @@ or you can use one of the provided themes. The simplest one of them is called `simple` (what a coincidence!). It is still very unintrusive but gives you some sensible defaults: ```typ -#import "@preview/polylux:0.2.0": * +#import "@preview/polylux:0.3.0": * #import themes.simple: * @@ -95,9 +98,8 @@ It is still very unintrusive but gives you some sensible defaults: == Dynamic slide Did you know that... - #uncover(2)[ - ...you can see the current section at the top of the slide? - ] + #pause + ...you can see the current section at the top of the slide? ] ``` This time, we obtain these PDF pages: @@ -111,23 +113,30 @@ The book on how to use (and create your own) themes. -For dynamic content, polylux also provides [a convenient API for complex +For dynamic content, Polylux also provides [a convenient API for complex overlays](https://andreaskroepelin.github.io/polylux/book/dynamic/dynamic.html). +If you use [pdfpc](https://pdfpc.github.io/) to display your slides, you can rely +on [Polylux' support for it](https://andreaskroepelin.github.io/polylux/book/external/pdfpc.html) +and create speaker notes, hide slides, configure the timer and more! + Visit the [book](https://andreaskroepelin.github.io/polylux/book) for more details or take a look at the [demo PDF](https://github.com/andreasKroepelin/polylux/releases/latest/download/demo.pdf) where you can see the features of this template in action. -**⚠ This package is under active development. -While I try to make sure that the `main`-branch always is in a usable state, -there are no compatibility guarantees!** +**⚠ This package is under active development and there are no backwards +compatibility guarantees!** ## Acknowledgements Thank you to... - [@drupol](https://github.com/drupol) for the `university` theme - [@Enivex](https://github.com/Enivex) for the `metropolis` theme - [@MarkBlyth](https://github.com/MarkBlyth) for contributing to the `clean` theme -- [@fncnt](https://github.com/fncnt) for coming up with the name "polylux" +- [@ntjess](https://github.com/ntjess) for contributing to the height fitting + feature +- [@JuliusFreudenberger](https://github.com/JuliusFreudenberger) for maintaining + the `polylux2pdfpc` AUR package +- [@fncnt](https://github.com/fncnt) for coming up with the name "Polylux" - the Typst authors and contributors for this refreshing piece of software diff --git a/book/src/IMPORT.typ b/book/src/IMPORT.typ new file mode 100644 index 0000000..f6ddd73 --- /dev/null +++ b/book/src/IMPORT.typ @@ -0,0 +1 @@ +#import "@preview/polylux:0.3.0": * diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index e810371..8987306 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -8,8 +8,8 @@ - [General syntax](./dynamic/syntax.md) - [Complex rules](./dynamic/complex.md) - [Helper functions](./dynamic/helper.md) - - [one-by-one and line-by-line](./dynamic/obo-lbl.md) - [pause](./dynamic/pause.md) + - [one-by-one and friends](./dynamic/obo-lbl.md) - [alternatives](./dynamic/alternatives.md) - [Cover mode](./dynamic/cover.md) - [Handout mode](./dynamic/handout.md) @@ -22,4 +22,11 @@ - [University](./themes/gallery/university.md) - [Bipartite](./themes/gallery/bipartite.md) - [Build your own](./themes/your-own.md) - - [Helpers for theme authors](./themes/helpers.md) +- [Utilities](./utils/utils.md) + - [Side by side](./utils/side-by-side.md) + - [Fit to height](./utils/fit-to-height.md) + - [Progress](./utils/progress.md) + - [Sections](./utils/sections.md) +- [External tools](./external/external.md) + - [pdfpc](./external/pdfpc.md) +- [Changelog](./changelog.md) diff --git a/book/src/changelog.md b/book/src/changelog.md new file mode 100644 index 0000000..4fa0442 --- /dev/null +++ b/book/src/changelog.md @@ -0,0 +1,29 @@ +# Changelog + +## v0.3.0 + +- The previously existing module `helpers` was transformed to `utils` and now + contains many more useful features. +- The modules `logic` and `utils` are now directly accesible when importing + Polylux (it was a bug that it did not work previously). +- We finally have an ergonomic `#pause` function that does not expect the user + to keep track of some counter themselves. +- The `#alternatives` function has gained lots of friends that make specific + situations a bit more convenient, namely `#alternatives-match`, + `alternatives-cases`, and `alternatives-fn`. + Also, there is a parameter `repeat-last` for `#alternatives` now. +- Bullet lists, enumerations, and term lists now have custom functions to display + them dynamically: `#list-one-by-one`, `#enum-one-by-one`, and `#terms-one-by-one`. +- There is a new function `#fit-to-height` that allows you to resize content to + a given height (especially make it fill the remaining space on a slide!) + Thank you to [@ntjess](https://github.com/ntjess) for the initial implementation! +- Previously, certain themes allowed you to easily put multiple content elements + next to each other. + This is now a commonly available function: `#side-by-side`. + You can use it regardless of any theme and the functionality was removed from + the previously implementing themes. +- Polylux now has special support for the pdfpc presentation viewer. + You can add speaker notes, hide slides, configure the timer, and more all from + within your Typst source file. + Thank you to [@JuliusFreudenberger](https://github.com/JuliusFreudenberger) + for the inspiration and for creating the `polylux2pdfpc` AUR package. diff --git a/book/src/diy/diy.md b/book/src/diy/diy.md index a7545ab..68ae2f5 100644 --- a/book/src/diy/diy.md +++ b/book/src/diy/diy.md @@ -83,7 +83,7 @@ As a quick example, let's add a little quiz to our slides: What is the capital of the Republic of Benin? - #uncover(2)[Cotonou] + #uncover(2)[Porto-Novo] ] ``` ![quiz](quiz.png) @@ -94,7 +94,7 @@ The next sections will explain dynamic content in polylux in all its details. For reference, here is the full source code for the slides we developed in this section: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../IMPORT.typ}} #set page(paper: "presentation-16-9", fill: teal.lighten(90%)) #set text(size: 25pt, font: "Blogger Sans") @@ -118,7 +118,7 @@ section: What is the capital of the Republic of Benin? - #uncover(2)[Cotonou] + #uncover(2)[Porto-Novo] ] ``` diff --git a/book/src/diy/hello-world.png b/book/src/diy/hello-world.png index 687ec12..933b0ce 100644 Binary files a/book/src/diy/hello-world.png and b/book/src/diy/hello-world.png differ diff --git a/book/src/diy/quiz.png b/book/src/diy/quiz.png index 579c730..f6e8799 100644 Binary files a/book/src/diy/quiz.png and b/book/src/diy/quiz.png differ diff --git a/book/src/diy/quiz.typ b/book/src/diy/quiz.typ index 7480042..0d20b3b 100644 --- a/book/src/diy/quiz.typ +++ b/book/src/diy/quiz.typ @@ -22,5 +22,5 @@ What is the capital of the Republic of Benin? - #uncover(2)[Cotonou] + #uncover(2)[Porto-Novo] ] diff --git a/book/src/diy/slide-title.png b/book/src/diy/slide-title.png index bbd8af4..34d7459 100644 Binary files a/book/src/diy/slide-title.png and b/book/src/diy/slide-title.png differ diff --git a/book/src/diy/title-slide.png b/book/src/diy/title-slide.png index 031486a..ea11c3d 100644 Binary files a/book/src/diy/title-slide.png and b/book/src/diy/title-slide.png differ diff --git a/book/src/dynamic/alternatives-cases.png b/book/src/dynamic/alternatives-cases.png new file mode 100644 index 0000000..823b763 Binary files /dev/null and b/book/src/dynamic/alternatives-cases.png differ diff --git a/book/src/dynamic/alternatives-cases.typ b/book/src/dynamic/alternatives-cases.typ new file mode 100644 index 0000000..a30233a --- /dev/null +++ b/book/src/dynamic/alternatives-cases.typ @@ -0,0 +1,22 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 50pt) + +/* +#alternatives-match(( + "1, 3" : [ + Some text + ], + "2" : [ + #set text(fill: teal) + Some text + ], +)) +*/ + +#polylux-slide[ +#alternatives-cases(("1, 3", "2"), case => [ + #set text(fill: teal) if case == 1 + Some text +]) +] diff --git a/book/src/dynamic/alternatives-fn.png b/book/src/dynamic/alternatives-fn.png new file mode 100644 index 0000000..7252717 Binary files /dev/null and b/book/src/dynamic/alternatives-fn.png differ diff --git a/book/src/dynamic/alternatives-fn.typ b/book/src/dynamic/alternatives-fn.typ new file mode 100644 index 0000000..de6cfc8 --- /dev/null +++ b/book/src/dynamic/alternatives-fn.typ @@ -0,0 +1,9 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 60pt) + +#polylux-slide[ +#alternatives-fn(start: 2, count: 7, subslide => { + numbering("(i)", subslide) +}) +] diff --git a/book/src/dynamic/alternatives-match.png b/book/src/dynamic/alternatives-match.png new file mode 100644 index 0000000..ddb491a Binary files /dev/null and b/book/src/dynamic/alternatives-match.png differ diff --git a/book/src/dynamic/alternatives-match.typ b/book/src/dynamic/alternatives-match.typ new file mode 100644 index 0000000..53d2c40 --- /dev/null +++ b/book/src/dynamic/alternatives-match.typ @@ -0,0 +1,10 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 50pt) + +#polylux-slide[ +#alternatives-match(( + "1, 3-5": [this text has the majority], + "2, 6": [this is shown less often] +)) +] diff --git a/book/src/dynamic/alternatives-position.png b/book/src/dynamic/alternatives-position.png index ffeae1f..6682688 100644 Binary files a/book/src/dynamic/alternatives-position.png and b/book/src/dynamic/alternatives-position.png differ diff --git a/book/src/dynamic/alternatives-repeat-last.png b/book/src/dynamic/alternatives-repeat-last.png new file mode 100644 index 0000000..25a56e2 Binary files /dev/null and b/book/src/dynamic/alternatives-repeat-last.png differ diff --git a/book/src/dynamic/alternatives-repeat-last.typ b/book/src/dynamic/alternatives-repeat-last.typ new file mode 100644 index 0000000..177c356 --- /dev/null +++ b/book/src/dynamic/alternatives-repeat-last.typ @@ -0,0 +1,9 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 50pt) + +#polylux-slide[ +#alternatives(repeat-last: true)[temporary][transitory][ephemeral][permanent!] + +#uncover(5)[Did I miss something?] +] diff --git a/book/src/dynamic/alternatives.md b/book/src/dynamic/alternatives.md index 6292ada..3801d0d 100644 --- a/book/src/dynamic/alternatives.md +++ b/book/src/dynamic/alternatives.md @@ -1,6 +1,5 @@ # `#alternatives` to substitute content -The so far discussed helpers `#one-by-one`, `#line-by-line`, and `pause` all -build upon `#uncover`. +The so far discussed helpers `#pause`, `#one-by-one` etc. all build upon `#uncover`. There is an analogon to `#one-by-one` that is based on `#only`, namely `#alternatives`. You can use it to show some content on one subslide, then substitute it by @@ -36,6 +35,19 @@ it uses the same amount of space as `Christopher`. In a sense, it is like a mix of `#only` and `#uncover` with some reserving of space. +### Repeat last content +In case you have other dynamic content on a slide that happens after the contents +of `#alternatives` are exhausted, you might want to not have the `#alternatives` +element disappear but instead continue to show its last content argument. +To achieve this, you can use the `repeat-last` parameter: +```typ +{{#include alternatives-repeat-last.typ:6:9}} +``` +resulting in + +![alternatives-repeat-last](alternatives-repeat-last.png) + +### Positioning By default, all elements that enter an `#alternatives` command are aligned at the bottom left corner. This might not always be the desired or the most pleasant way to position it, so @@ -49,6 +61,62 @@ makes the mathematical terms look better positioned: ![alternatives-position](alternatives-position.png) -Similar to `#one-by-one` and `#line-by-line`, `#alternatives` also has an optional -`start` argument that works just the same as for the other two. +All functions described on this page have such a `position` argument. + +Similar to `#one-by-one`, `#alternatives` also has an optional `start` argument +that works just the same. + +## `#alternatives-match` +`#alternatives` has a couple of "cousins" that might be more convenient in some +situations. +The first one is `#alternatives-match` that has a name inspired by match-statements +in many functional programming languages. +The idea is that you give it a dictionary mapping from subslides to content: +```typ +{{#include alternatives-match.typ:6:9}} +``` +resulting in + +![alternatives-match](alternatives-match.png) + +**Note** that it is your responsibility to make sure that the subslide sets are +mutually disjoint. + +## `#alternatives-cases` +You can use this function if you want to have one piece of content that changes +only slightly depending of what "case" of subslides you are in. +So instead of +```typ +{{#include alternatives-cases.typ:6:14}} +``` +you can avoid duplication and write +```typ +{{#include alternatives-cases.typ:18:21}} +``` +using a function that maps the current "case" to content, resulting in + +![alternatives-cases](alternatives-cases.png) + +**Note** that the cases are 0-indexed (as are Typst arrays). + + +## `#alternatives-fn` +Finally, you can have very fine-grained control over the content depending on +the current subslide by using `#alternatives-fn`. +It accepts a function (hence the name) that maps the current subslide index to +some content. + +Similar to `#alternatives`, it accepts an optional `start` parameter that has a +default of `1`. +`#alternatives-fn` only knows for how long to display something, though, if you +provide either the number of subslides (`count` parameter) or the last subslide +index (`end` parameter). +So exactly one of them is necessary. + +For example: +```typ +{{#include alternatives-fn.typ:6:8}} +``` +resulting in +![alternatives-fn](alternatives-fn.png) diff --git a/book/src/dynamic/alternatives.png b/book/src/dynamic/alternatives.png index c93f837..34e0edb 100644 Binary files a/book/src/dynamic/alternatives.png and b/book/src/dynamic/alternatives.png differ diff --git a/book/src/dynamic/cover.md b/book/src/dynamic/cover.md index 18dbb10..cefd82a 100644 --- a/book/src/dynamic/cover.md +++ b/book/src/dynamic/cover.md @@ -1,6 +1,6 @@ # Cover mode -Covered content (using `#uncover`, `#one-by-one`, `#line-by-line`, or `pause`) -is completely invisible, by default. +Covered content (using `#uncover`, `#one-by-one`, `#line-by-line`, or +`#{list|enum|terms}-one-by-one`) is completely invisible, by default. You can decide to make it visible but less prominent using the optional `mode` argument to each of those functions. The `mode` argument takes two different values: `"invisible"` (the default) and @@ -10,7 +10,7 @@ With `mode: "transparent"`, text is printed in a light gray. Use it as follows: ```typ -{{#include cover.typ:6:16}} +{{#include cover.typ:6:15}} ``` resulting in diff --git a/book/src/dynamic/cover.png b/book/src/dynamic/cover.png index 397bc8f..fe47e02 100644 Binary files a/book/src/dynamic/cover.png and b/book/src/dynamic/cover.png differ diff --git a/book/src/dynamic/cover.typ b/book/src/dynamic/cover.typ index 9f3bba1..85fbefc 100644 --- a/book/src/dynamic/cover.typ +++ b/book/src/dynamic/cover.typ @@ -3,7 +3,7 @@ #set text(size: 30pt) #polylux-slide[ -#uncover("3-4", mode: "transparent")[abc] +#uncover(3, mode: "transparent")[abc] #one-by-one(start: 2, mode: "transparent")[def ][ghi] @@ -12,6 +12,5 @@ - mno ] -#show: pause(4, mode: "transparent") -pqr +#enum-one-by-one(mode: "transparent", tight: false)[pqr][stu][vwx] ] diff --git a/book/src/dynamic/enum-one-by-one.png b/book/src/dynamic/enum-one-by-one.png new file mode 100644 index 0000000..db08a22 Binary files /dev/null and b/book/src/dynamic/enum-one-by-one.png differ diff --git a/book/src/dynamic/enum-one-by-one.typ b/book/src/dynamic/enum-one-by-one.typ new file mode 100644 index 0000000..766ff3f --- /dev/null +++ b/book/src/dynamic/enum-one-by-one.typ @@ -0,0 +1,7 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 50pt) + +#polylux-slide[ +#enum-one-by-one(numbering: "i)", number-align: start)[first][second][third] +] diff --git a/book/src/dynamic/handout.png b/book/src/dynamic/handout.png index fb55995..6997906 100644 Binary files a/book/src/dynamic/handout.png and b/book/src/dynamic/handout.png differ diff --git a/book/src/dynamic/helper.md b/book/src/dynamic/helper.md index d15c0e1..2fe5c49 100644 --- a/book/src/dynamic/helper.md +++ b/book/src/dynamic/helper.md @@ -4,8 +4,8 @@ situations for which helper functions are provided. We call them "higher level" because they use `#only` and `#uncover` under the hood and operate on larger pieces of content. -For the common case of succesively revealing content, there are `#one-by-one`, -`#line-by-line`, and `pause`. -For substituting content, we have `#alternatives`. +For the common case of succesively revealing content, there are `#pause` and +`#one-by-one` and its friends. +For substituting content, we have `#alternatives` in different variants. The following sections will describe these functions in detail. diff --git a/book/src/dynamic/line-by-line.png b/book/src/dynamic/line-by-line.png index 75b381f..4031e64 100644 Binary files a/book/src/dynamic/line-by-line.png and b/book/src/dynamic/line-by-line.png differ diff --git a/book/src/dynamic/list-one-by-one.png b/book/src/dynamic/list-one-by-one.png new file mode 100644 index 0000000..e1219ee Binary files /dev/null and b/book/src/dynamic/list-one-by-one.png differ diff --git a/book/src/dynamic/list-one-by-one.typ b/book/src/dynamic/list-one-by-one.typ new file mode 100644 index 0000000..393bcb9 --- /dev/null +++ b/book/src/dynamic/list-one-by-one.typ @@ -0,0 +1,7 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 50pt) + +#polylux-slide[ +#list-one-by-one(marker: [--], tight: false)[first][second][third] +] diff --git a/book/src/dynamic/obo-lbl.md b/book/src/dynamic/obo-lbl.md index 2a7ec75..b795314 100644 --- a/book/src/dynamic/obo-lbl.md +++ b/book/src/dynamic/obo-lbl.md @@ -1,13 +1,9 @@ -# `#one-by-one` and `#line-by-line` -Consider some code like the following: -```typ -#uncover("1-")[first ] -#uncover("2-")[second ] -#uncover("3-")[third] -``` -The goal here is to uncover parts of the slide one by one, so that an increasing -amount of content is shown. -A shorter but equivalent way would be to write +# More sophisticated piecewise revealing +## `#one-by-one` +`#pause` may be considered syntactically a bit surprising by some although (or +because) it is very convenient to use. +If you prefer to signal the grouping of content appearing together syntactically +by using scopes, you can use `#one-by-one`: ```typ {{#include one-by-one.typ:6}} ``` @@ -15,15 +11,8 @@ resulting in ![one-by-one](one-by-one.png) -And what about this? -```typ -#uncover("3-")[first ] -#uncover("4-")[second ] -#uncover("5-")[third] -``` -Now, we still want to uncover certain elements one after the other but starting -on subslide 3. -We can use the optional `start` argument of `#one-by-one` for that: +If we still want to uncover certain elements one after the other but starting +on a later subslide, we can use the optional `start` argument of `#one-by-one`: ```typ {{#include one-by-one-start.typ:6}} ``` @@ -31,10 +20,16 @@ resulting in ![one-by-one-start](one-by-one-start.png) +This optional `start` argument exists for all functions displayed on this page. + + +## `#line-by-line` `#one-by-one` is especially useful for arbitrary contents that you want to display in that manner. -Often, you just want to do that with very simple elements, however. -A very frequent use case are bullet lists. +Sometimes, it produces a bit too much syntactical noise again with +all the brackets between content, though. +That is especially true if each piece fits into a single line, as for example +for a simple bullet list. Instead of ```typ #one-by-one[ @@ -57,6 +52,50 @@ The content provided as an argument to `#line-by-line` is parsed as a `sequence` by Typst with one element per line (hence the name of this function). We then simply iterate over that `sequence` as if it were given to `#one-by-one`. -Note that there also is an optional `start` argument for `#line-by-line`, which -works just the same as for `#one-by-one`. +## `#list-one-by-one` + +What if you want a more customized bullet list, though? +The code above produces a tight list, for example, and maybe you do not want that. +All your needs are covered by the `#list-one-by-one` function: +```typ +{{#include list-one-by-one.typ:6}} +``` +resulting in + +![list-one-by-one](list-one-by-one.png) + +As you can see, you can provide any arguments that the +[`list`](https://typst.app/docs/reference/layout/list/) function accepts. + +## `#enum-one-by-one` + +Analogously, there is the same thing for enums, accepting the same arguments as +[`enum`](https://typst.app/docs/reference/layout/enum/): +```typ +{{#include enum-one-by-one.typ:6}} +``` +resulting in + +![enum-one-by-one](enum-one-by-one.png) + +## `#terms-one-by-one` + +And finally we have a function to produce a +[term list](https://typst.app/docs/reference/layout/terms/): + +```typ +{{#include terms-one-by-one.typ:6}} +``` +resulting in + +![terms-one-by-one](terms-one-by-one.png) + +**Note** that `#list-one-by-one` and `#enum-one-by-one` expect only the body of +the individual items while you need to provide an actual term item (using the +`/ term: description` syntax) to `#terms-one-by-one`. +Also, you will realise that the bullet markers, the numbers, and the terms in +the lists, enums, and term lists are not hidden for technical reasons, respectively. +You can truly consider this either a bug or a feature... +(This could be "fixed" for enums and term lists, so file an issue on GitHub if +this bothers you a lot!) diff --git a/book/src/dynamic/one-by-one-start.png b/book/src/dynamic/one-by-one-start.png index d5a729b..9347f6e 100644 Binary files a/book/src/dynamic/one-by-one-start.png and b/book/src/dynamic/one-by-one-start.png differ diff --git a/book/src/dynamic/one-by-one-start.typ b/book/src/dynamic/one-by-one-start.typ index 371de49..5ea3ecf 100644 --- a/book/src/dynamic/one-by-one-start.typ +++ b/book/src/dynamic/one-by-one-start.typ @@ -3,5 +3,5 @@ #set text(size: 50pt) #polylux-slide[ -#one-by-one(start: 3)[first ][second ][third] +#one-by-one(start: 3)[This ][came ][pretty late.] ] diff --git a/book/src/dynamic/one-by-one.png b/book/src/dynamic/one-by-one.png index 39fdf0d..aa068b7 100644 Binary files a/book/src/dynamic/one-by-one.png and b/book/src/dynamic/one-by-one.png differ diff --git a/book/src/dynamic/one-by-one.typ b/book/src/dynamic/one-by-one.typ index 8d7b839..ac71dd5 100644 --- a/book/src/dynamic/one-by-one.typ +++ b/book/src/dynamic/one-by-one.typ @@ -3,5 +3,5 @@ #set text(size: 50pt) #polylux-slide[ -#one-by-one[first ][second ][third] +#one-by-one[Do you know ][$pi$ ][to a thousand decimal places?] ] diff --git a/book/src/dynamic/only-uncover.png b/book/src/dynamic/only-uncover.png index c736a52..389f167 100644 Binary files a/book/src/dynamic/only-uncover.png and b/book/src/dynamic/only-uncover.png differ diff --git a/book/src/dynamic/pause.md b/book/src/dynamic/pause.md index 4d6fe19..dedbf3a 100644 --- a/book/src/dynamic/pause.md +++ b/book/src/dynamic/pause.md @@ -1,49 +1,26 @@ -# `pause` as an alternative to `#one-by-one` -There is yet another way to solve the same problem as `#one-by-one`. +# `pause` to reveal content piece by piece +Consider some code like the following: +```typ +#uncover("1-")[first ] +#uncover("2-")[second ] +#uncover("3-")[third] +``` +The goal here is to uncover parts of the slide one by one, so that an increasing +amount of content is shown, but we don't want to specify all subslide indices +manually, ideally. + If you have used the LaTeX beamer package before, you might be familiar with the `\pause` command. It makes everything after it on that slide appear on the next subslide. - -Remember that the concept of "do something with everything after it" is covered -by the `#show: ...` mechanism in Typst. -We exploit that to use the `pause` function in the following way. +In Polylux, this works very similar with `#pause`, so we can equivalently write +the above code as: ```typ -{{#include pause.typ:6:12}} +{{#include pause.typ:6}} ``` -This would be equivalent to: -```typ -#one-by-one[ - Show this first. -][ - Show this later. -][ - Show this even later. -][ - That took aaaages! -] -``` -and results in +This results in ![pause](pause.png) -It is obvious that `pause` only brings an advantage over `#one-by-one` when you -want to distribute a lot of code onto different subslides. - -**Hint:** -You might be annoyed by having to manually number the pauses as in the code -above. -You can diminish that issue a bit by using a counter variable: -```typ -Show this first. -#let pc = 1 // `pc` for pause counter -#{ pc += 1 } #show: pause(pc) -Show this later. -#{ pc += 1 } #show: pause(pc) -Show this even later. -#{ pc += 1 } #show: pause(pc) -That took aaaages! -``` -This has the advantage that every `pause` line looks identical and you can move -them around arbitrarily. -In later versions of this template, there could be a nicer solution to this -issue, hopefully. +`#pause` should mainly be used when you want to distribute a lot of code onto +different subslides. +For smaller pieces, consider one of the functions described next. diff --git a/book/src/dynamic/pause.png b/book/src/dynamic/pause.png index ce75d70..319f2e8 100644 Binary files a/book/src/dynamic/pause.png and b/book/src/dynamic/pause.png differ diff --git a/book/src/dynamic/pause.typ b/book/src/dynamic/pause.typ index a1dcec9..cc02b19 100644 --- a/book/src/dynamic/pause.typ +++ b/book/src/dynamic/pause.typ @@ -3,11 +3,5 @@ #set text(size: 50pt) #polylux-slide[ -Show this first. -#show: pause(2) -Show this later. -#show: pause(3) -Show this even later. -#show: pause(4) -That took aaaages! +first #pause second #pause third ] diff --git a/book/src/dynamic/poor-alternatives.png b/book/src/dynamic/poor-alternatives.png index f66a4fc..beb75c3 100644 Binary files a/book/src/dynamic/poor-alternatives.png and b/book/src/dynamic/poor-alternatives.png differ diff --git a/book/src/dynamic/rule-array.png b/book/src/dynamic/rule-array.png index 05494e5..283bcc9 100644 Binary files a/book/src/dynamic/rule-array.png and b/book/src/dynamic/rule-array.png differ diff --git a/book/src/dynamic/rule-interval.png b/book/src/dynamic/rule-interval.png index 5725310..84c2b31 100644 Binary files a/book/src/dynamic/rule-interval.png and b/book/src/dynamic/rule-interval.png differ diff --git a/book/src/dynamic/rule-string.png b/book/src/dynamic/rule-string.png index 0239a0e..0159f71 100644 Binary files a/book/src/dynamic/rule-string.png and b/book/src/dynamic/rule-string.png differ diff --git a/book/src/dynamic/terms-one-by-one.png b/book/src/dynamic/terms-one-by-one.png new file mode 100644 index 0000000..cfa6641 Binary files /dev/null and b/book/src/dynamic/terms-one-by-one.png differ diff --git a/book/src/dynamic/terms-one-by-one.typ b/book/src/dynamic/terms-one-by-one.typ new file mode 100644 index 0000000..35d13e6 --- /dev/null +++ b/book/src/dynamic/terms-one-by-one.typ @@ -0,0 +1,7 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 50pt) + +#polylux-slide[ +#terms-one-by-one(separator: [~---~])[/ first: 1st][/ second: 2nd][/ third: 3rd] +] diff --git a/book/src/external/external.md b/book/src/external/external.md new file mode 100644 index 0000000..182dbf4 --- /dev/null +++ b/book/src/external/external.md @@ -0,0 +1,6 @@ +# External tools +Most users will only come across Typst itself and some off-the-shelf PDF viewer. +However, there are some additional tools that might come in handy and Polylux +supports some of their special needs. + +So far, this support is limited to the pdfpc presentation tool. diff --git a/book/src/external/pdfpc.md b/book/src/external/pdfpc.md new file mode 100644 index 0000000..67dde35 --- /dev/null +++ b/book/src/external/pdfpc.md @@ -0,0 +1,140 @@ +# pdfpc + +[pdfpc](https://pdfpc.github.io/) is a "presenter console with multi-monitor +support for PDF-files". +That means, you can use it to display slides in the form of PDF-pages and also +have some of the nice features known from, for example, PowerPoint. +Check out their website to learn more. + +When pdfpc is provided a special `.pdfpc` file containing some JSON data, it can +use that to enhance the user experience by correctly handling overlay slides, +displaying speaker notes, setting up a specific timer, and more. +While you can write this file by hand or use the pdfpc-internal features to edit +it, some might find it more convenient to have all data about their presentation +in one place, i.e. the Typst source file. +Polylux allows you to do that. + +## Adding metadata to the Typst source + +Polylux exports the `pdfpc` module that comes with a bunch of useful functions +that do not actually add any content to the produced PDF but instead insert +metadata that can later be extracted from the document. + +### Speaker notes +This is possibly the most useful feature of pdfpc. +Using the function `#pdfpc.speaker-note` inside a slide, you can add a note to +that slide that will only be visible to the speaker in pdfpc. +It accepts either a string: +```typ +#pdfpc.speaker-note("This is a note that only the speaker will see.") +``` +or a `raw` block: +````typ +#pdfpc.speaker-note( + ```md + # My notes + Did you know that pdfpc supports Markdown notes? _So cool!_ + ``` +) +```` +Note that you can only specify one note per slide (only the first one will +survive if you use more than one.) + +### End slide +Sometimes the last slide in your presentation is not really the one you want to +end with. +Say, you have some bibliography or appendix for the sake of completeness after +your "I thank my mom and everyone who believed in me"-slide. + +With a simple `pdfpc.end-slide` inside any slide you can tell pdfpc that this is +the last slide you usually want to show and hitting the `End` key will jump there. + +### Save a slide +Similarly, there is a feature in pdfpc to bookmark a specific slide (and you can +jump to it using `Shift + M`). +In your Typst source, you can choose that slide by putting `pdfpc.save-slide` +inside it. + +### Hide slides +If you want to keep a certain slide in your presentation (just in case) but don't +normally intend to show it, you can hide it inside pdfpc. +It will be skipped during the presentation but it is still available in the +overview. +You can use `pdfpc.hidden-slide` in your Typst source to mark a slide as hidden. + +### Configure pdfpc +The previous commands are all supposed to be used _inside_ a slide. +To perform some additional global configuration, you can use `pdfpc.config()` +_before_ any of the slides (it will not be recognised otherwise). + +It accepts the following optional keyword arguments: + +- `duration-minutes`: how many minutes (a number) the presentation is supposed + to take, affects the timer in pdfpc +- `start-time`: wall-clock time when the presentation is supposed to start, either + as a `datetime(hour: ..., minute: ..., second: ...)` or as a string in the + `HH:MM` format +- `end-time`: same as `start-time` but when the presentation is supposed to end +- `last-minutes`: how many minutes (a number) before the time runs out the timer + is supposed to change its colour as a warning +- `note-font-size`: the font size (a number) the speaker notes are displayed in +- `disable-markdown`: whether or not to disable rendering the notes as markdown + (a bool), default `false` +- `default-transition`: the transition to use between subsequent slides, must be + given as a dictionary with (potentially) the following keys: + - `type`: one of `"replace"` (default), `"push"`, `"blinds"`, `"box"`, + `"cover"`, `"dissolve"`, `"fade"`, `"glitter"`, `"split"`, `"uncover"`, + `"wipe"` + - `duration-seconds`: the duration of the transition in seconds (a number) + - `angle`: in which angle the transition moves, one of `ltr`, `rtl`, `ttb`, + and `btt` (see [the `#stack` function](https://typst.app/docs/reference/layout/stack/#parameters-dir)) + - `alignment`: whether the transition is performed horizontally or vertically, + one of `"horizontal"` and `"vertical"` + - `direction`: whether the transition is performed inward or outward, one of + `"inward"` and `"outward"` + + Not all combinations of values are necessary or make sense for all transitions, + of course. + +## Extracting the data: `polylux2pdfpc` +As mentioned above, the functions from the `pdfpc` module don't alter the produced +PDF itself. +Instead, we need some other way to extract their data. +You could, in principle, do that by hand using the `typst query` CLI and then +assemble the correct `.pdfpc` file yourself. +However, this tedious task is better solved by the `polylux2pdfpc` tool. + +### Installation +If you have [Rust](https://www.rust-lang.org/tools/install) installed, you can +simply run +```sh +cargo install --git https://github.com/andreasKroepelin/polylux/ --branch release +``` +If you use Arch Linux btw, you can also install `polylux2pdfpc` from the AUR +package [polylux2pdfpc-git](https://aur.archlinux.org/packages/polylux2pdfpc-git) +(thank you to Julius Freudenberger!) + +### Usage +You invoke `polylux2pdfpc` with the same arguments you would also give to `typst +compile` when you wanted to build your slides. +For example, say you have a file called `talk.typ` in the folder `thesis` that +has some global utility files or so, you +would compile it using +```sh +typst compile --root .. thesis/talk.typ +``` +and extract the pdfpc data using +```sh +polylux2pdfpc --root .. thesis/talk.typ +``` + +Internally, `polylux2pdfpc` runs `typst query`, collects all the pdfpc-related +metadata and then writes a `.pdfpc` file that equals the input file up to the +suffix. +In our example with `thesis/talk.typ`, we obtain `thesis/talk.pdfpc`. +Since `typst compile` produced `thesis/talk.pdf`, you can now simply open the PDF +in pdpfc: +```sh +pdfpc thesis/talk.pdf +``` +and it will automatically recognise the `.pdfpc` file. diff --git a/book/src/getting-started.md b/book/src/getting-started.md index 58c0dc5..53905e0 100644 --- a/book/src/getting-started.md +++ b/book/src/getting-started.md @@ -4,7 +4,7 @@ You can find this package in the [official Typst package repository](https://github.com/typst/packages). To use it, start your document with ```typ -#import "@preview/polylux:0.2.0": * +{{#include IMPORT.typ}} ``` You now have two options: 1. use the features of polylux but define every visual aspect yourself, diff --git a/book/src/minimal.png b/book/src/minimal.png index 9929192..215426e 100644 Binary files a/book/src/minimal.png and b/book/src/minimal.png differ diff --git a/book/src/polylux.md b/book/src/polylux.md index e0172af..0331428 100644 --- a/book/src/polylux.md +++ b/book/src/polylux.md @@ -29,6 +29,13 @@ The German term for projector is *beamer*, and now you might understand how it all comes together. (The original author of the aforementioned LaTeX package is German as well.) +## About this book +This book documents all features currently implemented in Polylux. +Specifically, it describes the state of the package as it is pulished to the +Typst package registry. +The `main` branch of the Polylux repository may contain features not documented +here. + ## Contributing This package is free and open source. You can find the code on [GitHub](https://github.com/andreasKroepelin/polylux) diff --git a/book/src/themes/gallery/bipartite.md b/book/src/themes/gallery/bipartite.md index 9bada32..2382b6f 100644 --- a/book/src/themes/gallery/bipartite.md +++ b/book/src/themes/gallery/bipartite.md @@ -10,7 +10,7 @@ rather on the "artsy" than functional side. Use it via ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} #import themes.bipartite: * #show: bipartite-theme.with(...) @@ -84,6 +84,6 @@ Does not display a slide title. ## Example code The image at the top is created by the following code: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} {{#include bipartite.typ:3:}} ``` diff --git a/book/src/themes/gallery/bipartite.png b/book/src/themes/gallery/bipartite.png index 4199421..5cc92cf 100644 Binary files a/book/src/themes/gallery/bipartite.png and b/book/src/themes/gallery/bipartite.png differ diff --git a/book/src/themes/gallery/clean.md b/book/src/themes/gallery/clean.md index c1f1253..bf6d852 100644 --- a/book/src/themes/gallery/clean.md +++ b/book/src/themes/gallery/clean.md @@ -7,7 +7,7 @@ to be an off-the-shelf solution that fits many use cases. Use it via ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} #import themes.clean: * #show: clean-theme.with(...) @@ -56,25 +56,16 @@ Does not accept additional content. ```typ #slide(...)[ ... -][ - ... ] ``` Decorates the provided content with a header containing the current section (if any), the short title of the presentation, and the logo; and a footer containing some custom text and the slide number. -Accepts an arbitrary amount of content blocks, they are placed next to each other -as columns. -Configure using the `columns` and `gutter` keyword arguments. - Pass the slide title as a keyword argument `title`. Accepts the following keyword arguments: - `title`: title of the slide, default: `none`, -- `columns`: propagated to `grid` for placing the body columns, default: array - filled with as many `1fr` as there are content blocks -- `gutter`: propagated to `grid` for placing the body columns, default: `1em` --- @@ -104,6 +95,6 @@ Use `#polylux-outline()` to display all sections, similarly to how you would use ## Example code The image at the top is created by the following code: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} {{#include clean.typ:3:}} ``` diff --git a/book/src/themes/gallery/clean.png b/book/src/themes/gallery/clean.png index ef425af..950a463 100644 Binary files a/book/src/themes/gallery/clean.png and b/book/src/themes/gallery/clean.png differ diff --git a/book/src/themes/gallery/clean.typ b/book/src/themes/gallery/clean.typ index a748c2d..bba4b9f 100644 --- a/book/src/themes/gallery/clean.typ +++ b/book/src/themes/gallery/clean.typ @@ -24,12 +24,12 @@ #new-section-slide("The new section") -#slide(title: [Slide with multiple columns])[ - #lorem(20) -][ - #lorem(10) -][ - #lorem(30) +#slide(title: "Another slide")[ + Note that you can see the section title at the top. + + The rest of this slide will fill more than one page! + + #lorem(100) ] #focus-slide[ diff --git a/book/src/themes/gallery/metropolis.md b/book/src/themes/gallery/metropolis.md index 3761725..9eb11c4 100644 --- a/book/src/themes/gallery/metropolis.md +++ b/book/src/themes/gallery/metropolis.md @@ -8,7 +8,7 @@ created by Matthias Vogelgesang. Use it via ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} #import themes.metropolis: * #show: metropolis-theme.with(...) @@ -93,6 +93,6 @@ displays a table of contents with all sections. ## Example code The image at the top is created by the following code: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} {{#include metropolis.typ:3:}} ``` diff --git a/book/src/themes/gallery/metropolis.png b/book/src/themes/gallery/metropolis.png index d81e3a9..3932cf0 100644 Binary files a/book/src/themes/gallery/metropolis.png and b/book/src/themes/gallery/metropolis.png differ diff --git a/book/src/themes/gallery/simple.md b/book/src/themes/gallery/simple.md index f73019c..4f9f077 100644 --- a/book/src/themes/gallery/simple.md +++ b/book/src/themes/gallery/simple.md @@ -8,7 +8,7 @@ freely. Use it via ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} #import themes.simple: * #show: simple-theme.with(...) @@ -81,6 +81,6 @@ Not suitable for content that exceeds one page. ## Example code The image at the top is created by the following code: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} {{#include simple.typ:3:}} ``` diff --git a/book/src/themes/gallery/simple.png b/book/src/themes/gallery/simple.png index b8a5968..5790a29 100644 Binary files a/book/src/themes/gallery/simple.png and b/book/src/themes/gallery/simple.png differ diff --git a/book/src/themes/gallery/simple.typ b/book/src/themes/gallery/simple.typ index b916d55..ecd5b6c 100644 --- a/book/src/themes/gallery/simple.typ +++ b/book/src/themes/gallery/simple.typ @@ -39,7 +39,6 @@ == Dynamic slide Did you know that... - #uncover(2)[ - ...you can see the current section at the top of the slide? - ] + #pause + ...you can see the current section at the top of the slide? ] diff --git a/book/src/themes/gallery/university.md b/book/src/themes/gallery/university.md index a1c1b6c..ec2a1d4 100644 --- a/book/src/themes/gallery/university.md +++ b/book/src/themes/gallery/university.md @@ -5,11 +5,10 @@ This theme offers a simple yet versatile design, allowing for easy customization and flexibility. Additionally, it incorporates a progress bar at the top, which displays the current status of the presentation. -`university` also makes working with mulit-column content very easy. Use it via ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} #import themes.university: * #show: university-theme.with(...) @@ -39,7 +38,7 @@ Text is configured to have a base font size of 25 pt. regular sides, default: `true` ## Slide functions -`metropolis` provides the following custom slide functions: +`university` provides the following custom slide functions: ```typ #title-slide(...) @@ -63,8 +62,6 @@ Does not accept additional content. ```typ #slide(...)[ ... -][ - ... ] ``` Decorates the provided content with a header containing a progress bar (optionally), @@ -72,17 +69,10 @@ the slide title, and the current section (if any); and a footer containing short forms of authors, title, and date, and the slide number. Header and footer can also be overwritten by respective keyword arguments. -Accepts an arbitrary amount of content blocks, they are placed next to each other -as columns. -Configure using the `columns` and `gutter` keyword arguments. - Pass the slide title as a keyword argument `title`. Accepts the following keyword arguments: - `title`: title of the slide, default: `none`, -- `columns`: propagated to `grid` for placing the body columns, default: array - filled with as many `1fr` as there are content blocks -- `gutter`: propagated to `grid` for placing the body columns, default: `1em` - `header`: custom content to overwrite default header - `footer`: custom content to overwrite default footer - `new-section`: name of the new section that starts here if not `none`, default: @@ -137,6 +127,6 @@ Not suitable for content that exceeds one page. ## Example code The image at the top is created by the following code: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../../IMPORT.typ}} {{#include university.typ:3:}} ``` diff --git a/book/src/themes/gallery/university.png b/book/src/themes/gallery/university.png index ad41df1..253704f 100644 Binary files a/book/src/themes/gallery/university.png and b/book/src/themes/gallery/university.png differ diff --git a/book/src/themes/gallery/university.typ b/book/src/themes/gallery/university.typ index 5f6b71e..35d0472 100644 --- a/book/src/themes/gallery/university.typ +++ b/book/src/themes/gallery/university.typ @@ -21,13 +21,6 @@ #lorem(40) ] - -#slide(title: "A longer slide title with 2 columns")[ - #lorem(30) -][ - #lorem(30) -] - #focus-slide(background-img: image("background.svg"))[ *Another variant with an image in background...* ] diff --git a/book/src/themes/helpers.md b/book/src/themes/helpers.md index aa4fe6f..188301d 100644 --- a/book/src/themes/helpers.md +++ b/book/src/themes/helpers.md @@ -1,84 +1 @@ # Helpers for theme authors - -Let us have a look at some common use cases you run into as a theme author and -what solutions are provided by the `helpers` and `logic` modules inside polylux. -As a theme author, you have access to them due to the imports -```typ -#import "../logic.typ" -#import "../helpers.typ" -``` -(see previous page). - -## How much longer? 🥱 - -There are a handfull of features that let you display the progress of the -presentation. - -The most simple one is directly displaying the current slide number. -Remember that each slide might produce an arbitrary amount of subslides, i.e. -PDF pages, so we cannot rely on the builtin page counter. -Instead, there is the `logical-slide` counter in the `logic` module. -Therefore, you can use -```typ -#logic.logical-slide.display() -``` -to see what the current slide number is. - -If you want to put that into relation to how many slides there are in total, -you can also display -```typ -#helpers.last-slide-number -``` -which is a short-hand way of getting the final value of `logic.logical-slide`. - -Note that both these things are content, though, so you can only display them -and not calculate with the numbers. -A common calculation you might want do to is finding their ratio, i.e. current -slide number divided by total number of slides. -To that end, you can use the function `helpers.polylux-progress`. -You can pass a function to it that turns the current ratio into some content. -For example: -```typ -#helpers.polylux-progress( ratio => [ - You already made it through #calc.round(ratio * 100) #sym.percent of the presentation! -]) -``` -Some themes utilise this to display a little progress bar, for example. - -## Sections -Another way of expressing where we are in a presentation is working with sections. -In your theme, you can incorporate the following features from the `helpers` -module: - -First, whenever a user wants to start a new section, you can call -```typ -#helpers.register-section(the-section-name) -``` -with whatever name they specify. -It is up to you to decide what kind of interface you provide for the user and -how/if you visualise a new section, of course. - -Based on that, you can then display what section the presenter is currently in -by using: -```typ -#helpers.current-section -``` -If no section has been registered so far, this is empty content (`[]`). - -And finally, you might want to display some kind of overview over all the sections. -This is achieved by: -```typ -#helpers.polylux-outline() -``` -Unfortunately, it is hard to get the Typst-builtin `#outline` to work properly -with slides, partly again due to how page numbers are kind of meaningless. -`polylux-outline` is a good alternative to that and will return an `enum` with -all the registered sections (ever, not only so far, so you can safely use it -at the beginning of a presentation). - -`polylux-outline` has two optional keyword arguments: -- `enum-args`: pass a dictionary that is propagated to - [`enum` as keyword arguments](https://typst.app/docs/reference/layout/enum#parameters), - for example `enum-args: (tight: false)`, default: `(:)` -- `padding`: pass [something that `pad` accepts](https://typst.app/docs/reference/layout/pad#parameters), - will be used to pad the `enum`, default: `0pt` diff --git a/book/src/themes/science-slam.png b/book/src/themes/science-slam.png index 149b87f..59c3003 100644 Binary files a/book/src/themes/science-slam.png and b/book/src/themes/science-slam.png differ diff --git a/book/src/themes/themes.md b/book/src/themes/themes.md index 8871863..2c8f9a6 100644 --- a/book/src/themes/themes.md +++ b/book/src/themes/themes.md @@ -20,7 +20,7 @@ First of all, all themes reside in the `themes` module inside polylux. That means, if you want to employ, say, the `simple` theme, you add the following to your regular `#import` line at the top: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../IMPORT.typ}} #import themes.simple: * ``` diff --git a/book/src/themes/your-own.md b/book/src/themes/your-own.md index 31e42b5..efcc975 100644 --- a/book/src/themes/your-own.md +++ b/book/src/themes/your-own.md @@ -1,6 +1,6 @@ # Build your own theme -Again, there is no right or wrong when it comes to how a polylux theme works. +Again, there is no right or wrong when it comes to how a Polylux theme works. If you consider building a theme that you would like to contribute to the package ([which you are cordially invited to do!](https://github.com/andreasKroepelin/polylux/pulls)), we kindly ask you to follow the convention presented before. @@ -17,17 +17,17 @@ Depending on whether this is a theme for yourself or supposed to be part of polylux, you do one of two things: For yourself, you simply import polylux as always: ```typ -#import "@preview/polylux:0.2.0": * +{{#include ../IMPORT.typ}} ``` A theme that is shipped with polylux doesn't have to do that, and it shouldn't! Otherwise circular imports can occur. -Instead, you depend on the two files `logic.typ` and `helpers.typ`. +Instead, you depend on the two files `logic.typ` and `utils/utils.typ`. As your theme file `science-slam.typ` will be inside the `themes` directory, the imports will be: ```typ #import "../logic.typ" -#import "../helpers.typ" +#import "../utils/utils.typ" ``` Additionally, you have to make polylux know about your theme which you do by adding diff --git a/book/src/utils/fill-remaining.png b/book/src/utils/fill-remaining.png new file mode 100644 index 0000000..706f891 Binary files /dev/null and b/book/src/utils/fill-remaining.png differ diff --git a/book/src/utils/fill-remaining.typ b/book/src/utils/fill-remaining.typ new file mode 100644 index 0000000..1d1d68a --- /dev/null +++ b/book/src/utils/fill-remaining.typ @@ -0,0 +1,7 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 40pt) + +#polylux-slide[ +#fit-to-height(1fr)[BIG] +] diff --git a/book/src/utils/fit-to-height-width.png b/book/src/utils/fit-to-height-width.png new file mode 100644 index 0000000..86f7316 Binary files /dev/null and b/book/src/utils/fit-to-height-width.png differ diff --git a/book/src/utils/fit-to-height-width.typ b/book/src/utils/fit-to-height-width.typ new file mode 100644 index 0000000..57ba195 --- /dev/null +++ b/book/src/utils/fit-to-height-width.typ @@ -0,0 +1,10 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 40pt) + +#polylux-slide[ +#fit-to-height(5cm, prescale-width: 300%, width: 50%)[ + #set par(justify: true) + #lorem(200) +] +] diff --git a/book/src/utils/fit-to-height.md b/book/src/utils/fit-to-height.md new file mode 100644 index 0000000..74d4f9a --- /dev/null +++ b/book/src/utils/fit-to-height.md @@ -0,0 +1,43 @@ +# Fit to height + +Suppose you have some content and some size constraints for it but the two don't +match, i.e. the content does not have the size that you want. +The function `#fit-to-height` can help you with that. + +It expects a height and some content and will try to scale the content such that +it takes on the given height: + +```typ +{{#include fill-remaining.typ:6}} +``` + +resulting in + +![fill-remaining](fill-remaining.png) + +Using `1fr` as the height is probably also the prime use case for this function, +as it fills the remaining space of the slide with the given content. +Anything else (like `5pt`, `3cm`, `4em` etc.) is possible as well, of course. + +## Adjusting the width +To finetune the result of `#fit-to-height`, you have two optional parameters: +- `width`: If specified, this determines the width of the content _after_ scaling. + So, if you want the scaled content to fill half the slide's width for example, + you can use `width: 50%`. + By default, the scaled content will be constrained by the slide's width and + will have less than the requested height if necessary. +- `prescale-width`: This parameter allows you to make Typst's layouting assume + the given content is to be layouted in a container of a certain width _before_ + scaling. + You can pretend the slide is twice as wide using `prescale-width: 200%`, for + example. + +We can illustrate that using the following example: + +```typ +{{#include fit-to-height-width.typ:6:9}} +``` + +resulting in + +![fit-to-height-width](fit-to-height-width.png) diff --git a/book/src/utils/progress.md b/book/src/utils/progress.md new file mode 100644 index 0000000..f138580 --- /dev/null +++ b/book/src/utils/progress.md @@ -0,0 +1,35 @@ +# How much longer? 🥱 + +There are a handfull of features that let you display the progress of the +presentation. + +The most simple one is directly displaying the current slide number. +Remember that each slide might produce an arbitrary amount of subslides, i.e. +PDF pages, so we cannot rely on the builtin page counter. +Instead, there is the `logical-slide` counter in the `logic` module. +Therefore, you can use +```typ +#logic.logical-slide.display() +``` +to see what the current slide number is. + +If you want to put that into relation to how many slides there are in total, +you can also display +```typ +#utils.last-slide-number +``` +which is a short-hand way of getting the final value of `logic.logical-slide`. + +Note that both these things are content, though, so you can only display them +and not calculate with the numbers. +A common calculation you might want do to is finding their ratio, i.e. current +slide number divided by total number of slides. +To that end, you can use the function `utils.polylux-progress`. +You can pass a function to it that turns the current ratio into some content. +For example: +```typ +#utils.polylux-progress( ratio => [ + You already made it through #calc.round(ratio * 100) #sym.percent of the presentation! +]) +``` +Some themes utilise this to display a little progress bar, for example. diff --git a/book/src/utils/sections.md b/book/src/utils/sections.md new file mode 100644 index 0000000..165e28a --- /dev/null +++ b/book/src/utils/sections.md @@ -0,0 +1,40 @@ +# Sections +Another way of expressing where we are in a presentation is working with sections. +Usually, this is a topic that a theme will/should handle so **this page is +addressed more towards theme authors**. + +In your theme, you can incorporate the following features from the `utils` +module: + +First, whenever a user wants to start a new section, you can call +```typ +#utils.register-section(the-section-name) +``` +with whatever name they specify. +It is up to you to decide what kind of interface you provide for the user and +how/if you visualise a new section, of course. + +Based on that, you can then display what section the presenter is currently in +by using: +```typ +#utils.current-section +``` +If no section has been registered so far, this is empty content (`[]`). + +And finally, you might want to display some kind of overview over all the sections. +This is achieved by: +```typ +#utils.polylux-outline() +``` +Unfortunately, it is hard to get the Typst-builtin `#outline` to work properly +with slides, partly again due to how page numbers are kind of meaningless. +`polylux-outline` is a good alternative to that and will return an `enum` with +all the registered sections (ever, not only so far, so you can safely use it +at the beginning of a presentation). + +`polylux-outline` has two optional keyword arguments: +- `enum-args`: pass a dictionary that is propagated to + [`enum` as keyword arguments](https://typst.app/docs/reference/layout/enum#parameters), + for example `enum-args: (tight: false)`, default: `(:)` +- `padding`: pass [something that `pad` accepts](https://typst.app/docs/reference/layout/pad#parameters), + will be used to pad the `enum`, default: `0pt` diff --git a/book/src/utils/side-by-side-kwargs.png b/book/src/utils/side-by-side-kwargs.png new file mode 100644 index 0000000..26c84e5 Binary files /dev/null and b/book/src/utils/side-by-side-kwargs.png differ diff --git a/book/src/utils/side-by-side-kwargs.typ b/book/src/utils/side-by-side-kwargs.typ new file mode 100644 index 0000000..9ac6a3e --- /dev/null +++ b/book/src/utils/side-by-side-kwargs.typ @@ -0,0 +1,13 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 40pt) + +#polylux-slide[ +#side-by-side(gutter: 3mm, columns: (1fr, 2fr, 1fr))[ + #rect(width: 100%, stroke: none, fill: aqua) +][ + #rect(width: 100%, stroke: none, fill: teal) +][ + #rect(width: 100%, stroke: none, fill: eastern) +] +] diff --git a/book/src/utils/side-by-side.md b/book/src/utils/side-by-side.md new file mode 100644 index 0000000..a78ea2d --- /dev/null +++ b/book/src/utils/side-by-side.md @@ -0,0 +1,47 @@ +# Side by side +To make good use of the space on a slide, you will often want to place content +next to each other. +For convenience, Polylux provides the function `#side-by-side` for this purpose. +If you used +```typ +{{#include ../IMPORT.typ}} +``` +you have it directly available. +Otherwise you can get if from the `utils` module. + +It is basically a thin wrapper around the Typst function +[`#grid`](https://typst.app/docs/reference/layout/grid/) but tailored +towards this specific usecase. +In its simplest form, you can use it as +```typ +{{#include side-by-side.typ:6:12}} +``` + +resulting in + +![side-by-side](side-by-side.png) + +As you can see, the content arguments you provide will be placed next to each +other with equal proportions of width. +A spacing (gutter) of `1em` will also be put between them. + +The widths and gutter can be configured using the `columns` and `gutter` optional +arguments, respectively. +They are propagated to `#grid` directly so you can look up possible values in +its documentation +([`gutter`](https://typst.app/docs/reference/layout/grid/#parameters-gutter) +and [`columns`](https://typst.app/docs/reference/layout/grid/#parameters-columns) +arguments). +If not specified, they fall back to these defaults: +- `gutter`: `1em` +- `columns`: `(1fr,) * n` if you provided `n` content arguments, that means an + array with the value `1fr` repeated `n` times. + +A more complex example would therefore be: +```typ +{{#include side-by-side-kwargs.typ:6:12}} +``` + +resulting in + +![side-by-side-kwargs](side-by-side-kwargs.png) diff --git a/book/src/utils/side-by-side.png b/book/src/utils/side-by-side.png new file mode 100644 index 0000000..8bc67f9 Binary files /dev/null and b/book/src/utils/side-by-side.png differ diff --git a/book/src/utils/side-by-side.typ b/book/src/utils/side-by-side.typ new file mode 100644 index 0000000..57a4e6d --- /dev/null +++ b/book/src/utils/side-by-side.typ @@ -0,0 +1,13 @@ +#import "../../../polylux.typ": * +#set page(paper: "presentation-16-9") +#set text(size: 40pt) + +#polylux-slide[ +#side-by-side[ + #lorem(7) +][ + #lorem(10) +][ + #lorem(5) +] +] diff --git a/book/src/utils/utils.md b/book/src/utils/utils.md new file mode 100644 index 0000000..9d896a6 --- /dev/null +++ b/book/src/utils/utils.md @@ -0,0 +1,21 @@ +# Utility features + +Let us have a look at some common use cases you run into as either a theme +author or as an end user producing slides and what solutions are provided by +Polylux. + +Specifically, the functions discussed here reside in the `utils` and `logic` +modules. +They are exported by Polylux so if you (as someone making slides) imported it +using +```typ +{{#include ../IMPORT.typ}} +``` +you can direcly invoke them using `utils` and `logic`. +As a theme author, you have access to them due to the imports +```typ +#import "../logic.typ" +#import "../utils/utils.typ" +``` +(see previous page). + diff --git a/examples/demo.pdfpc b/examples/demo.pdfpc new file mode 100644 index 0000000..f1c82c8 --- /dev/null +++ b/examples/demo.pdfpc @@ -0,0 +1,339 @@ +{ + "pages": [ + { + "label": "1", + "idx": 0, + "overlay": 0 + }, + { + "label": "2", + "idx": 1, + "overlay": 0 + }, + { + "label": "3", + "overlay": 0, + "idx": 2 + }, + { + "idx": 3, + "label": "4", + "overlay": 0 + }, + { + "idx": 4, + "overlay": 0, + "label": "5" + }, + { + "idx": 5, + "overlay": 0, + "label": "6" + }, + { + "idx": 6, + "label": "7", + "overlay": 0 + }, + { + "idx": 7, + "label": "8", + "overlay": 0 + }, + { + "overlay": 1, + "label": "8", + "idx": 8, + "forcedOverlay": true + }, + { + "label": "8", + "idx": 9, + "forcedOverlay": true, + "overlay": 2 + }, + { + "idx": 10, + "overlay": 0, + "label": "9" + }, + { + "idx": 11, + "overlay": 0, + "label": "10" + }, + { + "idx": 12, + "overlay": 1, + "forcedOverlay": true, + "label": "10" + }, + { + "forcedOverlay": true, + "overlay": 2, + "idx": 13, + "label": "10" + }, + { + "overlay": 0, + "idx": 14, + "label": "11" + }, + { + "idx": 15, + "overlay": 1, + "label": "11", + "forcedOverlay": true + }, + { + "overlay": 2, + "label": "11", + "forcedOverlay": true, + "idx": 16 + }, + { + "forcedOverlay": true, + "label": "11", + "overlay": 3, + "idx": 17 + }, + { + "overlay": 0, + "idx": 18, + "label": "12" + }, + { + "forcedOverlay": true, + "overlay": 1, + "label": "12", + "idx": 19 + }, + { + "idx": 20, + "label": "12", + "forcedOverlay": true, + "overlay": 2 + }, + { + "forcedOverlay": true, + "overlay": 3, + "idx": 21, + "label": "12" + }, + { + "overlay": 4, + "label": "12", + "forcedOverlay": true, + "idx": 22 + }, + { + "idx": 23, + "label": "12", + "overlay": 5, + "forcedOverlay": true + }, + { + "forcedOverlay": true, + "idx": 24, + "overlay": 6, + "label": "12" + }, + { + "label": "12", + "overlay": 7, + "idx": 25, + "forcedOverlay": true + }, + { + "overlay": 0, + "idx": 26, + "label": "13" + }, + { + "idx": 27, + "overlay": 1, + "forcedOverlay": true, + "label": "13" + }, + { + "idx": 28, + "forcedOverlay": true, + "label": "13", + "overlay": 2 + }, + { + "forcedOverlay": true, + "idx": 29, + "overlay": 3, + "label": "13" + }, + { + "label": "13", + "overlay": 4, + "forcedOverlay": true, + "idx": 30 + }, + { + "idx": 31, + "forcedOverlay": true, + "label": "13", + "overlay": 5 + }, + { + "idx": 32, + "overlay": 0, + "label": "14" + }, + { + "overlay": 1, + "label": "14", + "forcedOverlay": true, + "idx": 33 + }, + { + "forcedOverlay": true, + "label": "14", + "overlay": 2, + "idx": 34 + }, + { + "label": "15", + "idx": 35, + "overlay": 0 + }, + { + "overlay": 1, + "forcedOverlay": true, + "label": "15", + "idx": 36 + }, + { + "forcedOverlay": true, + "overlay": 2, + "idx": 37, + "label": "15" + }, + { + "idx": 38, + "forcedOverlay": true, + "overlay": 3, + "label": "15" + }, + { + "label": "16", + "idx": 39, + "overlay": 0 + }, + { + "idx": 40, + "forcedOverlay": true, + "label": "16", + "overlay": 1 + }, + { + "idx": 41, + "label": "16", + "forcedOverlay": true, + "overlay": 2 + }, + { + "label": "16", + "forcedOverlay": true, + "overlay": 3, + "idx": 42 + }, + { + "label": "17", + "overlay": 0, + "idx": 43 + }, + { + "forcedOverlay": true, + "idx": 44, + "overlay": 1, + "label": "17" + }, + { + "idx": 45, + "overlay": 2, + "forcedOverlay": true, + "label": "17" + }, + { + "overlay": 0, + "label": "18", + "idx": 46 + }, + { + "overlay": 0, + "label": "19", + "idx": 47 + }, + { + "label": "20", + "overlay": 0, + "idx": 48 + }, + { + "idx": 49, + "overlay": 0, + "label": "21" + }, + { + "overlay": 0, + "idx": 50, + "label": "22" + }, + { + "overlay": 0, + "label": "23", + "idx": 51 + }, + { + "label": "24", + "idx": 52, + "overlay": 0 + }, + { + "overlay": 0, + "idx": 53, + "label": "25" + }, + { + "overlay": 0, + "label": "26", + "idx": 54 + }, + { + "overlay": 0, + "idx": 55, + "label": "27" + }, + { + "idx": 56, + "label": "28", + "overlay": 0 + }, + { + "idx": 57, + "overlay": 0, + "label": "29" + }, + { + "idx": 58, + "label": "30", + "overlay": 0 + }, + { + "label": "31", + "idx": 59, + "overlay": 0 + }, + { + "idx": 60, + "label": "32", + "overlay": 0 + } + ], + "pdfpcFormat": 2 +} \ No newline at end of file diff --git a/examples/demo.typ b/examples/demo.typ index b0d4ef8..96af3c9 100644 --- a/examples/demo.typ +++ b/examples/demo.typ @@ -68,22 +68,18 @@ You can also see the slide number there. ] + #new-section-slide("Dynamic content") #slide(title: [A dynamic slide with `pause`s])[ Sometimes we don't want to display everything at once. - #let pc = 1 - #{ pc += 1 } #show: pause(pc) + #pause - That's what the `pause` function is there for! - Use it as - ```typ - #show: pause(n) - ``` - #{ pc += 1 } #show: pause(pc) + That's what the `#pause` function is there for! + #pause - It makes everything after it appear at the `n`-th subslide. + It makes everything after it appear at the next subslide. #text(.6em)[(Also note that the slide number does not change while we are here.)] ] @@ -92,7 +88,7 @@ When `#pause` does not suffice, you can use more advanced commands to show or hide content. - These are your options: + These are some of your options: - `#uncover` - `#only` - `#alternatives` @@ -255,6 +251,32 @@ `start` is again optional and defaults to `1`. ] +#slide(title: [`#list-one-by-one` and Co: when `#line-by-line` doesn't suffice])[ + While `#line-by-line` is very convenient syntax-wise, it fails to produce + more sophisticated bullet lists, enumerations or term lists. + For example, non-tight lists are out of reach. + + For that reason, there are `#list-one-by-one`, `#enum-one-by-one`, and + `#terms-one-by-one`, respectively. + #example[ + #grid( + columns: (1fr, 1fr), + gutter: 1em, + ```typ + #enum-one-by-one(start: 2, tight: false, numbering: "i)")[first][second][third] + ```, + enum-one-by-one(start: 2, tight: false, numbering: "i)")[first][second][third] + ) + ] + + Note that, for technical reasons, the bullet points, numbers, or terms are + never covered. + + `start` is again optional and defaults to `1`. +] + + +/* #slide(title: "Different ways of covering content")[ When content is covered, it is completely invisible by default. @@ -272,6 +294,7 @@ - `#one-by-one(...)[...][...]` - `#line-by-line(...)[...][...]` ] +*/ #new-section-slide("Themes") @@ -293,11 +316,6 @@ It's very minimalist and helps the audience focus on an important point. ] -#slide( - title: [The `clean` theme also makes multiple colums very easy!], - lorem(20), lorem(30), lorem(25) -) - #slide(title: "Your own theme?")[ If you want to create your own design for slides, you can define custom themes! @@ -306,11 +324,32 @@ explains how to do so. ] -#new-section-slide("Typst features") +#new-section-slide("Utilities") -#slide(title: "Use Typst!")[ - Typst gives us so many cool things #footnote[For example footnotes!]. - Use them! +#slide(title: [The `utils` module])[ + Polylux ships a `utils` module with solutions for common tasks in slide + building. +] + +#slide(title: [Fit to height])[ + You can scale content such that it has a certain height using + `#fit-to-height(height, content)`: + + #fit-to-height(2.5cm)[Height is `2.5cm`] +] + +#slide(title: "Fill remaining space")[ + This function also allows you to fill the remaining space by using fractions + as heights, i.e. `fit-to-height(1fr)[...]`: + + #fit-to-height(1fr)[Wow!] +] + +#slide(title: "Side by side content")[ + Often you want to put different content next to each other. + We have the function `#side-by-side` for that: + + #side-by-side(lorem(10), lorem(20), lorem(15)) ] #slide(title: "Outline")[ @@ -318,6 +357,13 @@ #polylux-outline(padding: 1em, enum-args: (tight: false)) ] +#new-section-slide("Typst features") + +#slide(title: "Use Typst!")[ + Typst gives us so many cool things #footnote[For example footnotes!]. + Use them! +] + #slide(title: "Bibliography")[ Let us cite something so we can have a bibliography: @A @B @C #bibliography(title: none, "literature.bib") diff --git a/examples/gauss.pdfpc b/examples/gauss.pdfpc new file mode 100644 index 0000000..a4a68b5 --- /dev/null +++ b/examples/gauss.pdfpc @@ -0,0 +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}]} \ No newline at end of file diff --git a/examples/gauss.typ b/examples/gauss.typ index d4f70c4..724f456 100644 --- a/examples/gauss.typ +++ b/examples/gauss.typ @@ -9,6 +9,13 @@ footer: [Sum of natural numbers, CF Gauß], ) +#pdfpc.config( + duration-minutes: 15, + start-time: datetime(hour: 8, minute: 15, second: 0), + note-font-size: 5, + default-transition: (type: "push", duration-seconds: 0.3), +) + #title-slide( authors: "Carl Friedrich Gauß", title: [On a revolutionary way to \ sum up natural numbers], @@ -22,6 +29,7 @@ Let $n in NN$. We are interested in sums of the form $ 1 + ... + n = sum_(i=1)^n i $ + #pdfpc.speaker-note("Remember to explain Sigma notation!") ] #slide(title: "The theorem")[ @@ -30,6 +38,7 @@ $ sum_(i=1)^n i = n(n+1)/2 $ Let's prove that! + #pdfpc.save-slide ] #new-section-slide("Proof") @@ -40,6 +49,8 @@ + base case + induction hypothesis + induction step + + #pdfpc.hidden-slide ] #slide(title: "Proof")[ @@ -64,6 +75,11 @@ = ((k+1)(k+2))/2 #h(1em) checkmark$ ] + + #pdfpc.speaker-note(```md +# How the last steps work +We use _basic algebra_ rules for the last steps. + ```) ] #focus-slide[ @@ -75,4 +91,11 @@ #slide(title: "That's it!")[ Now you know how to calculate those sums more quickly. Nice! + + #pdfpc.end-slide +] + +#slide(title: [Further references])[ + If you want to learn more about this cool kind of math, you can start your + investigation here: https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss ] diff --git a/helpers.typ b/helpers.typ deleted file mode 100644 index 9d2321e..0000000 --- a/helpers.typ +++ /dev/null @@ -1,31 +0,0 @@ -#import "logic.typ" - -#let sections-state = state("polylux-sections", ()) -#let register-section(name) = locate( loc => { - sections-state.update(sections => { - sections.push((body: name, loc: loc)) - sections - }) -}) -#let current-section = locate( loc => { - let sections = sections-state.at(loc) - if sections.len() > 0 { - sections.last().body - } else { - [] - } -}) -#let polylux-outline(enum-args: (:), padding: 0pt) = locate( loc => { - let sections = sections-state.final(loc) - pad(padding, enum( - ..enum-args, - ..sections.map(section => link(section.loc, section.body)) - )) -}) - -#let polylux-progress(ratio-to-content) = locate( loc => { - let ratio = logic.logical-slide.at(loc).first() / logic.logical-slide.final(loc).first() - ratio-to-content(ratio) -}) - -#let last-slide-number = locate(loc => logic.logical-slide.final(loc).first()) \ No newline at end of file diff --git a/logic.typ b/logic.typ index 0d7a93d..1d2b86f 100644 --- a/logic.typ +++ b/logic.typ @@ -1,4 +1,5 @@ #let subslide = counter("subslide") +#let pause-counter = counter("pause-counter") #let logical-slide = counter("logical-slide") #let repetitions = counter("repetitions") #let handout-mode = state("handout-mode", false) @@ -126,21 +127,71 @@ } } -#let alternatives(start: 1, position: bottom + left, ..children) = { +#let alternatives-match(subslides-contents, position: bottom + left) = { + let subslides-contents = if type(subslides-contents) == "dictionary" { + subslides-contents.pairs() + } else { + subslides-contents + } + + let subslides = subslides-contents.map(it => it.first()) + let contents = subslides-contents.map(it => it.last()) style(styles => { - let sizes = children.pos().map(c => measure(c, styles)) + let sizes = contents.map(c => measure(c, styles)) let max-width = calc.max(..sizes.map(sz => sz.width)) let max-height = calc.max(..sizes.map(sz => sz.height)) - for (idx, child) in children.pos().enumerate() { - only(start + idx, box( + for (subslides, content) in subslides-contents { + only(subslides, box( width: max-width, height: max-height, - align(position, child) + align(position, content) )) } }) } +#let alternatives( + start: 1, + repeat-last: false, + ..args +) = { + let contents = args.pos() + let kwargs = args.named() + let subslides = range(start, start + contents.len()) + if repeat-last { + subslides.last() = (beginning: subslides.last()) + } + alternatives-match(subslides.zip(contents), ..kwargs) +} + +#let alternatives-fn( + start: 1, + end: none, + count: none, + ..kwargs, + fn +) = { + let end = if end == none { + if count == none { + panic("You must specify either end or count.") + } else { + start + count + } + } else { + end + } + + let subslides = range(start, end) + let contents = subslides.map(fn) + alternatives-match(subslides.zip(contents), ..kwargs.named()) +} + +#let alternatives-cases(cases, fn, ..kwargs) = { + let idcs = range(cases.len()) + let contents = idcs.map(fn) + alternatives-match(cases.zip(contents), ..kwargs.named()) +} + #let line-by-line(start: 1, mode: "invisible", body) = { let items = if repr(body.func()) == "sequence" { body.children @@ -159,10 +210,59 @@ } } -#let pause(beginning, mode: "invisible") = body => { - uncover((beginning: beginning), mode: mode, body) + +#let _items-one-by-one(fn, start: 1, mode: "invisible", ..args) = { + let kwargs = args.named() + let items = args.pos() + let covered-items = items.enumerate().map( + ((idx, item)) => uncover((beginning: idx + start), mode: mode, item) + ) + fn( + ..kwargs, + ..covered-items + ) +} + +#let list-one-by-one(start: 1, mode: "invisible", ..args) = { + _items-one-by-one(list, start: start, mode: mode, ..args) +} + +#let enum-one-by-one(start: 1, mode: "invisible", ..args) = { + _items-one-by-one(enum, start: start, mode: mode, ..args) +} + +#let terms-one-by-one(start: 1, mode: "invisible", ..args) = { + let kwargs = args.named() + let items = args.pos() + let covered-items = items.enumerate().map( + ((idx, item)) => terms.item( + item.term, + uncover((beginning: idx + start), mode: mode, item.description) + ) + ) + terms( + ..kwargs, + ..covered-items + ) +} + +#let pause = { + pause-counter.step() + locate( loc => { + repetitions.update(rep => calc.max(rep, pause-counter.at(loc).first() + 1)) + }) } +#let paused-content(body) = locate( loc => { + let current-subslide = subslide.at(loc).first() + let current-pause-counter = pause-counter.at(loc).first() + + if current-subslide > current-pause-counter { + body + } else { + hide(body) + } +}) #let polylux-slide(max-repetitions: 10, body) = { locate( loc => { @@ -174,13 +274,34 @@ subslide.update(1) repetitions.update(1) + show text: paused-content + show smartquote: paused-content + show box: paused-content + show block: paused-content + show path: paused-content + show rect: paused-content + show square: paused-content + show circle: paused-content + show ellipse: paused-content + show line: paused-content + show polygon: paused-content + show image: paused-content + for _ in range(max-repetitions) { + pause-counter.update(0) locate( loc => { let curr-subslide = subslide.at(loc).first() if curr-subslide <= repetitions.at(loc).first() { if curr-subslide > 1 { pagebreak(weak: true) } set heading(outlined: false) if curr-subslide > 1 + [ + #metadata((t: "NewSlide")) + #metadata((t: "Idx", v: counter(page).at(loc).first() - 1)) + #metadata((t: "Overlay", v: curr-subslide - 1)) + #metadata((t: "LogicalSlide", v: logical-slide.at(loc).first())) + ] + body } }) diff --git a/pdfpc-extractor/.gitignore b/pdfpc-extractor/.gitignore new file mode 100644 index 0000000..e420ee4 --- /dev/null +++ b/pdfpc-extractor/.gitignore @@ -0,0 +1 @@ +target/* diff --git a/pdfpc-extractor/Cargo.lock b/pdfpc-extractor/Cargo.lock new file mode 100644 index 0000000..e467692 --- /dev/null +++ b/pdfpc-extractor/Cargo.lock @@ -0,0 +1,474 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "backtrace", + "backtrace-ext", + "is-terminal", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "polylux2pdfpc" +version = "0.1.0" +dependencies = [ + "miette", + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "supports-color" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +dependencies = [ + "is-terminal", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "supports-unicode" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/pdfpc-extractor/Cargo.toml b/pdfpc-extractor/Cargo.toml new file mode 100644 index 0000000..651fe76 --- /dev/null +++ b/pdfpc-extractor/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "polylux2pdfpc" +license = "MIT" +description = "A tool to make pdfpc interpret slides created by polylux correctly" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +miette = { version = "5.10.0", features = ["fancy"] } +serde = { version = "1.0.186", features = ["derive"] } +serde_json = "1.0.105" diff --git a/pdfpc-extractor/src/main.rs b/pdfpc-extractor/src/main.rs new file mode 100644 index 0000000..2ad1d30 --- /dev/null +++ b/pdfpc-extractor/src/main.rs @@ -0,0 +1,290 @@ +use miette::{miette, IntoDiagnostic, WrapErr}; +use serde::{Deserialize, Serialize}; +use std::{convert::TryFrom, process::Command}; + +#[derive(Deserialize)] +#[serde(transparent)] +struct TypstQueryOutput(Vec); + +#[derive(Debug, Deserialize)] +#[serde(tag = "t", content = "v")] +enum QueryItem { + Duration(u32), + StartTime(String), + EndTime(String), + LastMinutes(u32), + DisableMarkdown(bool), + NoteFontSize(u32), + DefaultTransition(String), + + NewSlide, + Idx(u32), + LogicalSlide(u32), + Overlay(u32), + Note(String), + EndSlide, + SaveSlide, + HiddenSlide, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct Page { + idx: u32, + #[serde(rename = "label")] + logical_slide: u32, + overlay: u32, + forced_overlay: bool, + hidden: bool, + #[serde(skip_serializing_if = "Option::is_none")] + note: Option, + #[serde(skip)] + end: bool, + #[serde(skip)] + saved: bool, +} + +impl<'a> TryFrom<&'a [QueryItem]> for Page { + type Error = miette::Report; + + fn try_from(items: &'a [QueryItem]) -> miette::Result { + use QueryItem::*; + Ok(Page { + idx: items + .iter() + .find_map(|item| if let Idx(idx) = item { Some(idx) } else { None }) + .cloned() + .ok_or_else(|| miette!("Page has no idx."))?, + logical_slide: items + .iter() + .find_map(|item| { + if let LogicalSlide(logical_slide) = item { + Some(logical_slide) + } else { + None + } + }) + .cloned() + .ok_or_else(|| miette!("Page has no label."))?, + overlay: items + .iter() + .find_map(|item| { + if let Overlay(overlay) = item { + Some(overlay) + } else { + None + } + }) + .cloned() + .ok_or_else(|| miette!("Page has no overlay."))?, + note: items + .iter() + .find_map(|item| { + if let Note(note) = item { + Some(note) + } else { + None + } + }) + .cloned(), + forced_overlay: items.iter().any(|item| match item { + &Overlay(overlay) if overlay > 0 => true, + _ => false, + }), + hidden: items.iter().any(|item| matches!(item, HiddenSlide)), + end: items.iter().any(|item| matches!(item, EndSlide)), + saved: items.iter().any(|item| matches!(item, SaveSlide)), + }) + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct PdfpcConfig { + pdfpc_format: u32, + /// Duration in minutes + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + #[serde(skip_serializing_if = "Option::is_none")] + start_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + end_time: Option, + #[serde(skip_serializing_if = "Option::is_none")] + last_minutes: Option, + disable_markdown: bool, + #[serde(skip_serializing_if = "Option::is_none")] + note_font_size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + default_transition: Option, +} + +impl<'a> TryFrom<&'a [QueryItem]> for PdfpcConfig { + type Error = miette::Report; + + fn try_from(items: &'a [QueryItem]) -> miette::Result { + use QueryItem::*; + Ok(PdfpcConfig { + pdfpc_format: 2, + duration: items + .iter() + .find_map(|item| { + if let Duration(duration) = item { + Some(duration) + } else { + None + } + }) + .cloned(), + start_time: items + .iter() + .find_map(|item| { + if let StartTime(start_time) = item { + Some(start_time) + } else { + None + } + }) + .cloned(), + end_time: items + .iter() + .find_map(|item| { + if let EndTime(end_time) = item { + Some(end_time) + } else { + None + } + }) + .cloned(), + last_minutes: items + .iter() + .find_map(|item| { + if let LastMinutes(last_minutes) = item { + Some(last_minutes) + } else { + None + } + }) + .cloned(), + note_font_size: items + .iter() + .find_map(|item| { + if let NoteFontSize(note_font_size) = item { + Some(note_font_size) + } else { + None + } + }) + .cloned(), + default_transition: items + .iter() + .find_map(|item| { + if let DefaultTransition(default_transition) = item { + Some(default_transition) + } else { + None + } + }) + .cloned(), + disable_markdown: items + .iter() + .any(|item| matches!(item, DisableMarkdown(true))), + }) + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct Pdfpc { + #[serde(flatten)] + config: PdfpcConfig, + #[serde(skip_serializing_if = "Option::is_none")] + end_slide: Option, + #[serde(skip_serializing_if = "Option::is_none")] + saved_slide: Option, + pages: Vec, +} + +impl TryFrom for Pdfpc { + type Error = miette::Report; + + fn try_from(output: TypstQueryOutput) -> miette::Result { + let mut split_output = output.0.split(|item| matches!(item, QueryItem::NewSlide)); + let config_items = split_output + .next() + .ok_or_else(|| miette!("There are no slides."))?; + let config = PdfpcConfig::try_from(config_items) + .wrap_err("Failed to aggregate pdfpc configuration data.")?; + let pages = split_output + .map(Page::try_from) + .collect::, _>>() + .wrap_err("Failed to aggregate page information.")?; + + Ok(Pdfpc { + config, + end_slide: pages.iter().find_map(|page| { + if page.end { + Some(page.logical_slide - 1) + } else { + None + } + }), + saved_slide: pages.iter().find_map(|page| { + if page.saved { + Some(page.logical_slide - 1) + } else { + None + } + }), + pages, + }) + } +} + +fn main() -> miette::Result<()> { + let args: Vec<_> = std::env::args().skip(1).collect(); + let filename = args + .iter() + .find(|arg| arg.ends_with(".typ")) + .cloned() + .ok_or_else(|| miette!("No .typ file provided."))?; + + // dbg!(filename); + + let query_output = Command::new("typst") + .arg("query") + .args(args) + .arg("--field") + .arg("value") + .arg("") + .output() + .into_diagnostic() + .wrap_err("typst query failed.")?; + let query_str = String::from_utf8(query_output.stdout) + .into_diagnostic() + .wrap_err("typst query produced invalid UTF-8 data.")?; + + if query_str.is_empty() { + let query_errstr = String::from_utf8(query_output.stderr) + .into_diagnostic() + .wrap_err("typst query produced invalid UTF-8 on stderr.")?; + miette::bail!(miette::diagnostic!( + help = query_errstr, + "typst query did not produce any output." + )) + } + + let query: TypstQueryOutput = serde_json::from_str(&query_str) + .into_diagnostic() + .wrap_err("Failed to parse JSON produced by typst query.")?; + + let pdfpc = Pdfpc::try_from(query).wrap_err("Failed to construct pdfpc data")?; + + let output = serde_json::to_string(&pdfpc) + .into_diagnostic() + .wrap_err("Failed to create pdfpc JSON.")?; + let outfile = std::path::Path::new(&filename).with_extension("pdfpc"); + std::fs::write(outfile, output) + .into_diagnostic() + .wrap_err("Failed to write pdfpc JSON to file.")?; + Ok(()) +} diff --git a/polylux.typ b/polylux.typ index 3caf30d..25cfea1 100644 --- a/polylux.typ +++ b/polylux.typ @@ -1,3 +1,5 @@ #import "themes/themes.typ" -#import "logic.typ": polylux-slide, uncover, only, alternatives, one-by-one, line-by-line, pause, enable-handout-mode -#import "helpers.typ": polylux-outline +#import "logic.typ" +#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 +#import "utils/utils.typ" +#import "utils/utils.typ": polylux-outline, fit-to-height, side-by-side, pdfpc diff --git a/scripts/extract-package.fish b/scripts/extract-package.fish index fd59ae8..0d73182 100644 --- a/scripts/extract-package.fish +++ b/scripts/extract-package.fish @@ -1,4 +1,9 @@ function extract-package + if test (git branch --show-current) != "release" + echo "You are not on the release branch!" + return 1 + end + set target $argv[1] pwd mkdir -p $target @@ -7,8 +12,9 @@ function extract-package cp typst.toml $target cp polylux.typ $target cp logic.typ $target - cp helpers.typ $target mkdir -p $target/themes cp themes/* $target/themes + mkdir -p $target/utils + cp utils/* $target/utils echo "Done" -end \ No newline at end of file +end diff --git a/scripts/generate-previews.jl b/scripts/generate-previews.jl index 84b7f0b..51e1331 100644 --- a/scripts/generate-previews.jl +++ b/scripts/generate-previews.jl @@ -79,7 +79,7 @@ function generate_previews(items) mktempdir() do tmp typst_output = joinpath(tmp, "img-{n}.png") typst_input = item.input - `typst --root . compile $typst_input $typst_output` |> run + `typst compile --root . $typst_input $typst_output` |> run imgs = readdir(tmp, join = true) .|> load plt = montage(imgs, item.label) savefig(plt, item.output) @@ -108,6 +108,9 @@ dynamic = "book/src/dynamic" # ╔═╡ b7923561-c79b-4443-99c2-4306589313d6 themes = "book/src/themes" +# ╔═╡ 98d47b63-9744-4f68-aff9-6b80a1bd2212 +utils = "book/src/utils" + # ╔═╡ c3934766-e918-456d-81ea-de1e4726d3b6 gallery = joinpath(themes, "gallery") @@ -125,10 +128,17 @@ generate_previews([ typ2png(path = dynamic, file = "one-by-one", label = "subslide "), typ2png(path = dynamic, file = "one-by-one-start", label = "subslide "), typ2png(path = dynamic, file = "line-by-line", label = "subslide "), + typ2png(path = dynamic, file = "list-one-by-one", label = "subslide "), + typ2png(path = dynamic, file = "enum-one-by-one", label = "subslide "), + typ2png(path = dynamic, file = "terms-one-by-one", label = "subslide "), typ2png(path = dynamic, file = "pause", label = "subslide "), typ2png(path = dynamic, file = "poor-alternatives", label = "subslide "), typ2png(path = dynamic, file = "alternatives", label = "subslide "), + typ2png(path = dynamic, file = "alternatives-repeat-last", label = "subslide "), typ2png(path = dynamic, file = "alternatives-position", label = "subslide "), + typ2png(path = dynamic, file = "alternatives-match", label = "subslide "), + typ2png(path = dynamic, file = "alternatives-cases", label = "subslide "), + typ2png(path = dynamic, file = "alternatives-fn", label = "subslide "), typ2png(path = dynamic, file = "cover", label = "subslide "), typ2png(path = dynamic, file = "handout", label = "subslide "), typ2png(path = themes, file = "science-slam"), @@ -137,6 +147,10 @@ generate_previews([ typ2png(path = gallery, file = "metropolis"), typ2png(path = gallery, file = "university"), typ2png(path = gallery, file = "bipartite"), + typ2png(path = utils, file = "side-by-side"), + typ2png(path = utils, file = "side-by-side-kwargs"), + typ2png(path = utils, file = "fill-remaining"), + typ2png(path = utils, file = "fit-to-height-width"), ]) # ╔═╡ 00000000-0000-0000-0000-000000000001 @@ -1664,6 +1678,7 @@ version = "1.4.1+0" # ╠═59a40cbd-5ad7-4c22-b676-e85bbf9ee918 # ╠═09396295-f40a-4bb1-b261-81f784d93bc2 # ╠═b7923561-c79b-4443-99c2-4306589313d6 +# ╠═98d47b63-9744-4f68-aff9-6b80a1bd2212 # ╠═c3934766-e918-456d-81ea-de1e4726d3b6 # ╠═aa8102d8-029f-4a94-bce4-a7f362b64e4c # ╟─00000000-0000-0000-0000-000000000001 diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..a136337 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +*.pdf diff --git a/tests/alternatives.typ b/tests/alternatives.typ new file mode 100644 index 0000000..39febea --- /dev/null +++ b/tests/alternatives.typ @@ -0,0 +1,41 @@ +#import "../polylux.typ": * + +#set page(paper: "presentation-16-9") + +#polylux-slide[ + == Test that `repeat-last` works + + #alternatives[abc ][def ][ghi ] + + #alternatives(repeat-last: true)[jkl ][mno ][this stays ] + + #uncover(5)[You can go now.] +] + +#polylux-slide[ + == Test that `alternatives-match` works + + #alternatives-match(position: center, ( + "-2": [beginning], + "3, 5": [main part], + "4": [short break], + "6-" : [end] + )) + + #uncover("1-8")[I am always here, for technical reasons.] +] + +#polylux-slide[ + == Test that `alternatives-cases` works + + #alternatives-cases(("1,3,4", "2,5", "6"), case => { + set text(fill: lime) if case == 1 + lorem(10) + }) +] + +#polylux-slide[ + == Test that `alternatives-fn` works + + #alternatives-fn(count: 5, subslide => numbering("(i)", subslide)) +] diff --git a/tests/pause.typ b/tests/pause.typ new file mode 100644 index 0000000..a6edd60 --- /dev/null +++ b/tests/pause.typ @@ -0,0 +1,86 @@ +#import "../polylux.typ": * + +#set page(paper: "presentation-16-9") + +#polylux-slide[ + == Text like content + + Hello + #pause + $a + b$ + #pause + $ integral f(x) dif x $ + #pause + + - *item1* + - _item2_ + - `item3` + + #pause + + + #underline[item1] + + #strike[item2] + + #overline[item3] + + #pause + + / def1: abc + / def2: ghi + + #pause + + #box(stroke: 2pt + aqua, inset: 2pt)[boxed!] + + #pause + + #block(stroke: 2pt + lime, inset: 2pt)[blocked!] +] + +#polylux-slide[ + == Inside grid + + #grid(columns: 4 * (1fr,))[ + abc + #pause + ][ + def + #pause + ][ + ghi + #pause + ][ + jkl + ] +] + +#polylux-slide[ + == Visuals + + #pause + + // Fails to be hidden as of Typst 0.7.0 + #path( + fill: teal.lighten(50%), stroke: teal, closed: true, + (0cm, 0cm), (1cm, 0cm), (1cm, 1cm) + ) + + #rect() + + #square() + + #circle() + + #ellipse() + + // Fails to be hidden as of Typst 0.7.0 + #line() + + // Fails to be hidden as of Typst 0.7.0 + #polygon( + fill: teal.lighten(50%), stroke: teal, + (0cm, 0cm), (1cm, 0cm), (1cm, 1cm) + ) + + #image("../assets/logo.png", width: 3em) +] + diff --git a/themes/clean.typ b/themes/clean.typ index 60a59d8..a37905c 100644 --- a/themes/clean.typ +++ b/themes/clean.typ @@ -2,7 +2,7 @@ // https://github.com/MarkBlyth #import "../logic.typ" -#import "../helpers.typ" +#import "../utils/utils.typ" #let clean-footer = state("clean-footer", []) #let clean-short-title = state("clean-short-title", none) @@ -104,7 +104,7 @@ logic.polylux-slide(content) } -#let slide(title: none, columns: none, gutter: none, ..bodies) = { +#let slide(title: none, body) = { let header = align(top, locate( loc => { let color = clean-color.at(loc) let logo = clean-logo.at(loc) @@ -124,10 +124,10 @@ align(horizon + right, grid( columns: 1, rows: 1em, gutter: .5em, short-title, - helpers.current-section + utils.current-section )) } else { - align(horizon + right, helpers.current-section) + align(horizon + right, utils.current-section) } ) })) @@ -153,14 +153,7 @@ header-ascent: 1.5em, ) - let bodies = bodies.pos() - let gutter = if gutter == none { 1em } else { gutter } - let columns = if columns == none { (1fr,) * bodies.len() } else { columns } - if columns.len() != bodies.len() { - panic("number of columns must match number of content arguments") - } - - let body = pad(x: .0em, y: .5em, grid(columns: columns, gutter: gutter, ..bodies)) + let body = pad(x: .0em, y: .5em, body) let content = { @@ -176,7 +169,9 @@ #let focus-slide(background: teal, foreground: white, body) = { set page(fill: background, margin: 2em) set text(fill: foreground, size: 1.5em) - logic.polylux-slide(align(horizon, body)) + let content = { v(.1fr); body; v(.1fr) } + // logic.polylux-slide(align(horizon, body)) + logic.polylux-slide(content) } #let new-section-slide(name) = { @@ -187,7 +182,7 @@ show: block.with(stroke: ( bottom: 1mm + color ), inset: 1em,) set text(size: 1.5em) strong(name) - helpers.register-section(name) + utils.register-section(name) }) logic.polylux-slide(content) } diff --git a/themes/metropolis.typ b/themes/metropolis.typ index 95aa7e9..cb1a7f2 100644 --- a/themes/metropolis.typ +++ b/themes/metropolis.typ @@ -8,7 +8,7 @@ // #set par(justify: true) #import "../logic.typ" -#import "../helpers.typ" +#import "../utils/utils.typ" #let m-dark-teal = rgb("#23373b") #let m-light-brown = rgb("#eb811b") @@ -25,7 +25,7 @@ breakable: false ) -#let m-progress-bar = helpers.polylux-progress( ratio => { +#let m-progress-bar = utils.polylux-progress( ratio => { grid( columns: (ratio * 100%, 1fr), m-cell(fill: m-light-brown), @@ -124,7 +124,7 @@ #let new-section-slide(name) = { let content = { - helpers.register-section(name) + utils.register-section(name) set align(horizon) show: pad.with(20%) set text(size: 1.5em) @@ -142,4 +142,4 @@ #let alert = text.with(fill: m-light-brown) -#let metropolis-outline = helpers.polylux-outline(enum-args: (tight: false,)) +#let metropolis-outline = utils.polylux-outline(enum-args: (tight: false,)) diff --git a/themes/university.typ b/themes/university.typ index 323f506..f78b2fe 100644 --- a/themes/university.typ +++ b/themes/university.typ @@ -1,5 +1,5 @@ #import "../logic.typ" -#import "../helpers.typ" +#import "../utils/utils.typ" // University theme // @@ -97,28 +97,20 @@ #let slide( title: none, - columns: none, - gutter: none, header: none, footer: none, new-section: none, - ..bodies + body ) = { - let bodies = bodies.pos() - let gutter = if gutter == none { 1em } else { gutter } - let columns = if columns == none { (1fr,) * bodies.len() } else { columns } - if columns.len() != bodies.len() { - panic("number of columns must match number of content arguments") - } - let body = pad(x: 2em, y: .5em, grid(columns: columns, gutter: gutter, ..bodies)) + let body = pad(x: 2em, y: .5em, body) let progress-barline = locate( loc => { if uni-progress-bar.at(loc) { let cell = block.with( width: 100%, height: 100%, above: 0pt, below: 0pt, breakable: false ) let colors = uni-colors.at(loc) - helpers.polylux-progress( ratio => { + utils.polylux-progress( ratio => { grid( rows: 2pt, columns: (ratio * 100%, 1fr), cell(fill: colors.a), @@ -133,14 +125,14 @@ header } else if title != none { if new-section != none { - helpers.register-section(new-section) + utils.register-section(new-section) } locate( loc => { let colors = uni-colors.at(loc) block(fill: colors.c, inset: (x: .5em), grid( columns: (60%, 40%), align(top + left, heading(level: 2, text(fill: colors.a, title))), - align(top + right, text(fill: colors.a.lighten(65%), helpers.current-section)) + align(top + right, text(fill: colors.a.lighten(65%), utils.current-section)) )) }) } else { [] } @@ -171,7 +163,7 @@ cell(fill: colors.a, uni-short-author.display()), cell(uni-short-title.display()), cell(uni-short-date.display()), - cell(logic.logical-slide.display() + [~/~] + helpers.last-slide-number) + cell(logic.logical-slide.display() + [~/~] + utils.last-slide-number) ) }) } @@ -190,7 +182,7 @@ } #let focus-slide(background-color: none, background-img: none, body) = { - let background-color = if background-img == none and background-colour == none { + let background-color = if background-img == none and background-color == none { rgb("#0C6291") } else { background-color diff --git a/utils/pdfpc.typ b/utils/pdfpc.typ new file mode 100644 index 0000000..27d9e6c --- /dev/null +++ b/utils/pdfpc.typ @@ -0,0 +1,94 @@ +#let speaker-note(text) = { + let text = if type(text) == "string" { + text + } else if type(text) == "content" and text.func() == raw { + text.text.trim() + } else { + panic("A note must either be a string or a raw block") + } + [ #metadata((t: "Note", v: text)) ] +} + +#let end-slide = [ + #metadata((t: "EndSlide")) +] + +#let save-slide = [ + #metadata((t: "SaveSlide")) +] + +#let hidden-slide = [ + #metadata((t: "HiddenSlide")) +] + +#let config( + duration-minutes: none, + start-time: none, + end-time: none, + last-minutes: none, + note-font-size: none, + disable-markdown: false, + default-transition: none, +) = { + if duration-minutes != none { + [ #metadata((t: "Duration", v: duration-minutes)) ] + } + + let _time-config(time, msg-name, tag-name) = { + let time = if type(time) == "datetime" { + time.display("[hour padding:zero repr:24]:[minute padding:zero]") + } else if type(time) == "string" { + time + } else { + panic(msg-name + " must be either a datetime or a string in the HH:MM format.") + } + + [ #metadata((t: tag-name, v: time)) ] + } + + if start-time != none { + _time-config(start-time, "Start time", "StartTime") + } + + if end-time != none { + _time-config(end-time, "End time", "EndTime") + } + + if last-minutes != none { + [ #metadata((t: "LastMinutes", v: last_minutes)) ] + } + + if note-font-size != none { + [ #metadata((t: "NoteFontSize", v: note-font-size)) ] + } + + [ #metadata((t: "DisableMarkdown", v: disable-markdown)) ] + + if default-transition != none { + let dir-to-angle(dir) = if dir == ltr { + "0" + } else if dir == rtl { + "180" + } else if dir == ttb { + "90" + } else if dir == btt { + "270" + } else { + panic("angle must be a direction (ltr, rtl, ttb, or btt)") + } + + let transition-str = ( + default-transition.at("type", default: "replace") + + ":" + + str(default-transition.at("duration-seconds", default: 1)) + + ":" + + dir-to-angle(default-transition.at("angle", default: rtl)) + + ":" + + default-transition.at("alignment", default: "horizontal") + + ":" + + default-transition.at("direction", default: "outward") + ) + + [ #metadata((t: "DefaultTransition", v: transition-str)) ] + } +} diff --git a/utils/utils.typ b/utils/utils.typ new file mode 100644 index 0000000..ba2690b --- /dev/null +++ b/utils/utils.typ @@ -0,0 +1,142 @@ +#import "../logic.typ" + +#import "pdfpc.typ" + +// SECTIONS + +#let sections-state = state("polylux-sections", ()) +#let register-section(name) = locate( loc => { + sections-state.update(sections => { + sections.push((body: name, loc: loc)) + sections + }) +}) + +#let current-section = locate( loc => { + let sections = sections-state.at(loc) + if sections.len() > 0 { + sections.last().body + } else { + [] + } +}) + +#let polylux-outline(enum-args: (:), padding: 0pt) = locate( loc => { + let sections = sections-state.final(loc) + pad(padding, enum( + ..enum-args, + ..sections.map(section => link(section.loc, section.body)) + )) +}) + + +// PROGRESS + +#let polylux-progress(ratio-to-content) = locate( loc => { + let ratio = logic.logical-slide.at(loc).first() / logic.logical-slide.final(loc).first() + ratio-to-content(ratio) +}) + +#let last-slide-number = locate(loc => logic.logical-slide.final(loc).first()) + + +// HEIGHT FITTING + +#let _size-to-pt(size, styles, container-dimension) = { + let to-convert = size + if type(size) == "ratio" { + to-convert = container-dimension * size + } + measure(v(to-convert), styles).height +} + +#let _limit-content-width(width: none, body, container-size, styles) = { + let mutable-width = width + if width == none { + mutable-width = calc.min(container-size.width, measure(body, styles).width) + } else { + mutable-width = _size-to-pt(width, styles, container-size.width) + } + box(width: mutable-width, body) +} + +#let fit-to-height(height, width: none, prescale-width: none, body) = { + // Place two labels with the requested vertical separation to be able to + // measure their vertical distance in pt. + // Using this approach instead of using `measure` allows us to accept fractions + // like `1fr` as well. + // The label must be attached to content, so we use a show rule that doesn't + // display anything as the anchor. + let before-label = label("polylux-fit-height-before") + let after-label = label("polylux-fit-height-after") + [ + #show before-label: none + #show after-label: none + #v(1em) + hidden#before-label + #v(height) + hidden#after-label + ] + + locate(loc => { + let before = query(selector(before-label).before(loc), loc) + let before-pos = before.last().location().position() + let after = query(selector(after-label).before(loc), loc) + let after-pos = after.last().location().position() + + let available-height = after-pos.y - before-pos.y + + style(styles => { + layout(container-size => { + // Helper function to more easily grab absolute units + let get-pts(body, w-or-h) = { + let dim = if w-or-h == "w" {container-size.width} else {container-size.height} + _size-to-pt(body, styles, dim) + } + + // Provide a sensible initial width, which will define initial scale parameters. + // Note this is different from the post-scale width, which is a limiting factor + // on the allowable scaling ratio + let boxed-content = _limit-content-width( + width: prescale-width, body, container-size, styles + ) + + // post-scaling width + let mutable-width = width + if width == none { + mutable-width = container-size.width + } + mutable-width = get-pts(mutable-width, "w") + + let size = measure(boxed-content, styles) + let h-ratio = available-height / size.height + let w-ratio = mutable-width / size.width + let ratio = calc.min(h-ratio, w-ratio) * 100% + + let new-width = size.width * ratio + v(-available-height) + // If not boxed, the content can overflow to the next page even though it will fit. + // This is because scale doesn't update the layout information. + // Boxing in a container without clipping will inform typst that content + // will indeed fit in the remaining space + box( + width: new-width, + height: available-height, + scale(x: ratio, y: ratio, origin: top + left, boxed-content) + ) + }) + }) + }) +} + +// SIDE BY SIDE + +#let side-by-side(columns: none, gutter: 1em, ..bodies) = { + let bodies = bodies.pos() + let columns = if columns == none { (1fr,) * bodies.len() } else { columns } + if columns.len() != bodies.len() { + panic("number of columns must match number of content arguments") + } + + grid(columns: columns, gutter: gutter, ..bodies) +}