feat: sheet templates, margin profiles and delivering the sheet

Seeds the four sheet setups Till actually prints (Kita set, small school set,
full passport sheet, two 10x7,5 on 10x15 paper). A multi-format template
applied to a single image clones it into every format, so one click yields the
whole set. Margin gets one-click borderless / 5 mm / 10 mm profiles, and the
finished PDF can be pushed to Picdrop, the NAS or any extra target.
This commit is contained in:
2026-08-18 06:40:31 +00:00
parent 21a0a68972
commit 4fb34cdab8
6 changed files with 125 additions and 15 deletions
+10 -2
View File
@@ -85,8 +85,16 @@ import Base from '../layouts/Base.astro';
schiefem Schnitt kein weißer Rand entsteht. Der Abstand zwischen zwei Bildern wächst automatisch mit.</li>
<li><b>Einzeln herunterladen</b>: ein Bild exakt auf Maß als PNG/JPG mit dpi-Metadaten — ohne Bogen.</li>
<li><b>Auflösungswarnung</b>: reicht die Quelle nicht für die gewählte dpi, steht es rot unter dem Bild.</li>
<li><b>Vorlagen</b>: die ganze Bogen-Einstellung (Papier, Marken, Formate, Stückzahlen) speichern
z. B. „Kita-Satz Felix" oder „8 × Passbild".</li>
<li><b>Vorlagen</b>: die ganze Bogen-Einstellung (Papier, Marken, Formate, Stückzahlen) speichern.
Mitgeliefert sind <b>Kita-Satz (A4)</b> (1 × 13 × 18, 2 × 9 × 13, 8 × Passbild),
<b>Schulsatz klein (A4)</b>, <b>Passbildbogen 35 × 45 (A4)</b> und
<b>2 × 10 × 7,5 auf Fotopapier 10 × 15</b>. Lädst du eine mehrformatige Vorlage bei nur
<i>einem</i> Bild, wird das Bild automatisch für jedes Format übernommen — ein Klick,
fertiger Satz.</li>
<li><b>Randprofile</b>: <b>Randlos</b> (0 mm), <b>Standard</b> (5 mm) oder <b>Sicher</b> (10 mm)
per Knopf, der genaue Wert bleibt frei eintragbar.</li>
<li><b>Wohin?</b>: der fertige Bogen kann zusätzlich an Picdrop, aufs NAS oder an ein
Zusatzziel gelegt werden — wie die Bilder aus dem Studio.</li>
<li><b>Automatische Anordnung</b>: gleich große Bilder ergeben ein sauberes Raster; bei gemischten
Größen werden die kleinen automatisch in die Restflächen neben den großen gesetzt.</li>
</ul>
+26 -1
View File
@@ -3,6 +3,9 @@ import { layout, cutMarks, effectiveGap, type PlaceSpec, type SheetSpec, type Ma
import { renderCell, type CropRel, type SheetCellImage, buildSheetPdf } from '../../../lib/printrender';
import { loadSource, sheetFilename, type PrintSource } from '../../../lib/printsource';
import { paperById, parseSizeMm, labelMm } from '../../../lib/paper';
import { cfgForKey } from '../../../lib/delivery';
import { uploadBuffer } from '../../../lib/picdrop';
import { one } from '../../../lib/db';
export const prerender = false;
@@ -103,12 +106,34 @@ export const POST: APIRoute = async ({ request, locals }) => {
markWidthPt: numOr(body?.marks?.widthPt, 0.25, 0.1, 2),
});
const filename = sheetFilename(body?.name || 'druckbogen', 'pdf');
// Optional: den fertigen Bogen zusätzlich ins gewohnte Ziel schieben
// (Standard-Picdrop, NAS oder ein Zusatzziel) — wie bei den Bildern.
let delivered = '0', deliveryMsg = '';
if (body?.deliver) {
const key = body.deliver.target === '' || body.deliver.target == null ? 'picdrop' : String(body.deliver.target);
try {
const cfg = await cfgForKey(key);
if (!cfg) throw new Error('Ziel nicht konfiguriert.');
const s = await one<{ picdrop_default_gallery: string | null }>('SELECT picdrop_default_gallery FROM settings WHERE id=1');
const gallery = String(body.deliver.gallery || '').trim() || s?.picdrop_default_gallery || 'POSTER LEA';
await uploadBuffer(cfg, gallery, filename, Buffer.from(bytes));
delivered = '1';
deliveryMsg = `Bogen nach „${gallery}" ausgeliefert.`;
} catch (e: any) {
deliveryMsg = `Auslieferung fehlgeschlagen: ${e?.message || e}`;
}
}
return new Response(Buffer.from(bytes), {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${sheetFilename(body?.name || 'druckbogen', 'pdf')}"`,
'Content-Disposition': `attachment; filename="${filename}"`,
'X-Klarbild-Pages': String(pages.length),
'X-Klarbild-Per-Sheet': String(plan.perSheet),
'X-Klarbild-Delivered': delivered,
...(deliveryMsg ? { 'X-Klarbild-Delivery-Msg': encodeURIComponent(deliveryMsg) } : {}),
},
});
} catch (e: any) {