Touying (投影 in chinese, /tóuyǐng/, meaning projection) is an object-oriented and efficient package for creating presentation slides in Typst.
Touying is a package derived from Polylux. Therefore, most concepts and APIs remain consistent with Polylux. You can refer to the Polylux documentation for a better understanding of Touying.
Compared to Polylux, Touying employs a more object-oriented writing style, capable of simulating a mutable global singleton. So, Touying can conveniently access and update “global variables”, such as the ‘handout-mode’ boolean variable. At the same time, you can easily retrieve and modify page parameters by self.page-args
, avoiding the side effects of creating a new page caused by #set page(..)
.
Additionally, Touying does not rely on locate
and counter
for implementing #pause
, thus offering better performance, albeit with certain limitations. The advantage is that you can use #pause
inline. The drawback is that #pause
after set-show rule won’t take effect (you must use set-show rule in the setting
parameter). Currently, #pause
only works at the outermost level, and it won’t work inside layout functions like grid, but you can use the composer
parameter to add yourself layout function like utils.side-by-side
.
Warning: It is under development, and the API may change at any time.
Implemented Features
- [x] Object-oriented programming: Singleton
s
, binding methodsutils.methods(s)
and(self: obj, ..) => {..}
methods. - [x] Page arguments management: Instead of using
#set page(..)
, you should useself.page-args
to retrieve or set page parameters, thereby avoiding unnecessary creation of new pages. - [x]
#pause
for sequence content: You can use #pause at the outermost level of a slide, including inline and list. - [x]
#pause
for layout functions: You can use thecomposer
parameter to add yourself layout function likeutils.side-by-side
, and simply use multiple pos parameters like#slide[..][..]
. - [x] Callback-style
uncover
,only
andalternatives
: Based on the concise syntax provided by Polylux, allow precise control of the timing for displaying content.- You should manually control the number of subslides using the
repeat
parameter.
- You should manually control the number of subslides using the
- [x] Transparent cover: Enable transparent cover using oop syntax like
#let s = (s.methods.enable-transparent-cover)(self: s)
. - [x] Handout mode: enable handout mode by
#let s = (s.methods.enable-handout-mode)(self: s)
. - [x] Fit-to-width and fit-to-height: Fit-to-width for title in header and fit-to-height for image.
utils.fit-to-width(grow: true, shrink: true, width, body)
utils.fit-to-height(width: none, prescale-width: none, grow: true, shrink: true, height, body)
- [x] Slides counter:
states.slide-counter.display() + " / " + states.last-slide-number
andstates.touying-progress(ratio => ..)
. - [x] Appendix: Freeze the
last-slide-number
to prevent the slide number from increasing further. - [x] Sections: Touying’s built-in section support can be used to display the current section title and show progress.
- [x]
states.new-section(section)
to register a new section. - [x]
states.current-section
to get the current section. - [x]
states.touying-outline
ors.methods.touying-outline
to display a outline of sections. - [x]
states.touying-final-sections(sections => ..)
for custom outline display. - [x]
states.touying-progress-with-sections((current-sections: .., final-sections: .., current-slide-number: .., last-slide-number: ..) => ..)
for powerful progress display.
- [x]
- [x] Pdfpc: pdfpc support and export
.pdfpc
file without external tool bytypst query
command simply.
Features to Implement
- [ ] More themes: Add more themes.
- [ ] Combinable components: Combinable components for header, footer and sidebar, .
- [ ] Navigation bar: Navigation bar like here by
states.touying-progress-with-sections(..)
. - [ ] Document: Add a more detailed document.
- [ ] External viewers: Integration with external viewers like impress.js and typst-preview.
Feel free to suggest any ideas and contribute.
Dynamic slides
We can export example.pdfpc
file by command typst query --root . ./examples/example.typ --field value --one "<pdfpc-file>" > ./examples/example.pdfpc
#import "@preview/touying:0.1.0": s, pause, utils, states, pdfpc, themes
#let s = themes.metropolis.register(s, aspect-ratio: "16-9", footer: [Custom footer])
#let s = (s.methods.enable-transparent-cover)(self: s)
#let s = (s.methods.append-preamble)(self: s, pdfpc.config(
duration-minutes: 30,
start-time: datetime(hour: 14, minute: 10, second: 0),
end-time: datetime(hour: 14, minute: 40, second: 0),
last-minutes: 5,
note-font-size: 12,
disable-markdown: false,
default-transition: (
type: "push",
duration-seconds: 2,
angle: ltr,
alignment: "vertical",
direction: "inward",
),
))
// #let s = (s.methods.enable-handout-mode)(self: s)
#let (init, slide, touying-outline) = utils.methods(s)
#show: init
// simple animations
#slide[
a simple #pause dynamic
#pause
slide.
][
second #pause pause.
]
// complex animations
#slide(setting: body => {
set text(fill: blue)
body
}, repeat: 3, self => [
#let (uncover, only, alternatives) = utils.methods(self)
in subslide #self.subslide
test #uncover("2-")[uncover] function
test #only("2-")[only] function
#pause
and paused text.
])
// multiple pages for one slide
#slide[
#lorem(200)
test multiple pages
]
// appendix by freezing last-slide-number
#let s = (s.methods.appendix)(self: s)
#let (slide,) = utils.methods(s)
#slide[
appendix
]
Themes
#import "@preview/touying:0.1.0": s, pause, utils, states, pdfpc, themes
#let s = themes.metropolis.register(s, aspect-ratio: "16-9", footer: [Custom footer])
#let s = (s.methods.enable-transparent-cover)(self: s)
#let (init, slide, title-slide, new-section-slide, focus-slide, touying-outline, alert) = utils.methods(s)
#show: init
#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])[
#touying-outline()
]
#slide(title: [A long long long long long long long long long long long long long long long long long long long long long long long long 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!
]
// simple animations
#slide[
a simple #pause dynamic slide with #alert[alert]
#pause
text.
]
// appendix by freezing last-slide-number
#let s = (s.methods.appendix)(self: s)
#let (slide, new-section-slide) = utils.methods(s)
#new-section-slide[Appendix]
#slide[
appendix
]
Acknowledgements
Thanks to…
- @andreasKroepelin for the
polylux
package - @Enivex for the
metropolis
theme - @ntjess for contributing to
fit-to-height
,fit-to-width
andcover-with-rect