347 lines
9.6 KiB
Typst
347 lines
9.6 KiB
Typst
#let theme = (
|
|
bg-color: rgb("#fdf6e3"),
|
|
fg-color: rgb("#073642"),
|
|
accent-color: rgb("#268bd2"),
|
|
border-color: rgb("#073642"),
|
|
heading-font: "Atkinson Hyperlegible",
|
|
body-font: "DejaVu Sans",
|
|
border-radius: 0.8cm,
|
|
border-stroke: 4pt,
|
|
// Fixed sizes (fallback if responsive is disabled)
|
|
title-size: 20pt,
|
|
label-size: 12pt,
|
|
body-size: 12pt,
|
|
info-text-size: 12pt,
|
|
// Responsive sizing configuration
|
|
title-size-max: 28pt,
|
|
title-size-min: 14pt,
|
|
title-base-length: 45, // Character count baseline
|
|
body-size-max: 14pt,
|
|
body-size-min: 9pt,
|
|
body-base-length: 200, // Character count baseline
|
|
info-size-max: 11pt,
|
|
info-size-min: 9pt,
|
|
info-base-length: 60, // Character count baseline
|
|
)
|
|
|
|
// Default logo configuration
|
|
#let default-logo = (
|
|
path: none,
|
|
height: 4cm,
|
|
fit: "contain",
|
|
rotation: 10deg,
|
|
offset: (x: 0cm, y: 0cm),
|
|
pin-position: none, // "top-left", "top-center", "top-right", etc.
|
|
)
|
|
|
|
// Konfiguriert die Logos
|
|
#let configure-logo(logo-config) = {
|
|
if logo-config == none {
|
|
return default-logo
|
|
}
|
|
if type(logo-config) == str {
|
|
// String = nur Pfad
|
|
return default-logo + (path: logo-config)
|
|
}
|
|
return default-logo + logo-config
|
|
}
|
|
|
|
// Hilfsfunktion um ein einzelnes Logo zu positionieren
|
|
#let place-logo(logo, position, dx: 0cm, dy: 0cm) = {
|
|
let logo = configure-logo(logo)
|
|
if logo.path == none { return }
|
|
|
|
place(position, dx: logo.offset.x + dx, dy: logo.offset.y + dy)[
|
|
#rotate(logo.rotation)[
|
|
#image(logo.path, height: logo.height, fit: logo.fit)
|
|
]
|
|
]
|
|
}
|
|
|
|
// Positioniert mehrere Logos basierend auf Layout und Anzahl
|
|
#let position-logos(logos, layout: "horizontal") = {
|
|
let logos = if type(logos) == array {
|
|
logos.map(configure-logo)
|
|
} else if type(logos) == dictionary {
|
|
(configure-logo(logos),)
|
|
} else {
|
|
()
|
|
}
|
|
|
|
if layout == "horizontal" {
|
|
// Logos nebeneinander oben
|
|
if logos.len() == 1 {
|
|
place-logo(logos.first(), top + center)
|
|
} else if logos.len() == 2 {
|
|
place-logo(logos.at(0), top + left, dx: 1.5cm, dy: 0.7cm)
|
|
place-logo(logos.at(1), top + right, dx: -1.5cm, dy: 0.7cm)
|
|
} else if logos.len() == 3 {
|
|
place-logo(logos.at(0), top + left, dx: 1.5cm, dy: 0.7cm)
|
|
place-logo(logos.at(1), top + center, dy: 0.7cm)
|
|
place-logo(logos.at(2), top + right, dx: -1.5cm, dy: 0.7cm)
|
|
}
|
|
} else if layout == "vertical" {
|
|
// Logos auf der rechten Seite positioniert, vertikal gestapelt und zentral angeordnet
|
|
let card-height = 14cm
|
|
let logo-height = 4cm // Standard logo height
|
|
|
|
if logos.len() == 1 {
|
|
// Single logo: centered vertically
|
|
let dy = (card-height - logo-height) / 2
|
|
place-logo(logos.first(), top + right, dx: -1cm, dy: dy)
|
|
} else if logos.len() == 2 {
|
|
// Two logos: vertically spaced around center
|
|
let total-height = 2 * logo-height
|
|
let start-dy = (card-height - total-height) / 2
|
|
place-logo(logos.at(0), top + right, dx: -1cm, dy: start-dy)
|
|
place-logo(logos.at(1), top + right, dx: -1cm, dy: start-dy + logo-height)
|
|
} else {
|
|
// 3+ logos: evenly distributed vertically around center
|
|
let total-height = logos.len() * logo-height
|
|
let start-dy = (card-height - total-height) / 2
|
|
for (i, logo) in logos.enumerate() {
|
|
let dy = start-dy + i * logo-height
|
|
place-logo(logo, top + right, dx: -1cm, dy: dy)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hilfsfunktion um responsive Schriftgröße zu berechnen basierend auf Textlänge
|
|
#let calculate-responsive-size(text-content, max-size: 28pt, min-size: 14pt, base-length: 50) = {
|
|
// Zähle Zeichen in Text
|
|
let text-length = if type(text-content) == str {
|
|
text-content.len()
|
|
} else if type(text-content) == content {
|
|
// Für Content-Blöcke: Schätzung der Länge
|
|
repr(text-content).len()
|
|
} else {
|
|
0
|
|
}
|
|
|
|
// Berechne Reduktionsfaktor
|
|
let ratio = if text-length > base-length {
|
|
(base-length / text-length)
|
|
} else {
|
|
1.0
|
|
}
|
|
|
|
// Skaliere die Größe, halte sie in Grenzen
|
|
let range = max-size - min-size
|
|
let scaled = min-size + range * ratio
|
|
|
|
// Stelle sicher, dass die Größe in Grenzen bleibt
|
|
if scaled > max-size {
|
|
max-size
|
|
} else if scaled < min-size {
|
|
min-size
|
|
} else {
|
|
scaled
|
|
}
|
|
}
|
|
|
|
// Hilfsfunktion um responsive Abstände basierend auf Textmenge zu berechnen
|
|
#let calculate-responsive-spacing(about-content, max-spacing: 1.2em, min-spacing: 0.6em, base-length: 200) = {
|
|
let text-length = if type(about-content) == str {
|
|
about-content.len()
|
|
} else if type(about-content) == content {
|
|
repr(about-content).len()
|
|
} else {
|
|
0
|
|
}
|
|
|
|
// Reduziere Abstand wenn viel Text vorhanden
|
|
let ratio = if text-length > base-length {
|
|
(base-length / text-length)
|
|
} else {
|
|
1.0
|
|
}
|
|
|
|
let range = max-spacing - min-spacing
|
|
let scaled = min-spacing + range * ratio
|
|
|
|
if scaled > max-spacing {
|
|
max-spacing
|
|
} else if scaled < min-spacing {
|
|
min-spacing
|
|
} else {
|
|
scaled
|
|
}
|
|
}
|
|
|
|
// Hilfsfunktion um den Inhalt zu arrangieren
|
|
#let arrange-content(title: [], when: [], where: [], about: [], layout: "horizontal", current-theme: theme) = {
|
|
let left-padding = if layout == "vertical" { 1.5cm } else { 1.5cm }
|
|
let right-padding = if layout == "vertical" { 5cm } else { 1.5cm }
|
|
let top-padding = if layout == "vertical" { 0.8cm } else { 5.5cm }
|
|
|
|
// Berechne responsive Schriftgrößen
|
|
let responsive-title-size = calculate-responsive-size(
|
|
title,
|
|
max-size: current-theme.title-size-max,
|
|
min-size: current-theme.title-size-min,
|
|
base-length: current-theme.title-base-length
|
|
)
|
|
|
|
let responsive-body-size = calculate-responsive-size(
|
|
about,
|
|
max-size: current-theme.body-size-max,
|
|
min-size: current-theme.body-size-min,
|
|
base-length: current-theme.body-base-length
|
|
)
|
|
|
|
let responsive-info-size = calculate-responsive-size(
|
|
when + where,
|
|
max-size: current-theme.info-size-max,
|
|
min-size: current-theme.info-size-min,
|
|
base-length: current-theme.info-base-length
|
|
)
|
|
|
|
// Berechne responsive Abstände
|
|
let responsive-spacing = calculate-responsive-spacing(
|
|
about,
|
|
max-spacing: 1.2em,
|
|
min-spacing: 0.5em,
|
|
base-length: current-theme.body-base-length
|
|
)
|
|
|
|
// Berechne maximale Höhe für Beschreibungstext basierend auf verfügbarem Platz
|
|
// Card: 14cm x 14cm, Padding: 1.5cm L+R, 1.5cm B, 5.5cm T (horizontal)
|
|
// Ungefähre verfügbare Höhe: 14cm - 5.5cm - 1.5cm (Title/Info space) - 1.5cm (padding) = 5.5cm
|
|
let max-about-height = 5.5cm
|
|
|
|
pad(top: top-padding, bottom: 1.5cm, left: left-padding, right: right-padding)[
|
|
#stack(
|
|
dir: ttb,
|
|
spacing: responsive-spacing,
|
|
// Title mit responsiver Größe
|
|
if title != [] {
|
|
text(size: responsive-title-size, weight: "bold", font: current-theme.heading-font, title)
|
|
},
|
|
|
|
// Info-Block für wann/wo mit responsiver Größe
|
|
if when != [] or where != [] {
|
|
block(
|
|
inset: 0.8em,
|
|
radius: 0.4em,
|
|
width: 100%,
|
|
fill: current-theme.bg-color.mix(current-theme.accent-color).darken(10%),
|
|
stack(
|
|
dir: ttb,
|
|
spacing: 0.4em,
|
|
if when != [] {
|
|
align(center + horizon, stack(
|
|
dir: ltr,
|
|
spacing: 0.5em,
|
|
align(center + horizon, emoji.calendar),
|
|
text(size: responsive-info-size, when),
|
|
))
|
|
},
|
|
if where != [] {
|
|
align(center + horizon, stack(
|
|
dir: ltr,
|
|
spacing: 0.5em,
|
|
align(center + horizon, emoji.pin),
|
|
text(size: responsive-info-size, where),
|
|
))
|
|
},
|
|
),
|
|
)
|
|
},
|
|
|
|
// Beschreibungstext mit responsiver Größe und maximaler Höhe
|
|
if about != [] {
|
|
let about-align = if layout == "vertical" { left } else { center }
|
|
box(
|
|
height: max-about-height,
|
|
width: 100%,
|
|
clip: true,
|
|
align(about-align, text(size: responsive-body-size, about))
|
|
)
|
|
}
|
|
)
|
|
]
|
|
}
|
|
|
|
// Haupt-Vorlage-Funktion
|
|
#let sharepic(
|
|
logos: (),
|
|
layout: "horizontal",
|
|
title: [],
|
|
when: [],
|
|
where: [],
|
|
about: [],
|
|
theme-override: (:)
|
|
) = {
|
|
// Überschreiben der Theme
|
|
let current-theme = theme + theme-override
|
|
|
|
set page(
|
|
width: 15cm,
|
|
height: 15cm,
|
|
fill: current-theme.bg-color,
|
|
margin: 0cm,
|
|
)
|
|
set text(
|
|
font: current-theme.body-font,
|
|
size: 15pt,
|
|
fill: current-theme.fg-color,
|
|
)
|
|
|
|
align(center + horizon)[
|
|
#rect(
|
|
width: 14cm,
|
|
height: 14cm,
|
|
radius: current-theme.border-radius,
|
|
stroke: current-theme.border-stroke + current-theme.border-color,
|
|
)[
|
|
// Positioniert Logos basierend auf Layout
|
|
#{position-logos(logos, layout: layout)}
|
|
|
|
// Inhaltsbereich
|
|
#{arrange-content(
|
|
title: title,
|
|
when: when,
|
|
where: where,
|
|
about: about,
|
|
layout: layout,
|
|
current-theme: current-theme
|
|
)}
|
|
]
|
|
]
|
|
}
|
|
|
|
// Verwendungsbeispiele und Dokumentation -
|
|
/*
|
|
#sharepic(
|
|
logos: (
|
|
(path: "logo1.png", height: 4cm),
|
|
(path: "connection.png", height: 4cm, rotation: -8deg),
|
|
(path: "kew.jpg", height: 4cm, rotation: 8deg)
|
|
),
|
|
layout: "horizontal",
|
|
title: [Beispiel-Event],
|
|
when: [22.10.2025, 17:00 Uhr],
|
|
where: [Ort Hier],
|
|
about: [Beschreibung des Events...]
|
|
)
|
|
|
|
#sharepic(
|
|
logos: (
|
|
(path: "logo1.png", height: 3cm),
|
|
(path: "logo2.png", height: 3cm),
|
|
(path: "logo3.png", height: 3cm),
|
|
(path: "logo4.png", height: 3cm)
|
|
),
|
|
layout: "vertical",
|
|
title: [Beispiel-Event],
|
|
about: [Beschreibung des Events...]
|
|
)
|
|
|
|
// Einfache Verwendung mit String-Logos
|
|
#sharepic(
|
|
logos: ("connection.png", "kew.jpg"),
|
|
layout: "horizontal",
|
|
title: [Beispiel-Event]
|
|
)
|
|
*/
|