Das eigene Aussehen#
Ziel dieses Kapitels: eine Präsentation, die nach dem eigenen Fach und dem eigenen Geschmack aussieht – ohne dass die Bewegung darunter leidet.
Ein fertiges Theme wählen#
Das Aussehen steckt in einem Theme: Farben, Schriften, die Form des Folientitels, der Fortschritt am Rand sowie Titel- und Abschnittsfolie. Fünf sind mitgeliefert, theme: wählt eines aus:
#show: presentation.with(theme: themes.lesson)| Theme | Anlass |
|---|---|
themes.default | Der Vortrag im hellen Saal: dunkler Titelbalken, wachsender Fortschrittsbalken. Die Vorgabe. |
themes.lesson | Der Unterricht: eine Farbe je Sache, kein Balken, statt einer Fußzeile ein laufender Kopf mit Nummer und Abschnitt, wie im Schulbuch. |
themes.night | Der abgedunkelte Raum: tiefer Grund, heller Satz, kühler Akzent, Fortschritt als dünne Linie oben. |
themes.plain | So wenig wie möglich: keine Fläche, kein Fortschritt, kleiner Titel, viel Luft. |
themes.editorial | Werkdruckpapier, Antiqua, Haarlinien – ein Buch, keine Folie. |
Die fünf sind nicht dieselbe Folie in fünf Farben: Der Titel steht mal in einem Balken, mal frei, mal unter einer Linie, und der Fortschritt wächst oder fehlt ganz.
Ein Theme abwandeln#
Ein Theme ist ein Wörterbuch. + schreibt einzelne Einträge um – der kürzeste Weg zur eigenen Schulfarbe:
#show: presentation.with(theme: themes.lesson + (accent: rgb("#2f7d32")))Wer alles selbst bestimmen will, baut mit theme() eines von Grund auf. Ohne Argument kommt die Vorgabe heraus; jedes gesetzte Argument ändert eine Sache:
#let schule = theme(
paper: rgb("#fbfaf6"),
ink: rgb("#1c2126"),
strong: rgb("#2b4c7e"), // Titel, Kartenkopf, Abschnittsfläche
accent: rgb("#e0762a"), // Striche, Fortschritt, Merkkasten
muted: rgb("#6b7280"), // Fußzeile, Untertitel
font: ("Source Sans 3", "DejaVu Sans"),
size: 20pt, // Fließtext auf der Folie
title-size: 26pt,
header: "plain",
rule-size: 3pt,
footer: "fraction",
progress: "tick",
)
#show: presentation.with(theme: schule)Die drei Bauformen der gewöhnlichen Folie:
| Eintrag | Werte |
|---|---|
header | "band" – farbiger Balken über die ganze Breite; "plain" – Titel auf dem Papier, mit rule-size eine Linie darunter; "run" – Kopfzeile wie im Schulbuch: Nummer links, Abschnitt rechts, Haarlinie darunter. Sie liegt in der Ebene der Fußzeile und wandert beim Blättern nicht mit. |
footer | "fraction" (3 / 12), "number" (3), "center" (mittig) oder "none"; footer-rule legt eine Haarlinie darüber. |
progress | "bar" (wachsender Balken unten), "top" (dasselbe oben), "tick" (wandernde Marke auf einer Schiene) oder "none". |
box sagt, wie eine card gebaut ist. "bar" ist die Vorgabe: weiße Fläche, dünner Rahmen, farbiger Streifen mit versalem Etikett darüber. "label" kommt aus dem Schulbuch: keine Kante, keine Rundung, getönte Fläche, Beschriftung in der Farbe im Kasten. Die Tönung folgt der mitgegebenen Farbe; ohne eigene Farbe gilt surface.
Weitere Einträge steuern die Karten (surface, border), hell auf dunkel (inverted), die Luft um den Rumpf (head-gap, foot-gap, band-height) sowie title-slide und section – zwei ganze Bilder als Funktionen (t, s, geo) => content. Die vollständige Liste steht in der API-Referenz.
Eine Palette wählen#
Ein Theme sagt, wie eine Folie gebaut ist; eine Palette sagt, welche Farbe sie hat. presentation nimmt die Palette getrennt entgegen, und sie überschreibt nur die Einträge, die dastehen:
#show: presentation.with(theme: themes.lesson, palette: (accent: blue))
#show: presentation.with(theme: themes.lesson, palette: palettes.dark)Eine Palette trägt acht Einträge, genau die Farbeinträge eines Themes: paper der Grund der Folie, ink der Fließtext, strong die tragende dunkle Farbe, accent die Signalfarbe, muted das Nebensächliche, surface der Grund einer Karte, border deren Kante und inverted, ob heller Satz auf dunklem Grund steht. Einen Eintrag, den es nicht gibt, weist das Paket ab: palette: (acent: blue) bricht mit einer Meldung ab.
Fünf Paletten sind mitgeliefert. Jede läuft mit jedem der fünf Themes:
| Palette | Woher |
|---|---|
palettes.light | Die Farben von themes.default. Ändert an der Vorgabe nichts. |
palettes.mono | Das Grau von themes.plain, zwei Töne verschoben. |
palettes.textbook | Die Schulbuchfarben von themes.lesson, ein Grau verschoben. |
palettes.parchment | Das Werkdruckpapier von themes.editorial, zwei Töne verschoben. |
palettes.dark | Der dunkle Grund von themes.night, mit tieferem Akzent. |
Daraus folgt: ein weiteres dunkles Theme braucht es nicht, weil Dunkelheit eine Palette ist und keine Gestaltung. themes.lesson mit palettes.dark ist weiterhin der Unterrichtsentwurf, nur dunkel. themes.night bleibt trotzdem ein Theme: sein Zyan leuchtet auf dem eigenen Grund, hält aber auf einer umgedrehten Folie nicht – palettes.dark nimmt darum ein tieferes Blau.
title-fill und rule-fill sind keine Paletteneinträge. Ob sie einer Palette folgen, entscheidet das Theme; alle fünf mitgelieferten lassen sie folgen, entweder als Funktion der Palette (title-fill: p => p.strong) oder als none, was den Akzent meint. themes.X.title-fill auszulesen liefert deshalb eine Funktion, rule-fill liefert none. Wer dort eine feste Farbe hinschreibt, behält sie unter jeder Palette.Die Farben eines Themes#
Dieselben acht Einträge, die eine Palette trägt, belegen die fünf Themes verschieden:
#import "@preview/typstage:0.2.0": themes
#themes.night.accent // die Signalfarbe des Themes, als Farbecard und callout holen sich ihre Farben selbst aus dem laufenden Theme; ein Themewechsel färbt sie mit um. Eine einzelne Karte nimmt color: und fill: entgegen.
card(color: …), callout(color: …), ggb-style(color: …).Unabhängig vom Theme gibt das Paket vier Farbkonstanten heraus – dark, accent, paper und muted, die Farben des Vorgabe-Aussehens. Wer das Theme wechselt, greift besser auf dessen Einträge zu.
Eine Folie umdrehen#
Für die eine Folie, die nur eine große Zahl trägt, gibt es invert. Der Grund wird zur Schriftfarbe der Palette, der Satz zu ihrem Grund; muted, border und surface mischen sich aus beiden, strong und accent gehen unverändert mit. Kopf, Fuß, Foliennummer, Fortschritt, Karte und Merkkasten ziehen mit.
In der Überschriftenschreibweise steht #invert im Rumpf der Folie, so wie #pause:
== Erreicht bis 2026
#invert
#statement[74 %]In der Argumentschreibweise ist es ein Argument von slide:
#slide([Erreicht bis 2026], invert: true)[#statement[74 %]]Nur eine gewöhnliche Folie dreht sich um. Titel- und Abschnittsfolie sind ganze Bilder, die das Theme selbst malt; sie nehmen das Argument nicht an.
Die Marke #invert wird überall gefunden, wo der Rumpf begehbar ist – auch in Blöcken, Tabellenzellen, Rastern, in der Folienüberschrift und hinter #set- und #show-Regeln. Nicht gefunden wird sie, wo der Inhalt an eine Closure geht. Nachgemessen sind das neun: context, fit, anim, card, callout, tiles, cue, stagger und alternatives. Die Folie bleibt dann ohne Meldung stehen; wer eine davon braucht, schreibt slide(invert: true).
Der Kontrastvertrag#
Die mitgelieferten Paletten werden gemessen, bevor sie ausgeliefert werden. Gerechnet wird der WCAG-2-Kontrast, geprüft werden sieben Paarungen:
| Paarung | Mindestens | Wofür |
|---|---|---|
ink auf paper | 4,5 | Fließtext auf der Folie |
ink auf surface | 4,5 | Fließtext in einer Karte |
muted auf paper | 4,5 | Fußzeile, Untertitel, Kopfzeile |
accent auf paper | 3,0 | Striche, Fortschritt, Marke |
accent auf ink | 3,0 | dasselbe auf einer umgedrehten Folie |
accent auf Schwarz | 3,0 | die Überzeit der Vollbilduhr |
border auf paper | 1,2 | Haarlinien |
Die vorletzte fällt aus der Reihe: ihr Grund ist keine Rolle der Palette, sondern Schwarz selbst – die Vollbilduhr ist schwarz von Rand zu Rand, was immer die Palette sagt. Geprüft wird jede der fünf Paletten und ihre umgedrehte Form, als assert beim Laden des Pakets; eine Farbe, die den Vertrag verletzt, bricht den Bau mit der Zahl, die sie verfehlt hat.
Der Vertrag gilt nur für die mitgelieferten Paletten. Eine eigene Palette wird nicht geprüft – weder gewarnt noch umgefärbt. palette-report(…) gibt dieselbe Messung als Liste zurück:
#for f in palette-report((paper: white, ink: black, surface: white,
muted: luma(55%), accent: blue, border: luma(86%))) [
#f.pair: #calc.round(f.ratio, digits: 2) (will #f.min) #f.ok \
]contrast(a, b) ist die Rechnung selbst und nimmt zwei beliebige Farben.
Und die fünf Themes bestehen ihn nicht:
| Theme | Was durchfällt |
|---|---|
themes.default | nichts, alle sieben Paarungen halten |
themes.lesson | muted auf paper misst 4,25 statt 4,5 |
themes.night | accent auf ink misst 1,59 statt 3,0 |
themes.plain | muted auf paper misst 3,35 statt 4,5; accent auf ink misst 1,27 statt 3,0 |
themes.editorial | muted auf paper misst 3,51 statt 4,5; accent auf paper misst 2,84 statt 3,0 |
Geändert wurde keine dieser Farben: ein Wechsel hätte jedes bestehende Deck anders aussehen lassen, und was muted trägt, ist Nebensächliches. Wer die Zahlen einhalten will, legt die passende Palette darüber:
#show: presentation.with(theme: themes.editorial, palette: palettes.parchment)Aus der Füllfarbe wird nicht auf die Schriftfarbe geschlossen. Ein mattes Salbeigrün wie #aebdb3 sieht für eine Helligkeitsregel „hell“ aus, aber Weiß darauf misst 1,96 zu 1 – weit unter den 4,5, die Fließtext will. Deshalb färbt das Paket nirgends automatisch um.
Die eine Ausnahme steht im Theme: Wo ein Theme strong als Schrift setzt – die Überschrift in themes.lesson, der Abschnittstitel in themes.plain –, wählt es zwischen strong und ink nach dem gemessenen Kontrast gegen den Grund.
Die Leinwand#
presentation bestimmt das Format der Folie:
#show: presentation.with() // 16:9, die Vorgabe
#show: presentation.with(width: 800pt, height: 600pt) // 4:3
#show: presentation.with(margin: 48pt) // mehr LuftOhne Angabe ist die Folie 16:9 auf A4-Breite. So trägt sie den Text in derselben körperlichen Größe wie eine Handout-Seite. height ergibt jedes andere Verhältnis, margin den Abstand zum Rand.
Alles, was das Theme zeichnet, skaliert mit der Breite mit: eine halb so breite Präsentation sieht gleich aus, nur kleiner. Anders wird das Layout nur durch das Verhältnis, und der Browser passt Bühne, Übersichtsbildchen und gedruckte Seiten darauf ein.
Typografie#
Schrift und Schriftgröße kommen aus dem Theme (font, size, title-font, title-size). Für alles Weitere – Absätze, eigene Show-Regeln – gibt es den Haken style: eine Funktion, die um jeden Folienrumpf gelegt wird.
#show: presentation.with(
style: it => {
set par(leading: 0.68em, spacing: 0.85em)
show math.equation: set text(size: 1.05em)
it
},
)Alles, was über Größe, Farbe, Schrift, Schnitt, Lage, Sprache und Schreibrichtung des Textes hinausgeht, gehört in style und nicht in eine #set-Regel im Dokument. Im Browser wird jedes bewegte Element ein zweites Mal gesetzt, in einem eigenen Rahmen, der die #show-Regeln der Folie nicht kennt. style liegt auf beidem. Eine Ausnahme reicht typstage selbst hinüber, die Nummerierung von figure, math.equation und heading: ein #set math.equation(numbering: "(1)") im Dokument nummeriert eine Gleichung im bewegten Element wie auf Papier. Eine #show-Regel dagegen, die nummeriert oder zählt, erreicht es nicht, und dann stimmen im Browser auch die Nummern der Folien danach nicht mehr: mit #show math.equation.where(block: true): set math.equation(numbering: "(1)") im Dokument stand eine Gleichung zwei Folien hinter einem anim und einem alternatives im Browser bei (2), auf Papier bei (5). In style stimmt sie.
Für die Formen, die typstage selbst zeichnet, gibt es einen zweiten Weg: Label-Regeln vor #show: presentation. Sie erreichen auch Kopf, Fuß und Titelfolie. Siehe /Labels: jede gebaute Form ansprechen/ weiter unten.
Ein Folienrumpf ist ein Kasten fester Höhe. Ein style, der ihn zwischen zwei Bruchteilsabstände setzt, rückt auch eine kurze Folie in die senkrechte Mitte:
style: it => { v(1fr); it; v(1fr) }Zentriert wird mit Abständen, nicht mit align: Als Stilregel schlüge align bis in jede Rasterzelle durch, und das Aufzählungszeichen eines zweizeiligen Punktes rutschte neben dessen zweite Zeile.
Von rechts nach links#
Ein Deck auf Arabisch, Hebräisch, Persisch oder Urdu braucht eine Zeile, und die ist Typsts eigene:
#set text(lang: "fa")
#show: presentation.with(title: [چهار مثلث در یک مربع])lang genügt für eine Sprache, die Typst von rechts liest; #set text(dir: sagt es für jede andere ausdrücklich. Absätze, Listen und Spalten dreht Typst selbst um. typstage dreht um, was es von Hand zeichnet: den Titel im Band, den Balken neben einem
rtl)callout, die Nummer im Fuß, die Fortschrittsleiste, Titel- und Abschnittsfolien. alternatives, build und tiles setzen an start an, und das ist in so einem Deck die rechte Kante; ein callout ohne title: trägt seine Beschriftung auf Arabisch, Persisch oder Hebräisch.
Die Regel steht vor der Show-Regel. Dahinter erreicht sie noch jeden Folienrumpf und jedes bewegte Element, aber nicht Titelfolie, Abschnittsfolien und Fuß: die werden außerhalb des Rumpfes gezeichnet und läsen weiter von links.
Nicht gespiegelt werden die Übergänge. Ein "slide" kommt weiter von rechts herein; from: "left" dreht ihn um, wo das besser liest.
Bausteine für den Folienrumpf#
Sechs Bausteine für den Rumpf. Es sind Inhaltsfunktionen, keine eigenen Folienarten: sie lassen sich schachteln, in eine Rasterzelle setzen und mit anim einblenden.
card – der benannte Kasten#
#card(title: [Potenzfunktion])[$f(x) = x^n$ mit $n in NN$.]number: setzt eine Ziffernscheibe davor – für Ablaufpläne, bei denen die Nummer zur Sache gehört. color: färbt den Streifen, fill: die Fläche.
#card(number: 2, title: [Zweiter Schritt])[Ableiten, dann einsetzen.]callout – der Merksatz#
#callout[Der Exponent entscheidet über die Symmetrie.]title: ändert die Überschrift (Vorgabe „Merke“), color: die Farbe; title: none lässt sie weg.
side-by-side – zwei Spalten#
Links die Zeichnung oder das Applet, rechts der Text:
#side-by-side(
card(title: [Gerader Exponent])[Achsensymmetrisch zur $y$-Achse.],
stagger[
- $f(-x) = f(x)$
- Wertemenge $W = [0; oo[$
],
)split: nimmt die Spaltenbreiten; die Vorgabe gibt der ersten etwas mehr. Mehr als zwei Spalten sind erlaubt – dann bekommen alle dieselbe Breite, sofern split: nicht ebenso viele Werte nennt.
equal: true macht alle Spalten gleich hoch; ohne das steht jeder Kasten so hoch wie sein eigener Text. Ein height: 100% im Kasten täte es nicht, weil ein Prozentmaß gegen die Region auflöst und nicht gegen die Rasterzeile; deshalb wirkt equal nur auf card und callout. Soll einer davon aufgedeckt werden, gehört das anim in den Kasten, card[#anim[…]], und nicht der Kasten in ein anim: im Browser steht ein aufgedeckter Kasten nur so hoch wie sein eigener Text.
tiles – das Kachelraster#
Jede Kachel erscheint einen Schritt nach der vorigen.
#tiles(
card(title: [eins])[Beobachten],
card(title: [zwei])[Vermuten],
card(title: [drei])[Begründen],
)columns: legt die Spaltenzahl fest (Vorgabe: bis zu drei). stride: 0 lässt alle im selben Schritt erscheinen und staffelt nur über stagger in Millisekunden – dann läuft eine Welle durch das Raster. Deckt eine Kachel selbst etwas auf, rücken die Kacheln dahinter nach, wie die Stücke eines stagger:
#tiles(stride: 0, stagger: 90, [A], [B], [C], [D])duration: und easing: sind die von anim und gelten für jede Kachel gleich. Ohne Angabe gilt die Dauer der Präsentation.
#tiles(duration: 500, easing: "out-back", [A], [B], [C])statement – die große Aussage#
#statement[$ a^2 + b^2 = c^2 $]statement fordert die volle Breite an und zentriert darin – genau das, woran ein blankes align(center, …) in einem verfolgten Element scheitert.
fit – den Inhalt auf seinen Platz rechnen#
Für das eine Stück, dessen Größe nicht im Deck steht: die breite Tabelle aus der Auswertung, das erzeugte Diagramm, die Liste aus einer Datendatei. Ohne fit läuft so ein Block über den Rand, und im Browser wird abgeschnitten, was übersteht.
== Ergebnisse der Regression
#fit(wrap: false, meine-tabelle)fit misst den Block gegen den Platz, an dem er steht, und skaliert ihn geometrisch. Gerechnet wird beim Übersetzen, das Ergebnis steht in HTML und PDF gleich.
Erst die Breite anbieten, dann verkleinern. Der Block bekommt die volle Breite angeboten, bevor gemessen wird; ein Absatz bricht dann um, statt zu schrumpfen. Eine Tabelle oder Zeichnung ordnet sich dabei selbst um, und das ändert das Bild statt seiner Größe – wrap: false misst sie so, wie sie steht. Das ist die eine Angabe, die man vor dem ersten Gebrauch kennen sollte.
Es verkleinert nur. grow: true bläst auch auf, was kleiner ist als sein Platz; shrink: false lässt nur das Vergrößern übrig.
#fit(grow: true)[42%]width und height nehmen auto, eine Länge oder einen Anteil. Bei height: auto nimmt sich der Block, was unter dem übrigen Inhalt der Folie übrig bleibt. In einem card wird der Kasten damit folienhoch, unten abgeschnitten, und was nach dem card steht, fällt von der Folie – dort gibt man height: deshalb ausdrücklich an.
Keine Einblendung im fit. Ein pause findet sich nur, indem der Folienrumpf abläuft, und ein gemessener Block ist eine Closure, die dieser Lauf nicht erreicht – die Schritte fielen ohne Meldung weg. Ein gemessener Block hat außerdem keine feste Höhe, an der ein verfolgtes Element seine Größe festmacht.
fit bricht deshalb ab, mit Namen und Rat, für pause, anim, stagger, alternatives, morph, tiles, video, embed, flipbook, build, scene, camera und cue – auch dann, wenn das fit in einem anderen fit steckt. Der Ausweg: das fit innerhalb der Einblendung setzen, nicht darum herum:
#anim(fit(wrap: false, meine-tabelle)) // so
#fit(anim(meine-tabelle)) // nicht sospeaker-note und bridge-job dürfen im fit stehen. Umgekehrt nicht: eine Notiz, die nur aus einem fit besteht, trägt keinen Text und wird abgewiesen.
Die Rechnung dahinter ist von mosaic übernommen, das sie aus Touying 0.7.4 hat; Touying schreibt die Arbeit daran Andreas Kröpelin (Polylux PR 91) und ntjess zu.
overflow – der Prüflauf vor dem Vortrag#
fit richtet den einen Block, dessen Größe man ahnt. overflow beantwortet die Frage, die man nicht Folie für Folie stellen kann: läuft irgendwo in diesem Deck etwas über seinen Platz?
#show: presentation.with(overflow: "error")Standardmäßig aus, gedacht für einen Lauf vor dem Vortrag. Ein Bauskript muss dafür kein Deck anfassen, sondern hebt die Einstellung von der Kommandozeile an:
typst compile --features html --format html \
--input typstage-overflow=error deck.typ deck.htmlDie Eingabe hebt an, sie senkt nie ab: es gilt die strengere der beiden Angaben, "none" < "record" < "error".
"none"- es wird nichts gemessen. Der Vorgabewert.
"error"- das ganze Deck wird gebaut, und dann bricht es mit allen Stellen auf einmal ab statt mit der ersten.
"record"- es baut durch und legt je Fund einen abfragbaren Datensatz ab. Typst gibt einem Paket keinen Warnkanal,
"record"gibt also nichts aus.
Die Meldung nennt Folie, Schritt und das Maß (hier gekürzt):
error: assertion failed: typstage: 2 slides run over the room the body has. …
slide 2, from step 1 at the earliest: 311.14pt too tall, 675.76pt of content in 364.61pt of room
slide 3, from step 2 at the earliest: 296.49pt too tall, 661.1pt of content in 364.61pt of room
Shorten the slide, split it, or put the block that does not fit into fit(). …„at the earliest“ steht da, weil eine Folie auf Schritt eins genauso hoch ist wie auf Schritt fünf: jedes verfolgte Element hält von Anfang an seinen vollen Platz. Der Schritt ist eine untere Schranke, die Folie stimmt immer. Auf Papier gibt es keinen Schritt, dort steht step: 0.
Die Datensätze holt man mit typst eval, und dafür muss das Deck auf overflow: "record" stehen – auf "error" bricht auch dieser Befehl ab:
typst eval --target html --features html --in deck.typ \
'query(<typstage-overflow>).map(e => e.value)'[{"slide":2,"step":1,"height":675.76,"room":364.61,"over":311.14},
{"slide":3,"step":2,"height":661.1,"room":364.61,"over":296.49}]Was die Prüfung nicht sieht. Gemessen wird nur die Höhe: measure deckelt die Breite, die es meldet, bei der Breite, die es bekommt. Für zu breite Blöcke ist fit die Antwort.
Übersehen werden ein height: 100% im Rumpf (es misst 0), ein 1fr (es fällt zusammen) und alles, was außerhalb seines Layoutkastens zeichnet: scale, move, place mit Versatz. Titel- und Abschnittsfolien haben keinen Rumpfblock und werden nie gemessen. Umgekehrt wird ein v() am Ende eines Rumpfes gemeldet, obwohl es nichts zeichnet.
In der HTML kostet der Lauf merklich Zeit, auf Papier fast keine.
Mit pages: "step" misst die PDF nicht: jede Schrittseite setzt denselben Rumpf wie die eine Seite je Folie, und die Messung dort kostete Decks, die im Rumpf etwas nachschlagen, ihre Konvergenz. Ebenso ein Handzettel, der pages: "step" bekommt, wie ihn bundle() weiterreicht. Die HTML misst weiter und nennt dazu den Schritt.
drift – der Melder für wandernde Szenen#
drift fragt, ob eine Szene beim Blättern stillsteht. Eine Zeichnung ist so groß wie ihr Inhalt; ändert sich der Inhalt über die Halte einer scene, ist jedes Bild anders groß und sitzt in seinem Kasten woanders – beim Blättern wandert das ganze Bild, obwohl sich nur ein Punkt bewegen sollte. Jede Szene misst deshalb ihre Bilder nach, und drift sagt, was mit den Funden geschieht.
"error"- das ganze Deck wird gebaut, und dann bricht es mit allen Szenen auf einmal ab. Der Vorgabewert.
"record"- es baut durch und legt je Fund einen abfragbaren Datensatz ab.
"none"- es wird gar nicht erst gemessen.
#show: presentation.with(drift: "record")Die Meldung nennt Folie, Schritt und die Zahlen (hier gekürzt):
error: assertion failed: typstage: 1 scene draws frames of different sizes. …
slide 4, from step 1: 28 frames in 19 different sizes, up to 28.35pt apart across and 53.86pt downDie Datensätze holt man wie beim Überlauf, mit <typstage-drift> statt <typstage-overflow>; auch dafür muss das Deck auf "record" stehen.
Dieser Melder ist an und overflow nicht: er kostet nur, wer scene benutzt, und was er findet, ist beim Schreiben unsichtbar. Gemessen wird nur im Browserzweig – auf Papier steht ein Standbild, und das wandert nicht.
Was er nicht kann. Er sieht den Fall, er behebt ihn nicht: measure antwortet mit einer Größe und nie damit, wo die Tinte darin liegt.
Was er übersieht. Gemessen wird die Zeichnung selbst, ohne Breitenbezug. Was sich auf 100% setzt, misst für jedes Bild dasselbe und fällt aus der Prüfung – zu Recht, denn so ein Bild hat seinen festen Rahmen schon.
Was er meldet, wo nichts wandert. Eine Zeichnung, die nur nach rechts und unten wächst, bewegt ihre Tinte nicht, misst sich aber verschieden. Dafür steht steady: false an der Szene.
Folien ohne Titel#
Ein nacktes == lässt den Titelbalken weg; der Rumpf rückt an den oberen Rand und bekommt die Höhe, die sonst der Balken belegt hätte. Eine Laufzeile wie die von themes.lesson – Foliennummer, Abschnitt, Haarlinie – entfällt auf einer solchen Folie mit, und ihre Höhe bleibt nicht frei. Fußzeile und Fortschritt stehen wie auf jeder Folie. Das ist die Folienart für die eine große Formel – und das Ziel eines Morphs, der in die Mitte fliegen soll:
==
#place(center + horizon, morph(<ableitung>, text(size: 2.4em)[
$f'(x) = lim_(h -> 0) (f(x+h) - f(x)) / h$
]))In der Argumentform sind alle drei Schreibweisen erlaubt: slide[Rumpf] ohne Titel, slide(none)[Rumpf] ausdrücklich ohne, slide([Titel])[Rumpf] mit. Ohne Titel ist eine Folie, deren Überschrift nichts zeichnet: ==, slide(none), auch == #h(0pt). Eine Überschrift, die nur eine Formel oder ein Bild trägt, ist ein Titel und bekommt Band und Laufzeile.
bleed – bis an die Kante#
bleed legt seinen Inhalt über die ganze Leinwand: Ursprung ist die linke obere Ecke der Folie, der Raum ihre volle Breite und Höhe, gleich was Ränder, Titel und Laufzeile belegen. Es liegt direkt über dem Grund der Folie und unter allem anderen; Titel und Rumpf stehen obenauf.
==
#bleed[
#image("hafen.png", width: 100%, height: 100%, fit: "cover")
#place(dx: 480pt, dy: 300pt, morph(<schild>, card[Wie weit ist es?]))
]Ein place in bleed rechnet von der Ecke der Leinwand, auch ohne Anker und auch hinter einem Bild in voller Höhe. Es ist die Ecke, an der die Schrift beginnt: in einem Deck, das von rechts liest, die rechte obere, und ein positives dx führt von dort nach rechts aus der Folie hinaus. So rechnet Typsts place überall, auch im Rumpf und ohne dieses Paket. Wer von links zählen will, schreibt den Anker aus – #place(top + left, dx: 40pt, dy: 300pt, landet auch in einem persischen Deck genau bei (40, 300) –, und ein Absatz in einem so gesetzten Block richtet sich dann nach links aus, bis
…)align(start) um den Inhalt ihn zurückstellt.
anim, cue und morph darin arbeiten wie überall: das Schild fliegt von der Folie davor auf das Bild und von dort auf die nächste. Der Haken style legt sich um den Inhalt von bleed wie um den Rumpf; ein style, der den Rumpf mit pad einrückt, rückt auch das Bild ein. Und er läuft auf einer randlosen Folie zweimal, weil Bild und Rumpf getrennt gesetzt werden: ein Haken, der nebenbei zählt oder einen Zustand schreibt, tut das dort doppelt.
Eine Folie mit bleed zeichnet kein Chrome: keine Laufzeile, keine Foliennummer, keine Fußlinie, keinen Fortschritt – auf Papier, im Browser und in dessen Druckansicht. Mitgezählt wird sie trotzdem, und auf der nächsten Folie steht die Leiste mit deren Stand wieder da. Auch der Handzettel ist ein PDF: dort steht die randlose Folie als einzige ohne ihre Nummer. Wer sie im Gespräch benennen können will, setzt die Nummer selbst in das bleed – dort liegt sie auf der Leinwand und kommt mit. Die Überlaufprüfung misst nur den Rumpf; was in bleed steht, läuft nie über.
bleed steht oben im Rumpf einer gewöhnlichen Folie, vor anderem Inhalt und vor dem ersten #pause, einmal je Folie. Davor dürfen #set- und #show-Regeln stehen, #invert, #transition, #speaker-note und #class-clock. Es wird vor dem Rumpf gesetzt; weiter unten geschrieben, folgten Schritte, Fußnotenzahlen und Stapelung einer anderen Reihenfolge als der Quelle. Deshalb bricht die Übersetzung ab, statt still umzusortieren:
==
Wie weit ist es?
#bleed(rect(width: 100%, height: 100%, fill: blue))Ebenso bei bleed in einem Titel, in einer Notiz, außerhalb des Decks und in einem Block, einem Raster, einer Liste, align, context, anim, fit, card oder alternatives – auch unter einer Regel #show: it => block(it), die den Rest der Folie in einen Block legt. Was erst später erscheinen soll, steht in bleed in einem anim.
Was im Browser obenauf liegt. Verfolgte Elemente – anim, cue, morph – zeichnet der Browser in einer eigenen Ebene über der Folie. Ein Bild in bleed bleibt deshalb besser unverfolgt; stünde es selbst in einem anim, läge es im Browser über dem Text des Rumpfs, auf Papier darunter.
Was ein Bild kostet. Im HTML trägt jede Folie ihr Bild eigens eingebettet. Zwei Folien mit demselben Foto tragen es zweimal; ein Foto für den Beamer braucht selten mehr als 1920 Bildpunkte in der Breite.
Labels: jede gebaute Form ansprechen#
Jede Form, die typstage selbst zeichnet – Grundfläche, Kopfband, Folientitel, Fußzeile, Fortschritt, Kasten, Merksatz, große Aussage, Titel- und Abschnittsfolie, Ersatzfläche eines Videos –, trägt ein festes Typst-Label. Eine gewöhnliche show-Regel genügt dann, kein Theme-Schlüssel, kein Fork.
#import "@preview/typstage:0.2.0": *
#show label("ts-slide-header-band"): set rect(fill: rgb("#4c1d95"))
#show label("ts-slide-title"): set text(fill: rgb("#fde047"), style: "italic")
#show label("ts-card"): set block(fill: rgb("#eef2ff"))
#show label("ts-statement"): set text(fill: rgb("#be123c"), weight: "bold")
#show: presentation.with(theme: themes.default)Die Flächen nehmen set rect(..), set block(..), set circle(..) oder set line(..); die Schriften nehmen set text(..). Beides wirkt zur Übersetzungszeit und steht deshalb gleich in HTML und PDF – ausgenommen die sechs Labels unter /Medien und Handout/, die im Browser dem echten <video> oder <iframe> weichen, und ts-slide-progress bei progress: "bar" und "top": diesen Balken zieht im Browser die Laufzeit selbst, damit er beim Folienwechsel wachsen kann, in Farbe und Höhe des Themes, und dort erreicht ihn keine Regel auf das Label oder auf rect. Bei progress: "tick" erreicht eine solche Regel beide Ausgaben.
Bei den Flächen wirkt die Kurzform, die Langform nicht:
#show label("ts-slide-progress"): set rect(fill: green) // ja
#show label("ts-slide-progress"): it => { set rect(fill: green); it } // neinDie Kurzform legt die Stilregel um das gefundene Element, die Langform hinein – und im Rechteck steckt kein zweites Rechteck. Bei den 19 Schrift-Labels sind beide Schreibweisen gleichwertig.
Wo die Regel stehen muss#
Vor #show: presentation. Diese eine Stelle erreicht alles: den Folienhintergrund, Kopf, Fuß und Fortschritt, die Titelfolie und jedes bewegte Element.
style erreicht das nicht: der Haken liegt um den Folienrumpf, und Kopf, Fuß, Fortschritt sowie Titel- und Abschnittsfolie entstehen daneben. Gemessen, jede der 42 Regeln einzeln: aus style heraus wirken genau die 13, die im Folienrumpf stehen – ts-card…, ts-callout…, ts-statement und die drei ts-media-…. Die übrigen 29 bleiben dort stumm, ohne Warnung.
Eine show-Regel hinter #show: presentation erreicht ein getracktes Element (anim, morph) nicht:
#show: presentation.with(theme: themes.default)
#show label("ts-statement"): set text(fill: green) // zu spät
== Eine Folie
#statement[fest]
#anim(statement[bewegt])Hier ist fest grün und bewegt schwarz; eine Zeile weiter oben sehen beide gleich aus. Im PDF fällt es nicht auf, weil dort nichts zweimal gesetzt wird. Das gilt für jede #show-Regel, nicht nur für Label-Regeln.
Was eine Label-Regel ändert und was nicht#
Erreichbar ist, was das Paket nicht als ausdrückliches Argument schreibt. Für die Schrift ist das alles; für die Flächen sind es fill und stroke überall und radius dort, wo die Form eine Rundung hat.
width steht überall als Argument und ist deshalb nirgends erreichbar. Bei height gibt es drei Ausnahmen: ts-card, ts-card-bar und ts-callout bekommen ihre Höhe als auto, und auto schlägt keine Regel.
#show label("ts-card"): set block(height: 150pt) // wirkt
#show label("ts-card"): set block(width: 30%) // wirkt nichtBei den Chrome-Flächen, den Grundflächen und dem Handout-Rahmen wirkt weder das eine noch das andere. Nicht erreichbar ist auch die Anordnung der Folie: Kopfhöhe, Abstand der Titellinie, Sitz des Balkens entstehen in place und layout. Dafür sind die Theme-Schlüssel da. Was eine Regel kann, ist ein fertiges Stück als Ganzes verschieben: move auf ts-slide-footer rückt die Nummer, siehe „Die eingebaute Nummer verschieben“.
Eine Regel auf block oder rect reicht nach innen: Sie gilt für die gelabelte Fläche und für jeden Block darin. Bei fill, stroke und radius ist das abgefangen, bei den Abständen nicht – dann verschiebt eine Label-Regel die Folie:
#show label("ts-card"): set block(below: 60pt)
== Eine Folie
#card(title: [Kasten])[Rumpf]
#callout(title: [Merke])[Merksatz]Der Merksatz rückt nach unten und alles unter ihm mit, um 60pt minus dem Blockabstand, je Kante. Labels sind für Schrift und Fläche gedacht; wer Abstände will, nimmt die Argumente der Bausteine oder die Theme-Schlüssel.
Das vollständige Verzeichnis#
Die Namen folgen einem Schema: ts-, dann der Ort, dann der Teil. Steht slide vorn, geht es um die gewöhnliche Folie; steht es hinter title oder section, um jene Folienart – ts-slide-title und ts-title-slide-title sind zwei verschiedene Dinge, und ein Fehlgriff bleibt stumm.
Ein Label, das dieses Theme gerade nicht zeichnet – ein Kopfband bei header: "run" etwa –, gibt es auf dieser Folie nicht, und eine Regel darauf tut nichts.
Die gewöhnliche Folie
| Label | Was es ist | Regel |
|---|---|---|
ts-slide-ground | die Grundfläche | rect |
ts-slide-header-band | das Kopfband, nur bei header: "band" | rect |
ts-slide-header-text | die laufende Kopfzeile, nur bei header: "run" | text |
ts-slide-header-rule | die Haarlinie darunter, nur bei header: "run" | rect |
ts-slide-title | der Folientitel, bei allen drei Kopfarten | text |
ts-slide-title-rule | die Linie darunter, nur bei rule-size > 0pt | rect |
ts-slide-notes | die Anmerkungen der Folie, nur wenn sie welche hat | text |
ts-slide-notes-rule | der kurze Strich darüber | rect |
ts-slide-footer | die Fußzeile | text |
ts-slide-number | die Foliennummer darin | text |
ts-slide-footer-rule | die Haarlinie darüber, nur bei footer-rule > 0pt | rect |
ts-slide-progress | der Fortschrittsbalken, bei progress: "tick" der wandernde Reiter | rect |
ts-slide-progress-track | seine Bahn, nur bei progress: "tick" | rect |
Die Titelfolie
| Label | Was es ist | Regel |
|---|---|---|
ts-title-slide-ground | ihre Grundfläche | rect |
ts-title-slide-band | das Band oben, nur in themes.lesson | rect |
ts-title-slide-title | ihr Titel | text |
ts-title-slide-subtitle | ihr Untertitel | text |
ts-title-slide-rule | die Zierlinie; themes.editorial hat zwei, themes.plain keine | rect |
ts-title-slide-byline | die Zeile aus Verfasser und Datum | text |
Die Abschnittsfolie (einen Untertitel hat sie nicht)
| Label | Was es ist | Regel |
|---|---|---|
ts-section-slide-ground | ihre Grundfläche | rect |
ts-section-slide-bar | der Balken links, nur in themes.lesson | rect |
ts-section-slide-title | ihr Titel | text |
ts-section-slide-rule | die Zierlinie; themes.night hat zwei, themes.lesson keine | rect |
ts-section-slide-parent | die Zeile darüber mit den übergeordneten Abschnitten. Erst ab der zweiten Gliederungsebene | text |
ts-section-slide-back | der Verweis zurück auf das Verzeichnis, am Leseende unten – rechts in einem Deck, das von links liest, links in einem, das von rechts liest. Sein Wort kommt von section-back bei presentation und folgt in der Vorgabe text.lang. Er steht nur, wenn das Deck ein contents() hat und dieses nicht auf der Abschnittsfolie selbst liegt – und als einziges dieser Label zeichnet ihn das Thema auch dann, wenn es eine eigene section-Funktion mitbringt | text |
Die Bausteine im Folienrumpf
| Label | Was es ist | Regel |
|---|---|---|
ts-card | der Kasten: Fläche, Rand, Rundung und alles darin | block |
ts-card-bar | der farbige Reiter darüber, nur bei box: "bar" | block |
ts-card-title | seine Überschrift | text |
ts-card-disc | die Scheibe der Nummer, nur bei number: | circle |
ts-card-number | die Ziffer darin | text |
ts-card-body | sein Rumpf | text |
ts-callout | der Merksatz. Der Balken links ist kein eigenes Label, er ist der linke stroke dieses hier | block |
ts-callout-title | seine Überschrift | text |
ts-callout-body | sein Rumpf | text |
ts-statement | die große Aussage. size wirkt als Faktor darauf, weil statement in em misst | text |
Medien und Handout
| Label | Was es ist | Regel |
|---|---|---|
ts-media-fallback | die Ersatzfläche, die im PDF für ein bewegtes Element steht. Nur eine Hülle: radius sieht man nicht, fill schon | block |
ts-media-fallback-empty | der graue Kasten darin, wenn kein fallback: angegeben ist | block |
ts-media-poster | die graue Fläche eines video ohne poster: | rect |
ts-handout-frame | der gerahmte Kasten einer Folie auf der Handout-Seite | block |
ts-handout-lines | die Schreiblinien daneben oder darunter | line |
ts-handout-note | die Sprechernotiz, wo es eine gibt | text |
ts-canvas-note | die Randnotiz unter einer Folie, deren Leinwand größer ist als sie selbst – nur auf Papier | text |
Ein Theme mit eigener Titelfolie zeichnet keins dieser Labels. title-slide und section sind Funktionen und malen ihr Bild selbst; wer eine eigene mitbringt, verliert die sechs beziehungsweise vier Labels dieser Folienart, und nichts warnt davor.
Typst-Labels und die CSS-Klassen der Laufzeit sind zwei Namensräume. .ts-slide im Stylesheet ist der <section> einer Folie im Browser, ts-slide-title ein Typst-Label. Typsts HTML-Ausgabe legt an manche Formen ein data-typst-label-Attribut; das ist Beiwerk von Typst, kein Versprechen dieses Pakets.
info(): was das Deck über sich selbst weiß#
Labels sagen, wie eine gebaute Form aussieht, nicht was in ihr steht. Die Foliennummer, der Bruch, der Kapitelname in der Kopfzeile: info() gibt sie heraus.
#context {
let deck = info()
[#deck.section.title #h(1fr) #deck.slide.number / #deck.slide.total]
}Es ist dieselbe Lesung, die die eingebaute Fußzeile macht; beide können deshalb keine verschiedenen Zahlen drucken.
| Feld | Was darin steht |
|---|---|
title, subtitle | Titel und Untertitel des Decks |
author, date | Ebendaher. date ist ein datetime oder Inhalt |
slide.number | Diese Folie. Gezählt wie die Fußzeile zählt, Titel- und Abschnittsfolien also nicht mit |
slide.total | So viele Folien werden gezählt |
slide.numbered | Ob diese Folie mitgezählt wird. Auf einer Titel- oder Abschnittsfolie false |
step.number | Der Schritt, auf dem der aufrufende Inhalt selbst steht |
step.total | So viele Schritte hat diese Folie |
section.number | Der wievielte Abschnitt gerade läuft, 0 vor dem ersten |
section.total | So viele Abschnitte hat das Deck |
section.title | Sein Titel, oder none vor dem ersten |
levels | Ein Eintrag je Struktur-Ebene, von außen nach innen. Leer bei slide-level: 1 |
outline | Die ganze Gliederung, ein Eintrag je Abschnittsfolie |
section meint immer die Ebene direkt über der Folie; bei der Vorgabe slide-level: 2 ist das die einzige. Wer mehr Ebenen hat, findet sie in levels und outline. Ein Eintrag beider trägt depth, title und number; number zählt die Abschnitte dieser Ebene im ganzen Deck durch und geht nie zurück. Der Vergleich von outline.at(j).number mit levels.at(..).number sagt damit, ob ein Eintrag vorbei ist, läuft oder noch kommt. Bei gleicher Nummer läuft er nur, solange index dieser Ebene nicht 0 ist: nach einem neuen Teil behält das letzte Kapitel des vorigen seine Nummer in levels und ist vorbei. Die weiteren Felder stehen in der API-Referenz.
#context {
let d = info()
stack(spacing: 0.6em, ..d.outline.map(e => {
let ebene = d.levels.at(e.depth - 1)
let lauf = e.number == ebene.number and ebene.index > 0
text(
weight: if lauf { "bold" } else { "regular" },
fill: if e.number <= ebene.number { black } else { luma(60%) },
[#h((e.depth - 1) * 1.4em)#e.title],
)
}))
}Eine Zahl steht bewusst daneben: Sprecheransicht und Übersicht zählen alle Folien, info().slide.total zählt wie die Fußzeile und lässt Titel- und Abschnittsfolien aus.
Zwei Zählungen, nicht eine#
Das Paket zählt in Folien und in Schritten: eine Folie ist ein Bild, ein Schritt ist ein Tastendruck.
step.number ist der Schritt, auf dem der aufrufende Inhalt selbst steht: im Rumpf einer Folie 1, innerhalb eines anim, stagger oder alternatives der Schritt jener Einblendung. Eine Anzeige, die den laufenden Schritt nennt, muss deshalb in den Einblendungen sitzen, denn der Browser setzt nichts neu:
#let stand = context {
let d = info()
[Schritt #d.step.number von #d.step.total]
}
== Vier Fassungen
#alternatives(stand, stand, stand, stand)Auf dem Papier gibt es keinen laufenden Schritt: die Seite zeigt die Folie im Endzustand, und step.number ist dort gleich step.total.
Die eingebaute Nummer verschieben#
Die Fußzeile setzt das Theme, und an ihren Platz kommt keine Regel heran. Was dort steht, lässt sich aber als Ganzes verschieben: mit einer Regel auf ts-slide-footer, die es in move legt. move verschiebt die Zeichnung und lässt alles darum stehen, wo es war – die Nummer rückt also auf Papier und im Browser um dasselbe Stück:
#show <ts-slide-footer>: move.with(dx: 14pt, dy: 8pt)
#show: presentationPositive Werte gehen nach rechts und nach unten. Wie jede Label-Regel steht sie vor #show: presentation. Die Laufzeile von header: "run", in der themes.lesson seine Nummer trägt, rückt ebenso mit einer Regel auf ts-slide-header-text – als ganze Zeile samt Abschnittstitel, während die Haarlinie darunter stehenbleibt. Auch presentation(margin: …) verschiebt die Nummer, und den Rumpf mit ihr: sie hält den Abstand des seitlichen Randes zur Kante. Ein Rand in em zählt gegen die Schriftgröße des Themes, im PDF und im Browser gleich.
page.width, page.height und page.margin nennen dort die Seite des Dokuments (ohne eigene Seitenregel Typsts Blatt A4) statt der Folie, und here().position() nennt (0, 0) – ein move oder place, das daraus rechnet, landet im HTML woanders als im PDF. counter(page) zählt Seiten, und das HTML hat keine, also bleibt er bei 1; die Foliennummer ist info().slide.number. Und #set page(footer: …), numbering: oder foreground: zeichnen in den Seitenrand oder über die Seite: eine Folie hat keinen Rand, und Typsts HTML-Ausgabe verwirft Seitenregeln ganz. Eine so gezeichnete Nummer steht höchstens im PDF und nie im HTML.Wohin die eigene Fußzeile gehört#
Auf einer Titel- oder Abschnittsfolie zeichnet typstage keine Fußzeile, und in den Zahlenplatz gehört dort nichts. slide.numbered sagt, wann das der Fall ist:
#let fusszeile = context {
let d = info()
let zahl = if d.slide.numbered [#d.slide.number / #d.slide.total] else []
place(bottom + right, text(size: 12pt, fill: muted, zahl))
}Auf einer gewöhnlichen Folie steht sie im Rumpf:
== Eine Folie
#fusszeile
Der Text der Folie.Oben im Rumpf also: vor der ersten #pause und nicht in anim, stagger, alternatives oder einem anderen Baustein, der aufdeckt. Hinter einer #pause gehört die Fußzeile zu ihrem Lauf und steht auf der Höhe seiner letzten Zeile statt am Fuß der Folie, in beiden Ausgaben – solange sie ein place ist. Mit #v(1fr) nach unten geschoben steht sie dagegen im PDF am Fuß der Folie und im Browser unter der Bühne. In einer Einblendung setzt der Browser das Stück in einen eigenen Rahmen, der dort beginnt, wo das Stück beginnt, und ein place(bottom + …) darin landet im HTML tiefer als im PDF, bis unter die Bühne. Oben richtet sie sich am Rumpf aus, und das in beiden.
Auf Titel- und Abschnittsfolien muss sie ins Theme: beide Bilder sind Funktionen, und eine Funktion, die eine andere umschließt, ergänzt sie.
#let basis = themes.default
#let mit(f) = (t, s, geo) => { f(t, s, geo); fusszeile }
#show: presentation.with(
theme: basis + (title-slide: mit(basis.title-slide), section: mit(basis.section)),
)Nicht über style:. style: it => { fusszeile; it } sieht nach der bequemen Abkürzung aus. Der Haken ist aber zugleich die Vorlage, mit der jedes bewegte Element ein zweites Mal gesetzt wird: alles, was dort zeichnet, wird in jedem Sprite mitgezeichnet, und die Fußzeile steht dann mehrfach auf der Folie. Im Rumpf steht sie einmal.
Eine im Rumpf platzierte Fußzeile sitzt am unteren Rand des Rumpfes, nicht der Folie; dazwischen liegt der foot-gap des Themes. Ein dy: am place schiebt sie dorthin, wo sie hin soll.
Als Teil des Rumpfes ist sie Teil der Folie: eine camera nimmt sie mit, und sie kann aus dem Bild fahren, während die eingebaute Nummer stehenbleibt (siehe „Was mitfährt und was stehenbleibt“).
info() liest den Stand der Folie, die gerade gesetzt wird, und braucht deshalb ein context um sich. Vor der Präsentation bricht es mit einer Meldung ab, statt Nullen zu liefern. Danach nicht: wer die Folien als Argumente übergibt und unter den Aufruf noch ein info() schreibt, bekommt weiter die Zahlen der letzten Folie.deck-outline(): wie das Deck geschnitten ist#
info() sagt, wo man steht, nicht wie das Ganze gegliedert ist. Wer sich eine Navigationsleiste baut, braucht genau das. deck-outline() gibt einen Eintrag je Abschnitt heraus, in ihrer Reihenfolge:
#context for a in deck-outline() [
- #a.number. #a.title -- Folien #a.first bis #a.last (#a.count)
]target nennt die Folie des Abschnitts selbst – das braucht, wer wie contents() einen Verweis darauf setzen will.
first, last und count zählen transitiv: unter einen Abschnitt der Tiefe 1 fallen auch die Folien seiner Unterabschnitte. Ein Abschnitt ohne Folien hat none bei first und last und 0 bei count.
Gezählt werden nur Überschriften auf Dokumentebene, also die zwischen den Folien. Eine Überschrift in einer Folie – slide(none)[= Jede Karte lügt] – ist ein Folientitel und eröffnet keinen Abschnitt. Wer eine Navigationsleiste will, setzt die = also zwischen die Folien; examples/gliedern.typ macht das vor.
Ein fremdes Paket, das die Gliederung über query(heading) sucht, findet nichts: die Überschriftennotation zerlegt den Rumpf an seinen Überschriften und kopiert depth und body heraus, das Element selbst fällt weg. Das gilt in beiden Ausgaben. deck-outline() ist die Antwort darauf.
Einen Schritt weiter warten zwei weitere Fallstricke, beide von einem Begleitpaket auf die harte Tour gefunden. Jede Folie wird in einem html.frame gesetzt, und darin fällt ein location auf Seite 1 bei (0, 0) zusammen – wer nach Seiten gruppiert, sieht also eine einzige Gruppe für das ganze Deck, auch im PDF. Und target() meldet in diesem Rahmen "paged", während eine HTML-Datei geschrieben wird; auch damit lassen sich die beiden Ausgaben nicht unterscheiden. info() und deck-outline() umgehen beides: sie tragen die Gliederung selbst, statt sie aus dem Dokument zurückzulesen. info().levels beantwortet insbesondere „in welchem Abschnitt steht diese Folie„ ohne eine einzige Abfrage.
contents(): eine verknüpfte Agenda#
contents() macht aus der Gliederung Links, die in HTML und PDF funktionieren. Setze sie auf eine normale Folie, denn eine Abschnittsfolie hat keinen Rumpf:
== Inhalt
#contents(layout: "1x2-fill")Sie nennt die Abschnitte, also die Überschriften oberhalb von slide-level zwischen den Folien oder, wo die Folien als Argumente übergeben werden, die Aufrufe von section. Ein Deck ohne solche – in der Überschriftenschreibweise bei der Vorgabe slide-level: 2 eines ohne = – bekommt eine leere Liste, und keine Meldung weist darauf hin.
layout: "1x1" ist die standardmäßige einspaltige Liste. layout: "1x2" verteilt gleichmäßig auf zwei Spalten, während layout: "1x2-fill" die erste Spalte nach verfügbarer Höhe füllt und danach in die zweite fließt. Für eine lange Agenda kann mit dem inklusiven, bei eins beginnenden Bereich from: und to: auf mehrere Folien aufgeteilt werden:
== Inhalt 1
#contents(layout: "1x2-fill", from: 1, to: 8)
== Inhalt 2
#contents(layout: "1x2-fill", from: 9, to: 16)Hat ein Deck mehr als eine Gliederungsebene, rücken die tieferen ein. indent: nimmt ein eigenes Maß oder none, dann steht alles bündig:
== Inhalt
#contents(indent: none)highlight: true sagt, wo der Vortrag steht: der laufende Teil und das laufende Kapitel behalten die volle Tinte, alles davor und danach tritt zurück. Das ist die Agenda zwischen zwei Teilen – die, die der Klasse zeigt, wie weit sie gekommen ist.
== Wo wir stehen
#contents(highlight: true)number: ersetzt die komplette Nummernzelle und erhält einen Gliederungseintrag. title: ersetzt die komplette verknüpfte Titelzelle und erhält den Eintrag sowie sein Ziel. Beide Renderer können ihre Typografie vollständig selbst bestimmen. Jeder Eintrag trägt dabei when – "past", "running" oder "coming" –, sodass eine eigene Hervorhebung ohne eigene Rechnerei auskommt:
== Inhalt
#contents(title: (eintrag, ziel) => link(ziel, text(
fill: if eintrag.when == "running" { blue } else { gray },
eintrag.title,
)))Abschnittsfolien tragen von sich aus keine Nummer. section-numbering: bei presentation setzt eine davor – ein Nummerierungsmuster wie "1." oder eine eigene Funktion, die die Abschnittsnummer bekommt. Mit number: none lässt sich die Nummernzelle vollständig entfernen, sodass der Titel die gesamte Breite des Eintrags nutzt.
Der Rückverweis am Fuß der Abschnittsfolie
Jede Abschnittsfolie trägt unten einen Verweis zurück auf das Verzeichnis. section-back: bei presentation sagt, was dort steht:
#show: presentation.with(section-back: [Zur Agenda])Vier Werte. auto ist die Vorgabe und nimmt das Wort aus der Sprache des Decks. none lässt den Verweis auf jeder Abschnittsfolie weg. Inhalt oder eine Zeichenkette setzen ein eigenes Wort. Und eine Funktion bekommt ein Wörterbuch und gibt Inhalt zurück – oder none, und dann bleibt genau diese eine Folie ohne Verweis:
| Feld | Was darin steht |
|---|---|
back.location | die location der Verzeichnisfolie, fertig für link() |
back.word | das Vorgabewort in der Sprache des Decks |
back.contents.number | die gedruckte Foliennummer des Verzeichnisses |
back.section.number | die Nummer des Abschnitts, auf dem der Verweis steht |
back.section.title | sein Titel, mit dem Präfix aus section-numbering |
back.section.depth | seine Gliederungsebene |
back.section.parents | die Titel darüber, von außen nach innen |
#show: presentation.with(
section-back: back => [#back.word (Folie #back.contents.number)],
)Die Stelle bleibt beim Thema, der Körper kommt vom Deck. Weil der Körper innerhalb des text des Themas steht, sticht ein eigenes text darin die Vorgabe aus – so bekommt der Verweis eine andere Farbe oder Größe, etwa auf einem Grund, auf dem die Akzentfarbe zu leise ist:
#show: presentation.with(
theme: themes.editorial,
section-back: back => text(fill: white)[#back.word],
)Drei Dinge, die sich damit nicht machen lassen, und eines, das man versehentlich macht. Ein eigenes link() im Körper sticht das äußere aus – der Verweis führt dann dorthin und nicht mehr zum Verzeichnis; das ist der Weg zu einem anderen Ziel und zugleich die Falle. info() innerhalb der Funktion nennt die Folie vor der Abschnittsfolie, so wie in einer show-Regel; wer die Nummer des Verzeichnisses will, nimmt back.contents.number, das ist genau dafür da. Hat das Deck kein contents(), erscheint gar nichts, und die Funktion wird nicht einmal gerufen. Und einen anderen Ort gibt der Parameter nicht her: dafür bleibt section-back: none und eine eigene section-Funktion im Thema.
Das letzte Wort behält eine show-Regel auf ts-section-slide-back, die über dem Parameter steht.