Build Kantarci Markt as an eighth installation, allow content in own CSS and keep other fonts when fetching
- Coming-soon page "Kantarci Markt – Neueröffnung" rebuilt from the delivered package with imprint, privacy and 404 (scripts/reset-kantarci-markt.sh, ports 8100/8101). - Own CSS accepts a short quoted string for `content`, so ::before/::after work. - cms:fonts:fetch replaces only the rules of the fetched families in fonts.css. - A variable font is linked and preloaded once with a weight range instead of once per weight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6fb6d30e8d
commit
5e5d380fd3
19 files changed
+994
-8
No files matched your search
@@ -239,6 +239,14 @@ final class CustomCode
|
||||
if (1 !== preg_match('/^-{0,2}[a-z][a-z0-9-]{0,40}$/D', $property)) {
|
||||
throw new InvalidDocument(sprintf('„%s“ ist kein CSS-Eigenschaftsname.', mb_substr($property, 0, 40)));
|
||||
}
|
||||
// `content` braucht einen Text in Anfuehrungszeichen, sonst gibt es kein ::before und
|
||||
// ::after fuer Striche, Verlaeufe oder ein Wasserzeichen. Erlaubt ist ein kurzer Text
|
||||
// aus Buchstaben, Ziffern und wenigen Satzzeichen: kein <, kein Backslash, kein
|
||||
// weiteres Anfuehrungszeichen, also kein Weg aus dem String oder dem <style>.
|
||||
if ('content' === $property && 1 === preg_match('/^(["\'])[\p{L}\p{N} .,:!?·•–-]{0,40}\1$/uD', $value)) {
|
||||
$out[] = $property.':'.$value;
|
||||
continue;
|
||||
}
|
||||
if ('' === $value || 1 !== preg_match('/^[a-zA-Z0-9 ,.%#()\/:_+*-]{1,300}$/D', $value)) {
|
||||
throw new InvalidDocument(sprintf('Der Wert von „%s“ enthält Zeichen, die in eigenem CSS nicht vorgesehen sind.', $property));
|
||||
}
|
||||
|
||||
@@ -84,21 +84,41 @@ final class FontLibrary
|
||||
$bytes = 0;
|
||||
foreach ($wanted as $family => $weights) {
|
||||
$css = $this->stylesheet($family, $weights);
|
||||
// Eine variable Schrift (etwa DM Sans) liefert Google als eine Datei für alle
|
||||
// Schnitte, mit einer @font-face-Regel je Schnitt. Die Datei liegt trotzdem je Schnitt
|
||||
// im Ordner, damit missing() weiter nach Schnitten fragen kann, aber das Stylesheet
|
||||
// nennt sie nur einmal, mit einem Gewichtsbereich. Sonst lädt der Browser dieselben
|
||||
// Bytes je Schnitt einmal, und preload() holt sie dreimal vorab.
|
||||
$groups = [];
|
||||
foreach ($this->parse($css, $family) as $face) {
|
||||
$name = $this->slug($family).'-'.$face['weight'].'-'.$face['subset'].'.woff2';
|
||||
$path = $this->directory.'/'.$name;
|
||||
if (!is_file($path)) {
|
||||
file_put_contents($path, $this->download($face['url']));
|
||||
++$files;
|
||||
if (isset($groups[$face['url']])) {
|
||||
copy($this->directory.'/'.$groups[$face['url']]['name'], $path);
|
||||
} else {
|
||||
file_put_contents($path, $this->download($face['url']));
|
||||
++$files;
|
||||
}
|
||||
}
|
||||
if (isset($groups[$face['url']])) {
|
||||
$groups[$face['url']]['weights'][] = $face['weight'];
|
||||
continue;
|
||||
}
|
||||
$bytes += (int) filesize($path);
|
||||
$groups[$face['url']] = ['name' => $name, 'weights' => [$face['weight']], 'face' => $face];
|
||||
}
|
||||
foreach ($groups as $group) {
|
||||
$face = $group['face'];
|
||||
$low = min($group['weights']);
|
||||
$high = max($group['weights']);
|
||||
$rules[] = sprintf(
|
||||
"/* %s */\n@font-face{font-family:'%s';font-style:%s;font-weight:%d;font-display:swap;src:url(/assets/fonts/%s) format('woff2');unicode-range:%s}",
|
||||
"/* %s */\n@font-face{font-family:'%s';font-style:%s;font-weight:%s;font-display:swap;src:url(/assets/fonts/%s) format('woff2');unicode-range:%s}",
|
||||
$face['subset'],
|
||||
$family,
|
||||
$face['style'],
|
||||
$face['weight'],
|
||||
$name,
|
||||
$low === $high ? (string) $low : $low.' '.$high,
|
||||
$group['name'],
|
||||
$face['range'],
|
||||
);
|
||||
}
|
||||
@@ -106,11 +126,40 @@ final class FontLibrary
|
||||
if ([] === $rules) {
|
||||
throw new \RuntimeException('Google Fonts hat keine passende Schriftdatei geliefert.');
|
||||
}
|
||||
file_put_contents($this->directory.'/fonts.css', implode("\n", $rules)."\n");
|
||||
$stylesheet = $this->directory.'/fonts.css';
|
||||
$existing = is_file($stylesheet) ? (string) file_get_contents($stylesheet) : '';
|
||||
file_put_contents($stylesheet, $this->combine($existing, $rules, array_keys($wanted)));
|
||||
|
||||
return ['files' => $files, 'bytes' => $bytes, 'families' => array_keys($wanted)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt fonts.css aus den neuen Regeln und den Regeln anderer Familien zusammen.
|
||||
*
|
||||
* Bisher schrieb jeder Abruf die Datei neu und nur mit den Familien des aktuellen Designs.
|
||||
* Teilen sich mehrere Websites den Ordner (lokal alle Installationen) oder wechselt ein Design
|
||||
* die Schrift und wieder zurück, verschwanden damit Schriften, deren Dateien noch daliegen.
|
||||
* Regeln für Familien, die gerade neu geholt wurden, werden ersetzt; die übrigen bleiben.
|
||||
* Eine unbenutzte @font-face-Regel kostet nichts: der Browser lädt die Datei erst, wenn Text
|
||||
* in dieser Familie steht.
|
||||
*
|
||||
* @param list<string> $rules je Regel ein Kommentar mit dem Bereich und die @font-face
|
||||
* @param list<string> $families die Familien, zu denen $rules gehören
|
||||
*/
|
||||
public function combine(string $existing, array $rules, array $families): string
|
||||
{
|
||||
$kept = [];
|
||||
preg_match_all('~(?:/\*[^*]*\*/\s*)?@font-face\s*\{[^}]*\}~', $existing, $matches);
|
||||
foreach ($matches[0] as $rule) {
|
||||
if (1 !== preg_match("~font-family:\\s*'([^']+)'~", $rule, $family) || in_array($family[1], $families, true)) {
|
||||
continue;
|
||||
}
|
||||
$kept[] = trim($rule);
|
||||
}
|
||||
|
||||
return implode("\n", [...$kept, ...$rules])."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Dateien, die für den ersten Eindruck vorab geholt werden sollen.
|
||||
*
|
||||
@@ -124,11 +173,16 @@ final class FontLibrary
|
||||
*/
|
||||
public function preload(array $theme): array
|
||||
{
|
||||
// Nur Dateien, die das Stylesheet auch nennt: bei einer variablen Schrift steht dort eine
|
||||
// Datei für alle Schnitte, die Kopien je Schnitt holt der Browser nie.
|
||||
$stylesheet = is_file($this->directory.'/fonts.css') ? (string) file_get_contents($this->directory.'/fonts.css') : null;
|
||||
$preload = [];
|
||||
foreach ($this->wanted($theme) as $family => $weights) {
|
||||
foreach ($weights as $weight) {
|
||||
$name = $this->slug($family).'-'.$weight.'-latin.woff2';
|
||||
if (is_file($this->directory.'/'.$name)) {
|
||||
$genannt = null === $stylesheet || !str_contains($stylesheet, '/assets/fonts/'.$this->slug($family).'-')
|
||||
|| str_contains($stylesheet, '/assets/fonts/'.$name.')');
|
||||
if (is_file($this->directory.'/'.$name) && $genannt) {
|
||||
$preload[] = '/assets/fonts/'.$name;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user