--- // Public tour page for the print module. // Deliberately without a login and without controls: it shows what the tool can // do, but you cannot use it here. Meant as a landing page for an open-source // release (Gitea/GitHub) and for showing the tool to someone. import Base from '../layouts/Base.astro'; import { PHOTO_SIZES, PAPERS } from '../lib/paper'; import { layout, cutMarks, type SheetSpec } from '../lib/printlayout'; import { tFor, dictFor } from '../lib/i18n.server'; const locale = Astro.locals.locale ?? 'en'; const t = tFor(locale); const dict = dictFor(locale); const sizeCount = PHOTO_SIZES.length; const paperCount = PAPERS.length; const groupCount = new Set(PHOTO_SIZES.map((p) => p.group)).size; const features = [ { kicker: t('True to size'), title: t('Millimetres instead of “roughly”'), text: t('Every final size is computed in millimetres and only turned into pixels when it is rendered. The PDF has exactly the sheet size, and every image on it exactly its target size. Checking that is not guesswork but measurement: we rasterise the finished PDFs at 254 dpi and count the pixel edges.'), proof: t('13 × 18 cm → measured 130.1 × 180.1 mm'), }, { kicker: t('Space'), title: t('Several images on one sheet'), text: t('Images of the same size go into an exact grid. With mixed formats a packer (MaxRects, “best short side fit”) looks for the arrangement that needs the fewest sheets — and tries every combination of portrait and landscape to find it. A set of passport photos and a 13 × 18 fit on the same sheet that way.'), proof: t('School photo set: 9 pieces on one A4 sheet instead of 3'), }, { kicker: t('Cutting'), title: t('Crop marks as in prepress'), text: t('Fine corner marks outside every image, or continuous grid lines across the whole sheet. The lines break where another image lies — nothing is ever drawn across a picture. Length and offset can be set, and so can the bleed.'), proof: t('Corner marks · continuous grid · bleed 0–10 mm'), }, { kicker: t('Too large'), title: t('20 × 30 on A4 — even so'), text: t('The everyday case: it is meant to be a 20 × 30, but the only photo paper in the house is A4. Instead of “cannot be done” there are two ways out. Oversize places the image centred on the whole sheet; whatever hangs over is missing afterwards — and how much that is stands there beforehand.'), proof: t('20 × 30 cm on A4: exactly 3 mm are missing in height'), }, { kicker: t('Poster'), title: t('One image, several sheets'), text: t('The second way out for large sizes: the image is spread across several sheets. The tiles overlap by a glue flap, which is printed on both neighbouring sheets and marked with a dashed line. Every sheet carries its number with row and column.'), proof: t('30 × 40 cm on A4 → 2 × 2 sheets, 10 mm glue flap'), }, { kicker: t('Cropping'), title: t('The crop stays yours'), text: t('If the aspect ratio does not fit, you decide: crop or letterbox. In the crop window you move and zoom the original inside the target frame — with the mouse, a finger, a double-tap or the arrow keys. The crop is stored relatively and survives any resolution.'), proof: t('EXIF rotation is applied beforehand — phone photos come out upright'), }, ]; const stats = [ { value: String(sizeCount), label: t('Image sizes'), hint: t('in {n} groups — passport photo up to 100 × 140 cm', { n: groupCount }) }, { value: String(paperCount), label: t('Paper sizes'), hint: t('DIN A7–A0, photo paper, US sizes, custom size') }, { value: '0', label: t('AI involved'), hint: t('plain geometry, no model, nothing uploaded anywhere') }, { value: '1:1', label: t('Output'), hint: t('PDF at original size, 72–1200 dpi') }, ]; // The sample sheet below is not drawn, it is computed — with exactly the same // packer the tool uses. If the algorithm changes, the picture on this page // changes with it. const sampleSheet: SheetSpec = { wMm: 210, hMm: 297, marginMm: 5, gapMm: 8, bleedMm: 0, center: true }; const samplePlan = layout([ { id: 'large', wMm: 130, hMm: 180, count: 1, allowRotate: true }, { id: 'small', wMm: 90, hMm: 130, count: 1, allowRotate: true }, { id: 'passport', wMm: 35, hMm: 45, count: 6, allowRotate: true }, ], sampleSheet); const samplePage = samplePlan.pages[0]; const sampleMarks = samplePage ? cutMarks(samplePage, sampleSheet, { mode: 'corner', lengthMm: 3, offsetMm: 1 }) : []; const sampleColor: Record = { large: 'oklch(45% 0.23 267 / 0.09)', small: 'oklch(45% 0.23 267 / 0.20)', passport: 'oklch(45% 0.23 267 / 0.34)', }; const samplePieces = samplePage ? samplePage.placements.length : 0; /** Compact size for the little box: under 10 cm in mm, otherwise in cm. */ const shortSize = (w: number, h: number) => w < 100 && h < 100 ? `${Math.round(w)}×${Math.round(h)}` : `${Math.round(w / 10)}×${Math.round(h / 10)} cm`; const steps = [ { n: '01', title: t('Choose images'), text: t('Upload them, take them out of the library, or duplicate an image you already have to print it at two sizes.') }, { n: '02', title: t('Set the sizes'), text: t('One target size per image, the quantity, portrait or landscape, crop or letterbox.') }, { n: '03', title: t('Set up the sheet'), text: t('Paper, crop marks, margins, resolution. The preview shows straight away how many sheets it will be.') }, { n: '04', title: t('Print'), text: t('Create the PDF and choose “Actual size / 100%” in the print dialog. Done.') }, ]; ---

