Native Kotlin/Compose app met CameraX + OpenCV randdetectie/perspectiefcorrectie, multi-page PDF-export, en Paperless-ngx upload (of Android deelmenu). Alles lokaal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
2.4 KiB
Kotlin
61 lines
2.4 KiB
Kotlin
package eu.geyskens.pdfscan.pdf
|
|
|
|
import android.graphics.Bitmap
|
|
import android.graphics.Canvas
|
|
import android.graphics.Color
|
|
import android.graphics.Paint
|
|
import android.graphics.Rect
|
|
import android.graphics.pdf.PdfDocument
|
|
import java.io.File
|
|
import java.io.FileOutputStream
|
|
|
|
/** How each PDF page is sized relative to its scanned image. */
|
|
enum class PageSizeMode { A4, ORIGINAL }
|
|
|
|
object PdfBuilder {
|
|
|
|
// A4 in PostScript points (1/72"): 210 x 297 mm.
|
|
private const val A4_WIDTH_PT = 595
|
|
private const val A4_HEIGHT_PT = 842
|
|
|
|
/**
|
|
* Writes [pages] into a single multi-page PDF at [output].
|
|
* With [PageSizeMode.A4] each image is centered and scaled to fit an A4 page
|
|
* (portrait or landscape chosen per image); with ORIGINAL the page matches the image.
|
|
*/
|
|
fun build(pages: List<Bitmap>, output: File, mode: PageSizeMode = PageSizeMode.A4) {
|
|
require(pages.isNotEmpty()) { "Cannot build a PDF with no pages." }
|
|
val doc = PdfDocument()
|
|
val paint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG)
|
|
try {
|
|
pages.forEachIndexed { index, bitmap ->
|
|
val (pageW, pageH) = when (mode) {
|
|
PageSizeMode.ORIGINAL -> bitmap.width to bitmap.height
|
|
PageSizeMode.A4 ->
|
|
if (bitmap.width >= bitmap.height) A4_HEIGHT_PT to A4_WIDTH_PT // landscape
|
|
else A4_WIDTH_PT to A4_HEIGHT_PT
|
|
}
|
|
val info = PdfDocument.PageInfo.Builder(pageW, pageH, index + 1).create()
|
|
val page = doc.startPage(info)
|
|
val canvas = page.canvas
|
|
canvas.drawColor(Color.WHITE)
|
|
canvas.drawBitmap(bitmap, null, fitRect(bitmap, pageW, pageH), paint)
|
|
doc.finishPage(page)
|
|
}
|
|
FileOutputStream(output).use { doc.writeTo(it) }
|
|
} finally {
|
|
doc.close()
|
|
}
|
|
}
|
|
|
|
/** Largest centered rect inside the page that preserves the bitmap's aspect ratio. */
|
|
private fun fitRect(bitmap: Bitmap, pageW: Int, pageH: Int): Rect {
|
|
val scale = minOf(pageW.toFloat() / bitmap.width, pageH.toFloat() / bitmap.height)
|
|
val w = (bitmap.width * scale).toInt()
|
|
val h = (bitmap.height * scale).toInt()
|
|
val left = (pageW - w) / 2
|
|
val top = (pageH - h) / 2
|
|
return Rect(left, top, left + w, top + h)
|
|
}
|
|
}
|