fix: code-review findings in the print module
Security and robustness: - EXIF orientation is now applied before any geometry. Phone photos carry the rotation only as metadata; sharp was cropping the unrotated raster, so a portrait shot came out of the printer sideways and wrongly framed. - renderCell no longer materialises the padded image at source resolution. It is one extract-resize-extend chain now, which is also sharp's internal order. A panorama into a narrow contain target used to build a ~960 MB intermediate and then fail; it is 90 ms and a few MB now. - Target size is capped (300 Mpx) and bleedMm is clamped in /api/print/single, which had no bound at all. - The delivery gallery is validated before use - posixpath.join let a crafted name escape the target's base folder and create directories there. - Sheet requests are capped at 500 pieces and the packer has a step budget, so a degenerate request cannot block the single-threaded server. - Print presets: delete only your own (admins all), config size limit, count limit, and by_name honours anonymous_generations. - Telegram callbacks require an active pairing, like every other path. - Error responses no longer leak storage paths or delivery hostnames. Correctness: - allowRotate:undefined now means allowed, consistently with the packer. - The many-formats shortcut no longer drops a format that only fits rotated. - unplaced names the format that is actually missing, not the first one. - Crop marks never sit inside the printed bleed - the offset is raised. - capacity() computes the grid instead of probing with 200 copies. - Image keys in the sheet cannot collide with a cell literally named x::rot. - labelMm keeps real decimals; parseSizeMm reads a:b as width:height, so 3:4/15 is portrait and 4:3/15 is landscape. - The footer is skipped when there is no free space at the bottom. - The UI warns when corner marks do not fit the margin, and when continuous guides are used with mixed sizes. Tests: 21 -> 31, each finding has a regression test.
This commit is contained in:
12 files changed
+303
-76
No files matched your search
@@ -35,6 +35,10 @@ interface Cell {
|
||||
|
||||
const uid = () => (crypto.randomUUID ? crypto.randomUUID() : String(Math.random()).slice(2));
|
||||
|
||||
/** Farbwerte aus fremden Vorlagen landen in style — nur echte Hex-Werte zulassen. */
|
||||
const safeColor = (v: unknown): string =>
|
||||
typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v : '#ffffff';
|
||||
|
||||
/** Größter Ausschnitt im Zielverhältnis — füllt das Format aus, schneidet ab. */
|
||||
function coverCrop(natW: number, natH: number, aspect: number, around?: CropRel): CropRel {
|
||||
const imgA = natW / natH;
|
||||
@@ -292,7 +296,7 @@ export default function PrintApp() {
|
||||
const apply = (cell: Cell, f: any): Cell => {
|
||||
const next = { ...cell, id: cell.id, sizeId: f.sizeId, customSize: f.customSize || '',
|
||||
landscape: !!f.landscape, count: f.count || 1, allowRotate: f.allowRotate !== false,
|
||||
fit: (f.fit === 'contain' ? 'contain' : 'cover') as FitMode, bg: f.bg || '#ffffff' };
|
||||
fit: (f.fit === 'contain' ? 'contain' : 'cover') as FitMode, bg: safeColor(f.bg) };
|
||||
const a = aspectOf(next); if (a) next.crop = baseCrop(next.natW, next.natH, a, next.fit, cell.crop);
|
||||
return next;
|
||||
};
|
||||
@@ -405,6 +409,14 @@ export default function PrintApp() {
|
||||
: marks === 'grid' ? 'Durchgehende Hilfslinien über den ganzen Bogen — für Schneidelineal und Schlagschere.'
|
||||
: 'Keine Linien — Kanten selbst anlegen.'}
|
||||
</div>
|
||||
{marks === 'corner' && marginMm < markOff + markLen && (
|
||||
<div className="fein warn">Bei {marginMm} mm Rand ist außerhalb der Bilder kein Platz für die Marken —
|
||||
sie werden abgeschnitten. Rand auf mindestens {Math.ceil(markOff + markLen)} mm setzen.</div>
|
||||
)}
|
||||
{marks === 'grid' && new Set(ready.map((c) => { const s = sizeOfCell(c); return s ? `${s.w}x${s.h}` : ''; })).size > 1 && (
|
||||
<div className="fein warn">Durchgehende Linien passen nur zu <b>einem</b> Bildmaß. Bei gemischten Größen
|
||||
werden sie unterbrochen — ein Schnitt über das ganze Blatt würde andere Bilder treffen. Besser Eckmarken.</div>
|
||||
)}
|
||||
{marks === 'corner' && (
|
||||
<div className="reihe zwei">
|
||||
<label className="mini">Länge (mm)
|
||||
|
||||
Reference in new issue
Block a user