package com.peachpasswords.codex import android.graphics.Bitmap import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.graphics.pdf.PdfDocument import java.io.ByteArrayOutputStream /** * Renders the Aztec codes onto an A4 PDF in a 6×6 grid — the printed Codex. * A4 at 72pt/in is 595×842pt. Header carries the verify code; the footer * carries the generation date and page counter so a re-collated stack stays * unambiguous. */ object CodexPdf { private const val PAGE_W = 595 private const val PAGE_H = 842 private const val MARGIN = 36f private const val HEADER_H = 54f private const val FOOTER_H = 28f fun render(aztecs: List, manifest: CodexManifest): ByteArray { val doc = PdfDocument() val gridTop = MARGIN + HEADER_H val gridW = PAGE_W - 2 * MARGIN val gridH = PAGE_H - gridTop - MARGIN - FOOTER_H val cellW = gridW / CODES_PER_ROW val cellH = gridH / CODES_PER_COL val cell = minOf(cellW, cellH) // Tighter padding → larger codes (~78pt, close to the browser's 80pt), // which keeps modules above the camera/scan decode threshold. val pad = cell * 0.05f val title = Paint().apply { color = Color.BLACK; textSize = 16f; isAntiAlias = true; isFakeBoldText = true } val mono = Paint().apply { color = Color.rgb(0x55, 0x55, 0x55); textSize = 9f; isAntiAlias = true } val codePaint = Paint().apply { isFilterBitmap = false } val pageCount = Math.ceil(aztecs.size.toDouble() / CODES_PER_PAGE).toInt().coerceAtLeast(1) for (page in 0 until pageCount) { val info = PdfDocument.PageInfo.Builder(PAGE_W, PAGE_H, page + 1).create() val pg = doc.startPage(info) val canvas = pg.canvas canvas.drawText("Peach Codex", MARGIN, MARGIN + 18f, title) canvas.drawText(manifest.verifyCode, MARGIN, MARGIN + 36f, mono) canvas.drawText( "${manifest.entryCount} entries · ${manifest.totalChunks} codes · keep with your recovery words", MARGIN + 120f, MARGIN + 36f, mono, ) val first = page * CODES_PER_PAGE val last = minOf(first + CODES_PER_PAGE, aztecs.size) for (idx in first until last) { val slot = idx - first val row = slot / CODES_PER_ROW val col = slot % CODES_PER_ROW val left = MARGIN + col * cell + pad val top = gridTop + row * cell + pad val dst = Rect(left.toInt(), top.toInt(), (left + cell - 2 * pad).toInt(), (top + cell - 2 * pad).toInt()) canvas.drawBitmap(aztecs[idx], null, dst, codePaint) } canvas.drawText( "page ${page + 1} of $pageCount · ${manifest.verifyCode}", MARGIN, PAGE_H - MARGIN + 4f, mono, ) doc.finishPage(pg) } val out = ByteArrayOutputStream() doc.writeTo(out) doc.close() return out.toByteArray() } }