
Touying Author
- 79 installs
- 189 repo stars
- Updated July 8, 2026
- apcamargo/typst-skills
Touying Author is a skill that guides authoring, refactoring, and troubleshooting Typst presentation slide decks built with the Touying package.
About
Touying Author guides authoring, refactoring, and troubleshooting of Typst presentation slide decks built with the Touying package. A developer uses it to structure decks with centralized config-* calls, create slides from headings, add pause/uncover animations, and organize single- or multi-file projects. It bundles theme examples and documents the Touying export API.
- Authors and refactors Typst slide decks built with the Touying package
- Covers config-* API, headings-as-slides, animations, and multi-file deck structure
- Ships example decks for simple, metropolis, dewdrop, aqua, university, and stargazer themes
Touying Author by the numbers
- 79 all-time installs (skills.sh)
- Ranked #694 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
touying-author capabilities & compatibility
- Capabilities
- typst author · presentation authoring
- Use cases
- presentations · documentation
What touying-author says it does
Author, refactor, and troubleshoot Typst slide decks built with Touying
Guide Typst presentation authoring with Touying, emphasizing clean structure, repeatable configuration, and slide-safe patterns.
npx skills add https://github.com/apcamargo/typst-skills --skill touying-authorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 79 |
|---|---|
| repo stars | ★ 189 |
| Last updated | July 8, 2026 |
| Repository | apcamargo/typst-skills ↗ |
What it does
Author and refactor Typst slide decks with the Touying package using clean config-driven structure, headings-as-slides, and animation patterns.
Who is it for?
Writing and structuring Typst slide decks with the Touying package
When should I use this skill?
when authoring, refactoring, or troubleshooting a Typst presentation built with Touying
What you get
A cleanly structured Touying deck using centralized config-* calls and heading-driven slides
- structured Typst slide deck
- Touying theme configuration
By the numbers
- 7 bundled theme examples (simple, default, metropolis, dewdrop, aqua, university, stargazer)
Files
Touying Author
Guide Typst presentation authoring with Touying, emphasizing clean structure, repeatable configuration, and slide-safe patterns.
Quick start
- Import Touying and a theme, then apply the theme with
#show: <theme>.with(...). - Keep configuration centralized; include slide content from separate files.
- Use headings to create slides; use
#slidefor custom layouts or animations. - Start from
examples/simple.typfor a minimal deck, orexamples/default.typfor the bare theme.
Snippet from examples/simple.typ:
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(
aspect-ratio: "16-9",
footer: [Simple slides],
)Best practices and code structure
- Use a single entry file (e.g.,
main.typ) that applies#showand allconfig-*calls. - For multi-file decks, follow the
globals.typ+main.typ+content.typpattern; use#includefor content and#importfor globals to avoid circular refs. - Docs use both
config.typ(seedocs/start.md) andglobals.typ(seedocs/multi-file.md) for the shared config file; pick one name and use it consistently in your project. - For large decks, move
content.typintosections/and includesections/content.typ(and optionalsections/another-section.typ) frommain.typ. - Prefer
config-page,config-common,config-info,config-colors,config-methods, andconfig-storeover directset pageor ad-hoc globalshow. - Use headings for most slides; use
config-common(slide-level: n)to choose which heading levels create slides. - Use
#slideonly for custom layout or animation; always wrap slide functions withtouying-slide-wrapper. - For callback-style animation (
#slide(repeat: n, self => [...])), setrepeatexplicitly and useutils.methods(self)to accessuncover,only, andalternatives.
Single-file structure
.
├── globals.typ
├── main.typ
└── content.typMulti-file structure
.
├── globals.typ
├── main.typ
└── sections/
├── content.typ
└── another-section.typCore API map (exports)
- Slides:
touying-slides,slide,touying-slide,touying-slide-wrapper,empty-slide - Dynamics:
pause,meanwhile,uncover,only,effect,alternatives,alternatives-match,alternatives-fn,alternatives-cases - Config:
config-common,config-page,config-info,config-colors,config-methods,config-store,default-config,touying-set-config,appendix - Utilities:
utils.*(fit-to-height, fit-to-width, cover helpers, progress, heading helpers) - Components:
components.side-by-side,components.adaptive-columns,components.progressive-outline,components.custom-progressive-outline - Integrations:
touying-reducer,touying-equation,touying-mitex,speaker-note,pdfpc.* - Recall:
touying-recall,touying-fn-wrapper
Slide structure and headings
- Use heading labels to control numbering/outline/bookmarks: use
<touying:hidden>(seedocs/code-styles.md), and seedocs/changelog.mdfor<touying:unnumbered>,<touying:unoutlined>,<touying:unbookmarked>, and<touying:skip>. - Use
components.adaptive-columns(outline(...))for a table of contents slide; usecomponents.progressive-outlinefor progress-aware outlines. - Use
== <touying:hidden>to insert a blank title slide and clear the previous heading context. - Use
#pagebreak()or---to split slides without changing headings. - Use
#empty-slide[...]for slides without header/footer. - Use
config-common(handout: true)to keep only the last subslide per slide in handout output. - Use
#show: appendixto freeze last-slide counts after the main deck.
Snippet from examples/default.typ:
= Outline <touying:hidden>
#components.adaptive-columns(outline(title: none, indent: 1em))Animations and dynamic content
- Use
#pauseand#meanwhilefor simple reveals; avoid using them insidecontextwhere marks are not supported. - Use
#uncoverto reserve layout space; use#onlyto remove layout space when hidden. - When marks cause warnings, use callback-style slides with
repeatandutils.methods(self). - For math animations, use
pause/meanwhileinside$ ... $; usetouying-equationwhen you need the helper for inline equation text (you can also use#pauseor#pause;). - See
examples/example.typfor simple/complex/callback animations and equation animations.
Snippet from examples/simple.typ:
== Dynamic slide
Did you know that...
#pause
...you can see the current section at the top of the slide?Snippet from examples/example.typ:
#slide(
repeat: 3,
self => [
#let (uncover, only, alternatives) = utils.methods(self)
At subslide #self.subslide, we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.
],
)Layout and theming
- Use
composerin#slidefor columns; pass(1fr, 2fr)or a customgridfunction. - Use
config-pagefor margin, header, footer, and background; do not useset pagedirectly. - Use
config-methods(cover: utils.semi-transparent-cover.with(alpha: 85%))only when layout changes are acceptable. - Use
config-storeto stash theme-specific values for headers/footers or navigation elements. - Use
config-common(new-section-slide-fn: none)to disable automatic section slides, or provide a custom function to replace them. - Apply global styles either around
#show: <theme>.with(...)or viaconfig-methods(init: ...)in a theme. - Use
config-info(...)for title/author/date andconfig-common(datetime-format: ...)to control date formatting.
Snippet from examples/default.typ:
#import "@preview/touying:0.6.1": *
#import themes.default: *
#import "@preview/numbly:0.1.0": numbly
#show: default-theme.with(
aspect-ratio: "16-9",
config-common(
slide-level: 3,
zero-margin-header: false,
),
config-colors(primary: blue),
config-methods(alert: utils.alert-with-primary-color),
config-page(
header: text(gray, utils.display-current-short-heading(level: 2)),
),
)Speaker notes and presenter tools
- Add notes with
#speaker-note[...]. - Show notes on second screen with
config-common(show-notes-on-second-screen: right)orbottom. - Export pdfpc metadata with
enable-pdfpc: trueandtypst query --field value --one "<pdfpc-file>".
Use bundled docs and examples
- Read only the specific docs needed for the task; avoid loading the full docs tree.
- Read
docs/start.mdanddocs/intro.mdfor basics and import patterns. - Read
docs/multi-file.mdfor multi-file layout and include patterns. - Read
docs/layout.mdanddocs/sections.mdfor slide-level and heading behavior. - Read
docs/code-styles.mdfor simple vs block style and pagination tricks. - Read
docs/global-settings.mdfor global styles andconfig-infousage. - Read
docs/dynamic/*.mdforpause,meanwhile, cover behavior, and equation animation. - Read
docs/themes/*.mdfor theme-specific APIs and defaults. - Read
docs/external/*.mdfor pdfpc and Pympress usage. - Read
docs/integration/*.mdfor CeTZ/Fletcher/Theorion/MiTeX integrations. - Use
examples/*.typas working templates for each theme.
File tree (docs and examples)
docs/
├── build-your-own-theme.md
├── changelog.md
├── code-styles.md
├── dynamic
│ ├── complex.md
│ ├── cover.md
│ ├── equation.md
│ ├── handout.md
│ ├── other.md
│ └── simple.md
├── external
│ ├── pdfpc.md
│ ├── pympress.md
│ └── typst-preview.md
├── global-settings.md
├── integration
│ ├── cetz.md
│ ├── codly.md
│ ├── fletcher.md
│ ├── mitex.md
│ ├── pinit.md
│ └── theorion.md
├── intro.md
├── layout.md
├── multi-file.md
├── progress
│ ├── counters.md
│ └── sections.md
├── sections.md
├── start.md
├── themes
│ ├── aqua.md
│ ├── custom.md
│ ├── dewdrop.md
│ ├── metropolis.md
│ ├── simple.md
│ ├── stargazer.md
│ └── university.md
└── utilities
└── fit-to.md
examples/
├── aqua-zh.typ
├── aqua.typ
├── default.typ
├── dewdrop.typ
├── example.typ
├── metropolis.typ
├── simple.typ
├── stargazer.typ
└── university.typBuild Your Own Theme
Creating your own theme with Touying can be a bit complex due to the many concepts we've introduced. But rest assured, if you do create a theme with Touying, you might deeply appreciate the convenience and powerful customizability that Touying offers. You can refer to the source code of the themes. The main things you need to implement are:
- Customizing the
xxx-themefunction; - Customizing the color theme, i.e.,
config-colors(); - Customizing the header;
- Customizing the footer;
- Customizing the
slidemethod; - Customizing special slide methods, such as
title-slideandfocus-slidemethods;
To demonstrate how to create a theme with Touying, let's step by step create a simple and aesthetically pleasing Bamboo theme.
Modifying Existing Themes
If you want to modify a Touying internal theme locally instead of creating one from scratch, you can achieve this by:
1. Copying the theme code from the themes directory to your local, for example, copying themes/university.typ to your local university.typ. 2. Replacing the #import "../src/exports.typ": * command at the top of the university.typ file with #import "@preview/touying:0.6.1": *.
Then you can import and use the theme by:
#import "@preview/touying:0.6.1": *
#import "university.typ": *
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)Importing
Depending on whether the theme is your own or part of Touying, you can import it in two ways:
If it's just for your own use, you can directly import Touying:
#import "@preview/touying:0.6.1": *If you want the theme to be part of Touying, placed in the Touying themes directory, then you should change the import statement above to
#import "../src/exports.typ": *And add
#import "bamboo.typ"in Touying's themes/themes.typ.
register Function and init Method
Next, we will differentiate between the bamboo.typ template file and the main.typ file, which is sometimes omitted.
Generally, the first step in making slides is to determine the font size and page aspect ratio, so we need to register an initialization method:
// bamboo.typ
#import "@preview/touying:0.6.1": *
#let bamboo-theme(
aspect-ratio: "16-9",
..args,
body,
) = {
set text(size: 20pt)
show: touying-slides.with(
config-page(paper: "presentation-" + aspect-ratio),
config-common(
slide-fn: slide,
),
..args,
)
body
}
// main.typ
#import "@preview/touying:0.6.1": *
#import "bamboo.typ": *
#show: bamboo-theme.with(aspect-ratio: "16-9")
= First Section
== First Slide
A slide with a title and an *important* information.As you can see, we've created a bamboo-theme function and passed in an aspect-ratio parameter to set the page aspect ratio. We've also added set text(size: 20pt) to set the font size. You can also place some additional global style settings here, such as set par(justify: true), etc. If you need to use self, you might consider using config-methods(init: (self: none, body) => { .. }) to register an init method.
As you can see, later in main.typ, we apply our style settings through #show: bamboo-theme.with(aspect-ratio: "16-9"), and internally bamboo uses show: touying-slides.with() for corresponding configurations.
Color Theme
Picking an aesthetically pleasing color theme for your slides is key to making good slides. Touying provides built-in color theme support to minimize API differences between different themes. Touying offers two dimensions of color selection. The first dimension is neutral, primary, secondary, and tertiary, which are used to distinguish color tones, with primary being the most commonly used theme color. The second dimension is default, light, lighter, lightest, dark, darker, darkest, which are used to distinguish brightness levels.
Since we are creating the Bamboo theme, we have chosen a color close to bamboo for the primary theme color, rgb("#5E8B65"), and added neutral colors neutral-lightest, neutral-darkest, respectively, as the background and font colors.
As shown in the following code, we can use the config-colors() method to modify the color theme. Its essence is a wrapper for self.colors += (..).
#let bamboo-theme(
aspect-ratio: "16-9",
..args,
body,
) = {
set text(size: 20pt)
show: touying-slides.with(
config-page(paper: "presentation-" + aspect-ratio),
config-common(
slide-fn: slide,
),
config-colors(
primary: rgb("#5E8B65"),
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
),
..args,
)
body
}After adding the color theme as shown above, we can access this color through self.colors.primary.
It's also worth noting that users can change the color theme at any time in main.typ by using config-colors() or
#show: touying-set-config.with(config-colors(
primary: blue,
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
))This feature of being able to change the color theme at any time is a testament to Touying's powerful customizability.
Practical: Custom Alert Method
Generally, we need to provide a #alert[..] function for users, similar to #strong[..], both of which are used to emphasize the current text. Typically, #alert[..] will change the text color to the theme color, which will look more aesthetically pleasing, and this is our next goal.
We add a line in the register function:
config-methods(alert: (self: none, it) => text(fill: self.colors.primary, it))This code means to change the text color to self.colors.primary, and the self here is passed in through the parameter self: none, so that we can get the primary theme color in real-time.
We can also use a shorthand.
config-methods(alert: utils.alert-with-primary-color)Custom Header and Footer
Here, I assume you have read the page layout section, so we know that we should add a header and footer to the slides.
First, we add config-store(title: none), which means that we save the current slide's title as a member variable self.store.title inside self, making it convenient for us to use in the header and for subsequent modifications. Similarly, we also create a config-store(footer: footer) and save the footer: none parameter of the bamboo-theme function for display in the footer at the bottom left corner.
Then it's worth noting that our header is actually a content function with self as a parameter, like let header(self) = { .. }, rather than a simple content, so that we can get the information we need from the latest self, such as self.store.title. The footer is the same.
The components.cell used here is actually #let cell = block.with(width: 100%, height: 100%, above: 0pt, below: 0pt, breakable: false), and show: components.cell is also a shorthand for components.cell(body), and the show: pad.with(.4em) for the footer is the same.
Another point to note is that the utils module contains many contents and methods related to counters and states, such as utils.display-current-heading(level: 1) for displaying the current section, and context utils.slide-counter.display() + " / " + utils.last-slide-number for displaying the current page number and total number of pages.
We also find that we use syntax like utils.call-or-display(self, self.store.footer) to display self.store.footer, which is to deal with the situation of self.store.footer = self => {..}, so that we can unify the display of content functions and content.
To ensure that the header and footer are displayed correctly and have enough spacing from the main text, we need to set the margin, such as config-page(margin: (top: 4em, bottom: 1.5em, x: 2em)).
We also need to customize a slide method, which accepts #let slide(title: auto, ..args) = touying-slide-wrapper(self => {..}), where self in the callback function is a required parameter to get the latest self; the second title is used to update self.store.title for display in the header; the third ..args is used to collect the remaining parameters and pass them to touying-slide(self: self, ..args), which is also necessary for the normal functioning of Touying's slide feature. Moreover, we need to register this method in the bamboo-theme function using config-methods(slide: slide).
// bamboo.typ
#import "@preview/touying:0.6.1": *
#let slide(title: auto, ..args) = touying-slide-wrapper(self => {
if title != auto {
self.store.title = title
}
// set page
let header(self) = {
set align(top)
show: components.cell.with(fill: self.colors.primary, inset: 1em)
set align(horizon)
set text(fill: self.colors.neutral-lightest, size: .7em)
utils.display-current-heading(level: 1)
linebreak()
set text(size: 1.5em)
if self.store.title != none {
utils.call-or-display(self, self.store.title)
} else {
utils.display-current-heading(level: 2)
}
}
let footer(self) = {
set align(bottom)
show: pad.with(.4em)
set text(fill: self.colors.neutral-darkest, size: .8em)
utils.call-or-display(self, self.store.footer)
h(1fr)
context utils.slide-counter.display() + " / " + utils.last-slide-number
}
self = utils.merge-dicts(
self,
config-page(
header: header,
footer: footer,
),
)
touying-slide(self: self, ..args)
})
#let bamboo-theme(
aspect-ratio: "16-9",
footer: none,
..args,
body,
) = {
set text(size: 20pt)
show: touying-slides.with(
config-page(
paper: "presentation-" + aspect-ratio,
margin: (top: 4em, bottom: 1.5em, x: 2em),
),
config-common(
slide-fn: slide,
),
config-methods(
alert: utils.alert-with-primary-color,
),
config-colors(
primary: rgb("#5E8B65"),
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
),
config-store(
title: none,
footer: footer,
),
..args,
)
body
}
// main.typ
#import "@preview/touying:0.6.1": *
#import "bamboo.typ": *
#show: bamboo-theme.with(aspect-ratio: "16-9")
= First Section
== First Slide
A slide with a title and an *important* information.Custom Special Slides
On the basis of the basic slides we've created, we further add some special slide functions, such as title-slide, focus-slide, and custom slides methods.
For the title-slide method, first, we can obtain the information saved in self.info through let info = self.info + args.named(), and we can also update the information with args.named() passed in through the function parameters for subsequent use in the form of info.title. The specific page content body will vary for each theme, so I won't go into too much detail here.
For the new-section-slide method, it's the same, but the only thing to note is that we registered new-section-slide-fn: new-section-slide in config-methods(), so new-section-slide will be automatically called when encountering a first-level heading.
// bamboo.typ
#import "@preview/touying:0.6.1": *
#let slide(title: auto, ..args) = touying-slide-wrapper(self => {
if title != auto {
self.store.title = title
}
// set page
let header(self) = {
set align(top)
show: components.cell.with(fill: self.colors.primary, inset: 1em)
set align(horizon)
set text(fill: self.colors.neutral-lightest, size: .7em)
utils.display-current-heading(level: 1)
linebreak()
set text(size: 1.5em)
if self.store.title != none {
utils.call-or-display(self, self.store.title)
} else {
utils.display-current-heading(level: 2)
}
}
let footer(self) = {
set align(bottom)
show: pad.with(.4em)
set text(fill: self.colors.neutral-darkest, size: .8em)
utils.call-or-display(self, self.store.footer)
h(1fr)
context utils.slide-counter.display() + " / " + utils.last-slide-number
}
self = utils.merge-dicts(
self,
config-page(
header: header,
footer: footer,
),
)
touying-slide(self: self, ..args)
})
#let title-slide(..args) = touying-slide-wrapper(self => {
let info = self.info + args.named()
let body = {
set align(center + horizon)
block(
fill: self.colors.primary,
width: 80%,
inset: (y: 1em),
radius: 1em,
text(size: 2em, fill: self.colors.neutral-lightest, weight: "bold", info.title),
)
set text(fill: self.colors.neutral-darkest)
if info.author != none {
block(info.author)
}
if info.date != none {
block(utils.display-info-date(self))
}
}
touying-slide(self: self, body)
})
#let new-section-slide(self: none, body) = touying-slide-wrapper(self => {
let main-body = {
set align(center + horizon)
set text(size: 2em, fill: self.colors.primary, weight: "bold", style: "italic")
utils.display-current-heading(level: 1)
}
touying-slide(self: self, main-body)
})
#let focus-slide(body) = touying-slide-wrapper(self => {
self = utils.merge-dicts(
self,
config-page(
fill: self.colors.primary,
margin: 2em,
),
)
set text(fill: self.colors.neutral-lightest, size: 2em)
touying-slide(self: self, align(horizon + center, body))
})
#let bamboo-theme(
aspect-ratio: "16-9",
footer: none,
..args,
body,
) = {
set text(size: 20pt)
show: touying-slides.with(
config-page(
paper: "presentation-" + aspect-ratio,
margin: (top: 4em, bottom: 1.5em, x: 2em),
),
config-common(
slide-fn: slide,
new-section-slide-fn: new-section-slide,
),
config-methods(alert: utils.alert-with-primary-color),
config-colors(
primary: rgb("#5E8B65"),
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
),
config-store(
title: none,
footer: footer,
),
..args,
)
body
}
// main.typ
#import "@preview/touying:0.6.1": *
#import "bamboo.typ": *
#show: bamboo-theme.with(
aspect-ratio: "16-9",
footer: self => self.info.institution,
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#title-slide()
= First Section
== First Slide
A slide with a title and an *important* information.
#focus-slide[
Focus on it!
]Conclusion
Congratulations! You've created a simple and elegant theme. Perhaps you may find that Touying introduces a wealth of concepts, making it initially challenging to grasp. This is normal, as Touying opts for functionality over simplicity. However, thanks to Touying's comprehensive and unified approach, you can easily extract commonalities between different themes and transfer your knowledge seamlessly. You can also save global variables, modify existing themes, or switch between themes effortlessly, showcasing the benefits of Touying's decoupling.
Changelog
v0.6.1
Added support for the theorion package, and used it as the default math theorem environment.
v0.6.0
It's not a big update, but it's the first touying release since typst 0.13 was released.
Features
- feat: add auto style for display-current-heading.
- For users, you can use
show heading: set text(blue)to change color for heading in some themes likedewdrop. - For theme creator, you can use syntax like
utils.display-current-heading(level: 1, style: auto)to achieve the same result. - feat: apply config-info information to
set document. - feat: set
stretch: falseby default foralternativesfunctions. This is a minor breaking change, but I think it would be more intuitive: no auto empty space.
Fixes
- fix: fix error with uncover using semi-transparent-cover
- fix: fix type string comparison https://github.com/touying-typ/touying/pull/153
- fix: fix horizontal-line bug in typst 0.13.0
- refactor: fix display-current-short-heading
v0.5.4 & v0.5.5
Features
- docs: improve param documentation and we have better hints for tinymist https://github.com/touying-typ/touying/pull/98
- feat: fake frozon states support for
headinghttps://github.com/touying-typ/touying/pull/124 - feat: add alpha-changing-cover and color-changing-cover https://github.com/touying-typ/touying/pull/129
- feat: add effect function https://github.com/touying-typ/touying/issues/111
- Example:
#effect(text.with(fill: red), "2-")[Something]will display[Something]if the current slide is 2 or later. - feat: add argument
config: (..)forxxx-slidefunctions - feat: add
alignargument for university theme
Fixes
- fix: also hide enum numbers with show-hide-set-list-marker-none https://github.com/touying-typ/touying/pull/114
- fix: fixed progress bar not to break apart when global figure gutter is set nonzero https://github.com/touying-typ/touying/pull/120
- fix: fixed frozen-counters bug with multiple #pause commands https://github.com/touying-typ/touying/pull/124
- fix: fixed incorrect page num when draft is true https://github.com/touying-typ/touying/pull/125
- fix: fix behaviors of fit-to-height and fit-to-width partially https://github.com/touying-typ/touying/pull/131
- fix: duplicated footnotes in headings https://github.com/touying-typ/touying/pull/132
- fix: do not hardcode page sizes https://github.com/touying-typ/touying/pull/134
- fix: add default numbering for page https://github.com/touying-typ/touying/issues/100
- refactor: move show-strong-with-alert to per-slide level https://github.com/touying-typ/touying/issues/123
- refactor: remove unnecessary
config-page(fill: ...) - theme(metropolis): fix color of title page and fix https://github.com/touying-typ/touying/issues/103
- theme(metropolis): fixed metropolis slide's header to return content if title is specified https://github.com/touying-typ/touying/pull/126
- theme(metropolis): respect colors dict in metropolis theme https://github.com/touying-typ/touying/pull/133
Thanks for the contributions from @enklht.
v0.5.3
Features
- feat: add
stretchparameter for#alternatives[]function class. This allows us to handle cases where the internal element is a context expression. - feat: add
config-common(align-enum-marker-with-baseline: true)for aligning the enum marker with the baseline. - feat: add
linebreaksoption tocomponents.mini-slides. https://github.com/touying-typ/touying/pull/96 - feat: add
<touying:skip>label to skip a new-section-slide. - feat: add
config-common(show-hide-set-list-marker-none: true)to make the markers oflistandenuminvisible after#pause. - feat: add
config-common(bibliography-as-footnote: bibliography(title: none, "ref.bib"))to display the bibliography in footnotes. - refactor: add
config-common(show-strong-with-alert: true)configuration to display strong text with an alert. (small breaking change for some themes) - refactor: refactor
display-current-headingfor preserving heading style in title and subtitle. https://github.com/touying-typ/touying/issues/71 - refactor: make
new-section-slide-fnfunction class can receivebodyparameter. We can usereceive-body-for-new-section-slide-fnto control it. (Breaking change) - For example, you can add
#speaker-note[]for a new section slide, like= Section Title \ #speaker-note[]. - If you don't want to append content to the body of the new section slide, you can use
---after the section title.
Fixes
- fix outdated documentation.
- fix bug of
enable-frozen-states-and-countersin handout mode. - fix unusable
square()function. https://github.com/touying-typ/touying/issues/73 - fix hidden footer for
show-notes-on-second-screen: bottom. https://github.com/touying-typ/touying/issues/89 - fix metadata element in table cells. https://github.com/touying-typ/touying/issues/77 https://github.com/touying-typ/touying/issues/95
- fix
auto-offset-for-headingtofalseby default. - fix uncover/only hides more content than it should. https://github.com/touying-typ/touying/issues/85
- theme(simple): fix wrong title and subtitle. https://github.com/touying-typ/touying/issues/70
v0.5.1 & v0.5.2
- Fix somg bugs.
v0.5.0
This is a significant disruptive version update. Touying has removed many mistakes that resulted from incorrect decisions. We have redesigned numerous features. The goal of this version is to make Touying more user-friendly, more flexible, and more powerful.
Major changes include:
- Avoiding closures and OOP syntax, which makes Touying's configuration simpler and allows for the use of document comments to provide more auto-completion information for the slide function.
- The existing
#let slide(self: none, ..args) = { .. }is now#let slide(..args) = touying-slide-wrapper(self => { .. }), whereselfis automatically injected. - We can use
config-xxxsyntax to configure Touying, for example,#show: university-theme.with(aspect-ratio: "16-9", config-colors(primary: blue)). - The
touying-slidefunction no longer includes parameters likesection,subsection, andtitle. These will be automatically inserted into the slide as invisible level 1, 2, or 3 headings viaself.headings(controlled by theslide-levelconfiguration). - We can leverage the powerful headings provided by Typst to support numbering, outlines, and bookmarks.
- Headings within the
#slide[= XXX]function will be adjusted to levelslide-level + 1using theoffsetparameter. - We can use labels on headings to control many aspects, such as supporting the
<touying:hidden>and other special labels, implementing short headings, or recalling a slide with#touying-recall(). - Touying now supports the normal use of
setandshowrules at any position, without requiring them to be in specific locations.
A simple usage example is shown below, and more examples can be found in the examples directory:
#import "@preview/touying:0.6.1": *
#import themes.university: *
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: "1.1")
#title-slide()
= The Section
== Slide Title
#lorem(40)Theme Migration Guide:
For detailed changes to specific themes, you can refer to the themes directory. Generally, if you want to migrate an existing theme, you should:
1. Rename the register function to xxx-theme and remove the self parameter. 2. Add a show: touying-slides.with(..) configuration.
- Change
self.methods.colorstoconfig-colors(primary: rgb("#xxxxxx")). - Change
self.page-argstoconfig-page(). - Change
self.methods.slide = slidetoconfig-methods(slide: slide). - Change
self.methods.new-section-slide = new-section-slidetoconfig-methods(new-section-slide: new-section-slide). - Change private theme variables like
self.xxx-footertoconfig-store(footer: [..]), which you can access throughself.store.footer. - Move the configuration of headers and footers into the
slidefunction rather than in thexxx-themefunction. - You can directly use
setorshowrules inxxx-themeor configure them throughconfig-methods(init: (self: none, body) => { .. })to fully utilize theselfparameter.
3. For states.current-section-with-numbering, you can use utils.display-current-heading(level: 1) instead.
- If you only need the previous heading regardless of whether it is a section or a subsection, use
utils.display-current-heading().
4. The alert function can be replaced with config-methods(alert: utils.alert-with-primary-color). 5. The touying-outline() function is no longer needed; you can use components.adaptive-columns(outline()) instead. Consider using components.progressive-outline() or components.custom-progressive-outline(). 6. Replace states.slide-counter.display() + " / " + states.last-slide-number with context utils.slide-counter.display() + " / " + utils.last-slide-number. That is, we no longer use states but utils. 7. Remove the slides function; we no longer need this function. Instead of implicitly injecting title-slide(), explicitly use #title-slide(). If necessary, consider adding it in the xxx-theme function. 8. Change #let slide(self: none, ..args) = { .. } to #let slide(..args) = touying-slide-wrapper(self => { .. }), where self is automatically injected.
- Change specific parameter configurations to
self = utils.merge-dicts(self, config-page(fill: self.colors.neutral-lightest)). - Remove
self = utils.empty-page(self)and useconfig-common(freeze-slide-counter: true)andconfig-page(margin: 0em)instead. - Change
(self.methods.touying-slide)()totouying-slide().
9. You can insert visible headings into slides by configuring config-common(subslide-preamble: self => text(1.2em, weight: "bold", utils.display-current-heading(depth: self.slide-level))). 10. Finally, don't forget to add document comments to your functions so your users can get better auto-completion hints, especially when using the Tinymist plugin.
Other Changes:
- theme(stargazer): new stargazer theme modified from Coekjan/touying-buaa.
- feat: implemented fake frozen states support, allowing you to use numbering and
#pausenormally. This behavior can be controlled withenable-frozen-states-and-counters,frozen-states, andfrozen-countersinconfig-common(). - feat: implemented
label-only-on-last-subslidefunctionality to prevent non-unique label warnings when working with@equationand@figurein conjunction with#pauseanimations. - feat: added the
touying-recall(<label>)function to replay a specific slide. - feat: implemented
nontight-list-enum-and-terms, which defaults totrueand forceslist,enum, andtermsto have theirtightparameter set tofalse. You can control spacing size with#set list(spacing: 1em). - feat: replaced
listwithtermsimplementation to achievealign-list-marker-with-baseline, which is off by default. - feat: implemented
scale-list-items, scaling list items by a factor, e.g.,scale-list-items: 0.8scales list items by 0.8. - feat: supported direct use of
#pauseand#meanwhilein math expressions, such as$x + pause y$. - feat: provided
#pauseand#meanwhilesupport for most layout functions, such asgridandtable. - feat: added
#show: appendixsupport, essentially equivalent to#show: touying-set-config.with((appendix: true)). - feat: Introduced special labels
<touying:hidden>,<touying:unnumbered>,<touying:unoutlined>,<touying:unbookmarked>to simplify control over heading behavior. - feat: added basic
utils.short-headingsupport to display short headings using labels, such as displaying<sec:my-section>as "My Section". - feat: added
#components.adaptive-columns()to achieve adaptive columns that span a page, typically used with theoutline()function. - feat: added
#show: magic.bibliography-as-footnote.with(bibliography("ref.bib"))to display the bibliography in footnotes. - feat: added components like
custom-progressive-outline,mini-slides. - feat: removed
touying-outline(), which can be directly replaced withoutline(). - fix: replaced potentially incompatible code, such as
type(s) == "string"andlocate(loc => { .. }). - fix: Fixed some bugs.
v0.4.2
- theme(metropolis): decoupled text color with
neutral-dark(Breaking change) - feat: add mark-style uncover, only and alternatives
- feat: add warning for styled block for slides
- feat: add warning for touying-temporary-mark
- feat: add markup-text for speaker-note
- fix: fix bug of slides
v0.4.1
Features
- feat: support builtin outline and bookmark
- feat: support speaker note for dual-screen
- feat: add touying-mitex function
- feat: touying offers a gallery page via wiki
Fixes
- fix: add outline-slide for dewdrop theme
- fix: fix regression of default value "auto" for repeat
Miscellaneous Improvements
- feat: add list support for
touying-outlinefunction - feat: add auto-reset-footnote
- feat: add
freeze-in-empty-pagefor better page counter - feat: add
..argsfor register method to capture unused arguments
v0.4.0
Features
- feat: support
#footnote[]for all themes. - feat: access subslide and repeat in footer and header by
self => self.subslide. - feat: support numbered theorem environments by ctheorems.
- feat: support numbering for sections and subsections.
Fixes
- fix: make nested includes work correctly.
- fix: disable multi-page slides from creating the same section multiple times.
Breaking changes
- refactor: remove
self.paddingand addself.full-headerself.full-footerconfig.
v0.3.3
- template: move template to
touying-aquapackage, make Touying searchable in Typst Universe Packages - themes: fix bugs in university and dewdrop theme
- feat: make set-show rule work without
settingparameter - feat: make
composerparameter more simpler - feat: add
empty-slidefunction
v0.3.2
- fix critical bug: fix
is-sequencefunction, makegridandtablework correctly in touying - theme: add aqua theme, thanks for pride7
- theme: make university theme more configurable
- refactor: don't export variable
sby default anymore, it will be extracted byregisterfunction (Breaking Change) - meta: add
categoriesandtemplateconfig totypst.tomlfor Typst 0.11
v0.3.1
- fix some typos
- fix slide-level bug
- fix bug of pdfpc label
v0.3.0
Features
- better show-slides mode.
- support align and pad.
Documentation
- Add more detailed documentation.
Refactor
- simplify theme.
Fix
- fix many bugs.
v0.2.1
Features
- Touying-reducer: support cetz and fletcher animation
- university theme: add university theme
Fix
- fix footer progress in metropolis theme
- fix some bugs in simple and dewdrop themes
- fix bug that outline does not display more than 4 sections
v0.2.0
- Object-oriented programming: Singleton
s, binding methodsutils.methods(s)and(self: obj, ..) => {..}methods. - Page arguments management: Instead of using
#set page(..), you should useself.page-argsto retrieve or set page parameters, thereby avoiding unnecessary creation of new pages. - `#pause` for sequence content: You can use #pause at the outermost level of a slide, including inline and list.
- `#pause` for layout functions: You can use the
composerparameter to add yourself layout function likeutils.side-by-side, and simply use multiple pos parameters like#slide[..][..]. - `#meanwhile` for synchronous display: Provide a
#meanwhilefor resetting subslides counter. - `#pause` and `#meanwhile` for math equation: Provide a
#touying-equation("x + y pause + z")for math equation animations. - Slides: Create simple slides using standard headings.
- Callback-style `uncover`, `only` and `alternatives`: 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
repeatparameter. - Transparent cover: Enable transparent cover using oop syntax like
#let s = (s.methods.enable-transparent-cover)(self: s). - Handout mode: enable handout mode by
#let s = (s.methods.enable-handout-mode)(self: s). - 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)- Slides counter:
states.slide-counter.display() + " / " + states.last-slide-numberandstates.touying-progress(ratio => ..). - Appendix: Freeze the
last-slide-numberto prevent the slide number from increasing further. - Sections: Touying's built-in section support can be used to display the current section title and show progress.
sectionandsubsectionparameter in#slideto register a new section or subsection.states.current-section-titleto get the current section.states.touying-outlineors.methods.touying-outlineto display a outline of sections.states.touying-final-sections(sections => ..)for custom outline display.states.touying-progress-with-sections((current-sections: .., final-sections: .., current-slide-number: .., last-slide-number: ..) => ..)for powerful progress display.- Navigation bar: Navigation bar like here by
states.touying-progress-with-sections(..), indewdroptheme. - Pdfpc: pdfpc support and export
.pdfpcfile without external tool bytypst querycommand simply.
Code Style
Simple Style
If we just need to use it simply, we can directly input content under the title, just like writing a normal Typst document. The titles here serve to separate pages, and we can also normally use commands like #pause to achieve animation effects.
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(aspect-ratio: "16-9")
= Title
== First Slide
Hello, Touying!
#pause
Hello, Typst!And you can use an empty title == <touying:hidden> to create a new page, which is also helpful to clear the continued application of the previous title.
If we need to maintain the current title and just want to add a new page, we can use #pagebreak(), or directly use --- to split the page, the latter is parsed as #pagebreak() in Touying.
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(aspect-ratio: "16-9")
= Title
== First Slide
Hello, Touying!
---
Hello, Typst!Block Style
Many times, using only the simple style cannot achieve all the functions we need. For more powerful functions and clearer structure, we can also use the block style in the form of #slide[...].
For example, the above example can be transformed into
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(aspect-ratio: "16-9")
= Title
== First Slide
#slide[
Hello, Touying!
#pause
Hello, Typst!
]And #empty-slide[] can create an empty Slide without a header and footer.
There are many benefits to doing this:
1. Many times, we need more than the default #slide[...], we also need special slide functions like #focus-slide[...]; 2. The #slide[...] function of different themes may have more parameters than the default, for example, the #slide[...] function of the metropolis theme will have an align parameter that can set the alignment; 3. Only slide functions can use callback-style content blocks to use #only and #uncover functions to achieve complex animation effects. 4. It can have a clearer structure, by identifying #slide[...] blocks, we can easily distinguish the specific pagination effects of slides.
Convention Over Configuration
You may have noticed that when using the simple theme, using a first-level title automatically creates a section slide. This is because the simple theme registers a config-common(slide-fn: slide, new-section-slide-fn: new-section-slide) function, so Touying will call this function by default.
If we do not want it to automatically create such a section slide, we can remove this method:
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(
aspect-ratio: "16-9",
config-common(new-section-slide-fn: none),
)
= Title
== First Slide
Hello, Touying!
#pause
Hello, Typst!As you can see, this will only result in two pages, and the default section slide will disappear.
Similarly, we can also register a new section slide:
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(
aspect-ratio: "16-9",
config-common(new-section-slide-fn: section => {
touying-slide-wrapper(self => {
touying-slide(
self: self,
{
set align(center + horizon)
set text(size: 2em, fill: self.colors.primary, style: "italic", weight: "bold")
utils.display-current-heading(level: 1)
},
)
})
}),
)
= Title
== First Slide
Hello, Touying!
#pause
Hello, Typst!Complex Animations
Thanks to the syntax provided by Polylux, we can also use only, uncover, and alternatives in Touying.
Mark-Style Functions
We can use mark-style functions, which are very convenient to use.
At subslide #touying-fn-wrapper((self: none) => str(self.subslide)), we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.However, this does not work in all cases, for example if you put uncover into the context expression, you will get an error.
Callback-Style Functions
To overcome the limitations of layout functions mentioned earlier, Touying cleverly implements always-effective only, uncover, and alternatives using callback functions. Specifically, you need to introduce these three functions as follows:
#slide(repeat: 3, self => [
#let (uncover, only, alternatives) = utils.methods(self)
At subslide #self.subslide, we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.
])Notice that we no longer pass a content block but instead pass a callback function with a self parameter. Later, we extract only, uncover, and alternatives functions from self using:
#let (uncover, only, alternatives) = utils.methods(self)We then call these functions in subsequent steps.
Here's an interesting fact: the self.subslide of type int indicates the current subslide index, and in fact, the only, uncover, and alternatives functions rely on self.subslide to determine the current subslide index.
:::warning[Warning]
We manually specify the repeat: 3 parameter, indicating the display of 3 subslides. We need to do this manually because Touying cannot infer how many subslides only, uncover, and alternatives should display.
:::
only
The only function means it "appears" only on selected subslides. If it doesn't appear, it completely disappears and doesn't occupy any space. In other words, #only(index, body) is either body or none.
The index can be an int type or a str type like "2-" or "2-3". For more usage, refer to Polylux.
uncover
The uncover function means it "displays" only on selected subslides; otherwise, it will be covered by the cover function but still occupies the original space. In other words, #uncover(index, body) is either body or cover(body).
The index can be an int type or a str type like "2-" or "2-3". For more usage, refer to Polylux.
You may also have noticed that #pause actually uses the cover function, providing a more convenient syntax. In reality, their effects are almost identical.
alternatives
The alternatives function displays a series of different content in different subslides. For example:
#slide(repeat: 3, self => [
#let (uncover, only, alternatives) = utils.methods(self)
#alternatives[Ann][Bob][Christopher]
likes
#alternatives[chocolate][strawberry][vanilla]
ice cream.
])As you can see, alternatives can automatically expand to the most suitable width and height, a capability that only and uncover lack. In fact, alternatives has other parameters, such as start: 2, repeat-last: true, and position: center + horizon. For more usage, refer to Polylux.
Cover Function
As you already know, both uncover and #pause use the cover function to conceal content that is not visible. So, what exactly is the cover function here?
Default Cover Function: hide
The cover function is a method stored in s.methods.cover, which is later used by uncover and #pause.
The default cover function is the hide function. This function makes the internal content invisible without affecting the layout.
Updating the Cover Function
In some cases, you might want to use your own cover function. In that case, you can set your own cover function using:
config-methods(cover: (self: none, body) => hide(body))hack: handle enum and list
You will find that the existing cover function cannot hide the mark of enum and list, refer to here, so you can hack:
config-methods(cover: (self: none, body) => box(scale(x: 0%, body)))Semi-Transparent Cover Function
Touying supports a semi-transparent cover function, which can be enabled by adding:
config-methods(cover: utils.semi-transparent-cover.with(alpha: 85%))You can adjust the transparency through the alpha: .. parameter.
:::warning[Warning]
Note that the transparent-cover here does not preserve text layout like hide does because it adds an extra layer of box, which may disrupt the original structure of the page.
:::
:::tip[Internals]
The utils.semi-transparent-cover method is defined as:
#let semi-transparent-cover(self: none, constructor: rgb, alpha: 85%, body) = {
cover-with-rect(
fill: update-alpha(
constructor: constructor,
self.page.fill,
alpha,
),
body,
)
}It creates a semi-transparent rectangular mask with the same color as the background to simulate the effect of transparent content. Here, constructor: rgb and alpha: 85% indicate the background color's construction function and transparency level, respectively.
:::
Math Equation Animations
Touying also provides a unique and highly useful feature—math equation animations, allowing you to conveniently use pause and meanwhile within math equations.
Simple Animation
Let's start with an example:
#slide[
Touying equation with pause:
$
f(x) &= pause x^2 + 2x + 1 \
&= pause (x + 1)^2 \
$
#meanwhile
Touying equation is very simple.
]We use the touying-equation function to incorporate pause and meanwhile within the text of math equations (in fact, you can also use #pause or #pause;).
As you would expect, the math equation is displayed step by step, making it suitable for presenters to demonstrate their math reasoning.
Complex Animation
In fact, we can also use only, uncover, and alternatives:
#slide(repeat: 3, self => [
#let (uncover, only, alternatives) = utils.methods(self)
$
f(x) &= pause x^2 + 2x + uncover("3-", 1) \
&= pause (x + 1)^2 \
$
])Handout Mode
While watching slides and attending lectures, the audience often wishes to have handouts for reviewing challenging concepts. Therefore, it's beneficial for the author to provide handouts for the audience, preferably before the lecture for better preparation.
The handout mode differs from the regular mode as it doesn't require intricate animation effects. It retains only the last subslide of each slide.
Enabling handout mode is simple:
config-common(handout: true)Other Animations
Touying also provides touying-reducer, which adds pause and meanwhile animations to cetz and fletcher.
Simple Animations
Here's an example:
#import "@preview/touying:0.6.1": *
#import themes.university: *
#import "@preview/cetz:0.3.2"
#import "@preview/fletcher:0.5.5" as fletcher: node, edge
// cetz and fletcher bindings for touying
#let cetz-canvas = touying-reducer.with(reduce: cetz.canvas, cover: cetz.draw.hide.with(bounds: true))
#let fletcher-diagram = touying-reducer.with(reduce: fletcher.diagram, cover: fletcher.hide)
#show: university-theme.with(aspect-ratio: "16-9")
// cetz animation
#slide[
Cetz in Touying:
#cetz-canvas({
import cetz.draw: *
rect((0,0), (5,5))
(pause,)
rect((0,0), (1,1))
rect((1,1), (2,2))
rect((2,2), (3,3))
(pause,)
line((0,0), (2.5, 2.5), name: "line")
})
]
// fletcher animation
#slide[
Fletcher in Touying:
#fletcher-diagram(
node-stroke: .1em,
node-fill: gradient.radial(blue.lighten(80%), blue, center: (30%, 20%), radius: 80%),
spacing: 4em,
edge((-1,0), "r", "-|>", `open(path)`, label-pos: 0, label-side: center),
node((0,0), `reading`, radius: 2em),
edge((0,0), (0,0), `read()`, "--|>", bend: 130deg),
pause,
edge(`read()`, "-|>"),
node((1,0), `eof`, radius: 2em),
pause,
edge(`close()`, "-|>"),
node((2,0), `closed`, radius: 2em, extrude: (-2.5, 0)),
edge((0,0), (2,0), `close()`, "-|>", bend: -40deg),
)
]only and uncover
In fact, we can also use only and uncover within cetz, just requiring a bit of skill:
#slide(repeat: 3, self => [
#let (uncover, only) = utils.methods(self)
Cetz in Touying in subslide #self.subslide:
#cetz.canvas({
import cetz.draw: *
let self = utils.merge-dicts(
self,
config-methods(cover: utils.method-wrapper(hide.with(bounds: true))),
)
let (uncover,) = utils.methods(self)
rect((0,0), (5,5))
uncover("2-3", {
rect((0,0), (1,1))
rect((1,1), (2,2))
rect((2,2), (3,3))
})
only(3, line((0,0), (2.5, 2.5), name: "line"))
})
])Simple Animations
Touying provides two markers for simple animation effects: #pause and #meanwhile.
pause
The purpose of #pause is straightforward – it separates the subsequent content into the next subslide. You can use multiple #pause to create multiple subslides. Here's a simple example:
#slide[
First #pause Second
#pause
Third
]This example will create three subslides, gradually revealing the content.
As you can see, #pause can be used inline or on a separate line.
meanwhile
In some cases, you may need to display additional content simultaneously with #pause. In such cases, you can use #meanwhile.
#slide[
First
#pause
Second
#meanwhile
Third
#pause
Fourth
]This example will create only two subslides, with "First" and "Third" displayed simultaneously, and "Second" and "Fourth" displayed simultaneously.
Pdfpc
pdfpc is a "Presenter Console with multi-monitor support for PDF files." This means you can use it to display slides in the form of PDF pages and it comes with some known excellent features, much like PowerPoint.
pdfpc has a JSON-formatted .pdfpc file that can provide additional information for PDF slides. While you can manually write this file, you can also manage it through Touying.
Adding Metadata
Touying remains consistent with Polylux to avoid conflicts between APIs.
For example, you can add notes using #pdfpc.speaker-note("This is a note that only the speaker will see.").
Pdfpc Configuration
To add pdfpc configurations, you can use
#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",
),
)Add the corresponding configurations. Refer to Polylux for specific configuration details.
Exporting .pdfpc File
Assuming your document is ./example.typ, you can export the .pdfpc file directly using:
typst query --root . ./example.typ --field value --one "<pdfpc-file>" > ./example.pdfpcWith the compatibility of Touying and Polylux, you can make Polylux also support direct export by adding the following code:
#import "@preview/touying:0.6.1"
#context touying.pdfpc.pdfpc-file(here())Pympress
Pympress is a PDF presentation tool designed for dual-screen setups such as presentations and public talks. Highly configurable, fully-featured, and portable
Speaker Notes
#import "@preview/touying:0.6.1": *
#import themes.university: *
#show: university-theme.with(
aspect-ratio: "16-9",
config-common(show-notes-on-second-screen: right),
)
= Animation
== Simple Animation
We can use `#pause` to #pause display something later.
#pause
Just like this.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to #pause display other content synchronously.
#speaker-note[
+ This is a speaker note.
+ You won't see it unless you use `config-common(show-notes-on-second-screen: right)`
]Then we can use the pympress to show it.
Typst Preview in Tinymist
The Tinymist extension for VS Code provides an excellent slide mode, allowing us to preview and present slides.
Press Ctrl/Cmd + Shift + P and type Typst Preview: Preview current file in slide mode to open the preview in slide mode.
Press Ctrl/Cmd + Shift + P and type Typst Preview: Preview current file in browser and slide mode to open the slide mode in the browser.
Now, you can press keys like F11 to enter fullscreen mode in the browser, making it suitable for slide presentations.
Since Typst Preview is based on SVG, it can play GIF animations, which is very helpful for dynamic slides.
Global Settings
Global Styles
For Touying, global styles refer to set rules or show rules that need to be applied everywhere, such as #set text(size: 20pt).
Themes in Touying encapsulate some of their own global styles, which are placed in #self.methods.init. For example, the simple theme encapsulates:
config-methods(
init: (self: none, body) => {
set text(fill: self.colors.neutral-darkest, size: 25pt)
show footnote.entry: set text(size: .6em)
show strong: self.methods.alert.with(self: self)
show heading.where(level: self.slide-level + 1): set text(1.4em)
body
},
)If you are not a theme creator but simply want to add some of your own global styles to your slides, you can easily place them before or after #show: xxx-theme.with(). For example, the metropolis theme recommends that you add the following global styles yourself:
#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)Global Information
Like Beamer, Touying helps you better maintain global information through a unified API design, allowing you to easily switch between different themes. Global information is a typical example of this.
You can set the title, subtitle, author, date, and institution information of your slides with:
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
)Later on, you can access them through self.info.
This information is generally used in the theme's title-slide, header, and footer, such as #show: metropolis-theme.with(aspect-ratio: "16-9", footer: self => self.info.institution).
The date can accept datetime format and content format, and the date display format of the datetime format can be changed with:
config-common(datetime-format: "[year]-[month]-[day]")CeTZ
Touying provides the touying-reducer, which adds pause and meanwhile animations to CeTZ and Fletcher.
Simple Animation
An example:
#import "@preview/touying:0.6.1": *
#import themes.metropolis: *
#import "@preview/cetz:0.3.2"
#import "@preview/fletcher:0.5.5" as fletcher: node, edge
// cetz and fletcher bindings for touying
#let cetz-canvas = touying-reducer.with(reduce: cetz.canvas, cover: cetz.draw.hide.with(bounds: true))
#let fletcher-diagram = touying-reducer.with(reduce: fletcher.diagram, cover: fletcher.hide)
#show: metropolis-theme.with(aspect-ratio: "16-9")
// cetz animation
#slide[
Cetz in Touying:
#cetz-canvas({
import cetz.draw: *
rect((0,0), (5,5))
(pause,)
rect((0,0), (1,1))
rect((1,1), (2,2))
rect((2,2), (3,3))
(pause,)
line((0,0), (2.5, 2.5), name: "line")
})
]
// fletcher animation
#slide[
Fletcher in Touying:
#fletcher-diagram(
node-stroke: .1em,
node-fill: gradient.radial(blue.lighten(80%), blue, center: (30%, 20%), radius: 80%),
spacing: 4em,
edge((-1,0), "r", "-|>", `open(path)`, label-pos: 0, label-side: center),
node((0,0), `reading`, radius: 2em),
edge((0,0), (0,0), `read()`, "--|>", bend: 130deg),
pause,
edge(`read()`, "-|>"),
node((1,0), `eof`, radius: 2em),
pause,
edge(`close()`, "-|>"),
node((2,0), `closed`, radius: 2em, extrude: (-2.5, 0)),
edge((0,0), (2,0), `close()`, "-|>", bend: -40deg),
)
]only and uncover
In fact, we can also use only and uncover within CeTZ, but it requires a bit of technique:
#slide(repeat: 3, self => [
#let (uncover, only) = utils.methods(self)
Cetz in Touying in subslide #self.subslide:
#cetz.canvas({
import cetz.draw: *
let self = (self.methods.update-cover)(self: self, hide.with(bounds: true))
let (uncover,) = utils.methods(self)
rect((0,0), (5,5))
uncover("2-3", {
rect((0,0), (1,1))
rect((1,1), (2,2))
rect((2,2), (3,3))
})
only(3, line((0,0), (2.5, 2.5), name: "line"))
})
])Codly
When using Codly, we should initialize it using the config-common(preamble: {..}) method.
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#import "@preview/codly:1.0.0": *
#show: codly-init.with()
#show: simple-theme.with(
aspect-ratio: "16-9",
config-common(preamble: {
codly(languages: (
rust: (
name: "Rust",
icon: text(font: "tabler-icons", "\u{fa53}"),
color: rgb("#CE412B"),
),
))
}),
)
== First slide
#raw(lang: "rust", block: true,
`pub fn main() {
println!("Hello, world!");
}`.text)Fletcher
Touying provides the touying-reducer, which adds pause and meanwhile animations to Fletcher.
An example:
#import "@preview/touying:0.6.1": *
#import themes.metropolis: *
#import "@preview/cetz:0.3.2"
#import "@preview/fletcher:0.5.5" as fletcher: node, edge
// cetz and fletcher bindings for touying
#let cetz-canvas = touying-reducer.with(reduce: cetz.canvas, cover: cetz.draw.hide.with(bounds: true))
#let fletcher-diagram = touying-reducer.with(reduce: fletcher.diagram, cover: fletcher.hide)
#show: metropolis-theme.with(aspect-ratio: "16-9")
// cetz animation
#slide[
Cetz in Touying:
#cetz-canvas({
import cetz.draw: *
rect((0,0), (5,5))
(pause,)
rect((0,0), (1,1))
rect((1,1), (2,2))
rect((2,2), (3,3))
(pause,)
line((0,0), (2.5, 2.5), name: "line")
})
]
// fletcher animation
#slide[
Fletcher in Touying:
#fletcher-diagram(
node-stroke: .1em,
node-fill: gradient.radial(blue.lighten(80%), blue, center: (30%, 20%), radius: 80%),
spacing: 4em,
edge((-1,0), "r", "-|>", `open(path)`, label-pos: 0, label-side: center),
node((0,0), `reading`, radius: 2em),
edge((0,0), (0,0), `read()`, "--|>", bend: 130deg),
pause,
edge(`read()`, "-|>"),
node((1,0), `eof`, radius: 2em),
pause,
edge(`close()`, "-|>"),
node((2,0), `closed`, radius: 2em, extrude: (-2.5, 0)),
edge((0,0), (2,0), `close()`, "-|>", bend: -40deg),
)
]MiTeX
During the process of creating slides, we often already have a LaTeX math equation that we simply want to paste into the slides without transcribing it into a Typst math equation. In such cases, we can use MiTeX.
Example:
#import "@preview/mitex:0.2.5": *
Write inline equations like #mi("x") or #mi[y].
Also block equations (this case is from #text(blue.lighten(20%), link("https://katex.org/")[katex.org])):
#mitex(`
\newcommand{\f}[2]{#1f(#2)}
\f\relax{x} = \int_{-\infty}^\infty
\f\hat\xi\,e^{2 \pi i \xi x}
\,d\xi
`)Touying also provides a touying-mitex function, which can be used for example
#touying-mitex(mitex, `
f(x) &= \pause x^2 + 2x + 1 \\
&= \pause (x + 1)^2 \\
`)Pinit
Pinit package provides the ability to perform absolute positioning based on the page and relative positioning based on "pins," making it convenient to implement arrow pointing and explanatory effects for slides.
Simple Example
#import "@preview/pinit:0.2.2": *
#set text(size: 24pt)
A simple #pin(1)highlighted text#pin(2).
#pinit-highlight(1, 2)
#pinit-point-from(2)[It is simple.]Another example:
Complex Example
An example of shared usage with Touying:
#import "@preview/touying:0.6.1": *
#import themes.default: *
#import "@preview/pinit:0.2.2": *
#set text(size: 20pt, font: "Calibri", ligatures: false)
#show heading: set text(weight: "regular")
#show heading: set block(above: 1.4em, below: 1em)
#show heading.where(level: 1): set text(size: 1.5em)
// Useful functions
#let crimson = rgb("#c00000")
#let greybox(..args, body) = rect(fill: luma(95%), stroke: 0.5pt, inset: 0pt, outset: 10pt, ..args, body)
#let redbold(body) = {
set text(fill: crimson, weight: "bold")
body
}
#let blueit(body) = {
set text(fill: blue)
body
}
#show: default-theme.with(aspect-ratio: "4-3")
// Main body
#slide[
#set heading(offset: 0)
= Asymptotic Notation: $O$
Use #pin("h1")asymptotic notations#pin("h2") to describe asymptotic efficiency of algorithms.
(Ignore constant coefficients and lower-order terms.)
#pause
#greybox[
Given a function $g(n)$, we denote by $O(g(n))$ the following *set of functions*:
#redbold(${f(n): "exists" c > 0 "and" n_0 > 0, "such that" f(n) <= c dot g(n) "for all" n >= n_0}$)
]
#pinit-highlight("h1", "h2")
#pause
$f(n) = O(g(n))$: #pin(1)$f(n)$ is *asymptotically smaller* than $g(n)$.#pin(2)
#pause
$f(n) redbold(in) O(g(n))$: $f(n)$ is *asymptotically* #redbold[at most] $g(n)$.
#only("4-", pinit-line(stroke: 3pt + crimson, start-dy: -0.25em, end-dy: -0.25em, 1, 2))
#pause
#block[Insertion Sort as an #pin("r1")example#pin("r2"):]
- Best Case: $T(n) approx c n + c' n - c''$ #pin(3)
- Worst case: $T(n) approx c n + (c' \/ 2) n^2 - c''$ #pin(4)
#pinit-rect("r1", "r2")
#pause
#pinit-place(3, dx: 15pt, dy: -15pt)[#redbold[$T(n) = O(n)$]]
#pinit-place(4, dx: 15pt, dy: -15pt)[#redbold[$T(n) = O(n)$]]
#pause
#blueit[Q: Is $n^(3) = O(n^2)$#pin("que")? How to prove your answer#pin("ans")?]
#pause
#pinit-point-to("que", fill: crimson, redbold[No.])
#pinit-point-from("ans", body-dx: -150pt)[
Show that the equation $(3/2)^n >= c$ \
has infinitely many solutions for $n$.
]
]Theorion
Touying can work properly with the Theorion package, you can directly use the theorion package. Additionally, you can use #set heading(numbering: "1.1") to set numbering for sections and subsections.
Note: To make animation commands like `#pause` work properly with theorion, you need to use `config-common(frozen-counters: (theorem-counter,))` to bind counters that need to be frozen.
#import "@preview/touying:0.6.1": *
#import themes.university: *
#import "@preview/numbly:0.1.0": numbly
#import "@preview/theorion:0.3.2": *
#import cosmos.clouds: *
#show: show-theorion
#show: university-theme.with(
aspect-ratio: "16-9",
config-common(frozen-counters: (theorem-counter,)), // freeze theorem counter for animation
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
= Theorems
== Prime numbers
#definition[
A natural number is called a #highlight[_prime number_] if it is greater
than 1 and cannot be written as the product of two smaller natural numbers.
]
#example[
The numbers $2$, $3$, and $17$ are prime.
@cor_largest_prime shows that this list is not exhaustive!
]
#pause
#theorem(title: "Euclid")[
There are infinitely many primes.
]
#pagebreak(weak: true)
#proof[
Suppose to the contrary that $p_1, p_2, dots, p_n$ is a finite enumeration
of all primes. Set $P = p_1 p_2 dots p_n$. Since $P + 1$ is not in our list,
it cannot be prime. Thus, some prime factor $p_j$ divides $P + 1$. Since
$p_j$ also divides $P$, it must divide the difference $(P + 1) - P = 1$, a
contradiction.
]
#corollary[
There is no largest prime number.
] <cor_largest_prime>
#corollary[
There are infinitely many composite numbers.
]
#theorem[
There are arbitrarily long stretches of composite numbers.
]
#proof[
For any $n > 2$, consider $
n! + 2, quad n! + 3, quad ..., quad n! + n
$
]Introduction to Touying
Touying is a slide/presentation package developed for Typst. Touying is similar to LaTeX Beamer but benefits from Typst, providing faster rendering speed and a more concise syntax. After, we use "slides" to refer to slideshows, "slide" for a single slide, and "subslide" for a sub-slide.
Why Use Touying
- Unlike PowerPoint, Touying is not a "what you see is what you get" tool. You can write your slides in a "content and style separation" manner, especially with Typst, which offers a concise yet powerful syntax, better supporting content like code blocks, mathematical formulas, and theorems. Another advantage is that, with templates, writing slides with Touying is much faster than PowerPoint. Therefore, Touying is more suitable for users with a demand for "research writing."
- Compared to Markdown Slides, Touying, relying on Typst, has more powerful typesetting control, such as headers, footers, layout, and convenient custom functions. These are capabilities that Markdown struggles to provide, or does not do well. Additionally, Touying offers
#pauseand#meanwhilemarkers, providing more convenient dynamic slide capabilities. - Compared to Beamer, Touying has faster compilation speed, a more concise syntax, and simpler theme customization capabilities. Touying's compilation speed can be maintained in milliseconds or tens of milliseconds, compared to Beamer's compilation time of seconds or tens of seconds. Touying's syntax is more concise than Beamer, making it easier to change templates and create your own templates. In terms of features, Touying supports most of Beamer's capabilities and provides some convenient features that Beamer lacks.
- Compared to Polylux, Touying offers a concise yet powerful syntax and functionality, capable of providing 'global variables' capabilities through automatic injection, making it easier to write themes. Moreover, Touying does not rely on
counterandlocateto implement#pause, thus offering better performance. Touying is positioned as a community-driven project (we welcome more people to join), and it does not overly emphasize maintaining API consistency. Instead, it chooses to maintain documentation for multiple versions, thereby providing more innovative but powerful features.
About the Name
"Touying" is derived from the Chinese word "投影" (tóuyǐng), which means "projection" In English, it also conveys the meaning of a "project". In comparison, the term "beamer" in LaTeX means a projector in German.
About the Documentation
This documentation is powered by Docusaurus. We will maintain English and Chinese versions of the documentation for Touying, and for each major version, we will maintain a documentation copy. This allows you to easily refer to old versions of the Touying documentation and migrate to new versions.
Docusaurus creates a new version:
npm run docusaurus docs:version 0.y.xContribution
Touying is free, open-source, and community-driven. If you're interested, you can visit GitHub anytime and raise issues or submit pull requests. We also welcome you to join the touying-typ organization.
🥤A cup of coffee
If you feel that this project has helped you in your work, you can donate to Touying's development work in the following ways to keep the project going.
Sponsor: https://afdian.com/a/orangex4
QR Code:
Gallery
Touying offers a gallery page via wiki, where you can browse elegant slides created by Touying users. You're also encouraged to contribute your own beautiful slides here!
License
Touying is released under the MIT license.
Page Layout
Basic Concepts
To create stylish slides using Typst, it's essential to understand Typst's page model correctly. If you're not concerned with customizing page styles, you can choose to skip this section. However, it's still recommended to go through it.
Let's illustrate Typst's default page model through a specific example.
#let container = rect.with(height: 100%, width: 100%, inset: 0pt)
#let innerbox = rect.with(stroke: (dash: "dashed"))
#set text(size: 30pt)
#set page(
paper: "presentation-16-9",
header: container[#innerbox[Header]],
header-ascent: 30%,
footer: container[#innerbox[Footer]],
footer-descent: 30%,
)
#place(top + right)[Margin→]
#container[
#container[
#innerbox[Content]
]
]We need to distinguish the following concepts:
1. Model: Typst has a model similar to the CSS Box Model, divided into Margin, Padding, and Content. However, padding is not a property of set page(..) but is obtained by manually adding #pad(..). 2. Margin: Margins are the edges of the page, divided into top, bottom, left, and right. They are the core of Typst's page model, and all other properties are influenced by margins, especially Header and Footer. Header and Footer are actually located within the Margin. 4. Header: The Header is the content at the top of the page, divided into container and innerbox. We can observe that the edge of the header container and padding does not align but has some space in between, which is actually header-ascent: 30%, where the percentage is relative to the margin-top. Additionally, we notice that the header innerbox is actually located at the bottom left corner of the header container, meaning innerbox defaults to #set align(left + bottom). 5. Footer: The Footer is the content at the bottom of the page, similar to the Header but in the opposite direction. 6. Place: The place function enables absolute positioning relative to the parent container without affecting other elements inside the parent container. It allows specifying alignment, dx, and dy, making it suitable for placing decorative elements like logos.
Therefore, to apply Typst to create slides, we only need to set:
#set page(
margin: (x: 4em, y: 2em),
header: align(top)[Header],
footer: align(bottom)[Footer],
header-ascent: 0em,
footer-descent: 0em,
)However, we still need to address how the header occupies the entire page width. Here, we use negative padding to achieve this. For instance:
#let container = rect.with(stroke: (dash: "dashed"), height: 100%, width: 100%, inset: 0pt)
#let innerbox = rect.with(fill: rgb("#d0d0d0"))
#let margin = (x: 4em, y: 2em)
// negative padding for header and footer
#let negative-padding = pad.with(x: -margin.x, y: 0em)
#set text(size: 30pt)
#set page(
paper: "presentation-16-9",
margin: margin,
header: negative-padding[#container[#align(top)[#innerbox(width: 100%)[Header]]]],
header-ascent: 0em,
footer: negative-padding[#container[#align(bottom)[#innerbox(width: 100%)[Footer]]]],
footer-descent: 0em,
)
#place(top + right)[↑Margin→]
#container[
#container[
#innerbox[Content]
]
]Page Management
In Typst, using the set page(..) command to modify page parameters results in the creation of a new page, rather than modifying the current one. Therefore, Touying opts to maintain a self.page member variable.
For example, the previous example can be rewritten as:
#show: default-theme.with(
config-page(
margin: (x: 4em, y: 2em),
header: align(top)[Header],
footer: align(bottom)[Footer],
header-ascent: 0em,
footer-descent: 0em,
),
)Touying will automatically detect the value of margin.x and determine whether to apply negative padding to the header if config-common(zero-margin-header: true) is set, which is equivalent to self.zero-margin-header = true.
Similarly, if you are not satisfied with the style of the header or footer of a particular theme, you can also modify it through:
config-page(footer: [Custom Footer]):::warning[Warning]
Therefore, you should not use the set page(..) command yourself, as it will be reset by Touying.
:::
With this approach, we can also query the current page parameters in real-time using self.page, which is very useful for functions that need to obtain the page margins or the current page background color, such as transparent-cover. This is somewhat equivalent to context get rule, and in practice, it is more convenient to use.
Page Columnization
If you need to divide a page into two or three columns, you can use the composer feature provided by the default slide function in Touying. The simplest example is as follows:
#slide[
First column.
][
Second column.
]If you need to change the way columns are divided, you can modify the composer parameter of slide, where the default parameter is components.side-by-side.with(columns: auto, gutter: 1em). If we want the left column to take up the remaining width, we can use:
#slide(composer: (1fr, auto))[
First column.
][
Second column.
]Multi-File Architecture
Touying features a syntax as concise as native Typst documents, along with numerous customizable configuration options, yet it still maintains real-time incremental compilation performance, making it suitable for writing large-scale slides.
If you need to write a large set of slides, such as a course manual spanning tens or hundreds of pages, you can also try Touying's multi-file architecture.
Configuration and Content Separation
A simple Touying multi-file architecture consists of three files: a global configuration file globals.typ, a main entry file main.typ, and a content file content.typ for storing the actual content.
These three files are separated to allow both main.typ and content.typ to import globals.typ without causing circular references.
globals.typ can be used to store some global custom functions and initialize Touying themes:
// globals.typ
#import "@preview/touying:0.6.1": *
#import themes.university: *
// as well as some utility functionsmain.typ, as the main entry point of the project, applies show rules by importing globals.typ and includes content.typ using #include:
// main.typ
#import "/globals.typ": *
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#include "content.typ"content.typ is where you write the actual content:
// content.typ
#import "/globals.typ": *
= The Section
== Slide Title
Hello, Touying!
#focus-slide[
Focus on me.
]Multiple Sections
Implementing multiple sections is also straightforward. You only need to create a sections directory and move the content.typ file to the sections.typ directory, for example:
// main.typ
#import "/globals.typ": *
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#include "sections/content.typ"
// #include "sections/another-section.typ"And
// sections/content.typ
#import "/globals.typ": *
= The Section
== Slide Title
Hello, Touying!
#focus-slide[
Focus on me.
]Now, you have learned how to use Touying to achieve a multi-file architecture for large-scale slides.
Touying Counters
The utils of Touying are placed under the utils namespace, including all counters.
Slide Counter
You can access the slide counter using utils.slide-counter and display the current slide number with utils.slide-counter.display().
Last-Slide Counter
In some cases, we may need to add an appendix to slides, leading to the requirement to freeze the last-slide counter. Therefore, a second counter is maintained here.
You can use utils.last-slide-number to display the number of the last slide before the appendix.
Progress
You can use
#utils.touying-progress(ratio => ..)to show the current progress.
Appendix
You can use
#show: appendix
= Appendix
appendixsyntax to enter the appendix.
Additionally, label <touying:unoutlined> can be used to hide the appendix section from the outline.
Touying Sections
Touying adds invisible headings to each slide, so you can use query(heading) to retrieve the current section information.
Sections and Subsections
Structure
Like Beamer, Touying also has the concept of sections and subsections.
Generally, first-level, second-level, and third-level headings correspond to sections, subsections, and subsubsections, respectively, such as in the dewdrop theme.
#import "@preview/touying:0.6.1": *
#import themes.dewdrop: *
#show: dewdrop-theme.with(aspect-ratio: "16-9")
= Section
== Subsection
=== Title
Hello, Touying!However, there are many times when we do not need subsections, so we also use first-level and second-level headings to correspond to sections and titles, respectively, such as in the university theme.
#import "@preview/touying:0.6.1": *
#import themes.university: *
#show: university-theme.with(aspect-ratio: "16-9")
= Section
== Title
Hello, Touying!In fact, we can control this behavior through the slide-level parameter of the config-common function. slide-level represents the complexity of the nesting structure, starting from 0. For example, #show: university-theme.with(config-common(slide-level: 2)) is equivalent to both section and subsection creating new slides; while #show: university-theme.with(config-common(slide-level: 3)) is equivalent to section, subsection, and subsubsection all creating new slides.
Numbering
To add numbering to sections and subsections, we simply use
#set heading(numbering: "1.1")
#show heading.where(level: 1): set heading(numbering: "1.")This sets the default numbering to 1.1, and the section corresponds to the numbering 1..
Table of Contents
Displaying a table of contents in Touying is straightforward:
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#import "@preview/numbly:0.1.0": numbly
#set heading(numbering: numbly("{1}.", default: "1.1"))
#show: simple-theme.with(aspect-ratio: "16-9")
= Section
== Subsection
#components.adaptive-columns(outline(indent: 1em))The outline(indent: 1em) is a native Typst function for the table of contents. The #components.adaptive-columns() function ensures that the table of contents occupies only one page, adapting by setting #columns(1, body) or #columns(2, body), and so on.
If you need a outline function that can display the current progress, you might consider using #components.progressive-outline() or #components.custom-progressive-outline(), as seen in the dewdrop theme.
Getting Started
Before you begin, make sure you have the Typst environment installed. If not, you can use the Web App or install the Tinymist LSP plugins for VS Code.
To use Touying, you just need to include the following in your document:
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(aspect-ratio: "16-9")
= Title
== First Slide
Hello, Touying!
#pause
Hello, Typst!It's that simple! You've created your first Touying slides. Congratulations! 🎉
Tip: You can use Typst syntax like #import "config.typ": * or #include "content.typ" to implement Touying's multi-file architecture.
More Complex Examples
In fact, Touying provides various styles for slide writing. You can also use the #slide[..] syntax to access more powerful features provided by Touying.
Touying offers many built-in themes to easily create beautiful slides. For example, in this case:
#import "@preview/touying:0.6.1": *
#import themes.university: *
#import "@preview/cetz:0.3.2"
#import "@preview/fletcher:0.5.5" as fletcher: node, edge
#import "@preview/numbly:0.1.0": numbly
#import "@preview/theorion:0.3.2": *
#import cosmos.clouds: *
#show: show-theorion
// cetz and fletcher bindings for touying
#let cetz-canvas = touying-reducer.with(reduce: cetz.canvas, cover: cetz.draw.hide.with(bounds: true))
#let fletcher-diagram = touying-reducer.with(reduce: fletcher.diagram, cover: fletcher.hide)
#show: university-theme.with(
aspect-ratio: "16-9",
// align: horizon,
// config-common(handout: true),
config-common(frozen-counters: (theorem-counter,)), // freeze theorem counter for animation
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
== Outline <touying:hidden>
#components.adaptive-columns(outline(title: none, indent: 1em))
= Animation
== Simple Animation
We can use `#pause` to #pause display something later.
#pause
Just like this.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to #pause display other content synchronously.
#speaker-note[
+ This is a speaker note.
+ You won't see it unless you use `config-common(show-notes-on-second-screen: right)`
]
== Complex Animation
At subslide #touying-fn-wrapper((self: none) => str(self.subslide)), we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.
== Callback Style Animation
#slide(
repeat: 3,
self => [
#let (uncover, only, alternatives) = utils.methods(self)
At subslide #self.subslide, we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.
],
)
== Math Equation Animation
Equation with `pause`:
$
f(x) &= pause x^2 + 2x + 1 \
&= pause (x + 1)^2 \
$
#meanwhile
Here, #pause we have the expression of $f(x)$.
#pause
By factorizing, we can obtain this result.
== CeTZ Animation
CeTZ Animation in Touying:
#cetz-canvas({
import cetz.draw: *
rect((0, 0), (5, 5))
(pause,)
rect((0, 0), (1, 1))
rect((1, 1), (2, 2))
rect((2, 2), (3, 3))
(pause,)
line((0, 0), (2.5, 2.5), name: "line")
})
== Fletcher Animation
Fletcher Animation in Touying:
#fletcher-diagram(
node-stroke: .1em,
node-fill: gradient.radial(blue.lighten(80%), blue, center: (30%, 20%), radius: 80%),
spacing: 4em,
edge((-1, 0), "r", "-|>", `open(path)`, label-pos: 0, label-side: center),
node((0, 0), `reading`, radius: 2em),
edge((0, 0), (0, 0), `read()`, "--|>", bend: 130deg),
pause,
edge(`read()`, "-|>"),
node((1, 0), `eof`, radius: 2em),
pause,
edge(`close()`, "-|>"),
node((2, 0), `closed`, radius: 2em, extrude: (-2.5, 0)),
edge((0, 0), (2, 0), `close()`, "-|>", bend: -40deg),
)
= Theorems
== Prime numbers
#definition[
A natural number is called a #highlight[_prime number_] if it is greater
than 1 and cannot be written as the product of two smaller natural numbers.
]
#example[
The numbers $2$, $3$, and $17$ are prime.
@cor_largest_prime shows that this list is not exhaustive!
]
#theorem(title: "Euclid")[
There are infinitely many primes.
]
#pagebreak(weak: true)
#proof[
Suppose to the contrary that $p_1, p_2, dots, p_n$ is a finite enumeration
of all primes. Set $P = p_1 p_2 dots p_n$. Since $P + 1$ is not in our list,
it cannot be prime. Thus, some prime factor $p_j$ divides $P + 1$. Since
$p_j$ also divides $P$, it must divide the difference $(P + 1) - P = 1$, a
contradiction.
]
#corollary[
There is no largest prime number.
] <cor_largest_prime>
#corollary[
There are infinitely many composite numbers.
]
#theorem[
There are arbitrarily long stretches of composite numbers.
]
#proof[
For any $n > 2$, consider $
n! + 2, quad n! + 3, quad ..., quad n! + n
$
]
= Others
== Side-by-side
#slide(composer: (1fr, 1fr))[
First column.
][
Second column.
]
== Multiple Pages
#lorem(200)
#show: appendix
= Appendix
== Appendix
Please pay attention to the current slide number.For more detailed tutorials on themes, you can refer to the following sections.
Aqua Theme
This theme is created by @pride7, featuring beautiful backgrounds made with Typst's visualization capabilities.
Initialization
You can initialize it with the following code:
#import "@preview/touying:0.6.1": *
#import themes.aqua: *
#show: aqua-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#title-slide()
#outline-slide()The register function in the Aqua theme accepts the following parameters:
aspect-ratio: The aspect ratio of the slides, which can be "16-9" or "4-3", with a default of "16-9".header: The content displayed in the header of the slides, with a default ofutils.display-current-heading(). You can also provide a function likeself => self.info.titleto customize the header content.footer: The content displayed on the right side of the footer, with a default ofcontext utils.slide-counter.display().
Additionally, the Aqua theme provides a #alert[..] function, which you can use with the #show strong: alert syntax to emphasize text within your slides.
Color Theme
The Aqua theme uses the following color scheme by default:
config-colors(
primary: rgb("#003F88"),
primary-light: rgb("#2159A5"),
primary-lightest: rgb("#F2F4F8"),
neutral-lightest: rgb("#FFFFFF"),
)You can modify this color scheme using the config-colors() function to suit your preferences or to match the branding of your presentation.
Slide Function Family
Aqua theme offers a series of custom slide functions:
#title-slide(..args)title-slide will read information from self.info for display.
---
#let outline-slide(self: none, enum-args: (:), leading: 50pt)Display an outline slide.
---
#slide(
repeat: auto,
setting: body => body,
composer: components.side-by-side,
// Aqua theme
title: auto,
)[
...
]A default ordinary slide function with title and footer, where title defaults to the current section title.
---
#focus-slide[
...
]Used to draw the audience's attention. The background color is self.colors.primary.
---
#new-section-slide(title)Start a new section with the given title.
Example
#import "@preview/touying:0.6.1": *
#import themes.aqua: *
#show: aqua-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#title-slide()
#outline-slide()
= The Section
== Slide Title
#lorem(40)
#focus-slide[
Another variant with primary color in background...
]
== Summary
#slide(self => [
#align(center + horizon)[
#set text(size: 3em, weight: "bold", fill: self.colors.primary)
THANKS FOR ALL
]
])Custom Theme
You could consider making your own theme, you can refer to the "Build Your Own Theme" section later.
If you have any questions or need to customize a template, you can also contact orangex4@qq.com for free or inexpensive paid help. All forms are welcome.
🥤A cup of coffee
If you feel that this project has helped you in your work, you can donate to Touying's development work in the following ways to keep the project going.
Sponsor: https://afdian.com/a/orangex4
QR Code:
Dewdrop Theme
This theme takes inspiration from Zhibo Wang's BeamerTheme and has been modified by OrangeX4.
The Dewdrop theme features an elegantly designed navigation, including two modes: sidebar and mini-slides.
Initialization
You can initialize it using the following code:
#import "@preview/touying:0.6.1": *
#import themes.dewdrop: *
#import "@preview/numbly:0.1.0": numbly
#show: dewdrop-theme.with(
aspect-ratio: "16-9",
footer: self => self.info.institution,
navigation: "mini-slides",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#title-slide()
#outline-slide()The register function in the Dewdrop theme accepts the following parameters:
aspect-ratio: The aspect ratio of the slides, which can be "16-9" or "4-3", with a default of "16-9".navigation: The style of the navigation bar, which can be"sidebar","mini-slides", ornone, with a default of"sidebar".sidebar: Settings for the sidebar navigation, with default values of(width: 10em, filled: false, numbered: false, indent: .5em, short-heading: true).mini-slides: Settings for the mini-slides navigation, with default values of(height: 4em, x: 2em, display-section: false, display-subsection: true, short-heading: true).height: The height of the mini-slides, with a default of2em.x: The x-axis padding for the mini-slides, with a default of2em.section: Whether to display slides after the section and before the subsection, with a default offalse.subsection: Whether to separate mini-slides based on subsections, with a default oftrue. Setting this tofalsewill squash them into a single line.footer: The content displayed in the footer of the slides, with a default of an empty array[]. You can customize it with a function, such asself => self.info.author.footer-right: The content displayed on the right side of the footer, with a default ofcontext utils.slide-counter.display() + " / " + utils.last-slide-number.primary: The primary color of the theme, with a default ofrgb("#0c4842").alpha: The transparency level, with a default of70%.
Additionally, the Dewdrop theme provides a #alert[..] function, which you can use with the #show strong: alert syntax to create emphasized alert text.
Color Theme
The Dewdrop theme uses the following color scheme by default:
config-colors(
neutral-darkest: rgb("#000000"),
neutral-dark: rgb("#202020"),
neutral-light: rgb("#f3f3f3"),
neutral-lightest: rgb("#ffffff"),
primary: primary,
)You can modify this color scheme using the config-colors() function. This allows you to tailor the color palette of your slides to match the aesthetic you're aiming for or to conform to a specific branding guideline.
Slide Function Family
The Dewdrop theme provides a variety of custom slide functions:
#title-slide(extra: none, ..args)title-slide reads information from self.info for display, and you can also pass in an extra parameter to display additional information.
---
#slide(
config: (:),
repeat: auto,
setting: body => body,
composer: components.side-by-side,
)[
...
]A default slide with navigation and footer, where the footer is what you set.
---
#focus-slide[
...
]Used to draw attention, with the background color set to self.colors.primary.
Example
#import "@preview/touying:0.6.1": *
#import themes.dewdrop: *
#import "@preview/numbly:0.1.0": numbly
#show: dewdrop-theme.with(
aspect-ratio: "16-9",
footer: self => self.info.institution,
navigation: "mini-slides",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
#outline-slide()
= Section A
== Subsection A.1
$ x_(n+1) = (x_n + a/x_n) / 2 $
== Subsection A.2
A slide without a title but with *important* infos
= Section B
== Subsection B.1
#lorem(80)
#focus-slide[
Wake up!
]
== Subsection B.2
We can use `#pause` to #pause display something later.
#pause
Just like this.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to #pause display other content synchronously.
#show: appendix
= Appendix
== Appendix
Please pay attention to the current slide number.Metropolis Theme
This theme draws inspiration from Matthias Vogelgesang's Metropolis beamer theme and has been modified by Enivex.
The Metropolis theme is elegant and suitable for everyday use. It is recommended to have Fira Sans and Fira Math fonts installed on your computer for the best results.
Initialization
You can initialize it using the following code:
#import "@preview/touying:0.6.1": *
#import themes.metropolis: *
#import "@preview/numbly:0.1.0": numbly
#show: metropolis-theme.with(
aspect-ratio: "16-9",
footer: self => self.info.institution,
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.city,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()The metropolis-theme in the theme accepts the following parameters:
aspect-ratio: The aspect ratio of the slides, which can be "16-9" or "4-3", with a default of "16-9".align: The alignment of the content within the slides, with a default ofhorizon(horizontal alignment).header: The content displayed in the header of the slides, with a default that displays the current heading adjusted to fit the width (utils.display-current-heading(setting: utils.fit-to-width.with(grow: false, 100%))). Alternatively, you can provide a function likeself => self.info.titleto customize the header content.header-right: The content displayed on the right side of the header, with a default that shows the logo specified inself.info.logo.footer: The content displayed in the footer of the slides, with a default of an empty array[]. You can customize it with a function, for example, to display the author's information:self => self.info.author.footer-right: The content displayed on the right side of the footer, with a default that shows the slide number and the total number of slides (context utils.slide-counter.display() + " / " + utils.last-slide-number).footer-progress: A boolean value indicating whether to display a progress bar at the bottom of the slides, with a default oftrue.
Color Theme
Metropolis uses the following default color theme:
config-colors(
primary: rgb("#eb811b"),
primary-light: rgb("#d6c6b7"),
secondary: rgb("#23373b"),
neutral-lightest: rgb("#fafafa"),
neutral-dark: rgb("#23373b"),
neutral-darkest: rgb("#23373b"),
)You can modify this color theme using config-colors().
Slide Function Family
The Metropolis theme provides a variety of custom slide functions:
#title-slide(extra: none, ..args)title-slide reads information from self.info for display, and you can also pass in an extra parameter to display additional information.
---
#slide(
config: (:),
repeat: auto,
setting: body => body,
composer: components.side-by-side,
// metropolis theme
title: auto,
footer: auto,
align: horizon,
)[
...
]A default slide with headers and footers, where the title defaults to the current section title, and the footer is what you set.
---
#focus-slide[
...
]Used to draw attention, with the background color set to self.colors.primary-dark.
---
#new-section-slide(short-title: auto, title)Creates a new section with the given title.
Example
#import "@preview/touying:0.6.1": *
#import themes.metropolis: *
#import "@preview/numbly:0.1.0": numbly
#show: metropolis-theme.with(
aspect-ratio: "16-9",
footer: self => self.info.institution,
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.city,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
= Outline <touying:hidden>
#outline(title: none, indent: 1em, depth: 1)
= First Section
---
A slide without a title but with some *important* information.
== 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
=== sdfsdf
A slide with equation:
$ x_(n+1) = (x_n + a/x_n) / 2 $
#lorem(200)
= Second Section
#focus-slide[
Wake up!
]
== Simple Animation
We can use `#pause` to #pause display something later.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to display other content synchronously.
#speaker-note[
+ This is a speaker note.
+ You won't see it unless you use `config-common(show-notes-on-second-screen: right)`
]
#show: appendix
= Appendix
---
Please pay attention to the current slide number.Simple Theme
This theme originates from Polylux, created by Andreas Kröpelin.
Considered a relatively straightforward theme, you can use it to create simple slides and freely incorporate features you like.
Initialization
You can initialize it using the following code:
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(
aspect-ratio: "16-9",
footer: [Simple slides],
)The register function in the theme accepts the following parameters:
aspect-ratio: The aspect ratio of the slides, which can be "16-9" or "4-3", with a default of "16-9".header: The content displayed in the header, with a default ofutils.display-current-heading(setting: utils.fit-to-width.with(grow: false, 100%)). You can also pass a function likeself => self.info.title.header-right: The content displayed on the right side of the header, with a default ofself => self.info.logo.footer: The content displayed in the footer, with a default of[](empty). You can also pass a function likeself => self.info.author.footer-right: The content displayed on the right side of the footer, with a default ofcontext utils.slide-counter.display() + " / " + utils.last-slide-number.primary: The primary color of the theme, with a default ofaqua.darken(50%).subslide-preamble: By default, it adds the subsection title to the current slide.
Slide Function Family
The Simple theme provides a variety of custom slide functions:
#centered-slide(section: ..)[
...
]A slide with content centered, and the section parameter can be used to create a new section.
---
#title-slide[
...
]Similar to centered-slide, this is provided for consistency with Polylux syntax.
---
#slide(
config: (:),
repeat: auto,
setting: body => body,
composer: components.side-by-side,
)[
...
]A default slide with headers and footers, where the header corresponds to the current section, and the footer is what you set.
---
#focus-slide(foreground: ..., background: ...)[
...
]Used to draw attention, it optionally accepts a foreground color (defaulting to white) and a background color (defaulting to auto, i.e., self.colors.primary).
Example
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(
aspect-ratio: "16-9",
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
]
== First slide
#lorem(20)
#focus-slide[
_Focus!_
This is very important.
]
= Let's start a new section!
== Dynamic slide
Did you know that...
#pause
...you can see the current section at the top of the slide?Stargazer Theme
The Stargazer theme, originally created by Coekjan for the touying-buaa project, is an aesthetically pleasing and versatile theme suitable for everyday use.
Initialization
You can initialize the theme with the following code:
#import "@preview/touying:0.6.1": *
#import themes.stargazer: *
#import "@preview/numbly:0.1.0": numbly
#show: stargazer-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Stargazer in Touying: Customize Your Slide Title Here],
subtitle: [Customize Your Slide Subtitle Here],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
#outline-slide()The stargazer-theme accepts the following parameters:
aspect-ratio: The aspect ratio of the slides, either "16-9" or "4-3", with a default of "16-9".align: The alignment of the slides, with a default ofhorizon.alpha: The transparency of the slides, with a default of20%.title: The content displayed in the header, with a default ofutils.display-current-heading(), or you can pass a function likeself => self.info.title.progress-bar: Whether to display a progress bar at the bottom of the slide, with a default oftrue.footer-columns: The widths of the three footer columns, with a default of(25%, 25%, 1fr, 5em).footer-a: The first column, with a default ofself => self.info.author.footer-b: The second column, with a default ofself => utils.display-info-date(self).footer-c: The third column, with a default ofself => if self.info.short-title == auto { self.info.title } else { self.info.short-title }.footer-d: The fourth column, with a default ofcontext utils.slide-counter.display() + " / " + utils.last-slide-number.
Color Theme
The Stargazer theme uses the following color scheme by default:
config-colors(
primary: rgb("#005bac"),
primary-dark: rgb("#004078"),
secondary: rgb("#ffffff"),
tertiary: rgb("#005bac"),
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
)You can modify this color scheme using config-colors().
Slide Function Family
The Stargazer theme offers a variety of custom slide functions:
#title-slide(extra: none, ..args)title-slide reads information from self.info for display, and you can also pass an extra parameter for additional information.
---
#slide(
config: (:),
repeat: auto,
setting: body => body,
composer: components.side-by-side,
// stargazer theme
title: auto,
footer: auto,
align: horizon,
)[
...
]A standard slide function with a title and footer by default, where title defaults to the current section title, and the footer is the one you set.
---
#outline-slide[
...
]Used to add a table of contents slide.
---
#focus-slide[
...
]Used to draw the audience's attention. The background color is self.colors.primary-dark.
---
#new-section-slide(short-title: auto, title)Start a new section with the given title.
Example
#import "@preview/touying:0.6.1": *
#import themes.stargazer: *
#import "@preview/numbly:0.1.0": numbly
#show: stargazer-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Stargazer in Touying: Customize Your Slide Title Here],
subtitle: [Customize Your Slide Subtitle Here],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
#outline-slide()
= Section A
== Subsection A.1
#tblock(title: [Theorem])[
A simple theorem.
$ x_(n+1) = (x_n + a / x_n) / 2 $
]
== Subsection A.2
A slide without a title but with *important* information.
= Section B
== Subsection B.1
#lorem(80)
#focus-slide[
Wake up!
]
== Subsection B.2
We can use `#pause` to #pause display something later.
#pause
Just like this.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to #pause display other content synchronously.
#show: appendix
= Appendix
== Appendix
Please pay attention to the current slide number.University Theme
This aesthetically pleasing theme is courtesy of Pol Dellaiera.
Initialization
You can initialize the theme with the following code:
#import "@preview/touying:0.6.1": *
#import themes.university: *
#import "@preview/numbly:0.1.0": numbly
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()The register function accepts the following parameters:
aspect-ratio: The aspect ratio of the slides, either "16-9" or "4-3", with a default of "16-9".progress-bar: Whether to display a progress bar at the top of the slide, with a default oftrue.header: The content displayed in the header, with a default ofutils.display-current-heading(level: 2), or you can pass a function likeself => self.info.title.header-right: The content displayed on the right side of the header, with a default ofself => self.info.logo.footer-columns: The widths of the three columns in the footer, with a default of(25%, 1fr, 25%).footer-a: The first column, with a default ofself => self.info.author.footer-b: The second column, with a default ofself => if self.info.short-title == auto { self.info.title } else { self.info.short-title }.footer-c: The third column, with a default of
self => {
h(1fr)
utils.display-info-date(self)
h(1fr)
context utils.slide-counter.display() + " / " + utils.last-slide-number
h(1fr)
}Color Theme
The University theme uses the following color scheme by default:
config-colors(
primary: rgb("#04364A"),
secondary: rgb("#176B87"),
tertiary: rgb("#448C95"),
neutral-lightest: rgb("#ffffff"),
neutral-darkest: rgb("#000000"),
)You can modify this color scheme using config-colors().
Slide Function Family
The University theme provides a series of custom slide functions:
#title-slide(logo: none, authors: none, ..args)The title-slide function reads information from self.info for display, and you can also pass a logo parameter and an array-type authors parameter.
---
#slide(
config: (:),
repeat: auto,
setting: body => body,
composer: components.side-by-side,
// university theme
title: none,
)[
...
]A standard slide function with a title and footer by default, where title defaults to the current section title, and the footer is the one you set.
Focus Slide
#focus-slide(background-img: ..., background-color: ...)[
...
]Used to capture the audience's attention. The default background color is self.colors.primary.
Matrix Slide
#matrix-slide(columns: ..., rows: ...)[
...
][
...
]Refer to the documentation.
Example
#import "@preview/touying:0.6.1": *
#import themes.university: *
#import "@preview/numbly:0.1.0": numbly
#show: university-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide(authors: ([Author A], [Author B]))
= The Section
== Slide Title
#lorem(40)
#focus-slide[
Another variant with primary color in background...
]
#matrix-slide[
left
][
middle
][
right
]
#matrix-slide(columns: 1)[
top
][
bottom
]
#matrix-slide(columns: (1fr, 2fr, 1fr), ..(lorem(8),) * 9)Fit to Height / Width
Thanks to ntjess for the code.
Fit to Height
If you need to make an image fill the remaining slide height, you can try the fit-to-height function:
#utils.fit-to-height(1fr)[BIG]Function definition:
#let fit-to-height(
width: none, prescale-width: none, grow: true, shrink: true, height, body
) = { .. }Parameters:
width: If specified, this will determine the width of the content after scaling. So, if you want the scaled content to fill half of the slide width, you can usewidth: 50%.prescale-width: This parameter allows you to make Typst's layout assume that the given content is to be laid out in a container of a certain width before scaling. For example, you can useprescale-width: 200%assuming the slide's width is twice the original.grow: Whether it can grow, default istrue.shrink: Whether it can shrink, default istrue.height: The specified height.body: The specific content.
Fit to Width
If you need to limit the title width to exactly fill the slide width, you can try the fit-to-width function:
#utils.fit-to-width(1fr)[#lorem(20)]Function definition:
#let fit-to-width(grow: true, shrink: true, width, body) = { .. }Parameters:
grow: Whether it can grow, default istrue.shrink: Whether it can shrink, default istrue.width: The specified width.body: The specific content.
#import "@preview/touying:0.6.1": *
#import themes.aqua: *
#show: aqua-theme.with(
aspect-ratio: "16-9",
config-info(
title: [标题],
subtitle: [副标题],
author: [作者],
date: datetime.today(),
institution: [机构],
),
)
#set text(lang: "zh")
#title-slide()
#outline-slide()
= 第一节
== 小标题
#slide[
#lorem(40)
]
#slide[
#lorem(40)
]
== 总结
#slide(self => [
#align(center + horizon)[
#set text(size: 3em, weight: "bold", self.colors.primary)
THANKS FOR ALL
敬请指正!
]
])
#import "@preview/touying:0.6.1": *
#import themes.aqua: *
#show: aqua-theme.with(
aspect-ratio: "16-9",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#title-slide()
#outline-slide()
= The Section
== Slide Title
#lorem(40)
#focus-slide[
Another variant with primary color in background...
]
== Summary
#slide(self => [
#align(center + horizon)[
#set text(size: 3em, weight: "bold", fill: self.colors.primary)
THANKS FOR ALL
]
])
#import "@preview/touying:0.6.1": *
#import themes.default: *
#import "@preview/numbly:0.1.0": numbly
#show: default-theme.with(
aspect-ratio: "16-9",
config-common(
slide-level: 3,
zero-margin-header: false,
),
config-colors(primary: blue),
config-methods(alert: utils.alert-with-primary-color),
config-page(
header: text(gray, utils.display-current-short-heading(level: 2)),
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
= Outline <touying:hidden>
#components.adaptive-columns(outline(title: none, indent: 1em))
= Title
== Recall <recall>
*Recall*
#speaker-note[Recall]
#show: touying-set-config.with(config-methods(
cover: utils.semi-transparent-cover,
))
== Animation
#set math.equation(numbering: "(1)")
Simple
#pause
$ x + y $
animation
#touying-recall(<recall>)
#show: appendix
= Appendix
Appendix
#import "@preview/touying:0.6.1": *
#import themes.dewdrop: *
#import "@preview/numbly:0.1.0": numbly
#show: dewdrop-theme.with(
aspect-ratio: "16-9",
footer: self => self.info.institution,
navigation: "mini-slides",
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
#outline-slide()
= Section A
== Subsection A.1
$ x_(n+1) = (x_n + a / x_n) / 2 $
== Subsection A.2
A slide without a title but with *important* infos
= Section B
== Subsection B.1
#lorem(80)
#focus-slide[
Wake up!
]
== Subsection B.2
We can use `#pause` to #pause display something later.
#pause
Just like this.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to #pause display other content synchronously.
#show: appendix
= Appendix
== Appendix
Please pay attention to the current slide number.
#import "@preview/touying:0.6.1": *
#import themes.university: *
#import "@preview/cetz:0.4.1"
#import "@preview/fletcher:0.5.8" as fletcher: edge, node
#import "@preview/numbly:0.1.0": numbly
#import "@preview/theorion:0.4.1": *
#import cosmos.clouds: *
#show: show-theorion
// cetz and fletcher bindings for touying
#let cetz-canvas = touying-reducer.with(
reduce: cetz.canvas,
cover: cetz.draw.hide.with(bounds: true),
)
#let fletcher-diagram = touying-reducer.with(
reduce: fletcher.diagram,
cover: fletcher.hide,
)
#show: university-theme.with(
aspect-ratio: "16-9",
// align: horizon,
// config-common(handout: true),
config-common(frozen-counters: (theorem-counter,)), // freeze theorem counter for animation
config-info(
title: [Title],
subtitle: [Subtitle],
author: [Authors],
date: datetime.today(),
institution: [Institution],
logo: emoji.school,
),
)
#set heading(numbering: numbly("{1}.", default: "1.1"))
#title-slide()
== Outline <touying:hidden>
#components.adaptive-columns(outline(title: none, indent: 1em))
= Animation
== Simple Animation
We can use `#pause` to #pause display something later.
#pause
Just like this.
#meanwhile
Meanwhile, #pause we can also use `#meanwhile` to #pause display other content synchronously.
#speaker-note[
+ This is a speaker note.
+ You won't see it unless you use `config-common(show-notes-on-second-screen: right)`
]
== Complex Animation
At subslide #touying-fn-wrapper((self: none) => str(self.subslide)), we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.
== Callback Style Animation
#slide(
repeat: 3,
self => [
#let (uncover, only, alternatives) = utils.methods(self)
At subslide #self.subslide, we can
use #uncover("2-")[`#uncover` function] for reserving space,
use #only("2-")[`#only` function] for not reserving space,
#alternatives[call `#only` multiple times \u{2717}][use `#alternatives` function #sym.checkmark] for choosing one of the alternatives.
],
)
== Math Equation Animation
Equation with `pause`:
$
f(x) & = pause x^2 + 2x + 1 \
& = pause (x + 1)^2 \
$
#meanwhile
Here, #pause we have the expression of $f(x)$.
#pause
By factorizing, we can obtain this result.
== CeTZ Animation
CeTZ Animation in Touying:
#cetz-canvas({
import cetz.draw: *
rect((0, 0), (5, 5))
(pause,)
rect((0, 0), (1, 1))
rect((1, 1), (2, 2))
rect((2, 2), (3, 3))
(pause,)
line((0, 0), (2.5, 2.5), name: "line")
})
== Fletcher Animation
Fletcher Animation in Touying:
#fletcher-diagram(
node-stroke: .1em,
node-fill: gradient.radial(
blue.lighten(80%),
blue,
center: (30%, 20%),
radius: 80%,
),
spacing: 4em,
edge((-1, 0), "r", "-|>", `open(path)`, label-pos: 0, label-side: center),
node((0, 0), `reading`, radius: 2em),
edge((0, 0), (0, 0), `read()`, "--|>", bend: 130deg),
pause,
edge(`read()`, "-|>"),
node((1, 0), `eof`, radius: 2em),
pause,
edge(`close()`, "-|>"),
node((2, 0), `closed`, radius: 2em, extrude: (-2.5, 0)),
edge((0, 0), (2, 0), `close()`, "-|>", bend: -40deg),
)
= Theorems
== Prime numbers
#definition[
A natural number is called a #highlight[_prime number_] if it is greater
than 1 and cannot be written as the product of two smaller natural numbers.
]
#example[
The numbers $2$, $3$, and $17$ are prime.
@cor_largest_prime shows that this list is not exhaustive!
]
#theorem(title: "Euclid")[
There are infinitely many primes.
]
#pagebreak(weak: true)
#proof[
Suppose to the contrary that $p_1, p_2, dots, p_n$ is a finite enumeration
of all primes. Set $P = p_1 p_2 dots p_n$. Since $P + 1$ is not in our list,
it cannot be prime. Thus, some prime factor $p_j$ divides $P + 1$. Since
$p_j$ also divides $P$, it must divide the difference $(P + 1) - P = 1$, a
contradiction.
]
#corollary[
There is no largest prime number.
] <cor_largest_prime>
#corollary[
There are infinitely many composite numbers.
]
#theorem[
There are arbitrarily long stretches of composite numbers.
]
#proof[
For any $n > 2$, consider $ n! + 2, quad n! + 3, quad ..., quad n! + n $
]
= Others
== Side-by-side
#slide(composer: (1fr, 1fr))[
First column.
][
Second column.
]
== Multiple Pages
#lorem(200)
#show: appendix
= Appendix
== Appendix
Please pay attention to the current slide number.
#import "@preview/touying:0.6.1": *
#import themes.simple: *
#show: simple-theme.with(
aspect-ratio: "16-9",
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
]
== First slide
#lorem(20)
#focus-slide[
_Focus!_
This is very important.
]
= Let's start a new section!
== Dynamic slide
Did you know that...
#pause
...you can see the current section at the top of the slide?
Related skills
FAQ
How do you create slides in Touying?
Use headings to create most slides, and use #slide only for custom layout or animation.
How should config be structured?
Use a single entry file that applies #show and all config-* calls, preferring config-page and config-common over direct set page.