diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt index 5fa18b0..b8b6d8a 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt @@ -7,6 +7,8 @@ import org.springframework.stereotype.Service import java.awt.Color import java.awt.Font +import java.awt.Graphics2D +import java.awt.RenderingHints import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.util.zip.ZipEntry @@ -20,6 +22,9 @@ import java.io.PrintWriter import java.io.DataOutputStream import java.nio.charset.Charset +// 1. Data class to store both the raw bytes and the width for XML injection +data class BitmapResult(val bytes: ByteArray, val width: Int) + @Service open class PlasticBagPrinterService( val jobOrderRepository: JobOrderRepository, @@ -28,65 +33,105 @@ open class PlasticBagPrinterService( val baos = ByteArrayOutputStream() ZipOutputStream(baos).use { zos -> - // Use unique names based on the Lot Number + // Generate Bitmaps and capture their widths + val productBmp = createMonochromeBitmap(productName, 704) + val codeBmp = createMonochromeBitmap(itemCode, 1173) + val expBmp = createMonochromeBitmap(expiryDate, 1173) + val qrBmp = createQrCodeBitmap("$itemCode|$lotNo|$expiryDate", 1000) + val nameFile = "${lotNo}_product.bmp" + val codeFile = "${lotNo}_code.bmp" val expFile = "${lotNo}_expiry.bmp" val qrFile = "${lotNo}_qr.bmp" - // 1. Generate Bitmaps - addToZip(zos, nameFile, createMonochromeBitmap(productName, 5365, 704)) - addToZip(zos, expFile, createMonochromeBitmap(expiryDate, 4203, 1173)) - addToZip(zos, qrFile, createQrCodeBitmap("$itemCode|$lotNo|$expiryDate", 1000)) + // Now "bytes" property is resolved + addToZip(zos, nameFile, productBmp.bytes) + addToZip(zos, codeFile, codeBmp.bytes) + addToZip(zos, expFile, expBmp.bytes) + addToZip(zos, qrFile, qrBmp.bytes) - // 2. Generate the .image file with dynamic references + // 2. Generate the .image file with XML headers and mandatory X40 tags val imageXml = """ + + + false + DEG_0 + 53007100 - - PRODUCT_NAME + + LOGO 0250 + BLACK $nameFile + ${productBmp.width} + 704 - - EXPIRY_DATE + + LOGO_2 + 10001250 + BLACK + $codeFile + ${codeBmp.width} + 1173 + + + LOGO_3 5002500 + BLACK $expFile + ${expBmp.width} + 1173 - - QR_CODE + + LOGO_4 7503750 + BLACK $qrFile + ${qrBmp.width} + 1000 """.trimIndent() - addToZip(zos, "$lotNo.image", imageXml.toByteArray()) + addToZip(zos, "$lotNo.image", imageXml.toByteArray(Charsets.UTF_8)) - // 3. Generate the .job file pointing to the new .image [cite: 2] - val jobXml = "$lotNo.image" - addToZip(zos, "$lotNo.job", jobXml.toByteArray()) + val jobXml = """$lotNo.image""" + addToZip(zos, "$lotNo.job", jobXml.toByteArray(Charsets.UTF_8)) } return baos.toByteArray() } - private fun createMonochromeBitmap(text: String, width: Int, height: Int): ByteArray { - val image = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY) // Essential for printers - val g = image.createGraphics() - g.color = Color.WHITE - g.fillRect(0, 0, width, height) - g.color = Color.BLACK - g.font = Font("Arial", Font.BOLD, 400) - g.drawString(text, 50, height - 200) - g.dispose() + private fun createMonochromeBitmap(text: String, maxHeight: Int): BitmapResult { + val tempImg = BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY) + val g2dTemp = tempImg.createGraphics() + val font = Font("SimSun", Font.BOLD, (maxHeight * 0.8).toInt()) + g2dTemp.font = font + val metrics = g2dTemp.getFontMetrics(font) + + val width = metrics.stringWidth(text).let { if (it < 1) 1 else it } + val height = maxHeight + g2dTemp.dispose() + + val finalImg = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY) + val g2d = finalImg.createGraphics() + g2d.color = Color.WHITE + g2d.fillRect(0, 0, width, height) + g2d.color = Color.BLACK + g2d.font = font + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF) + g2d.drawString(text, 0, metrics.ascent) + g2d.dispose() val baos = ByteArrayOutputStream() - ImageIO.write(image, "bmp", baos) - return baos.toByteArray() + ImageIO.write(finalImg, "bmp", baos) + return BitmapResult(baos.toByteArray(), width) } - private fun createQrCodeBitmap(content: String, size: Int): ByteArray { - val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size) + private fun createQrCodeBitmap(content: String, size: Int): BitmapResult { + val writer = QRCodeWriter() + val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, size, size) val image = BufferedImage(size, size, BufferedImage.TYPE_BYTE_BINARY) for (x in 0 until size) { for (y in 0 until size) { @@ -95,11 +140,13 @@ open class PlasticBagPrinterService( } val baos = ByteArrayOutputStream() ImageIO.write(image, "bmp", baos) - return baos.toByteArray() + // For QR code, width and height are the same (size) + return BitmapResult(baos.toByteArray(), size) } - private fun addToZip(zos: ZipOutputStream, fileName: String, content: ByteArray) { - zos.putNextEntry(ZipEntry(fileName)) + private fun addToZip(zos: ZipOutputStream, entryName: String, content: ByteArray) { + val entry = ZipEntry(entryName) + zos.putNextEntry(entry) zos.write(content) zos.closeEntry() }