{t('Klarbild · Print module')}

{t('Bring images to exact sizes.')}
{t('Without AI.')}

{t('A passport photo is 35 × 45 millimetres. A picture frame is 13 × 18 centimetres. Both are measurements, not opinions — and still it is remarkably laborious to get a photo out of the printer at exactly that size. This tool turns it into one move: enter the target size, choose the crop, print the PDF. Accurate to the millimetre, with crop marks, several images on one sheet.')}

{stats.map((d) => (
{d.value} {d.label}

{d.hint}

))}

{t('The problem')}

{t('The usual route runs through a layout program or through the printer driver, and both of them mean too well: “fit to page”, “scale to fit”, “scale borderless”. What comes out is a print that is almost right. For a passport photo, “almost” is too little, and using a whole A4 sheet for a 4 × 3 snapshot, only because it would otherwise not fit on the page at 100% size, is plain waste.')}

{t('Klarbild Print computes in millimetres instead, packs as many images onto a sheet as will go, and puts crop marks where the cut is made. What comes out of the printer has the size you entered.')}

{t('One sheet, computed')}

{t('One 13 × 18, one 9 × 13 and six passport photos — on a single A4 sheet, all at 100% size, with corner marks for cutting. This drawing is not a sketch: it comes from the same packer that produces the PDF.')}

{samplePage?.placements.map((pl) => ( {shortSize(pl.w, pl.h)} ))} {sampleMarks.map((l) => ( ))}
  • 1 × 13 × 18 cm
  • 1 × 9 × 13 cm
  • 6 × 35 × 45 mm
  • {t('{pieces} pieces on {sheets} sheets · 5 mm margin · 8 mm gap', { pieces: samplePieces, sheets: samplePlan.pages.length })}

{t('What it can do')}

{features.map((k) => (

{k.kicker}

{k.title}

{k.text}

{k.proof}

))}

{t('In four steps')}

    {steps.map((s) => (
  1. {s.n}{s.title}

    {s.text}

  2. ))}

{t('Under the hood')}

{t('Unit')}
px = mm / 25.4 × dpi), PDF points via mm / 25.4 × 72.')} />
{t('Compute core')}
{t('Dependency-free TypeScript. The browser preview and the server PDF use literally the same code — what you see is what gets printed.')}
{t('Packing')}
{t('An exact grid for equal sizes, MaxRects (best short side fit) for mixed ones. Scored by fewest sheets, fullest first page, fewest rotations.')}
{t('Image')}
{t('sharp in a single chain: EXIF rotation, crop, scaling, border. The crop is stored relatively (0…1) and is therefore independent of resolution.')}
{t('PDF')}
{t('pdf-lib. Page size exactly the sheet size, images placed 1 : 1, crop marks as vector lines, glue flaps dashed.')}
{t('Proof')}
{t('Automated tests for the geometry plus a raster sample of the finished PDFs at 254 dpi (= 10 px/mm) with edge measurement.')}

{t('Run it yourself')}

{t('Klarbild runs on a server of your own: Astro with Node, PostgreSQL, a queue in the same process, files locally or on S3. The print module needs no external service at all — no model, no key, no images that leave the house. If all you want is printing, the AI parts never have to be configured.')}

{t('This page is a tour, not a user interface — nothing can be uploaded and nothing generated here.')}

{t('Klarbild · Print module')}