Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
b7af2f1d9a
468 changed files with 262141 additions and 0 deletions
338
bilder_04/erzeuge_titelseite.py
Normal file
338
bilder_04/erzeuge_titelseite.py
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_titelseite.py
|
||||
"""
|
||||
Baut die Titelseite bilder_04/titelseite.svg (und die HTML-Kopie
|
||||
OR_HTML_04/bilder_04/titelseite.svg) als SVG mit matplotlib. Das Datum im Fußband
|
||||
('Stand: <datum>') wird dynamisch aus datetime.now() gesetzt, damit es nach
|
||||
jedem Build den aktuellen Stand zeigt.
|
||||
|
||||
Die Koordinaten unten sind SVG-Punkte (595.44 x 841.68, A4 @ 72 dpi). Die
|
||||
y-Achse wird invertiert (set_ylim(841.68, 0)), sodass y von oben zählt und
|
||||
die Werte direkt mit der alten, von Hand erzeugten titelseite.svg
|
||||
übereinstimmen.
|
||||
|
||||
Design (aus der Vorgängerversion abgeleitet):
|
||||
- weißer Hintergrund
|
||||
- 5 farbige Akzentbalken oben
|
||||
- 7 konzentrische Ringe oben rechts (indigo, transparent)
|
||||
- violette Trennlinie unter dem Untertitel
|
||||
- grüne, konkave 'Optimierungskurve' mit rotem Stern-Marker (Optimum)
|
||||
- große Solver-Karte (hell, violetter Rand) mit vier kleinen farbigen
|
||||
Solver-Karten (HiGHS indigo, SciPy grün, Google OR-Tools cyan, CVXPY violett)
|
||||
- indigo Fußband unten mit Autor (weiß) und Stand-Datum (hellviolett)
|
||||
|
||||
Aufruf:
|
||||
python3 bilder_04/erzeuge_titelseite.py
|
||||
(wird vom Build-Skript build_version_03.py vor dem PDF-/HTML-Bau aufgerufen).
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Circle, FancyBboxPatch, Rectangle
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe. Ohne diese beiden Vorkehrungen unterscheiden sich
|
||||
# zwei Laeufe - und sogar die zwei Kopien AUS DEMSELBEN Lauf - in zwei Punkten:
|
||||
# * svg.hashsalt: matplotlib wuerfelt sonst die clip-path-IDs je Figure neu.
|
||||
# * metadata={"Date": None} beim Speichern: sonst steht der Zeitstempel des
|
||||
# Schreibvorgangs (auf die Mikrosekunde) in den SVG-Metadaten.
|
||||
# Beides ist unsichtbar, macht aber jeden git-diff unlesbar und die beiden
|
||||
# Kopien bilder_04/ und OR_HTML_04/bilder_04/ dauerhaft verschieden. Die Datei
|
||||
# aendert sich jetzt nur noch, wenn sich das SICHTBARE Datum im Fussband
|
||||
# aendert - also einmal pro Tag.
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
# Hardcodierte deutsche Monatsnamen statt strftime("%B") - so ist das Datum
|
||||
# unabhängig von der gesetzten Locale (de_DE.UTF-8) immer korrekt geschrieben,
|
||||
# auch in CI-/Docker-/Headless-Umgebungen ohne deutsche Locale.
|
||||
MONATE = ["Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||
"Juli", "August", "September", "Oktober", "November", "Dezember"]
|
||||
|
||||
# A4 bei 72 dpi -> 595.44 x 841.68 pt (matplotlib figure-Koordinaten).
|
||||
BREITE, HOEHE = 595.44, 841.68
|
||||
|
||||
# Geometrie des Mittelteils. _design() zeichnet danach, _texte() setzt danach -
|
||||
# beide lesen aus DIESEN Werten, damit Kasten und Text nie auseinanderlaufen.
|
||||
# Trennlinie und Kartenoberkante sind gegenueber dem urspruenglichen Entwurf
|
||||
# (336.7 / 368.7) nach oben gerueckt: Der Untertitel braucht seit der
|
||||
# Titelaenderung nur noch zwei statt vier Zeilen, und der gewonnene Platz kommt
|
||||
# der Solver-Karte zugute, die jetzt zwei Absaetze traegt.
|
||||
TRENNLINIE_Y = 308.0
|
||||
KARTE_X0, KARTE_BREITE = 64.30752, 466.82496
|
||||
KARTE_Y0, KARTE_Y1 = 340.0, 481.44096
|
||||
|
||||
# Solver-Karten (x0, Breite, Farbe, Label) - bewusst NICHT gleich breit:
|
||||
# "Google OR-Tools" ist deutlich laenger als die anderen Labels und liefe bei
|
||||
# einheitlicher Breite ueber die Karte hinaus - da das Label weiss ist, wuerde
|
||||
# der ueberstehende Teil auf dem weissen Seitenhintergrund unsichtbar. Reihe
|
||||
# beginnt bei x=71.4528 (linker Rand wie Titel/Untertitel) und endet bei
|
||||
# x=523.9872 (wie die violette Trennlinie), dazwischen 3 Luecken von je 10pt.
|
||||
KARTEN_Y0, KARTEN_Y1 = 494.06616, 541.20024
|
||||
KARTEN = [
|
||||
(71.4528, 94.0, "#4338ca", "HiGHS"),
|
||||
(175.4528, 94.0, "#059669", "SciPy"),
|
||||
(279.4528, 140.5344, "#0891b2", "Google OR-Tools"),
|
||||
(429.9872, 94.0, "#7c3aed", "CVXPY"),
|
||||
]
|
||||
|
||||
|
||||
def datum_string() -> str:
|
||||
"""Heutiges Datum als '5. September 2026' (locale-unabhängig)."""
|
||||
n = datetime.now()
|
||||
return f"{n.day}. {MONATE[n.month - 1]} {n.year}"
|
||||
|
||||
|
||||
def _ring(ax, x, y, r):
|
||||
ax.add_patch(Circle((x, y), r, fill=False, ec="#4338ca", alpha=0.1,
|
||||
lw=1.4, clip_on=True))
|
||||
|
||||
|
||||
def _design(ax):
|
||||
# Weißer Hintergrund.
|
||||
ax.add_patch(Rectangle((0, 0), BREITE, HOEHE, facecolor="white",
|
||||
edgecolor="none", zorder=0))
|
||||
|
||||
# 5 farbige Akzentbalken oben (Leiste), y 34.51..46.29, je 90.51 breit.
|
||||
balken_farben = ["#4338ca", "#0891b2", "#7c3aed", "#059669", "#d97706"]
|
||||
for i, farbe in enumerate(balken_farben):
|
||||
ax.add_patch(Rectangle((71.4528 + i * 90.50688, 34.50888),
|
||||
90.50688, 11.78352, facecolor=farbe,
|
||||
edgecolor="none", zorder=1))
|
||||
|
||||
# 7 konzentrische Ringe oben rechts, Zentrum (553.76, 84.17).
|
||||
for r in (42.084, 77.154, 112.224, 147.294, 182.364, 217.434, 252.504):
|
||||
_ring(ax, 553.7592, 84.168, r)
|
||||
|
||||
# Große Solver-Karte (abgerundet): (64.31, 368.66) bis (531.13, 481.44),
|
||||
# Radius ~11.91 (Q-Offset in der Original-SVG).
|
||||
ax.add_patch(FancyBboxPatch(
|
||||
(KARTE_X0, KARTE_Y0), KARTE_BREITE, KARTE_Y1 - KARTE_Y0,
|
||||
boxstyle="round,pad=0,rounding_size=11.9098",
|
||||
facecolor="#f5f3ff", edgecolor="#7c3aed", lw=1.6, zorder=1))
|
||||
|
||||
# Vier farbige Solver-Karten (HiGHS, SciPy, Google OR-Tools, CVXPY),
|
||||
# individuelle Breiten (siehe KARTEN), y 494.07..541.20.
|
||||
for x0, breite, farbe, _label in KARTEN:
|
||||
ax.add_patch(FancyBboxPatch(
|
||||
(x0, KARTEN_Y0), breite, KARTEN_Y1 - KARTEN_Y0,
|
||||
boxstyle="round,pad=0,rounding_size=11.9098",
|
||||
facecolor=farbe, edgecolor="none", zorder=2))
|
||||
|
||||
# Violette Trennlinie unter dem Untertitel, x 71.45..523.99.
|
||||
ax.plot([71.4528, 523.9872], [TRENNLINIE_Y, TRENNLINIE_Y], color="#7c3aed",
|
||||
lw=2.5, solid_capstyle="butt", zorder=3)
|
||||
|
||||
# Indigo Fußband unten: (0, 757.51) bis (595.44, 841.68).
|
||||
ax.add_patch(Rectangle((0, 757.512), BREITE, HOEHE - 757.512,
|
||||
facecolor="#4338ca", edgecolor="none", zorder=1))
|
||||
|
||||
# Grüne 'Optimierungskurve' (konkav, abflachend) von (71.45, 673.34) nach
|
||||
# (523.99, 639.68) - approximiert die dekorative Kurve der Vorgängerversion
|
||||
# (dort 99 geglättete Stützpunkte, hier eine konkave Wurzelfunktion, die
|
||||
# denselben visuellen Eindruck erzeugt).
|
||||
import numpy as np
|
||||
xs = np.linspace(71.4528, 523.9872, 100)
|
||||
t = (xs - 71.4528) / (523.9872 - 71.4528)
|
||||
ys = 673.344 - 33.664 * np.sqrt(t) # konkav: steiler Start, flaches Ende
|
||||
ax.plot(xs, ys, color="#059669", lw=3, solid_capstyle="butt", zorder=3)
|
||||
# Roter Stern-Marker ('Optimum') bei (446.28, 641.67) auf der Kurve.
|
||||
ax.plot([446.279273], [641.670502], marker="*", color="#e11d48",
|
||||
markersize=12, mec="#e11d48", mfc="#e11d48", zorder=4)
|
||||
|
||||
|
||||
# --- Textumbruch: gemessen statt geschaetzt -------------------------------
|
||||
# Bis zur Titelaenderung standen alle Textzeilen mit von Hand vermessenen
|
||||
# Koordinaten hier - uebernommen aus der urspruenglichen, handgezeichneten SVG.
|
||||
# Das traegt genau so lange, wie sich der Text nicht aendert: Der neue Titel ist
|
||||
# knapp dreimal so lang wie der alte, der Untertitel mehr als doppelt so lang.
|
||||
# Deshalb werden Umbruch und Schriftgroesse jetzt GEMESSEN. Wer den Titel das
|
||||
# naechste Mal aendert, aendert nur noch die Zeichenketten weiter unten.
|
||||
|
||||
def _textbreite(ax, text, groesse, fett=False) -> float:
|
||||
"""Breite eines Textes in Seitenpunkten - ueber den echten Renderer."""
|
||||
marke = ax.text(0, 0, text, fontsize=groesse,
|
||||
fontweight="bold" if fett else "normal")
|
||||
kasten = marke.get_window_extent(
|
||||
renderer=ax.figure.canvas.get_renderer()).transformed(
|
||||
ax.transData.inverted())
|
||||
marke.remove()
|
||||
return abs(kasten.width)
|
||||
|
||||
|
||||
def _umbrechen(ax, text, groesse, maxbreite, fett=False) -> list[str]:
|
||||
"""Bricht 'text' so um, dass keine Zeile breiter als 'maxbreite' wird."""
|
||||
zeilen, zeile = [], ""
|
||||
for wort in text.split():
|
||||
probe = f"{zeile} {wort}".strip()
|
||||
if not zeile or _textbreite(ax, probe, groesse, fett) <= maxbreite:
|
||||
zeile = probe
|
||||
else:
|
||||
zeilen.append(zeile)
|
||||
zeile = wort
|
||||
if zeile:
|
||||
zeilen.append(zeile)
|
||||
return zeilen
|
||||
|
||||
|
||||
def _setze_block(ax, zeilen, x, y0, groesse, durchschuss, **stil) -> float:
|
||||
"""Setzt die Zeilen ab Grundlinie y0 und liefert die Grundlinie danach."""
|
||||
for i, zeile in enumerate(zeilen):
|
||||
ax.text(x, y0 + i * groesse * durchschuss, zeile,
|
||||
fontsize=groesse, zorder=5, **stil)
|
||||
return y0 + (len(zeilen) - 1) * groesse * durchschuss
|
||||
|
||||
|
||||
# --- Die Texte der Titelseite ----------------------------------------------
|
||||
# Nur diese Zeichenketten aendern, wenn das Buch anders heissen soll. Der Text
|
||||
# stammt aus Kritik_und_Verbesserungsvorschlaege/NEUER_TITEL.md; die beiden
|
||||
# Absaetze KOMPENDIUM und ANWENDUNGEN
|
||||
# stehen gemeinsam in der grossen Solver-Karte.
|
||||
#
|
||||
# Im TITEL steht ein \n: An dieser Stelle wird bewusst umbrochen. Ohne ihn
|
||||
# wuerde automatisch umbrochen und "mit Python" auseinandergerissen. Die Breite
|
||||
# jeder Zeile wird in beiden Faellen geprueft.
|
||||
#
|
||||
# Warum drei Zeilen und nicht zwei: "Entscheidungsfindung" ist bei 34 pt schon
|
||||
# 429 pt breit und damit fast so breit wie der ganze Satzspiegel (453 pt). Jede
|
||||
# zweizeilige Aufteilung braeuchte mindestens 649 pt - das passt bei keiner
|
||||
# lesbaren Schriftgroesse.
|
||||
TITEL = "Optimierte\nEntscheidungsfindung\nmit Python"
|
||||
UNTERTITEL = ("Von der mathematischen Modellierung zu praktischen "
|
||||
"intelligenten Lösungen für Betrieb, Technik und Finanzmärkte")
|
||||
KOMPENDIUM = ("Ein praxisorientiertes Kompendium für Entscheider mit "
|
||||
"Constraint Programming, Vektor- und Matrixmethoden sowie "
|
||||
"HiGHS, SciPy, Google OR-Tools und CVXPY.")
|
||||
ANWENDUNGEN = ("Von Personaleinsatz-, Schicht- und Vertretungsplänen, "
|
||||
"Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung "
|
||||
"über Energieverteilung "
|
||||
"und Ressourcenplanung bis zur Portfoliooptimierung und besseren "
|
||||
"Entscheidungen an den Finanzmärkten – mit Python modellieren, "
|
||||
"optimieren und belegbar fundierte Entscheidungen treffen.")
|
||||
AUTOR = "Autor / Herausgeber: Dieter Schlüter"
|
||||
|
||||
# Satzspiegel: linker Rand und rechte Kante wie Trennlinie und Kartenreihe.
|
||||
LINKS, RECHTS = 71.4528, 523.9872
|
||||
SATZBREITE = RECHTS - LINKS
|
||||
|
||||
# Schriftgroessen und Durchschuss (Faktor auf die Schriftgroesse). Nachgemessen,
|
||||
# nicht geraten: Bei 34 pt ist "Entscheidungsfindung" 429 pt breit und bleibt
|
||||
# damit im Satzspiegel von 453 pt; bei 36 pt waeren es 454 pt - eines zu viel.
|
||||
TITEL_GROESSE, TITEL_DURCHSCHUSS = 34, 1.40
|
||||
# Untertitel 14 statt 15 pt: Bei 15 pt braucht er drei Zeilen, und die dritte
|
||||
# traegt nur noch das Wort "Finanzmärkte" - eine Hurenkind-Zeile mitten auf dem
|
||||
# Titelblatt. Bei 14 pt sind es zwei ausgeglichene Zeilen.
|
||||
UNTER_GROESSE, UNTER_DURCHSCHUSS = 14, 1.23
|
||||
# Kartentext 11.5 statt 12 pt: Der Text ist auf neun Zeilen gewachsen; bei 12 pt
|
||||
# blieben nur noch 9 pt Luft ueber und unter dem Block, und der Text geriete in
|
||||
# die abgerundeten Kartenecken (Radius 11.9 pt).
|
||||
KOMP_GROESSE, KOMP_DURCHSCHUSS = 11.5, 1.27
|
||||
KOMP_BREITE = 440.0 # nutzbare Textbreite in der Karte
|
||||
|
||||
# Anker des Satzes.
|
||||
TITEL_START = 135.0 # erste Grundlinie des Haupttitels
|
||||
ABSTAND_TITEL_UNTER = 42.0 # Luft zwischen Titel- und Untertitelblock
|
||||
|
||||
|
||||
def _texte(ax, datum):
|
||||
# Haupttitel: die letzte Zeile ("mit Python") in Anthrazit statt Indigo -
|
||||
# so bleibt die Zweifarbigkeit des urspruenglichen Entwurfs erhalten, in
|
||||
# dem "Operations Research" indigo und "mit Python" dunkel gesetzt war.
|
||||
# Ein \n im TITEL heisst: hier wird umbrochen. Sonst automatisch.
|
||||
if "\n" in TITEL:
|
||||
titelzeilen = TITEL.split("\n")
|
||||
zu_breit = [z for z in titelzeilen
|
||||
if _textbreite(ax, z, TITEL_GROESSE, True) > SATZBREITE]
|
||||
if zu_breit:
|
||||
raise SystemExit(
|
||||
f"Titelblatt: Diese Titelzeile passt bei {TITEL_GROESSE} pt "
|
||||
f"nicht in den Satzspiegel ({SATZBREITE:.0f} pt): {zu_breit[0]!r}")
|
||||
else:
|
||||
titelzeilen = _umbrechen(ax, TITEL, TITEL_GROESSE, SATZBREITE, fett=True)
|
||||
for i, zeile in enumerate(titelzeilen):
|
||||
ax.text(LINKS, TITEL_START + i * TITEL_GROESSE * TITEL_DURCHSCHUSS,
|
||||
zeile, fontsize=TITEL_GROESSE, fontweight="bold", zorder=5,
|
||||
color="#1a1a2e" if i == len(titelzeilen) - 1 else "#4338ca")
|
||||
titel_ende = TITEL_START + (len(titelzeilen) - 1) * TITEL_GROESSE * TITEL_DURCHSCHUSS
|
||||
|
||||
# Untertitel direkt darunter; die Zeilenzahl ergibt sich aus dem Umbruch.
|
||||
unterzeilen = _umbrechen(ax, UNTERTITEL, UNTER_GROESSE, SATZBREITE)
|
||||
unter_ende = _setze_block(ax, unterzeilen, LINKS,
|
||||
titel_ende + ABSTAND_TITEL_UNTER,
|
||||
UNTER_GROESSE, UNTER_DURCHSCHUSS, color="#334155")
|
||||
# Sicherung: Der Untertitel darf die Trennlinie nicht beruehren. Lieber ein
|
||||
# Abbruch beim Erzeugen als ein Titelblatt, auf dem Text auf der Linie liegt.
|
||||
if unter_ende > TRENNLINIE_Y - 8:
|
||||
raise SystemExit(
|
||||
f"Titelblatt: Der Untertitel reicht bis y={unter_ende:.1f} und "
|
||||
f"kollidiert mit der Trennlinie bei y={TRENNLINIE_Y}. "
|
||||
f"Schriftgroesse oder Startpunkt anpassen.")
|
||||
|
||||
# KOMPENDIUM-Label
|
||||
ax.text(LINKS, 84.168, "K O M P E N D I U M",
|
||||
fontsize=13, color="#0891b2", zorder=5)
|
||||
|
||||
# Die beiden Absaetze in der grossen Solver-Karte, durch eine Leerzeile
|
||||
# getrennt und als Block vertikal zentriert. Zentriert gesetzt statt links,
|
||||
# weil die Karte breiter ist als der Satzspiegel.
|
||||
kartenmitte_x = KARTE_X0 + KARTE_BREITE / 2
|
||||
kartenzeilen = (_umbrechen(ax, KOMPENDIUM, KOMP_GROESSE, KOMP_BREITE)
|
||||
+ [""]
|
||||
+ _umbrechen(ax, ANWENDUNGEN, KOMP_GROESSE, KOMP_BREITE))
|
||||
hoehe = (len(kartenzeilen) - 1) * KOMP_GROESSE * KOMP_DURCHSCHUSS
|
||||
start = (KARTE_Y0 + KARTE_Y1) / 2 - hoehe / 2 + KOMP_GROESSE * 0.35
|
||||
# Sicherung: Der Block darf nicht ueber den Kartenrand hinauslaufen.
|
||||
if start - KOMP_GROESSE < KARTE_Y0 or start + hoehe > KARTE_Y1 - 4:
|
||||
raise SystemExit(
|
||||
f"Titelblatt: Der Kartentext ({len(kartenzeilen)} Zeilen) passt "
|
||||
f"nicht in die Karte ({KARTE_Y1 - KARTE_Y0:.0f} pt hoch). "
|
||||
f"Schriftgroesse verkleinern oder Karte vergroessern.")
|
||||
_setze_block(ax, kartenzeilen, kartenmitte_x, start,
|
||||
KOMP_GROESSE, KOMP_DURCHSCHUSS, color="#1a1a2e", ha="center")
|
||||
|
||||
# Autor (im Fußband, weiß)
|
||||
ax.text(LINKS, 793.402715, AUTOR,
|
||||
fontsize=14, color="#ffffff", zorder=5)
|
||||
# text_7: Stand-Datum (im Fußband, hellviolett) - DYNAMISCH
|
||||
ax.text(71.4528, 818.05749, f"Stand: {datum}",
|
||||
fontsize=12, color="#c7d2fe", zorder=5)
|
||||
# Solver-Namen, zentriert in ihrer Karte (weiß). ha/va="center"
|
||||
# macht die Zentrierung unabhängig von Schriftmetriken - robuster als
|
||||
# von Hand vermessene x/y-Offsets.
|
||||
mitte_y = (KARTEN_Y0 + KARTEN_Y1) / 2
|
||||
for x0, breite, _farbe, label in KARTEN:
|
||||
ax.text(x0 + breite / 2, mitte_y, label,
|
||||
fontsize=13, color="#ffffff", ha="center", va="center",
|
||||
zorder=5)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
basis = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ausgaben = [
|
||||
os.path.join(basis, "bilder_04", "titelseite.svg"),
|
||||
os.path.join(basis, "OR_HTML_04", "bilder_04", "titelseite.svg"),
|
||||
]
|
||||
d = datum_string()
|
||||
|
||||
fig = plt.figure(figsize=(8.27, 11.69))
|
||||
ax = fig.add_axes([0, 0, 1, 1])
|
||||
ax.set_xlim(0, BREITE)
|
||||
ax.set_ylim(HOEHE, 0) # invertiert: y zählt von oben (wie SVG)
|
||||
ax.axis("off")
|
||||
|
||||
_design(ax)
|
||||
_texte(ax, d)
|
||||
|
||||
for pfad in ausgaben:
|
||||
os.makedirs(os.path.dirname(pfad), exist_ok=True)
|
||||
fig.savefig(pfad, format="svg", metadata={"Date": None})
|
||||
plt.close(fig)
|
||||
print(f"Titelseite erzeugt ({d}): {', '.join(ausgaben)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue