Logo-Downloadseite /logo mit Erzeuger-Skript
Damit die Abteilungen das Wappen selbst holen können, statt jedes Mal zu fragen:
drei Varianten (farbig, einfarbig schwarz, einfarbig weiß) in SVG, PDF, PNG und JPG,
dazu ein ZIP über alles und die Vereinsfarben. Verlinkt nur in der Fußzeile.
Bisher lag das Logo genau einmal im Projekt, als eine einzige SVG. Es gab kein PNG,
kein JPG, kein PDF und keine einfarbige Fassung.
bin/logo-build.php erzeugt alle Dateien aus einer Master-SVG unter storage/logo-quellen/.
Gerastert wird mit headless Chrome, weil GD kein SVG lesen kann und weder Inkscape noch
rsvg-convert noch ImageMagick verfügbar sind; das PDF ist dabei echter Vektor. Das Skript
läuft einmalig lokal, nichts davon im Request-Pfad. bin/preflight.php meldet als Fehler,
wenn eine Download-Datei fehlt: die Komponente ließe den Knopf sonst still weg.
Die beiden einfarbigen Fassungen sind exakte Negative voneinander, nur Linien, alle
Flächen frei. EPS gibt es bewusst nicht, dafür fehlt jedes Werkzeug; SVG und PDF decken
die Druckereien ab. Frühere Vereinslogos sind vorbereitet, aber im Projekt liegt bisher
kein einziges historisches Wappen vor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 10:10:08 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Alle Download-Dateien des Vereinslogos aus den Master-Dateien erzeugen (nur CLI).
|
|
|
|
|
*
|
|
|
|
|
* php bin/logo-build.php [-v]
|
|
|
|
|
*
|
|
|
|
|
* Quelle: storage/logo-quellen/<datei>.svg — die Master-Dateien. `aktuell.svg` ist
|
|
|
|
|
* das heutige Logo (identisch zu public/assets/img/logo.svg, das bleibt das
|
|
|
|
|
* Website-Asset und wird hier nicht angefasst). Alte Vereinslogos kommen als
|
|
|
|
|
* weitere Dateien dazu und werden unten in $OUTPUTS eingetragen.
|
|
|
|
|
*
|
|
|
|
|
* Ziel: public/assets/downloads/logo/ — je Ausgabe SVG, PNG (transparent, drei
|
|
|
|
|
* Breiten), JPG (weißer Grund, zwei Breiten) und PDF, dazu ein ZIP über alles.
|
|
|
|
|
* Der Ordner wird bei jedem Lauf GELEERT und neu gefüllt; nur so lässt sich eine
|
|
|
|
|
* Variante auch wieder entfernen.
|
|
|
|
|
*
|
|
|
|
|
* WARUM CHROME UND NICHT bin/img-resize.php: GD kennt kein SVG, und Inkscape,
|
|
|
|
|
* rsvg-convert oder ImageMagick sind hier nicht vorhanden (und wären auf dem
|
|
|
|
|
* Shared Hosting auch nicht zu haben). Headless Chrome kann beides, was wir
|
|
|
|
|
* brauchen: transparent rastern und ein echtes VEKTOR-PDF drucken (geprüft:
|
|
|
|
|
* das PDF enthält Kurven-Operatoren, kein eingebettetes Pixelbild).
|
|
|
|
|
*
|
|
|
|
|
* Das Skript läuft EINMALIG LOKAL, das Ergebnis wird committet. Nichts davon
|
|
|
|
|
* läuft auf dem Server, es gibt keinen Cron und keine Laufzeit-Abhängigkeit.
|
|
|
|
|
*
|
|
|
|
|
* Die Dateinamen sind absichtlich ausgeschrieben: sie landen im Download-Ordner
|
|
|
|
|
* fremder Leute, wo „logo-2.png“ niemandem hilft.
|
|
|
|
|
*/
|
|
|
|
|
if (PHP_SAPI !== 'cli') {
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require dirname(__DIR__) . '/app/bootstrap.php';
|
Instagram: Scraper raus, Beitraege einzeln ueber oEmbed aufnehmen
bin/instagram-sync.php lieferte seit dem 03.09.2026 bei jedem Lauf "Keine Posts
ermittelbar". Von einem privaten Anschluss gegengeprueft: feed/user und
web_profile_info antworten mit 401 require_login, das Profil-HTML traegt keine
Beitragsdaten mehr, auch /embed/ liefert nur noch eine App-Huelle. Es lag also
nicht an der Server-IP, und der Scraper ist geloescht, damit ihn niemand
wiederbelebt. Graph API (Meta-Konto noetig) und kommerzielle Dienste wurden
verworfen.
Neu: bin/instagram-add.php. --profil liest die Beitragsliste ueber headless
Chrome aus dem gerenderten Profil-DOM (im HTML stehen die Codes nicht) und holt
zu jedem neuen Beitrag Bild, Caption und Datum ueber Instagrams oEmbed-Endpunkt;
das Datum steckt in der media_id. Daneben einzelne URLs, --liste, --entfernen und
--bild als Notfall. Der Shortcode ist Primaerschluessel und damit die komplette
Dedupe-Logik. Die Struktur von data/instagram.json bleibt exakt gleich, deshalb
war an Startseite, News-Feed und Stadionzeitung nichts zu aendern.
Kein Cron mehr: der Job faellt aus public/cron.php, auf dem Webspace gibt es kein
Chrome. Die Chrome-Suche liegt jetzt gemeinsam in bin/chrome-lib.php, genutzt von
bin/logo-build.php und bin/instagram-add.php.
data/instagram.json ist ab jetzt versioniert. Solange sie ignoriert war, lief sie
gegen die versionierten Bilder auseinander: lokal lagen 10 Bilder zu 9 Eintraegen,
drei Beitraege fehlten im Feed, zwei Eintraege hatten kein Bild. Repariert, jetzt
16 Eintraege zu 16 Bildern. bin/preflight.php prueft das in beide Richtungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 11:23:12 +02:00
|
|
|
require_once __DIR__ . '/chrome-lib.php';
|
Logo-Downloadseite /logo mit Erzeuger-Skript
Damit die Abteilungen das Wappen selbst holen können, statt jedes Mal zu fragen:
drei Varianten (farbig, einfarbig schwarz, einfarbig weiß) in SVG, PDF, PNG und JPG,
dazu ein ZIP über alles und die Vereinsfarben. Verlinkt nur in der Fußzeile.
Bisher lag das Logo genau einmal im Projekt, als eine einzige SVG. Es gab kein PNG,
kein JPG, kein PDF und keine einfarbige Fassung.
bin/logo-build.php erzeugt alle Dateien aus einer Master-SVG unter storage/logo-quellen/.
Gerastert wird mit headless Chrome, weil GD kein SVG lesen kann und weder Inkscape noch
rsvg-convert noch ImageMagick verfügbar sind; das PDF ist dabei echter Vektor. Das Skript
läuft einmalig lokal, nichts davon im Request-Pfad. bin/preflight.php meldet als Fehler,
wenn eine Download-Datei fehlt: die Komponente ließe den Knopf sonst still weg.
Die beiden einfarbigen Fassungen sind exakte Negative voneinander, nur Linien, alle
Flächen frei. EPS gibt es bewusst nicht, dafür fehlt jedes Werkzeug; SVG und PDF decken
die Druckereien ab. Frühere Vereinslogos sind vorbereitet, aber im Projekt liegt bisher
kein einziges historisches Wappen vor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 10:10:08 +02:00
|
|
|
|
|
|
|
|
$verbose = in_array('-v', $argv, true);
|
|
|
|
|
|
|
|
|
|
$log = static function (string $msg, string $level = 'INFO') use ($verbose): void {
|
|
|
|
|
log_write('logo', $level, $msg);
|
|
|
|
|
if ($verbose || $level === 'ERROR' || $level === 'WARN') {
|
|
|
|
|
echo '[' . date('c') . "] {$level} {$msg}\n";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$fail = static function (string $msg) use ($log): never {
|
|
|
|
|
$log($msg, 'ERROR');
|
|
|
|
|
exit(1);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Was erzeugt wird ---------------------------------------------------------
|
|
|
|
|
// Schlüssel = Dateiname-Suffix UND Schlüssel in data/logo.json (bin/preflight.php
|
|
|
|
|
// prüft, dass beide Listen zusammenpassen). `ersetzen` färbt die Master-SVG um,
|
|
|
|
|
// indem die Farbklassen im <defs>-Block ausgetauscht werden — die CorelDRAW-Datei
|
|
|
|
|
// legt alle Farben genau dort ab (.fil0/.fil3 schwarz, .fil1 weiß, .fil2 rot).
|
|
|
|
|
$OUTPUTS = [
|
|
|
|
|
'farbig' => [
|
|
|
|
|
'quelle' => 'aktuell.svg',
|
|
|
|
|
'ersetzen' => [],
|
|
|
|
|
],
|
|
|
|
|
// Das genaue Gegenstück zu einfarbig-schwarz: dort trägt die weiße Fläche die
|
|
|
|
|
// schwarzen Linien, hier tragen die weißen Linien den freien Grund. KEIN Rot —
|
|
|
|
|
// eine einfarbige Fassung ist einfarbig, sonst wäre sie nur ein farbiges Logo
|
|
|
|
|
// mit weißen Konturen (so war es bis zum 17.09.2026 und war schlicht falsch).
|
|
|
|
|
'einfarbig-weiss' => [
|
|
|
|
|
'quelle' => 'aktuell.svg',
|
|
|
|
|
// Weiße Konturen auf weißem JPG wären unsichtbar: diese Variante wird auf
|
|
|
|
|
// den Anthrazit-Grund der Website gelegt, denn genau dafür ist sie gedacht.
|
|
|
|
|
'jpg_bg' => [0x22, 0x22, 0x22],
|
|
|
|
|
'ersetzen' => [
|
|
|
|
|
'.fil3 {fill:black}' => '.fil3 {fill:white}',
|
|
|
|
|
'.fil0 {fill:black;' => '.fil0 {fill:white;',
|
|
|
|
|
'.fil1 {fill:white}' => '.fil1 {fill:none}',
|
|
|
|
|
'.fil2 {fill:red}' => '.fil2 {fill:none}',
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
// Reine Strichzeichnung: Linien schwarz, ALLE Flächen frei. Vorher wurde das Rot
|
|
|
|
|
// durch Weiß ersetzt, dann steckte im PNG eine deckend weiße Schildfläche — auf
|
|
|
|
|
// farbigem Papier oder einem Briefkopf lag da ein weißer Klotz. Für Stempel,
|
|
|
|
|
// Gravur und Stickerei will man die Kontur, nicht die Fläche.
|
|
|
|
|
'einfarbig-schwarz' => [
|
|
|
|
|
'quelle' => 'aktuell.svg',
|
|
|
|
|
'ersetzen' => [
|
|
|
|
|
'.fil2 {fill:red}' => '.fil2 {fill:none}',
|
|
|
|
|
'.fil1 {fill:white}' => '.fil1 {fill:none}',
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const LOGO_PNG_WIDTHS = [500, 1000, 2000];
|
|
|
|
|
const LOGO_JPG_WIDTHS = [1000, 2000];
|
|
|
|
|
// 90 statt der 78 aus bin/img-resize.php: ein Logo ist Fläche mit harten Kanten,
|
|
|
|
|
// bei 78 franst die Rundung des Wappens sichtbar aus. Fotos vertragen 78, Grafik nicht.
|
|
|
|
|
const LOGO_JPG_QUALITY = 90;
|
|
|
|
|
const LOGO_PREFIX = 'tsv08-kulmbach-logo';
|
|
|
|
|
|
|
|
|
|
$srcDir = STORAGE_PATH . '/logo-quellen';
|
|
|
|
|
$destDir = PUBLIC_PATH . '/assets/downloads/logo';
|
|
|
|
|
$tmpDir = sys_get_temp_dir() . '/tsv08-logo-' . getmypid();
|
|
|
|
|
|
Instagram: Scraper raus, Beitraege einzeln ueber oEmbed aufnehmen
bin/instagram-sync.php lieferte seit dem 03.09.2026 bei jedem Lauf "Keine Posts
ermittelbar". Von einem privaten Anschluss gegengeprueft: feed/user und
web_profile_info antworten mit 401 require_login, das Profil-HTML traegt keine
Beitragsdaten mehr, auch /embed/ liefert nur noch eine App-Huelle. Es lag also
nicht an der Server-IP, und der Scraper ist geloescht, damit ihn niemand
wiederbelebt. Graph API (Meta-Konto noetig) und kommerzielle Dienste wurden
verworfen.
Neu: bin/instagram-add.php. --profil liest die Beitragsliste ueber headless
Chrome aus dem gerenderten Profil-DOM (im HTML stehen die Codes nicht) und holt
zu jedem neuen Beitrag Bild, Caption und Datum ueber Instagrams oEmbed-Endpunkt;
das Datum steckt in der media_id. Daneben einzelne URLs, --liste, --entfernen und
--bild als Notfall. Der Shortcode ist Primaerschluessel und damit die komplette
Dedupe-Logik. Die Struktur von data/instagram.json bleibt exakt gleich, deshalb
war an Startseite, News-Feed und Stadionzeitung nichts zu aendern.
Kein Cron mehr: der Job faellt aus public/cron.php, auf dem Webspace gibt es kein
Chrome. Die Chrome-Suche liegt jetzt gemeinsam in bin/chrome-lib.php, genutzt von
bin/logo-build.php und bin/instagram-add.php.
data/instagram.json ist ab jetzt versioniert. Solange sie ignoriert war, lief sie
gegen die versionierten Bilder auseinander: lokal lagen 10 Bilder zu 9 Eintraegen,
drei Beitraege fehlten im Feed, zwei Eintraege hatten kein Bild. Repariert, jetzt
16 Eintraege zu 16 Bildern. bin/preflight.php prueft das in beide Richtungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 11:23:12 +02:00
|
|
|
// --- Chrome finden ---
|
|
|
|
|
$chrome = chrome_binary();
|
|
|
|
|
if ($chrome === null) {
|
Logo-Downloadseite /logo mit Erzeuger-Skript
Damit die Abteilungen das Wappen selbst holen können, statt jedes Mal zu fragen:
drei Varianten (farbig, einfarbig schwarz, einfarbig weiß) in SVG, PDF, PNG und JPG,
dazu ein ZIP über alles und die Vereinsfarben. Verlinkt nur in der Fußzeile.
Bisher lag das Logo genau einmal im Projekt, als eine einzige SVG. Es gab kein PNG,
kein JPG, kein PDF und keine einfarbige Fassung.
bin/logo-build.php erzeugt alle Dateien aus einer Master-SVG unter storage/logo-quellen/.
Gerastert wird mit headless Chrome, weil GD kein SVG lesen kann und weder Inkscape noch
rsvg-convert noch ImageMagick verfügbar sind; das PDF ist dabei echter Vektor. Das Skript
läuft einmalig lokal, nichts davon im Request-Pfad. bin/preflight.php meldet als Fehler,
wenn eine Download-Datei fehlt: die Komponente ließe den Knopf sonst still weg.
Die beiden einfarbigen Fassungen sind exakte Negative voneinander, nur Linien, alle
Flächen frei. EPS gibt es bewusst nicht, dafür fehlt jedes Werkzeug; SVG und PDF decken
die Druckereien ab. Frühere Vereinslogos sind vorbereitet, aber im Projekt liegt bisher
kein einziges historisches Wappen vor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 10:10:08 +02:00
|
|
|
$fail('Kein Chrome gefunden. Pfad per Umgebungsvariable setzen: CHROME="/Pfad/zu/Chrome" php bin/logo-build.php');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!is_dir($srcDir)) {
|
|
|
|
|
$fail("Quellordner fehlt: {$srcDir}");
|
|
|
|
|
}
|
|
|
|
|
if (!is_dir($destDir) && !mkdir($destDir, 0775, true) && !is_dir($destDir)) {
|
|
|
|
|
$fail("Zielordner nicht anlegbar: {$destDir}");
|
|
|
|
|
}
|
|
|
|
|
if (!mkdir($tmpDir, 0775, true) && !is_dir($tmpDir)) {
|
|
|
|
|
$fail("Arbeitsordner nicht anlegbar: {$tmpDir}");
|
|
|
|
|
}
|
|
|
|
|
register_shutdown_function(static function () use ($tmpDir): void {
|
|
|
|
|
foreach (glob($tmpDir . '/*') ?: [] as $file) {
|
|
|
|
|
@unlink($file);
|
|
|
|
|
}
|
|
|
|
|
@rmdir($tmpDir);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$runChrome = static function (array $args) use ($chrome): void {
|
Instagram: Scraper raus, Beitraege einzeln ueber oEmbed aufnehmen
bin/instagram-sync.php lieferte seit dem 03.09.2026 bei jedem Lauf "Keine Posts
ermittelbar". Von einem privaten Anschluss gegengeprueft: feed/user und
web_profile_info antworten mit 401 require_login, das Profil-HTML traegt keine
Beitragsdaten mehr, auch /embed/ liefert nur noch eine App-Huelle. Es lag also
nicht an der Server-IP, und der Scraper ist geloescht, damit ihn niemand
wiederbelebt. Graph API (Meta-Konto noetig) und kommerzielle Dienste wurden
verworfen.
Neu: bin/instagram-add.php. --profil liest die Beitragsliste ueber headless
Chrome aus dem gerenderten Profil-DOM (im HTML stehen die Codes nicht) und holt
zu jedem neuen Beitrag Bild, Caption und Datum ueber Instagrams oEmbed-Endpunkt;
das Datum steckt in der media_id. Daneben einzelne URLs, --liste, --entfernen und
--bild als Notfall. Der Shortcode ist Primaerschluessel und damit die komplette
Dedupe-Logik. Die Struktur von data/instagram.json bleibt exakt gleich, deshalb
war an Startseite, News-Feed und Stadionzeitung nichts zu aendern.
Kein Cron mehr: der Job faellt aus public/cron.php, auf dem Webspace gibt es kein
Chrome. Die Chrome-Suche liegt jetzt gemeinsam in bin/chrome-lib.php, genutzt von
bin/logo-build.php und bin/instagram-add.php.
data/instagram.json ist ab jetzt versioniert. Solange sie ignoriert war, lief sie
gegen die versionierten Bilder auseinander: lokal lagen 10 Bilder zu 9 Eintraegen,
drei Beitraege fehlten im Feed, zwei Eintraege hatten kein Bild. Repariert, jetzt
16 Eintraege zu 16 Bildern. bin/preflight.php prueft das in beide Richtungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 11:23:12 +02:00
|
|
|
chrome_run($chrome, $args);
|
Logo-Downloadseite /logo mit Erzeuger-Skript
Damit die Abteilungen das Wappen selbst holen können, statt jedes Mal zu fragen:
drei Varianten (farbig, einfarbig schwarz, einfarbig weiß) in SVG, PDF, PNG und JPG,
dazu ein ZIP über alles und die Vereinsfarben. Verlinkt nur in der Fußzeile.
Bisher lag das Logo genau einmal im Projekt, als eine einzige SVG. Es gab kein PNG,
kein JPG, kein PDF und keine einfarbige Fassung.
bin/logo-build.php erzeugt alle Dateien aus einer Master-SVG unter storage/logo-quellen/.
Gerastert wird mit headless Chrome, weil GD kein SVG lesen kann und weder Inkscape noch
rsvg-convert noch ImageMagick verfügbar sind; das PDF ist dabei echter Vektor. Das Skript
läuft einmalig lokal, nichts davon im Request-Pfad. bin/preflight.php meldet als Fehler,
wenn eine Download-Datei fehlt: die Komponente ließe den Knopf sonst still weg.
Die beiden einfarbigen Fassungen sind exakte Negative voneinander, nur Linien, alle
Flächen frei. EPS gibt es bewusst nicht, dafür fehlt jedes Werkzeug; SVG und PDF decken
die Druckereien ab. Frühere Vereinslogos sind vorbereitet, aber im Projekt liegt bisher
kein einziges historisches Wappen vor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9mubC8gtpjtJntbhSJBRS
2026-09-17 10:10:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Maße der SVG in mm aus dem Wurzelelement lesen (für die PDF-Seitengröße). */
|
|
|
|
|
$svgSize = static function (string $svg) use ($fail): array {
|
|
|
|
|
if (!preg_match('/width="([\d.]+)mm"/', $svg, $w) || !preg_match('/height="([\d.]+)mm"/', $svg, $h)) {
|
|
|
|
|
if (preg_match('/viewBox="[\d.\s-]*?([\d.]+)\s+([\d.]+)"/', $svg, $vb)) {
|
|
|
|
|
return [(float) $vb[1], (float) $vb[2]]; // viewBox-Einheiten als mm deuten
|
|
|
|
|
}
|
|
|
|
|
$fail('SVG ohne verwertbare Maße (weder mm-Angaben noch viewBox).');
|
|
|
|
|
}
|
|
|
|
|
return [(float) $w[1], (float) $h[1]];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PNG mit Alphakanal auf eine deckende Fläche legen und als JPG sichern.
|
|
|
|
|
* JPG kann keine Transparenz; ohne dieses Unterlegen rechnet GD den Alphakanal
|
|
|
|
|
* gegen Schwarz und das Wappen bekäme einen schwarzen Kasten.
|
|
|
|
|
*/
|
|
|
|
|
$flattenToJpg = static function (string $pngPath, string $jpgPath, int $quality, array $bg): bool {
|
|
|
|
|
$png = @imagecreatefrompng($pngPath);
|
|
|
|
|
if (!$png) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$canvas = imagecreatetruecolor(imagesx($png), imagesy($png));
|
|
|
|
|
imagefill($canvas, 0, 0, imagecolorallocate($canvas, $bg[0], $bg[1], $bg[2]));
|
|
|
|
|
imagecopy($canvas, $png, 0, 0, 0, 0, imagesx($png), imagesy($png));
|
|
|
|
|
|
|
|
|
|
return imagejpeg($canvas, $jpgPath, $quality);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Zielordner leeren --------------------------------------------------------
|
|
|
|
|
foreach (glob($destDir . '/*') ?: [] as $old) {
|
|
|
|
|
if (is_file($old)) {
|
|
|
|
|
unlink($old);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Erzeugen -----------------------------------------------------------------
|
|
|
|
|
$built = [];
|
|
|
|
|
|
|
|
|
|
foreach ($OUTPUTS as $key => $spec) {
|
|
|
|
|
$srcFile = $srcDir . '/' . $spec['quelle'];
|
|
|
|
|
if (!is_file($srcFile)) {
|
|
|
|
|
$fail("Master-Datei fehlt: {$srcFile}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$base = LOGO_PREFIX . '-' . $key;
|
|
|
|
|
$isVector = strtolower(pathinfo($srcFile, PATHINFO_EXTENSION)) === 'svg';
|
|
|
|
|
|
|
|
|
|
if (!$isVector) {
|
|
|
|
|
// Alte Logos liegen oft nur als Scan vor: dann gibt es kein SVG und kein PDF,
|
|
|
|
|
// aber PNG und JPG lassen sich wie überall sonst im Projekt erzeugen.
|
|
|
|
|
foreach (resize_image_variants($srcFile, $destDir . '/' . $base, LOGO_PNG_WIDTHS, LOGO_JPG_QUALITY, true) as $v) {
|
|
|
|
|
$built[] = $v['file'];
|
|
|
|
|
}
|
|
|
|
|
foreach (resize_image_variants($srcFile, $destDir . '/' . $base, LOGO_JPG_WIDTHS, LOGO_JPG_QUALITY) as $v) {
|
|
|
|
|
$built[] = $v['file'];
|
|
|
|
|
}
|
|
|
|
|
$log("{$key}: aus Rasterquelle erzeugt (kein SVG/PDF möglich).", 'WARN');
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1. Varianten-SVG
|
|
|
|
|
$svg = file_get_contents($srcFile);
|
|
|
|
|
if ($svg === false) {
|
|
|
|
|
$fail("Master-Datei nicht lesbar: {$srcFile}");
|
|
|
|
|
}
|
|
|
|
|
foreach ($spec['ersetzen'] as $from => $to) {
|
|
|
|
|
if (!str_contains($svg, $from)) {
|
|
|
|
|
$fail("Farbklasse „{$from}“ steht nicht in {$spec['quelle']} — die Variante „{$key}“ wäre unverändert.");
|
|
|
|
|
}
|
|
|
|
|
$svg = str_replace($from, $to, $svg);
|
|
|
|
|
}
|
|
|
|
|
$svgFile = $destDir . '/' . $base . '.svg';
|
|
|
|
|
file_put_contents($svgFile, $svg);
|
|
|
|
|
$built[] = $svgFile;
|
|
|
|
|
|
|
|
|
|
[$mmW, $mmH] = $svgSize($svg);
|
|
|
|
|
$ratio = $mmH / $mmW;
|
|
|
|
|
|
|
|
|
|
// Arbeitskopie: Chrome lädt die SVG relativ zur Wrapper-Datei.
|
|
|
|
|
$workSvg = $tmpDir . '/' . $base . '.svg';
|
|
|
|
|
file_put_contents($workSvg, $svg);
|
|
|
|
|
|
|
|
|
|
// 2. PNG, transparent
|
|
|
|
|
foreach (LOGO_PNG_WIDTHS as $width) {
|
|
|
|
|
$height = (int) round($width * $ratio);
|
|
|
|
|
$wrap = $tmpDir . "/wrap-{$key}-{$width}.html";
|
|
|
|
|
file_put_contents($wrap, <<<HTML
|
|
|
|
|
<!doctype html><meta charset="utf-8">
|
|
|
|
|
<style>html,body{margin:0;padding:0;background:transparent}
|
|
|
|
|
img{display:block;width:{$width}px;height:{$height}px}</style>
|
|
|
|
|
<img src="{$base}.svg" alt="">
|
|
|
|
|
HTML);
|
|
|
|
|
|
|
|
|
|
$pngFile = $destDir . '/' . $base . '-' . $width . '.png';
|
|
|
|
|
$runChrome([
|
|
|
|
|
'--default-background-color=00000000',
|
|
|
|
|
'--window-size=' . $width . ',' . $height,
|
|
|
|
|
'--screenshot=' . escapeshellarg($pngFile),
|
|
|
|
|
escapeshellarg('file://' . $wrap),
|
|
|
|
|
]);
|
|
|
|
|
if (!is_file($pngFile) || filesize($pngFile) < 1000) {
|
|
|
|
|
$fail("PNG nicht erzeugt: {$pngFile} — läuft Chrome?");
|
|
|
|
|
}
|
|
|
|
|
$built[] = $pngFile;
|
|
|
|
|
|
|
|
|
|
// 3. JPG auf weiß, aus der passenden PNG-Breite
|
|
|
|
|
if (in_array($width, LOGO_JPG_WIDTHS, true)) {
|
|
|
|
|
$jpgFile = $destDir . '/' . $base . '-' . $width . '.jpg';
|
|
|
|
|
if (!$flattenToJpg($pngFile, $jpgFile, LOGO_JPG_QUALITY, $spec['jpg_bg'] ?? [255, 255, 255])) {
|
|
|
|
|
$fail("JPG nicht erzeugt: {$jpgFile}");
|
|
|
|
|
}
|
|
|
|
|
$built[] = $jpgFile;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. PDF, Vektor
|
|
|
|
|
$pdfWrap = $tmpDir . "/pdf-{$key}.html";
|
|
|
|
|
file_put_contents($pdfWrap, <<<HTML
|
|
|
|
|
<!doctype html><meta charset="utf-8">
|
|
|
|
|
<style>@page{size:{$mmW}mm {$mmH}mm;margin:0}html,body{margin:0;padding:0}
|
|
|
|
|
img{display:block;width:{$mmW}mm;height:{$mmH}mm}</style>
|
|
|
|
|
<img src="{$base}.svg" alt="">
|
|
|
|
|
HTML);
|
|
|
|
|
|
|
|
|
|
$pdfFile = $destDir . '/' . $base . '.pdf';
|
|
|
|
|
$runChrome([
|
|
|
|
|
'--no-pdf-header-footer',
|
|
|
|
|
'--print-to-pdf=' . escapeshellarg($pdfFile),
|
|
|
|
|
escapeshellarg('file://' . $pdfWrap),
|
|
|
|
|
]);
|
|
|
|
|
if (!is_file($pdfFile) || filesize($pdfFile) < 500) {
|
|
|
|
|
$fail("PDF nicht erzeugt: {$pdfFile}");
|
|
|
|
|
}
|
|
|
|
|
$built[] = $pdfFile;
|
|
|
|
|
|
|
|
|
|
$log("{$key}: SVG, " . count(LOGO_PNG_WIDTHS) . ' PNG, ' . count(LOGO_JPG_WIDTHS) . ' JPG und PDF erzeugt.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- LIESMICH + ZIP -----------------------------------------------------------
|
|
|
|
|
// Die Texte kommen aus data/logo.json, damit Heft-, Seiten- und ZIP-Fassung nicht
|
|
|
|
|
// auseinanderlaufen. Fehlt die JSON noch (erster Lauf), bleibt es beim Grundgerüst.
|
|
|
|
|
$page = json_load('logo');
|
|
|
|
|
$club = json_load('club');
|
|
|
|
|
$lines = ['Logo des ' . ($club['name'] ?? 'TSV 08 Kulmbach'), str_repeat('=', 40), ''];
|
|
|
|
|
|
|
|
|
|
if (!empty($page['notice']['text'])) {
|
|
|
|
|
$lines[] = trim($page['notice']['title'] ?? 'Hinweis');
|
|
|
|
|
$lines[] = trim($page['notice']['text']);
|
|
|
|
|
$lines[] = '';
|
|
|
|
|
}
|
|
|
|
|
$lines[] = 'Welche Datei wofür';
|
|
|
|
|
$lines[] = '-------------------';
|
|
|
|
|
$lines[] = 'SVG Vektor, beliebig skalierbar. Erste Wahl für die Druckerei.';
|
|
|
|
|
$lines[] = 'PDF Vektor, für Druckereien, die kein SVG annehmen.';
|
|
|
|
|
$lines[] = 'PNG Pixelbild mit freigestelltem Grund, für Word, PowerPoint und Aushänge.';
|
|
|
|
|
$lines[] = 'JPG Pixelbild mit gefülltem Grund (weiß, bei der Fassung für dunklen Grund anthrazit),';
|
|
|
|
|
$lines[] = ' für Portale und Programme, die keine Transparenz können.';
|
|
|
|
|
$lines[] = '';
|
|
|
|
|
|
|
|
|
|
$texte = array_column($page['varianten'] ?? [], null, 'key');
|
|
|
|
|
foreach (array_keys($OUTPUTS) as $key) {
|
|
|
|
|
$lines[] = ($texte[$key]['title'] ?? $key) . ' (Dateien: ' . LOGO_PREFIX . '-' . $key . '.*)';
|
|
|
|
|
if (!empty($texte[$key]['text'])) {
|
|
|
|
|
$lines[] = ' ' . $texte[$key]['text'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!empty($page['farben']['items'])) {
|
|
|
|
|
$lines[] = '';
|
|
|
|
|
$lines[] = 'Farben';
|
|
|
|
|
$lines[] = '------';
|
|
|
|
|
foreach ($page['farben']['items'] as $farbe) {
|
|
|
|
|
$lines[] = sprintf(
|
|
|
|
|
'%s HEX %s RGB %s CMYK %s',
|
|
|
|
|
$farbe['name'] ?? '',
|
|
|
|
|
$farbe['hex'] ?? '',
|
|
|
|
|
$farbe['rgb'] ?? '',
|
|
|
|
|
$farbe['cmyk'] ?? ''
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$lines[] = '';
|
|
|
|
|
$lines[] = 'Alle Dateien und weitere Formate: ' . club_url('logo');
|
|
|
|
|
$lines[] = 'Fragen: ' . ($club['email'] ?? '');
|
|
|
|
|
|
|
|
|
|
$readme = $destDir . '/LIESMICH.txt';
|
|
|
|
|
file_put_contents($readme, implode("\n", $lines) . "\n");
|
|
|
|
|
|
|
|
|
|
$zipFile = $destDir . '/' . LOGO_PREFIX . '-paket.zip';
|
|
|
|
|
$zip = new ZipArchive();
|
|
|
|
|
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
|
|
|
|
$fail("ZIP nicht anlegbar: {$zipFile}");
|
|
|
|
|
}
|
|
|
|
|
$zip->addFile($readme, 'LIESMICH.txt');
|
|
|
|
|
foreach ($built as $file) {
|
|
|
|
|
$zip->addFile($file, LOGO_PREFIX . '/' . basename($file));
|
|
|
|
|
}
|
|
|
|
|
$zip->close();
|
|
|
|
|
|
|
|
|
|
// --- Bericht ------------------------------------------------------------------
|
|
|
|
|
$total = 0;
|
|
|
|
|
echo "\nErzeugt in public/assets/downloads/logo/:\n";
|
|
|
|
|
foreach (array_merge($built, [$readme, $zipFile]) as $file) {
|
|
|
|
|
$size = (int) filesize($file);
|
|
|
|
|
$total += $size;
|
|
|
|
|
$dim = '';
|
|
|
|
|
if (in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ['png', 'jpg'], true)) {
|
|
|
|
|
$info = @getimagesize($file);
|
|
|
|
|
$dim = $info ? " {$info[0]}x{$info[1]}" : '';
|
|
|
|
|
}
|
|
|
|
|
printf(" %-46s %8s KB%s\n", basename($file), number_format($size / 1024, 1, ',', '.'), $dim);
|
|
|
|
|
}
|
|
|
|
|
printf(" %-46s %8s KB\n", '= gesamt (ohne ZIP doppelt gezählt)', number_format($total / 1024, 1, ',', '.'));
|
|
|
|
|
|
|
|
|
|
$log('Fertig: ' . (count($built) + 2) . ' Dateien.');
|