Logo in der Mitte des QR-Codes

Zur Wahl stehen zwei Quellen: das Logo der Installation aus dem
Erscheinungsbild oder ein eigenes je Code. Die Groesse ist zwischen 10 und
25 Prozent der Kantenlaenge einstellbar, hinter dem Bild liegt eine Flaeche
in der Hintergrundfarbe.

Mit Logo gilt die Fehlerkorrektur H, unabhaengig von der Auswahl — was das
Bild verdeckt, muss sie ausgleichen. Die Auswahl steht dann fest auf H und
sagt auch warum; heimlich geschieht das nicht. Ab 22 Prozent kommt eine
Warnung dazu.

Gerendert wird weiterhin serverseitig, damit Vorschau und Download
uebereinstimmen: das SVG bekommt ein eingebettetes Bild, das PNG wird mit
sharp zusammengesetzt. sharp lag ohnehin als Abhaengigkeit von Next im
Baum und steht jetzt ausdruecklich in der package.json.

Ein eigenes Logo gehoert zur Gestaltung, nicht zum Inhalt — es muss
trotzdem an den Code gehaengt werden. Sonst raeumt die Aufraeumroutine es
nach einem Tag weg und der Code verliert sein Bild.

Refs #8
This commit is contained in:
Florian Wilde
2026-08-21 03:22:36 +02:00
parent b96e9ddbe5
commit c06f87e6d9
21 changed files with 611 additions and 28 deletions
+55 -1
View File
@@ -13,6 +13,29 @@ export const EC_LEVEL_LABELS: Record<EcLevel, string> = {
export const MIN_SIZE = 256;
export const MAX_SIZE = 2048;
/**
* Logo in der Mitte des Codes.
*
* `branding` nimmt das Logo der Installation aus dem Erscheinungsbild,
* `upload` ein eigenes, das zu diesem Code gehört. Ohne Angabe kein Logo.
*/
export const LOGO_SOURCES = ["branding", "upload"] as const;
export type LogoSource = (typeof LOGO_SOURCES)[number];
/**
* Kantenlänge des Logos in Prozent der Kantenlänge des Codes.
*
* Bei 20 % sind knapp 4 % der Fläche verdeckt — das steckt die
* Fehlerkorrektur H (30 %) mühelos weg. Die Obergrenze liegt bei 25 %, weil
* darüber weniger die Fehlerkorrektur das Problem ist als die Scanner-Apps:
* Manche suchen in der Mitte nach Ausrichtungsmustern.
*/
export const MIN_LOGO_SCALE = 10;
export const MAX_LOGO_SCALE = 25;
/** Ab hier ein Hinweis, dass es knapp wird. */
export const LOGO_SCALE_WARN = 22;
export const DEFAULT_LOGO_SCALE = 20;
const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
const hexColor = z
@@ -39,7 +62,25 @@ export const designSchema = z.object({
.int({ error: "Der Rand muss eine ganze Zahl sein." })
.min(0, { error: "Der Rand darf nicht negativ sein." })
.max(16, { error: "Der Rand darf höchstens 16 Module breit sein." }),
});
logoSource: z
.enum(LOGO_SOURCES, { error: "Unbekannte Herkunft des Logos." })
.nullable()
.default(null),
logoUploadId: z.string().trim().min(1).nullable().default(null),
logoScale: z.coerce
.number({ error: "Die Logogröße muss eine Zahl sein." })
.int({ error: "Die Logogröße muss eine ganze Zahl sein." })
.min(MIN_LOGO_SCALE, {
error: `Das Logo muss mindestens ${MIN_LOGO_SCALE} % breit sein.`,
})
.max(MAX_LOGO_SCALE, {
error: `Das Logo darf höchstens ${MAX_LOGO_SCALE} % breit sein — darüber wird der Code unzuverlässig.`,
})
.default(DEFAULT_LOGO_SCALE),
}).refine(
(design) => design.logoSource !== "upload" || design.logoUploadId !== null,
{ error: "Für ein eigenes Logo fehlt die hochgeladene Datei." },
);
export type QrDesign = z.infer<typeof designSchema>;
@@ -49,8 +90,21 @@ export const defaultDesign: QrDesign = {
size: 512,
ecLevel: "M",
margin: 4,
logoSource: null,
logoUploadId: null,
logoScale: DEFAULT_LOGO_SCALE,
};
/**
* Ein Logo verdeckt einen Teil des Codes. Was fehlt, muss die Fehlerkorrektur
* ausgleichen — deshalb gilt mit Logo immer **H**, unabhängig davon, was in
* der Auswahl steht. Die Oberfläche sagt das dazu; heimlich geschieht hier
* nichts.
*/
export function effectiveEcLevel(design: QrDesign): EcLevel {
return design.logoSource === null ? design.ecLevel : "H";
}
/** Farbvorschläge für die schnelle Auswahl in der Oberfläche. */
export const COLOR_PRESETS: ReadonlyArray<{
label: string;