659 lines
16 KiB
Typst
659 lines
16 KiB
Typst
#import "@preview/touying:0.6.1": *
|
|
|
|
// Project
|
|
#import "helper.typ": *
|
|
#import "logos.typ": *
|
|
|
|
// Core Imports
|
|
#import "@preview/codly:1.3.0": * // For bindings
|
|
#import "@preview/cetz:0.4.1" // For bindings
|
|
#import "@preview/fletcher:0.5.8" as fletcher: edge, node // For bindings
|
|
#import "@preview/tiaoma:0.3.0" // For auto QR generation
|
|
|
|
// Styling Macro Imports
|
|
#import "@preview/showybox:2.0.4": showybox
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// General Config
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Touying bindings for cetz
|
|
#let cetz-canvas = touying-reducer.with(
|
|
reduce: cetz.canvas,
|
|
cover: cetz.draw.hide.with(bounds: true),
|
|
)
|
|
|
|
// Touying bindings for fletcher
|
|
#let fletcher-diagram = touying-reducer.with(
|
|
reduce: fletcher.diagram,
|
|
cover: fletcher.hide,
|
|
)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Slide Types
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/// Normal slide for the presentation, with title (header) and footer
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #slide(title: [Slide Title])[
|
|
/// #lorem(20)
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// - title (content): Title for the slide
|
|
/// - alignment (alignment): Alignment of the contents of the slide
|
|
/// - outlined (boolean): If the slide shows on the PDF ToC
|
|
///
|
|
/// -> content
|
|
#let slide(
|
|
title: auto,
|
|
alignment: none,
|
|
outlined: true,
|
|
..args,
|
|
) = touying-slide-wrapper(self => {
|
|
let info = self.info + args.named()
|
|
|
|
// Header:
|
|
// ---------------------------------------------------------------------------
|
|
// [ ] Slide Title [ ] Logo [ ]
|
|
// ---------------------------------------------------------------------------
|
|
let header(self) = {
|
|
// Slide Title: if the user overrides the title of a certain slide, use it
|
|
// Progress bar
|
|
if self.store.progress-bar {
|
|
place(top + left, float: false,
|
|
move(dy: 0.00cm, // Bad solution, I know
|
|
components.progress-bar(
|
|
height: 3pt,
|
|
self.colors.primary,
|
|
white,
|
|
)
|
|
)
|
|
)
|
|
}
|
|
let hdr = if title != auto { title } else { self.store.header }
|
|
|
|
show heading: set text(size: 24pt, weight: "semibold")
|
|
|
|
grid(
|
|
columns: (self.page.margin.left, 1fr, 1cm, auto, 1.2cm),
|
|
block(),
|
|
heading(level: 1, outlined: outlined, hdr),
|
|
block(),
|
|
move(dy: -0.31cm, institute-logo(self)),
|
|
block(),
|
|
)
|
|
}
|
|
|
|
// Footer:
|
|
// ---------------------------------------------------------------------------
|
|
// Slide Number [ ] First Author
|
|
// ---------------------------------------------------------------------------
|
|
let footer(self) = context {
|
|
set block(height: 100%, width: 100%)
|
|
set text(size: 15pt, fill: self.colors.footer)
|
|
|
|
grid(
|
|
columns: (self.page.margin.bottom, 1.3%, auto, 1cm),
|
|
move(dx:0.3em, dy: 0.3em, circle(radius: 0.75em, stroke: self.colors.primary + 2pt)[
|
|
#set align(center + horizon)
|
|
#set text(size: 12pt)
|
|
#utils.slide-counter.display()
|
|
]),
|
|
block(),
|
|
block[
|
|
#set align(left + horizon)
|
|
#set text(size: 13pt)
|
|
#info.at("footer", default: "")
|
|
],
|
|
block(),
|
|
)
|
|
|
|
}
|
|
|
|
let self = utils.merge-dicts(self, config-page(
|
|
header: header,
|
|
footer: footer,
|
|
))
|
|
|
|
set align(
|
|
if alignment == none {
|
|
self.store.default-alignment
|
|
} else {
|
|
alignment
|
|
}
|
|
)
|
|
|
|
touying-slide(self: self, ..args)
|
|
})
|
|
|
|
/// Title slide for the presentation
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #title-slide()
|
|
/// ```
|
|
///
|
|
/// -> content
|
|
#let title-slide(..args) = touying-slide-wrapper(self => {
|
|
let info = self.info + args.named()
|
|
let body = {
|
|
let footer-kfu = [
|
|
#set text(size: 13.3pt, weight: "medium")
|
|
|
|
#let circle-icon = [
|
|
#move(dy: 0.03cm, circle(
|
|
fill: none,
|
|
radius: 0.18cm,
|
|
stroke: self.colors.primary + 1.35pt,
|
|
)
|
|
)
|
|
]
|
|
|
|
#v(-0.5cm)
|
|
#box(circle-icon) #h(0.1cm) #self.store.institute
|
|
]
|
|
|
|
set page(footer: footer-kfu, header: none)
|
|
set block(below: 0pt, above: 0pt)
|
|
|
|
place(center + horizon, dx: -15em, [
|
|
#circle(stroke: self.colors.primary + 13em, radius: 32em)
|
|
])
|
|
|
|
// Top-right icon + text
|
|
place(top + right, dy: -1.9cm, dx: 0.78cm, [
|
|
#self.store.logo
|
|
])
|
|
|
|
v(0.8cm)
|
|
|
|
block(width: 83%)[
|
|
#let title = text(size: 40.5pt, weight: "bold")[#info.at(
|
|
"title",
|
|
default: "",
|
|
)]
|
|
|
|
#move(dx: 0.04em)[
|
|
#grid(
|
|
columns: (0.195cm, auto),
|
|
column-gutter: 0.7cm,
|
|
context [
|
|
#let s = measure(title)
|
|
#move(dy: -0.4cm, rect(
|
|
fill: self.colors.primary,
|
|
height: s.height + 0.65cm,
|
|
))
|
|
],
|
|
title,
|
|
)
|
|
]
|
|
]
|
|
|
|
v(0.6cm)
|
|
|
|
block(width: 70%)[
|
|
#text(
|
|
size: 28.3pt,
|
|
fill: self.colors.fore,
|
|
weight: "bold",
|
|
)[#info.subtitle]
|
|
]
|
|
|
|
v(1.48cm)
|
|
|
|
block(width: 70%)[
|
|
#set text(size: 19pt)
|
|
#if type(info.authors) == array [
|
|
#for author in info.authors [
|
|
#author #h(1.1em)
|
|
]
|
|
] else [
|
|
#info.authors
|
|
]
|
|
]
|
|
|
|
v(0.95cm)
|
|
|
|
block(width: 70%)[
|
|
#info.extra
|
|
]
|
|
|
|
if (
|
|
self.info.at("download-qr", default: none) != none
|
|
and self.info.at("download-qr", default: none) != ""
|
|
) {
|
|
place(bottom + right)[
|
|
#align(center + horizon)[
|
|
#let s = 4.9cm
|
|
#tiaoma.qrcode(self.info.download-qr, width: s, height: s)
|
|
]
|
|
]
|
|
}
|
|
}
|
|
|
|
touying-slide(self: self, body)
|
|
})
|
|
|
|
/// Standout slide for the presentation
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #standout-slide(title: [Text])
|
|
/// ```
|
|
///
|
|
/// - title (content): Title for the standout slide
|
|
///
|
|
/// -> content
|
|
#let standout-slide(
|
|
title: none,
|
|
..args,
|
|
) = touying-slide-wrapper(self => {
|
|
let body = {
|
|
set align(center + horizon)
|
|
set text(size: 28pt)
|
|
if title != none {
|
|
move(dy: -2.08cm)[
|
|
#text(weight: "semibold")[#title]
|
|
]
|
|
}
|
|
}
|
|
|
|
let self = utils.merge-dicts(self, config-page(
|
|
header: none,
|
|
footer: none,
|
|
))
|
|
|
|
//counter("touying-slide-counter").update(n => if n > 0 { n - 1 } else { 0 })
|
|
|
|
touying-slide(self: self, body, ..args)
|
|
})
|
|
|
|
/// Section slide for the presentation
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #section-slide(title: [Section A], subtitle: [Subtitle])
|
|
/// ```
|
|
///
|
|
/// - title (content): Title for the section
|
|
/// - subtitle (content): Subtitle for the section
|
|
///
|
|
/// -> content
|
|
#let section-slide(
|
|
title: none,
|
|
subtitle: none,
|
|
..args,
|
|
) = touying-slide-wrapper(self => {
|
|
let body = {
|
|
align(center + horizon)[
|
|
#move(dy: -0.4cm)[
|
|
#if title != none [
|
|
#text(size: 36pt, weight: "semibold")[#title]
|
|
]
|
|
|
|
#if subtitle != none [
|
|
#text(size: 20pt)[#subtitle]
|
|
]
|
|
]
|
|
]
|
|
}
|
|
|
|
let self = utils.merge-dicts(self, config-page(
|
|
header: none,
|
|
footer: none,
|
|
))
|
|
|
|
touying-slide(self: self, body, ..args)
|
|
})
|
|
|
|
/// Blank slide for free content in the presentation
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #blank-slide[
|
|
/// #align(center + horizon)[#lorem(5)]
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// -> content
|
|
#let blank-slide(
|
|
..args,
|
|
body,
|
|
) = touying-slide-wrapper(self => {
|
|
let body = {
|
|
align(center + horizon)[
|
|
#body
|
|
]
|
|
}
|
|
|
|
let self = utils.merge-dicts(self, config-page(
|
|
header: none,
|
|
footer: none,
|
|
margin: 0pt,
|
|
))
|
|
|
|
touying-slide(self: self, body, ..args)
|
|
})
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Main Function
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/// Theme configuration
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #show: definitely-not-kfu-theme.with(
|
|
/// aspect-ratio: "16-9",
|
|
/// slide-alignment: top,
|
|
/// config-info(
|
|
/// title: [Long Paper Title \ with One to Three Lines],
|
|
/// subtitle: [An optional short subtitle],
|
|
/// authors: ([*First Author*], [Second Author], [Third Author]),
|
|
/// extra: [SomeConf 2025],
|
|
/// footer: [First Author, Second Author, Third Author],
|
|
/// download-qr: "",
|
|
/// ),
|
|
/// config-common(
|
|
/// handout: false,
|
|
/// ),
|
|
/// config-colors(
|
|
/// ),
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// - aspect-ratio (str): Aspect ratio for the page. See typst documentatin.
|
|
/// - slide-alignemnt (alignment): Default alignment for `#slide()`
|
|
/// - config-info (dict):
|
|
/// - title (content): Title for the presentation
|
|
/// - subtitle (content): Subtitle for the presentation
|
|
/// - authors (array): Arrray of authors (content)
|
|
/// - extra (content): Extra information for the presentation
|
|
/// - footer (content): Footer for each `#slide()`
|
|
/// - download-qr (str): URL to show on `#title-slide()` with a QR
|
|
/// - config-common (dict):
|
|
/// - handout (bool): Boolean for handout mode
|
|
/// - config-colors (dict): Colors for the presentation
|
|
/// - ... see definition of `#definitely-not-kfu-theme`
|
|
#let definitely-not-kfu-theme(
|
|
aspect-ratio: "16-9",
|
|
header: utils.display-current-heading(level: 1),
|
|
font: "Open Sans",
|
|
institute: [uni-graz.at],
|
|
logo: unigrazgraz-logo,
|
|
slide-alignment: top,
|
|
progress-bar: true,
|
|
..args,
|
|
body,
|
|
) = {
|
|
// Touying configuration
|
|
show: touying-slides.with(
|
|
config-page(
|
|
paper: "presentation-" + aspect-ratio,
|
|
margin: (
|
|
left: 1.49cm,
|
|
right: 1.48cm,
|
|
top: 2.6cm,
|
|
bottom: 1.6cm,
|
|
)
|
|
),
|
|
config-store(
|
|
header: header,
|
|
font: font,
|
|
institute: institute,
|
|
logo: logo,
|
|
default-alignment: slide-alignment,
|
|
progress-bar: progress-bar,
|
|
),
|
|
config-common(
|
|
slide-fn: slide,
|
|
new-section-slide-fn: none,
|
|
preamble: {
|
|
codly(
|
|
display-name: false,
|
|
display-icon: false,
|
|
radius: 0pt,
|
|
stroke: 1pt + black,
|
|
smart-indent: true,
|
|
fill: luma(260),
|
|
zebra-fill: luma(250),
|
|
number-format: number => [#text(size: 12pt, fill: gray)[#number]],
|
|
number-align: right + horizon,
|
|
breakable: true,
|
|
)
|
|
}
|
|
),
|
|
config-colors( // Exported from official template
|
|
kfu: rgb("FED501"),
|
|
primary: rgb("FED501"),
|
|
secondary: rgb("9933cc"),
|
|
footer: rgb("808080"),
|
|
foot: rgb("e1e1e1"),
|
|
web: rgb("0c5a77"),
|
|
csbme: rgb("19b4e3"),
|
|
arch: rgb("0a98a2"),
|
|
bauw: rgb("d68e23"),
|
|
etec: rgb("68242c"),
|
|
mach: rgb("3066ba"),
|
|
chem: rgb("5e60a8"),
|
|
math: rgb("1e6934"),
|
|
crypto: rgb("a6c947"),
|
|
system: rgb("1171a8"),
|
|
formal: rgb("f7931e"),
|
|
applied: rgb("7d219e"),
|
|
page: rgb("e4154b"),
|
|
fore: rgb("0f0f0f"),
|
|
back: rgb("ffffff"),
|
|
dark: rgb("3b5a70"),
|
|
lite: rgb("fefcf1"),
|
|
head: rgb("245b78"),
|
|
body: rgb("e2e9ed"),
|
|
urlA: rgb("0066d8"),
|
|
urlB: rgb("6c2f91"),
|
|
colA: rgb("e4154b"),
|
|
colB: rgb("5191c1"),
|
|
colC: rgb("a5a5a5"),
|
|
colD: rgb("285f82"),
|
|
colE: rgb("78b473"),
|
|
colF: rgb("e59352"),
|
|
main: rgb("e4154b"),
|
|
emph: rgb("285f82"),
|
|
standout: rgb("245b78"),
|
|
),
|
|
config-methods(
|
|
cover: (self: none, body) => hide(body),
|
|
init: (
|
|
self: none,
|
|
body,
|
|
) => {
|
|
set text(size: 20pt, lang: "en", region: "US", font: font)
|
|
show emph: it => { text(self.colors.secondary, it.body) }
|
|
show cite: it => { text(self.colors.secondary, it) }
|
|
show strong: it => { text(weight: "bold", it.body) }
|
|
|
|
// Bibliography
|
|
set bibliography(title: none, style: "ieee")
|
|
set cite(style: "alphanumeric")
|
|
show bibliography: set par(spacing: 0.4cm)
|
|
show bibliography: set grid(align: top + left)
|
|
show bibliography: set text(17pt)
|
|
show bibliography: t => {
|
|
show grid.cell.where(x: 0): set text(fill: self.colors.secondary)
|
|
show grid.cell.where(x: 0): set align(top + left)
|
|
show link: set text(fill: gray)
|
|
t
|
|
}
|
|
|
|
// Lists & Enums
|
|
set list(
|
|
marker: (
|
|
(move(dx: 0.15cm, dy: 0.1cm, circle(radius: 0.2em, stroke: self.colors.primary + 2pt, fill: self.colors.primary))),
|
|
(move(dy: 0.2cm, circle(width: 0.2em, fill: black))),
|
|
(move(dy: 0.2cm, circle(width: 0.2em, fill: gray))),
|
|
),
|
|
body-indent: 1.2em,
|
|
)
|
|
set enum(
|
|
numbering: n => {
|
|
circle(stroke: self.colors.primary + 2pt, radius: 0.3cm)[
|
|
#align(center + horizon)[ #text(size: 12pt, fill: black)[#n] ]
|
|
]
|
|
},
|
|
body-indent: 0.6cm
|
|
)
|
|
|
|
// Code blocks
|
|
show: codly-init.with()
|
|
show raw.where(block: true): set text(size: 13pt)
|
|
|
|
// Hotfixes, the messy part
|
|
|
|
// https://github.com/touying-typ/touying/issues/136
|
|
set par(spacing: 0.65em)
|
|
|
|
body
|
|
}),
|
|
..args,
|
|
)
|
|
|
|
body
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Macros
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/// Macro for a pdfpc note
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #note("This will show on pdfpc speaker notes")
|
|
/// ```
|
|
///
|
|
/// - text (str): Note for pdfpc
|
|
///
|
|
/// -> content
|
|
#let note(text) = [
|
|
#pdfpc.speaker-note(text)
|
|
]
|
|
|
|
/// Quote block for phrases. Has a color.primary rectangle in the left
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #quote-block[
|
|
/// #lorem(10)
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// - top-pad (length): Extra height of the quote colored block
|
|
/// - color (color): Color of the quote block
|
|
/// - spacing (length): Spacing after the `#quote-block`
|
|
///
|
|
/// -> content
|
|
#let quote-block(
|
|
top-pad: 0.55cm,
|
|
color: none,
|
|
spacing: 0.3cm,
|
|
body,
|
|
) = [
|
|
#touying-fn-wrapper((self: none) => [
|
|
// Grid with the design
|
|
#let g(s: 0cm, body) = [
|
|
#grid(
|
|
columns: (0.195cm, auto),
|
|
column-gutter: 0.7cm,
|
|
row-gutter: 0cm,
|
|
[
|
|
#rect(
|
|
fill: if color == none { self.colors.primary } else { color },
|
|
height: s + top-pad,
|
|
)
|
|
],
|
|
align(horizon, body),
|
|
)
|
|
]
|
|
|
|
// We compute its "auto" heigth and then print it with the correct height
|
|
#layout(size => {
|
|
let (height,) = measure(width: size.width, g(body))
|
|
g(s: height, body)
|
|
})
|
|
|
|
#v(spacing)
|
|
])
|
|
]
|
|
|
|
/// Block with title and content
|
|
///
|
|
/// Example:
|
|
///
|
|
/// ```typst
|
|
/// #color-block(title: [Advantages])[
|
|
/// - A
|
|
/// - B
|
|
/// - C
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// - title (content): Title for the color block
|
|
/// - icon (str): Icon to show at the left of the title (Tableau Icons) https://tabler.io/icons
|
|
/// - spacing (length): Spacing before and after the color block
|
|
/// - color (color): Color for the title block
|
|
/// - color-body (color): Color for the background of the body
|
|
///
|
|
/// -> content
|
|
#let color-block(
|
|
title: [],
|
|
icon: none,
|
|
spacing: 0.78em,
|
|
color: none,
|
|
color-body: none,
|
|
body
|
|
) = [
|
|
#import "@preview/tableau-icons:0.331.0": *
|
|
#touying-fn-wrapper((self: none) => [
|
|
#show emph: it => {
|
|
text(weight: "medium", fill: self.colors.primary, it.body)
|
|
}
|
|
|
|
#showybox(
|
|
title-style: (
|
|
color: black,
|
|
sep-thickness: 0pt,
|
|
),
|
|
frame: (
|
|
//inset: 0.4em,
|
|
radius: 0pt,
|
|
thickness: 0pt,
|
|
border-color: if color == none { self.colors.primary } else { color },
|
|
title-color: if color == none { self.colors.primary } else { color },
|
|
body-color: if color-body == none { self.colors.lite } else { color-body },
|
|
inset: (x: 0.55em, y: 0.65em),
|
|
),
|
|
above: spacing,
|
|
below: spacing,
|
|
title: if icon == none {
|
|
align(horizon)[#strong(title)]
|
|
} else {
|
|
align(horizon)[
|
|
#draw-icon(icon, height: 1.2em, baseline: 20%, fill: white) #h(0.2cm) #strong[#title]
|
|
]
|
|
},
|
|
body,
|
|
)
|
|
])
|
|
]
|
|
|
|
//vim:tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab colorcolumn=81
|