fix(logos): SVG-Größe aus Breite und Höhe mit ergänzter viewBox, Pixelgrenze vor dem Dekodieren
Entscheidungen des Controllers zu Task 6 (Bedenken 2 bis 4).
- logos/prepare-logo.ts: sizeSvg(svg) ersetzt sizeSvgRoot und liefert { svg, w, h }. Das Seitenverhältnis kommt aus der brauchbaren
viewBox des Wurzelelements, sonst aus dessen Breite und Höhe (Pixel oder ohne Einheit), sonst ist es quadratisch; Prozent und
andere Einheiten, 0 und negative Werte zählen als unbrauchbar. Ohne brauchbare viewBox, aber mit Größe, wird viewBox="0 0 <Breite>
<Höhe>" ergänzt (eine unbrauchbare viewBox wird dabei ersetzt, nie doppelt geschrieben), damit der Inhalt auf das Raster skaliert
statt in Originalgröße oben links zu stehen. Eine brauchbare viewBox bleibt immer unberührt. Gelesen werden nur die Attribute des
Wurzelelements (vorher die erste viewBox irgendwo im Text, etwa die eines inneren <symbol>); Anführungszeichen " und ' gehen
beide. Tokenizer und Längenparser laufen linear (zwei quadratische Entwürfe fielen im Zeittest durch).
- logos/prepare-logo.ts: Raster und SVG werden über decodeImage aus editor/upload.ts geladen: erst die Maße lesen und gegen
40 Megapixel prüfen, erst dann dekodieren (vorher dekodierte createImageBitmap zuerst). decodeImages Text für „zu groß“ wird zum
Text des Logos, jeder andere Fehler bleibt „Das Logo konnte nicht gelesen werden.“ Die eigene Ladefunktion für SVG und
createImageBitmap entfallen. LOGO_SOURCE_MAX_PIXELS ist jetzt MAX_IMAGE_PIXELS (eine Quelle, Wert 40 000 000 per Test festgehalten).
- logos/prepare-logo.ts: SVG_FALLBACK_SIZE entfernt (nichts benutzte es).
- card/marks.ts: neu viewBoxAspect(value) mit den Regeln aus svgAspect; svgAspect ruft es auf (Verhalten unverändert), so stehen die
viewBox-Regeln nur einmal da.
- Tests: sizeSvg als Tabelle (Größe ohne Einheit, px, hochkant, Prozent, mm/em/pt/auto, nur Breite oder Höhe, 0 und negativ,
vorhandene viewBox bleibt, unbrauchbare viewBox wird ersetzt, innere Elemente zählen nicht, XML-Kopf, „>“ im Wert, Zeitgrenze),
prepareLogo mit gestelltem Image: Zahl der Pixel wird vor decode() geprüft (8001 x 5000 und 20 000 x 20 000 ohne decode, genau
8000 x 5000 mit), SVG nur mit Breite und Höhe wird 2048 x 614 gerastert und bekommt viewBox="0 0 200 60"; marks.test.ts für
viewBoxAspect. Vitest 47 Dateien, 1478 Tests grün, tsc sauber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0f66136d82
commit
56d841b8e2
4 files changed
+354
-133
No files matched your search
@@ -2,7 +2,7 @@ import { readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest';
|
||||
import favicon from '../../public/favicon.svg?raw';
|
||||
import { BRAND_MARKS, getBrandMark, loadMarkImage, svgAspect, tintSvg } from './marks';
|
||||
import { BRAND_MARKS, getBrandMark, loadMarkImage, svgAspect, tintSvg, viewBoxAspect } from './marks';
|
||||
|
||||
const BRAND_IDS = ['aircards-symbol', 'aircards-text'];
|
||||
|
||||
@@ -61,6 +61,15 @@ describe('SVG-Inhalt', () => {
|
||||
expect(svgAspect('<svg viewBox="10 20 300 100"></svg>')).toBeCloseTo(3, 1);
|
||||
});
|
||||
|
||||
it('viewBoxAspect: Breite durch Höhe aus dem Wert einer viewBox (Leerraum oder Kommas), sonst null', () => {
|
||||
expect(viewBoxAspect('0 0 300 100')).toBeCloseTo(3, 5);
|
||||
expect(viewBoxAspect(' 10,20,200,100 ')).toBeCloseTo(2, 5);
|
||||
expect(viewBoxAspect('0 0 100 100')).toBe(1);
|
||||
for (const unusable of ['', 'none', '0 0 0 10', '0 0 10 0', '0 0 -10 5', '0 0 10', '0 0 1 2 3', '0 0 a b', '0 0 Infinity 5']) {
|
||||
expect(viewBoxAspect(unusable), JSON.stringify(unusable)).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it('beide Marken sind einfärbbar und enthalten currentColor', () => {
|
||||
for (const m of BRAND_MARKS) {
|
||||
expect(m.tint, m.id).toBe(true);
|
||||
|
||||
@@ -36,13 +36,17 @@ export function getBrandMark(id: Exclude<BrandId, 'none'>): MarkDef {
|
||||
return BRAND_MARKS.find((m) => m.id === id) ?? BRAND_MARKS[0];
|
||||
}
|
||||
|
||||
/** Breite durch Höhe aus dem Wert einer viewBox („x y Breite Höhe“, mit Leerraum oder Kommas); null, wenn er nicht brauchbar ist. */
|
||||
export function viewBoxAspect(value: string): number | null {
|
||||
const parts = value.trim().split(/[\s,]+/).map(Number);
|
||||
const [w, h] = [parts[2], parts[3]];
|
||||
return parts.length === 4 && Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 ? w / h : null;
|
||||
}
|
||||
|
||||
/** Breite durch Höhe aus der viewBox; 1, wenn keine brauchbare viewBox da ist. */
|
||||
export function svgAspect(svg: string): number {
|
||||
const hit = /viewBox\s*=\s*"([^"]+)"/.exec(svg);
|
||||
if (!hit) return 1;
|
||||
const parts = hit[1].trim().split(/[\s,]+/).map(Number);
|
||||
const [w, h] = [parts[2], parts[3]];
|
||||
return parts.length === 4 && Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 ? w / h : 1;
|
||||
return (hit && viewBoxAspect(hit[1])) ?? 1;
|
||||
}
|
||||
|
||||
/** Ersetzt jedes `currentColor` durch die Details-Farbe. */
|
||||
|
||||
Reference in new issue
Block a user