Compare commits

...
Sign in to create a new pull request.

3 commits
main ... main

4 changed files with 430 additions and 0 deletions

79
demo.typ Normal file
View file

@ -0,0 +1,79 @@
#import "template.typ": sharepic
// Demo 1: Horizontales layout mit 3 logos
#sharepic(
logos: (
(path: "kew.jpg", height: 3.5cm, rotation: -8deg),
(path: "connection.png", height: 3.5cm, rotation: 0deg),
(path: "tux.svg", height: 3.5cm, rotation: 8deg)
),
layout: "horizontal",
title: [ÜberwachtAtlas „Gefährliche Orte" & MWVZ],
when: [22.10.2025, 17:00 Uhr],
where: [Uni Leipzig, Hörsaal 16 (Hauptcampus)],
about: [
*Vortrag:* Sogenannte „gefährliche Orte" und Messer- und Waffenverbotszonen ermöglichen der Polizei ortsbezogene, verdachtsunabhängige Kontrollen. Die Polizei setzt diese Kontrollen um und legt die Orte zumeist selbst fest. Im ÜberwachtAtlas haben wir die „gefährlichen Orte" und MWVZ visualisiert.
]
)
// Demo 2: Vertikales layout mit 2 logos
#sharepic(
logos: (
(path: "tux.svg", height: 3.5cm, rotation: -25deg),
(path: "connection.png", height: 3.5cm, rotation: 0deg)
),
layout: "vertical",
title: [AG Link goes KEW],
when: [23.10.2025 13:00],
where: [Uni Leipzig \ Seminargebäude \ Raum S125],
about: [
Wir versuchen in diesem Vortrag zu zeigen warum es ein Problem ist wenn Konzerne unsere Daten sammeln und was man dagegen tun kann.
\ *Alle sind willkommen.
Kein Vorwissen benötigt.*
]
)
// Demo 3: Einfaches layout mit einem Logo
#sharepic(
logos: ("tux.svg",),
layout: "horizontal",
title: [Simple Event],
about: [This is a simple event with just one logo.]
)
// Demo 5: Vertikales layout mit benutzerdefinierter Positionierung
#sharepic(
logos: (
(path: "tux.svg", height: 3cm, rotation: 0deg),
(path: "connection.png", height: 3cm, rotation: 0deg),
(path: "kew.jpg", height: 3cm, rotation: 0deg)
),
layout: "vertical",
title: [Vertical Layout Demo],
about: [This shows how logos are stacked vertically on the left side.]
)
// Demo 6: Verwendung von String-Logos (einfache Verwendung)
#sharepic(
logos: ("tux.svg", "connection.png", "kew.jpg"),
layout: "horizontal",
title: [String Logos Demo],
when: [Today at 2 PM],
where: [Main Hall],
about: [This demonstrates the simplest usage - just pass logo file names as strings.]
)
// Demo 7: Benutzerdefinierte Theme-Überschreibung
#sharepic(
logos: (
(path: "connection.png", height: 4cm, rotation: 0deg)
),
layout: "horizontal",
title: [Custom Theme Demo],
about: [This uses a custom theme override for different colors.],
theme-override: (
bg-color: rgb("#f0f8ff"),
fg-color: rgb("#2f4f4f"),
accent-color: rgb("#4682b4")
)
)

BIN
kew.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

347
template.typ Normal file
View file

@ -0,0 +1,347 @@
#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]
)
*/

4
tux.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB