Polylux is a package for the typesetting system Typst to create presentation slides, just like you would use the beamer package in LaTeX. (So far, it is much less advanced than beamer, obviously.)
If you haven't heard of things like LaTeX's beamer before, here is how this works: As a rule of thumb, one slide becomes one PDF page, and most PDF viewers can display PDFs in the form of a slide show (usually by hitting the F5-key).
Polylux gives you:
- Elegant yet powerfull typesetting by the ever-improving Typst.
- Fully customisable slides.
- Dynamic slides (or overlays or (dis-)appearing content, or however you want to call it).
- Decently looking themes.
If you like it, consider giving a star on GitHub!
Why the name?
A polylux is a brand of overhead projectors very common in Eastern German schools (where the main author of this package grew up). It fulfils a similar function to a projector, namely projecting visuals to a wall to aid a presentation. 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 where you can also create issues or pull requests.
License
Polylux is released under the MIT license.
Getting started
You can find this package in the official Typst package repository. To use it, start your document with
#import "@preview/polylux:0.3.1": *
You now have two options:
- use the features of polylux but define every visual aspect yourself,
- use one of the provided themes.
We will first explore how you can get started without the support of themes. Later, we will see how you can employ them to get beautiful slides without having to do the design work yourself.
Do it yourself
Let's start with the absolute minimal effort. What characterises a set of slides? Well, each slide (or PDF page, as we already established) has specific dimensions. Some time ago, a 4:3 format was common, nowadays 16:9 is used more often. Typst has those built in:
#set page(paper: "presentation-16-9")
You probably don't want your audience to carry magnifying glasses, so let's set the font size to something readable from the last row:
#set text(size: 25pt)
We should be ready do go to create some actual slides now.
We will use the function polylux-slide
for this, which is kind of at the core
of this package.
// Remember to actually import polylux before this!
#polylux-slide[
Hello, world!
]
And here is the result (the gray border is not part of the output but it makes
the slide easier to see here):
Already kinda looks like a slide, but also a bit boring, maybe.
We should add a title slide before that so that our audience actually knows what
talk they are attending.
Also, let us choose a nicer font and maybe add some colour?
We modify the #set page
and #set text
commands for that:
#set page(paper: "presentation-16-9", fill: teal.lighten(90%))
#set text(size: 25pt, font: "Blogger Sans")
#polylux-slide[
#set align(horizon + center)
= My fabulous talk
Jane Doe
Conference on Advances in Slide Making
]
Not bad, right? Another thing that is usually a good idea is to have a title on each slide. That is also no big deal by using off-the-shelf Typst features, so let's modify our first slide:
#polylux-slide[
== My slide title
Hello, world!
]
This is starting to look like a real presentation:
So what?
To be honest, everything we did so far would have been just as easy without using polylux at all. So why should you care about it?
Consider the following situation: You have a slide where parts of the content appear or disappear, or the colour of some text changes, or some other small-sized change. Would you like to duplicate the whole slide just so to create this affect? And then maintain multiple copies of the same content, making sure never to forget updating all copies when your content evolves? Of course you wouldn't and, gladly, polylux can handle this for you.
This kind of feature is called dynamic content or overlays (loosely speaking, you might also say animations but that might be a bit of a stretch, nothing actually "moves" on PDF pages).
So how does that work in polylux? As a quick example, let's add a little quiz to our slides:
#polylux-slide[
== A quiz
What is the capital of the Republic of Benin?
#uncover(2)[Porto-Novo]
]
Note how two more slides have been created even though we declared only one.
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:
#import "@preview/polylux:0.3.1": *
#set page(paper: "presentation-16-9", fill: teal.lighten(90%))
#set text(size: 25pt, font: "Blogger Sans")
#polylux-slide[
#set align(horizon + center)
= My fabulous talk
Jane Doe
Conference on Advances in Slide Making
]
#polylux-slide[
== My slide title
Hello, world!
]
#polylux-slide[
== A quiz
What is the capital of the Republic of Benin?
#uncover(2)[Porto-Novo]
]
Dynamic slides
The PDF format does not (trivially) allow to include animations, as one would be used to from, say, Powerpoint. The solution PDF-based presentation slides use is to create multiple PDF pages for one slide, each with slightly different content. This enables us to have some basic dynamic elements on our slides.
In this book, we will use the term logical slide for a section of content that
was created by one call to #polylux-slide
, and subslide for a resulting PDF
page.
Each logical side can have an arbitrary amount of subslides and every subslide
is part of exactly one logical slide.
Note that the same page number is displayed for all subslides of a logical slide.
As you will see soon, the commands for creating dynamic content all have some way of specifying on what subslides some content is supposed to be shown. One of those subslides has the highest index, of course. Of all those commands with their respective highest subslide to show something, the maximum is take again and that defines the number of PDF pages produced for one logical slide. For example, suppose we have a slide with the following commands:
- show something on subslides 1 and 3
- show someting from subslide 2 to subslide 4
- show someting until subslide 6
This results in 6 PDF pages for this logical slide.
In the LaTeX beamer package, the functionalities described in this part are called "overlays".
Everything discussed here works just as well when you use themes. For simplicity, we will work through the material without them, though.
Reserve space or not?
When you want to specify that a certain piece of content should be displayed one some subslides but not on others, the first question should be what should happen on the subslides it is not displayed on. You could either want
- that it is completely not existing there, or
- that it is invisible but it still occupies the space it would need otherwise
(see the docs of the
#hide
function)
The two different behaviours can be achieved using either #only
or #uncover
,
respectively.
The intuition behind it is that, in one case, content is only existing on some
slides, and, in the other case, it is merely covered when not displayed.
General syntax for #only
and #uncover
Both functions are used in the same way. They each take two positional arguments, the first is a description of the subslides the content is supposed to be shown on, the second is the content itself. Note that Typst provides some syntactic sugar for trailing content arguments, namely putting the content block behind the function call.
You could therefore write:
#polylux-slide[
before #only(2)[*displayed only on subslide 2*] after
before #uncover(2)[*uncovered only on subslide 2*] after
]
...resulting in
(Note again that the gray border is not part of the slides and that the labels indicating the index of the subslide are also added afterwards.)
You can clearly see the difference in behaviour between only
and uncover
.
In the first line, "after" moves but not in the second line.
In this example, we specified only a single subslide index, resulting in content that is shown on that exact subslide and at no other one. Let's explore more complex rules next.
Complex display rules
There are multiple options to define more complex display rules than a single number.
Array
The simplest extension is to use an array. For example
#polylux-slide[
#uncover((1, 2, 4))[uncovered only on subslides 1, 2, and 4]
]
results in:
The array elements can actually themselves be any kind of rule that is explained on this page.
Interval
You can also provide a (bounded or half-bounded) interval in the form of a
dictionary with a beginning
and/or an until
key:
#polylux-slide[
#only((beginning: 1, until: 5))[Content displayed on subslides 1, 2, 3, 4, and 5 \ ]
#only((beginning: 2))[Content displayed on subslide 2 and every following one \ ]
#only((until: 3))[Content displayed on subslides 1, 2, and 3 \ ]
#only((:))[Content that is always displayed]
]
results in:
In the last case, you would not need to use #only
anyways, obviously.
Convenient syntax as strings
In principle, you can specify every rule using numbers, arrays, and intervals. However, consider having to write
#uncover(((until: 2), 4, (beginning: 6, until: 8), (beginning: 10)))[polylux]
That's only fun the first time. Therefore, we provide a convenient alternative. You can equivalently write:
#uncover("-2, 4, 6-8, 10-")[polylux]
which results in:
Much better, right? The spaces are optional, so just use them if you find it more readable.
Unless you are creating those function calls programmaticly, it is a good
recommendation to use the single-number syntax (#only(1)[...]
) if that
suffices and the string syntax for any more complex use case.
Higher level helper functions
With #only
and #uncover
you can come a long way but there are some reoccurring
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 #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.
pause
to reveal content piece by piece
Consider some code like the following:
#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.
In Polylux, this works very similar with #pause
, so we can equivalently write
the above code as:
first #pause
second #pause
third
This results in
#pause
should mainly be used when you want to distribute a lot of code onto
different subslides.
Note that it does not affect content in the same paragraph as itself, for example.
For smaller pieces of code, consider one of the functions described next.
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
:
#one-by-one[Do you know ][$pi$ ][to a thousand decimal places?]
resulting in
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
:
#one-by-one(start: 3)[This ][came ][pretty late.]
resulting in
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.
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
#one-by-one[
- first
][
- second
][
- third
]
you can also write
#line-by-line[
- first
- second
- third
]
resulting in
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
.
#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:
#list-one-by-one(marker: [--], tight: false)[first][second][third]
resulting in
As you can see, you can provide any arguments that the
list
function accepts.
#enum-one-by-one
Analogously, there is the same thing for enums, accepting the same arguments as
enum
:
#enum-one-by-one(numbering: "i)", number-align: start)[first][second][third]
resulting in
#terms-one-by-one
And finally we have a function to produce a term list:
#terms-one-by-one(separator: [~---~])[/ first: 1st][/ second: 2nd][/ third: 3rd]
resulting in
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!)
#alternatives
to substitute content
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
something else, then by something else, etc.
Consider this example:
#only(1)[Ann] #only(2)[Bob] #only(3)[Christopher]
likes
#only(1)[chocolate] #only(2)[strawberry] #only(3)[vanilla]
ice cream.
This sentence is a visual reference.
Here, we want to display three different sentences with the same structure: Some person likes some sort of ice cream.
As you can see, the positioning of likes
and ice cream
moves
around in the produced slide because, for example, Ann
takes much less space
than Christopher
when using #only
for that job.
This somewhat disturbs the perception of the constant structure of the sentence
and that only the names and kinds of ice cream change.
To avoid such movement and only subsitute certain parts of content, you can use
the #alternatives
function.
With it, our example becomes:
#alternatives[Ann][Bob][Christopher]
likes
#alternatives[chocolate][strawberry][vanilla]
ice cream.
This sentence is a visual reference.
resulting in
#alternatives
will put enough empty space around, for example, Ann
such that
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:
#alternatives(repeat-last: true)[temporary][transitory][ephemeral][permanent!]
#uncover(5)[Did I miss something?]
]
resulting in
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
you can provide an optional position
argument to #alternatives
that takes an
alignment
or 2d alignment
.
For example:
We know that
#alternatives(position: center + horizon)[$pi$][$sqrt(2)^2 + 1/3$]
is
#alternatives[irrational][rational].
makes the mathematical terms look better positioned:
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:
#alternatives-match((
"1, 3-5": [this text has the majority],
"2, 6": [this is shown less often]
))
resulting in
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
#alternatives-match((
"1, 3" : [
Some text
],
"2" : [
#set text(fill: teal)
Some text
],
))
you can avoid duplication and write
#alternatives-cases(("1, 3", "2"), case => [
#set text(fill: teal) if case == 1
Some text
])
using a function that maps the current "case" to content, resulting in
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:
#alternatives-fn(start: 2, count: 7, subslide => {
numbering("(i)", subslide)
})
resulting in
Cover mode
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
"transparent"
.
(This terminology is taken from LaTeX beamer as well.)
With mode: "transparent"
, text is printed in a light gray.
Use it as follows:
#uncover(3, mode: "transparent")[abc]
#one-by-one(start: 2, mode: "transparent")[def ][ghi]
#line-by-line(mode: "transparent")[
- jkl
- mno
]
#enum-one-by-one(mode: "transparent", tight: false)[pqr][stu][vwx]
resulting in
Warning! The transparent mode really only wraps the covered content in a
#text(fill: gray.lighten(50%)[...]
so it has only limited control over the actual display. Especially
- text that defines its own color (e.g. syntax highlighting),
- visualisations,
- images
will not be affected by that. This makes the transparent mode only somewhat useful today. (Relevant GitHub issue)
Handout mode
If you distribute your slides after your talk for further reference, you might
not want to keep in all the dynamic content.
Imagine using one-by-one
on a bullet point list and readers having to scroll
through endless pages when they just want to see the full list.
You can use #enable-handout-mode(true)
at the top of your code to achieve this:
It has the effect that all dynamic visibility of elements that reserve space is switched off. For example,
#enable-handout-mode(true)
// ...
#polylux-slide[
Some text.
#uncover("3-")[You cannot always see this.]
...Or can you?
]
becomes:
Note that only
and alternatives
are not affected as there is no obvious
way to unify their content to one slide.
Internals
Here, topics regarding the internal implementation of dynamic content in polylux is discussed. Usually, you can completely ignore this section.
Internal number of repetitions
TL;DR:
For slides with more than ten subslides, you need to set the max-repetitions
argument of the #polylux-slide
function to a higher number.
For technical reasons (this might change in the future when we find a better
solution), producing PDF pages for subslides is implemented in the following way:
Each dynamic element, such as #only
or #beginning
"knows" how many subslides a
logical slide must have for it to "make sense".
For example, a #beginning(5)[...]
only makes sense if at least 5 subslides are
produced.
Internally, when typesetting a slide, we now look at all the dynamic elements in
it and find the maximum of those individual "required" subslide counts.
So if a slide contains a #only(2)[...]
, a #until(4)[...]
, and nothing else,
we know that exactly 4 subslides are necessary.
However, we only acquire this knowledge after the first subslide has been produced, i.e. when all of the slide's content has been "looked at" once. This is why we cannot simply implement something like "produce 4 pages by iterating this loop 4 times". Instead, the (admittedly hacky) solution is to iterate "very often" and check in each iteration if we still need to produce another page. This works because we always need to produce at least one page for a slide, so we can unhurriedly inspect all dynamic elements and find the maximum subslide count at the first iteration. After that, we have the information we need.
Now, the question arises how often "very often" should be. This requires a trade-off: Iterating too few times (say, twice) will lead to frequent situations where we ignore dynamic behaviour that was supposed to happen in later subslides (say, in the third). Iterating, say, a thousand times means that we will practically never encounter such situations but we now perform a thousand iterations per slide. Especially when you want to see a live update of your produced PDF as you type, this leads to severe lagging that somewhat defeats the purpose of Typst's speed. (Still faster than LaTeX, though...)
It appears reasonable that occasions are rare where one needs more than ten subslides for one slide. Therefore, ten is the default value for how often we try to create a new subslide. This should not produce noticable lag. (If it does for you, consider creating an issue so we can discuss this.)
For those hopefully rare occasions where you do, in fact, need more than ten
subslides, you can manually increase this number using the max-repetitions
argument of the #slide
function:
#polylux-slide(max-repetitions: 20)[
This is gonna take a while:
#uncover(20)[I can wait...]
]
Again, use this feature sparingly, as it decreases typesetting performance.
Themes
As we have already discussed, you can use polylux completely without using themes. For most users, themes will simplify their preparation of decent slides quite a bit, however. So let's take a look at how they work.
It is important to note that polylux does not define a specific way a theme has to work. Similarly, if you define your own slide layout, don't feel obliged to do it in any way related to how the provided themes do it. To improve the user experience and avoid having to learn everything anew when switching to another theme, the existing themes all follow a certain convention, though.
The theme convention
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:
#import "@preview/polylux:0.3.1": *
#import themes.simple: *
Next, a theme usually provides some initialisation function that has a name
ending in -theme
.
It is supposed to be used in a #show: ...
rule, i.e. (again for the simple
theme):
#show: simple-theme.with(...)
Inside the with()
, you can set some theme-specific configuration.
Here you find options that concern the whole presentation, such as the aspect
ratio of your slides.
Speaking of which, when you use a theme, you do not have to set the paper size
yourself anymore, such things are handled by the theme (the convention is that
every theme has an aspect-ratio
keyword for its initialisation function that
can be set to "16-9"
or "4-3"
).
The other major feature of themes is that they usually come with custom slide
functions.
That means that you will not use the #polylux-slide
function!
It is called under the hood by the wrapper functions from the theme.
To be more accurate:
Nothing stops you from still calling #polylux-slide
and you can always build
something custom along the "regular" theme-slides if you are not satisfied with
what a theme offers you.
It's just that you usually will not have to do this.
The range of theme-specific slide functions varies from theme to theme but there
is again one convention:
A theme usually has a #title-slide
function for, well, the title slide and a
#slide
function that you will use for "normal" slides.
Each of these functions might accept some keyword arguments and/or one or multiple content blocks to define what is supposed to be seen on the slide.
Theme gallery
If you decide to use one of the themes shipped with polylux, you can use this gallery to get to know how each theme works and if you like how it looks. All themes follow the convention introduced on the previous page if not stated otherwise. For many things, most themes are interchangable, meaning that you can simply change what theme you import and its initialisation and everything still works. However, this is not guaranteed and might break at any point.
Simple theme
This theme is rather unobstrusive and might still be considered bare-bones. It uses a minimal amount of colour and lets you define your slides' content very freely.
Use it via
#import "@preview/polylux:0.3.1": *
#import themes.simple: *
#show: simple-theme.with(...)
simple
uses regular headings for sections.
Unless specified otherwise, first level headings create new sections.
The regular #outline
is configured such that it lists the names of all sections.
Second level headings are supposed to be used as slide titles and introduce some spacing below them.
Text is configured to have a base font size of 25 pt.
Options for initialisation
simple-theme
accepts the following optional keyword arguments:
aspect-ratio
: the aspect ratio of the slides, either"16-9"
or"4-3"
, default is"16-9"
footer
: text to display in the footer of every slide, default is[]
background
: background colour, default iswhite
foreground
: text colour, default isblack
Slide functions
simple
provides the following custom slide functions:
#centered-slide[
...
]
A slide where the content is positioned in the center of the slide. Not suitable for content that exceeds one page.
#title-slide[
...
]
Same as centered-slide
but makes heading of level 1 not outlined, so that the
presentation title does not show up in the outline.
Not suitable for content that exceeds one page.
#slide[
...
]
Decorates the provided content with a header containing the current section (if any) and a footer containing some custom text and the slide number.
#focus-slide(foreground: ..., background: ...)[
...
]
Draw attention with this variant where the content is displayed centered and text
is enlarged.
Optionally accepts a foreground colour (default: white
) and background color
(default: aqua.darken(50%)
).
Not suitable for content that exceeds one page.
Example code
The image at the top is created by the following code:
#import "@preview/polylux:0.3.1": *
#import themes.simple: *
#set text(font: "Inria Sans")
#show: simple-theme.with(
footer: [Simple slides],
)
#title-slide[
= Keep it simple!
#v(2em)
Alpha #footnote[Uni Augsburg] #h(1em)
Bravo #footnote[Uni Bayreuth] #h(1em)
Charlie #footnote[Uni Chemnitz] #h(1em)
July 23
]
#slide[
== First slide
#lorem(20)
]
#focus-slide[
_Focus!_
This is very important.
]
#centered-slide[
= Let's start a new section!
]
#slide[
== Dynamic slide
Did you know that...
#pause
...you can see the current section at the top of the slide?
]
Clean theme
This theme is a bit more opinionated than the simple
theme but still supposed
to be an off-the-shelf solution that fits many use cases.
Use it via
#import "@preview/polylux:0.3.1": *
#import themes.clean: *
#show: clean-theme.with(...)
clean
uses polylux' section handling, the regular #outline()
will not work
properly, use #polylux-outline()
instead.
Text is configured to have a base font size of 25 pt.
Options for initialisation
clean-theme
accepts the following optional keyword arguments:
aspect-ratio
: the aspect ratio of the slides, either"16-9"
or"4-3"
, default is"16-9"
footer
: text to display in the footer of every slide, default is[]
short-title
: short form of the presentation title, to be displayed on every slide, default:none
logo
: some content (most likely an image) used as a logo on every slide, default:none
color
: colour of decorative lines, default:teal
Slide functions
clean
provides the following custom slide functions:
#title-slide(...)
Creates a title slide where title and subtitle are put between decorative lines, along with logos and author and date infos. Accepts the following keyword arguments:
title
: title of the presentation, default:none
subtitle
: subtitle of the presentation, default:none
authors
: authors of presentation, can be an array of contents or a single content, will be displayed in a grid, default:()
date
: date of the presentation, default:none
watermark
: some content (most likely an image) used as a watermark behind the titlepage, default:none
secondlogo
: some content (most likely an image) used as a second logo opposite to the regular logo on the title page, default:none
Does not accept additional content.
#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.
Pass the slide title as a keyword argument title
.
Accepts the following keyword arguments:
title
: title of the slide, default:none
,
#focus-slide(foreground: ..., background: ...)[
...
]
Draw attention with this variant where the content is displayed centered and text
is enlarged.
Optionally accepts a foreground colour (default: white
) and background color
(default: teal
).
Not suitable for content that exceeds one page.
#new-section-slide(name)
Start a new section with the given name
(string or content, positional argument).
Creates a slide with this name in the center, a decorative line below, and
nothing else.
Use #polylux-outline()
to display all sections, similarly to how you would use
#outline()
otherwise.
Example code
The image at the top is created by the following code:
#import "@preview/polylux:0.3.1": *
#import themes.clean: *
#set text(font: "Inria Sans")
#show: clean-theme.with(
footer: [Author, institution],
short-title: [Short title],
logo: image("dummy-logo.png"),
)
#title-slide(
title: [Presentation title],
subtitle: [Presentation subtitle],
authors: ([Author A], [Author B], [Author C]),
date: [January 1, 1970],
watermark: image("dummy-watermark.png"),
)
#slide(title: [First slide title])[
#lorem(20)
]
#new-section-slide("The new section")
#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[
_Focus!_
This is very important.
]
Metropolis theme
This theme is inspired by the Metropolis beamer theme, created by Matthias Vogelgesang.
Use it via
#import "@preview/polylux:0.3.1": *
#import themes.metropolis: *
#show: metropolis-theme.with(...)
metropolis
uses polylux' section handling, the regular #outline()
will not work
properly, use #metropolis-outline
instead.
Options for initialisation
metropolis-theme
accepts the following optional keyword arguments:
aspect-ratio
: the aspect ratio of the slides, either"16-9"
or"4-3"
, default is"16-9"
footer
: text to display in the footer of every slide, default is[]
Slide functions
metropolis
provides the following custom slide functions:
#title-slide(...)
Creates a title slide where title and subtitle are separated by additional information by a bright line. Accepts the following keyword arguments:
title
: title of the presentation, default:[]
subtitle
: subtitle of the presentation, default:none
author
: author of presentation, can be arbitrary contet, default:none
date
: date of the presentation, default:none
extra
: some extra information, can be used for affiliation, instiution, logos, etc., default:none
Does not accept additional content.
#slide(...)[
...
]
Decorates the provided content with a header containing the slide title and a footer containing some custom text and the slide number.
Pass the slide title as a keyword argument title
(default: none
).
#focus-slide[
...
]
Draw attention with this variant where the content is displayed centered and text is enlarged and bright. Uses the background colour of the title on regular slides as the background colour for the whole slide. Not suitable for content that exceeds one page.
#new-section-slide(name)
Start a new section with the given name
(string or content, positional argument).
Creates a slide with this name in the center, a progress bar indicating the
current progress of the presentation below, and nothing else.
Use #metropolis-outline
to display all sections, similarly to how you would use
#outline()
otherwise.
Additional features
metropolis
provides a further way to highlight text besids #emph
and #strong
,
namely #alert
(as known from LaTex's beamer).
#alert[very important]
prints its content in a bright colour.
There is also #metropolis-outline
which customises #polylux-outline
and
displays a table of contents with all sections.
Example code
The image at the top is created by the following code:
#import "@preview/polylux:0.3.1": *
#import themes.metropolis: *
#show: metropolis-theme.with(
footer: [Custom footer]
)
#set text(font: "Fira Sans", weight: "light", size: 20pt)
#show math.equation: set text(font: "Fira Math")
#set strong(delta: 100)
#set par(justify: true)
#title-slide(
author: [Authors],
title: "Title",
subtitle: "Subtitle",
date: "Date",
extra: "Extra"
)
#slide(title: "Table of contents")[
#metropolis-outline
]
#slide(title: "Slide title")[
A slide with some maths:
$ x_(n+1) = (x_n + a/x_n) / 2 $
#lorem(200)
]
#new-section-slide("First section")
#slide[
A slide without a title but with #alert[important] infos
]
#new-section-slide([Second section])
#focus-slide[
Wake up!
]
University theme
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.
Use it via
#import "@preview/polylux:0.3.1": *
#import themes.university: *
#show: university-theme.with(...)
university
uses polylux' section handling, the regular #outline()
will not work
properly, use #polylux-outline
instead.
Starting a new section is done by the corresponding keyword argument of #slide
.
Text is configured to have a base font size of 25 pt.
Options for initialisation
university-theme
accepts the following optional keyword arguments:
aspect-ratio
: the aspect ratio of the slides, either"16-9"
or"4-3"
, default is"16-9"
short-title
: short title of the presentation to display on every slide, default:none
short-author
: short author of the presentation to display on every slide, default:none
short-date
: short date of presentation to display on every slide, default:none
color-a
: main colour of decorations, default:rgb("#0C6291")
color-b
: accent colour, default:rgb("#A63446")
color-c
: second accent colour, default:rgb("#FBFEF9")
progress-bar
: boolean value whether or not to display a progress bar on regular sides, default:true
Slide functions
university
provides the following custom slide functions:
#title-slide(...)
Creates a title slide where title and subtitle are separated by additional information by a bright line. Accepts the following keyword arguments:
title
: title of the presentation, default:[]
subtitle
: subtitle of the presentation, default:none
authors
: authors of presentation, can be an array of contents or a single content, will be displayed in a grid, default:()
date
: date of the presentation, default:none
institution-name
: name of the institution, default:"University"
logo
: some content (most likely an image) used as a logo on the title slide, default:none
Does not accept additional content.
#slide(...)[
...
]
Decorates the provided content with a header containing a progress bar (optionally), 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.
Pass the slide title as a keyword argument title
.
Accepts the following keyword arguments:
title
: title of the slide, default:none
,header
: custom content to overwrite default headerfooter
: custom content to overwrite default footernew-section
: name of the new section that starts here if notnone
, default:none
#focus-slide(background-img: ..., background-color: ...)[
...
]
Draw attention with this variant where the content is displayed centered and text
is enlarged and bright.
You can either specify a background image or a background colour as keyword
arguments.
If you specify none of them, a background colour of rgb("#0C6291")
is used as
a default.
Not suitable for content that exceeds one page.
#matrix-slide(columns: ..., rows: ...)[
...
][
...
]
Create a slide where the provided content blocks are displayed in a grid and
coloured in a checkerboard pattern without further decoration.
You can configure the grid using the rows
and columns
keyword arguments
(both default to none
).
It is determined in the following way:
- If
colmuns
is an integer, create that many columns of width1fr
. - If
columns
isnone
, create as many columns of width1fr
as there are content blocks. - Otherwise assume that
columns
is an array of widths already, use that. - If
rows
is an integer, create that many rows of height1fr
. - If
rows
isnone
create that many rows of height1fr
as are needed given the number of content blocks and columns. - Otherwise assume that
rows
is an array of heights already, use that. - Check that there are enough rows and columns to fit in all the content blocks.
That means that #matrix-slide[...][...]
stacks horizontally and
#matrix-slide(columns: 1)[...][...]
stacks vertically.
Not suitable for content that exceeds one page.
Example code
The image at the top is created by the following code:
#import "@preview/polylux:0.3.1": *
#import themes.university: *
#show: university-theme.with(
short-author: "Short author",
short-title: "Short title",
short-date: "Short date",
)
#title-slide(
authors: ("Author A", "Author B"),
title: "Title",
subtitle: "Subtitle",
date: "Date",
institution-name: "University Name",
logo: image("dummy-logo.png", width: 60mm)
)
#slide(title: [Slide title], new-section: [The section])[
#lorem(40)
]
#focus-slide(background-img: image("background.svg"))[
*Another variant with an image in background...*
]
#matrix-slide[
left
][
middle
][
right
]
#matrix-slide(columns: 1)[
top
][
bottom
]
#matrix-slide(columns: (1fr, 2fr, 1fr), ..(lorem(8),) * 9)
Bipartite theme
This theme is inspired by Modern Annual Report. and a bit more opinionated. It features a dominant partition of space into a bright and a dark side and is rather on the "artsy" than functional side.
Use it via
#import "@preview/polylux:0.3.1": *
#import themes.bipartite: *
#show: bipartite-theme.with(...)
The bipartite
theme cannot display content that exceeds one page, in general.
Note that, against the convention, bipartite
offers no #slide
function.
Use either #west-slide
or #east-slide
for regular content.
Also, this theme features no sections or slide numbers.
Options for initialisation
bipartite-theme
accepts the following optional keyword arguments:
aspect-ratio
: the aspect ratio of the slides, either"16-9"
or"4-3"
, default is"16-9"
Slide functions
bipartite
provides the following custom slide functions:
#title-slide(...)
Displays presentation title on a large bright portion above the subtitle, the author and the date. If a date was given, separates it from the author by a central dot. Accepts the following keyword arguments:
title
: title of the presentation, default:[]
subtitle
: subtitle of the presentation, default:none
author
: author of presentation, arbitrary content, default:[]
date
: date of the presentation, default:none
Does not accept additional content.
#west-slide(title: ...)[
...
]
Splits the slide into a larger bright section on the right where the content goes and a smaller, darker, left section where the title is displayed. Everything is left aligned.
#east-slide(title: ...)[
...
]
Same as #west-slide
but with the title and the content switching places, and
everything being right aligned.
#split-slide[
...
][
...
]
Splits the slide into two equal sections on the left and the right that both
contain content (#split-slide
requires exactly two content blocks to be passed).
The left half is dark text on a bright background and right aligned, the right
half is bright text on dark background and left aligned.
Does not display a slide title.
Example code
The image at the top is created by the following code:
#import "@preview/polylux:0.3.1": *
#import themes.bipartite: *
#show: bipartite-theme
#set text(size: 25pt)
#title-slide(
author: [Author],
title: [Title],
subtitle: [Subtitle],
date: [Date],
)
#west-slide(title: "A longer slide title")[
#lorem(40)
]
#east-slide(title: "On the right!")[
#lorem(40)
]
#split-slide[
#lorem(40)
][
#lorem(40)
]
Build your own theme
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!), we kindly ask you to follow the convention presented before. In any case, it is probably a good idea to take that as an orientation.
To demonstrate how one would go about defining a custom theme, let us create one together! How about we make one for science slams? If you have ever been to one, you might have noticed that the presenters there love sparse dark-mode slides with huge font sizes.
Imports
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:
#import "@preview/polylux:0.3.1": *
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 utils/utils.typ
.
As your theme file science-slam.typ
will be inside the themes
directory, the
imports will be:
#import "../logic.typ"
#import "../utils/utils.typ"
Additionally, you have to make polylux know about your theme which you do by adding
#import "science-slam.typ"
to themes/themes.typ
.
The initialisation function
With that out of the way, we start with the function that sets the scene for
everything else to come.
By convention, we call it science-slam-theme
and it can accept some keyword
arguments along a single content argument.
The keyword arguments are for configuration options, the content is for the rest
of the document (read here
if you are unfamiliar with this kind of function, this feature is rather unique
to Typst).
#let science-slam-theme(aspect-ratio: "16-9", darkness: "dark", body) = {
let background-color = if darkness == "dark" {
navy
} else if darkness == "very dark" {
navy.darken(50%)
} else if darkness == "ultra dark" {
black
} else {
panic("illegal darkness, must be one of dark, very dark, ultra dark")
}
set page(
paper: "presentation-" + aspect-ratio,
fill: background-color,
)
set text(fill: white.darken(10%), size: 40pt, font: "Fira Sans")
body
}
As you can see, we have two configuration options:
One for the aspect ratio (as is convention) and one to determine the background
colour — the more serious you are, the darker your background colour is, of course.
After we have set the page
parameters accordingly, we also define the text to
be huge, bright and in a sans serif font.
Using it looks like this:
#show: science-slam-theme.with(darkness: "very dark")
Title slide
Next up, let us define a cool title slide.
The only thing you have to keep in mind when defining your own slide functions
is that you need to put the content you produce into the #polylux-slide
function in the end.
It might look as if it works without that as well but it actually breaks when you
use #uncover
or similar polylux features.
If you build a theme as part of polylux and you have followed the import
instructions from above, you will qualify the function as logic.polylux-slide
.
Our title slide here is very simple, it just makes very sure to let the audience know what the topic is and who is speaking:
#let title-slide(title: [], author: []) = {
polylux-slide({
set align(center + horizon)
smallcaps(strong(title))
parbreak()
text(size: .7em, author)
})
}
You can use it like this:
#title-slide(title: [My awesome topic], author: [A really funny guy])
Note that the user does not actually provide any content themselves to this function. That is a common thing for title slides because their structure is so well-defined that a theme can produce all the content by just asking for a few pieces of information (like the title etc.).
Regular slides
The principle is the same as with the title slide.
Define a function, create some content, pass it to polylux-slide
.
By convention, you should name the function for regular slides slide
because
it will be used most often.
Here you will typically accept arbitrary content as a positional parameter that
will make up the main content of the slide.
For example:
#let slide(title: [], body) = {
polylux-slide({
strong(title)
set align(horizon)
body
})
}
And you can use it like this:
#slide(title: "A hilarious slide")[
You didn't expect that!
]
In case you wondered, this is how the theme and the slides we just put together look like:
Any number of further variants
Be creative! There are no limits but your own to the slide functions your theme can contain once you grasped the simple structure. For "serious" themes (other than this demo) you will probably want to think about adding headers, footers, slide numbers etc. Why not look into the source code of existing themes to get some inspiration?
The next page also lists some small tools that polylux provides to make common tasks simpler when creating a slide.
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
#import "@preview/polylux:0.3.1": *
you can direcly invoke them using utils
and logic
.
As a theme author, you have access to them due to the imports
#import "../logic.typ"
#import "../utils/utils.typ"
(see previous page).
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
#import "@preview/polylux:0.3.1": *
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
but tailored
towards this specific usecase.
In its simplest form, you can use it as
#side-by-side[
#lorem(7)
][
#lorem(10)
][
#lorem(5)
]
resulting in
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
and columns
arguments).
If not specified, they fall back to these defaults:
gutter
:1em
columns
:(1fr,) * n
if you providedn
content arguments, that means an array with the value1fr
repeatedn
times.
A more complex example would therefore be:
#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)
]
resulting in
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:
#fit-to-height(1fr)[BIG]
resulting in
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 usewidth: 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 usingprescale-width: 200%
, for example.
We can illustrate that using the following example:
#fit-to-height(5cm, prescale-width: 300%, width: 50%)[
#set par(justify: true)
#lorem(200)
]
resulting in
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
#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
#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:
#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.
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
#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:
#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:
#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 toenum
as keyword arguments, for exampleenum-args: (tight: false)
, default:(:)
padding
: pass something thatpad
accepts, will be used to pad theenum
, default:0pt
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.
pdfpc
pdfpc 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:
#pdfpc.speaker-note("This is a note that only the speaker will see.")
or a raw
block:
#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 adatetime(hour: ..., minute: ..., second: ...)
or as a string in theHH:MM
format -
end-time
: same asstart-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), defaultfalse
-
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 ofltr
,rtl
,ttb
, andbtt
(see the#stack
function)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 installed, you can simply run
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
(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
typst compile --root .. thesis/talk.typ
and extract the pdfpc data using
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:
pdfpc thesis/talk.pdf
and it will automatically recognise the .pdfpc
file.
Changelog
v0.3.0
- The previously existing module
helpers
was transformed toutils
and now contains many more useful features. - The modules
logic
andutils
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
, andalternatives-fn
. Also, there is a parameterrepeat-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 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
for the inspiration and for creating the
polylux2pdfpc
AUR package.