Initial commit: PaperScan — lokale Android documentscanner voor Paperless-ngx
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>
This commit is contained in:
60
app/src/main/java/eu/geyskens/pdfscan/pdf/PdfBuilder.kt
Normal file
60
app/src/main/java/eu/geyskens/pdfscan/pdf/PdfBuilder.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user