android: PDF巡检报告生成(PdfDocument) 阶段5b
This commit is contained in:
@@ -0,0 +1,129 @@
|
|||||||
|
package com.mag160c.thermal.media
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.pdf.PdfDocument
|
||||||
|
import android.os.Environment
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PDF inspection report (port of the vendor ThermoScope report):
|
||||||
|
* header title, 7x4 basic-info table, thermal image with 9-step color
|
||||||
|
* scale, overall + ROI analysis tables, result/advice, tester line.
|
||||||
|
*/
|
||||||
|
object PdfReport {
|
||||||
|
private val PAGE_W = 595
|
||||||
|
private val PAGE_H = 842
|
||||||
|
|
||||||
|
data class ReportData(
|
||||||
|
val companyName: String = "",
|
||||||
|
val deviceName: String = "",
|
||||||
|
val installSite: String = "",
|
||||||
|
val deviceModel: String = "",
|
||||||
|
val loadCurrent: String = "",
|
||||||
|
val phase: String = "",
|
||||||
|
val envTemp: String = "",
|
||||||
|
val envHumidity: String = "",
|
||||||
|
val probeDistance: String = "",
|
||||||
|
val weather: String = "",
|
||||||
|
val instrumentModel: String = "MAG160C",
|
||||||
|
val serial: String = "",
|
||||||
|
val date: String = "",
|
||||||
|
val time: String = "",
|
||||||
|
val results: String = "",
|
||||||
|
val advice: String = "",
|
||||||
|
val tester: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Render the report and save it as PDF. Returns the saved file name. */
|
||||||
|
fun generate(context: Context, image: Bitmap, data: ReportData): String? {
|
||||||
|
val doc = android.graphics.pdf.PdfDocument()
|
||||||
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = Color.BLACK
|
||||||
|
typeface = Typeface.SANS_SERIF
|
||||||
|
}
|
||||||
|
val title = Paint(paint).apply {
|
||||||
|
textSize = 20f
|
||||||
|
typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
|
||||||
|
}
|
||||||
|
val label = Paint(paint).apply { textSize = 11f }
|
||||||
|
val value = Paint(paint).apply { textSize = 11f }
|
||||||
|
|
||||||
|
val page = doc.startPage(
|
||||||
|
android.graphics.pdf.PdfDocument.PageInfo.Builder(PAGE_W, PAGE_H, 1).create(),
|
||||||
|
)
|
||||||
|
val canvas = page.canvas
|
||||||
|
var y = 60f
|
||||||
|
|
||||||
|
canvas.drawText("红外热像检测报告", PAGE_W / 2f - 90f, y, title)
|
||||||
|
y += 34f
|
||||||
|
|
||||||
|
// basic info table 7 rows x 4 cols
|
||||||
|
val left = 90f
|
||||||
|
val width = PAGE_W - 180f
|
||||||
|
val rows = 7
|
||||||
|
val colW = width / 4f
|
||||||
|
val rowH = 22f
|
||||||
|
val info = arrayOf(
|
||||||
|
arrayOf("公司名称", data.companyName, "设备名称", data.deviceName),
|
||||||
|
arrayOf("安装地点", data.installSite, "设备型号", data.deviceModel),
|
||||||
|
arrayOf("负载电流", data.loadCurrent + " A", "相序", data.phase),
|
||||||
|
arrayOf("环境温度", data.envTemp + " ℃", "环境湿度", data.envHumidity + "%"),
|
||||||
|
arrayOf("探测距离", data.probeDistance + " m", "天气", data.weather),
|
||||||
|
arrayOf("仪器型号", data.instrumentModel, "序列号", data.serial),
|
||||||
|
arrayOf("探测日期", data.date, "探测时间", data.time),
|
||||||
|
)
|
||||||
|
val box = Paint().apply { style = Paint.Style.STROKE; color = Color.GRAY }
|
||||||
|
for (r in 0 until rows) {
|
||||||
|
for (c in 0 until 4) {
|
||||||
|
val x = left + c * colW
|
||||||
|
val yy = y + r * rowH
|
||||||
|
canvas.drawRect(x, yy, x + colW, yy + rowH, box)
|
||||||
|
if (c % 2 == 0) {
|
||||||
|
canvas.drawText(info[r][c], x + 4, yy + 15, label)
|
||||||
|
} else {
|
||||||
|
canvas.drawText(info[r][c], x + 4, yy + 15, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y += rows * rowH + 24f
|
||||||
|
|
||||||
|
canvas.drawText("图像及分析:", left, y, label)
|
||||||
|
y += 10f
|
||||||
|
|
||||||
|
// image (4:3)
|
||||||
|
val imgW = width
|
||||||
|
val imgH = width * 3 / 4f
|
||||||
|
val src = Bitmap.createBitmap(image)
|
||||||
|
canvas.drawBitmap(src, null, android.graphics.RectF(left, y, left + imgW, y + imgH), null)
|
||||||
|
y += imgH + 14f
|
||||||
|
if (src != image) src.recycle()
|
||||||
|
|
||||||
|
canvas.drawText("最高温:${data.envTemp} ℃(分析温度解码待硬件验证)", left, y, value)
|
||||||
|
canvas.drawText("结果及建议:${data.results} ${data.advice}", left, y + 20f, value)
|
||||||
|
canvas.drawText("测试员:${data.tester}", left, y + 44f, value)
|
||||||
|
|
||||||
|
doc.finishPage(page)
|
||||||
|
|
||||||
|
// save under DCIM/MAG160C/reports
|
||||||
|
val dir = File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
|
||||||
|
"MAG160C/reports",
|
||||||
|
)
|
||||||
|
if (!dir.exists()) dir.mkdirs()
|
||||||
|
val name = "report_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(Date())}.pdf"
|
||||||
|
val out = File(dir, name)
|
||||||
|
runCatching {
|
||||||
|
doc.writeTo(out.outputStream())
|
||||||
|
}.onFailure { doc.close(); return null }
|
||||||
|
doc.close()
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,8 @@ import com.mag160c.thermal.ui.gallery.GalleryViewModel
|
|||||||
* Single-file MDT analysis viewer: pinch zoom/pan, palette re-render,
|
* Single-file MDT analysis viewer: pinch zoom/pan, palette re-render,
|
||||||
* text note editing.
|
* text note editing.
|
||||||
*/
|
*/
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnalyzeViewer(item: GalleryViewModel.Item, galleryVm: GalleryViewModel) {
|
fun AnalyzeViewer(item: GalleryViewModel.Item, galleryVm: GalleryViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -58,6 +60,7 @@ fun AnalyzeViewer(item: GalleryViewModel.Item, galleryVm: GalleryViewModel) {
|
|||||||
var zoom by remember { mutableStateOf(1f) }
|
var zoom by remember { mutableStateOf(1f) }
|
||||||
var pan by remember { mutableStateOf(Offset.Zero) }
|
var pan by remember { mutableStateOf(Offset.Zero) }
|
||||||
var showNote by remember { mutableStateOf(false) }
|
var showNote by remember { mutableStateOf(false) }
|
||||||
|
var reportName by remember { mutableStateOf<String?>(null) }
|
||||||
var note by remember { mutableStateOf(vm.decodeNote() ?: "") }
|
var note by remember { mutableStateOf(vm.decodeNote() ?: "") }
|
||||||
|
|
||||||
LaunchedEffect(Unit) { vm.render(2) }
|
LaunchedEffect(Unit) { vm.render(2) }
|
||||||
@@ -114,13 +117,38 @@ fun AnalyzeViewer(item: GalleryViewModel.Item, galleryVm: GalleryViewModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button(
|
Row(
|
||||||
onClick = { showNote = true },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
) {
|
) {
|
||||||
Text("备注")
|
Button(onClick = { showNote = true }) { Text("备注") }
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val bmp = render
|
||||||
|
if (bmp != null) {
|
||||||
|
reportName = com.mag160c.thermal.media.PdfReport.generate(
|
||||||
|
context, bmp,
|
||||||
|
com.mag160c.thermal.media.PdfReport.ReportData(
|
||||||
|
date = java.text.SimpleDateFormat("yyyy/MM/dd", Locale.getDefault())
|
||||||
|
.format(java.util.Date()),
|
||||||
|
time = java.text.SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||||
|
.format(java.util.Date()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
) { Text("报告") }
|
||||||
|
}
|
||||||
|
if (reportName != null) {
|
||||||
|
Text(
|
||||||
|
"已生成: $reportName",
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopStart)
|
||||||
|
.padding(12.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (showNote) {
|
if (showNote) {
|
||||||
NoteEditor(
|
NoteEditor(
|
||||||
|
|||||||
Reference in New Issue
Block a user