| @@ -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 = """ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <Legend type="Image.V1" PaperColor="White" BackgroundColor="DarkGray"> | |||
| <DeviceInfo DeviceURI="NGEDriver[SDX40c.cadb94e3-677c-4bcb-9c80-88bf9526d6fe]/"><DeviceName /></DeviceInfo> | |||
| <Mirrored>false</Mirrored> | |||
| <Orientation>DEG_0</Orientation> | |||
| <Width>5300</Width><Height>7100</Height> | |||
| <FieldList> | |||
| <Logo> | |||
| <Name>PRODUCT_NAME</Name> | |||
| <Logo type="Logo.V1"> | |||
| <Name>LOGO</Name> | |||
| <Geometry><X>0</X><Y>250</Y></Geometry> | |||
| <FieldColor>BLACK</FieldColor> | |||
| <FileName>$nameFile</FileName> | |||
| <Width>${productBmp.width}</Width> | |||
| <Height>704</Height> | |||
| </Logo> | |||
| <Logo> | |||
| <Name>EXPIRY_DATE</Name> | |||
| <Logo type="Logo.V1"> | |||
| <Name>LOGO_2</Name> | |||
| <Geometry><X>1000</X><Y>1250</Y></Geometry> | |||
| <FieldColor>BLACK</FieldColor> | |||
| <FileName>$codeFile</FileName> | |||
| <Width>${codeBmp.width}</Width> | |||
| <Height>1173</Height> | |||
| </Logo> | |||
| <Logo type="Logo.V1"> | |||
| <Name>LOGO_3</Name> | |||
| <Geometry><X>500</X><Y>2500</Y></Geometry> | |||
| <FieldColor>BLACK</FieldColor> | |||
| <FileName>$expFile</FileName> | |||
| <Width>${expBmp.width}</Width> | |||
| <Height>1173</Height> | |||
| </Logo> | |||
| <Logo> | |||
| <Name>QR_CODE</Name> | |||
| <Logo type="Logo.V1"> | |||
| <Name>LOGO_4</Name> | |||
| <Geometry><X>750</X><Y>3750</Y></Geometry> | |||
| <FieldColor>BLACK</FieldColor> | |||
| <FileName>$qrFile</FileName> | |||
| <Width>${qrBmp.width}</Width> | |||
| <Height>1000</Height> | |||
| </Logo> | |||
| </FieldList> | |||
| </Legend> | |||
| """.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 = "<Job><ImageFileName>$lotNo.image</ImageFileName></Job>" | |||
| addToZip(zos, "$lotNo.job", jobXml.toByteArray()) | |||
| val jobXml = """<?xml version="1.0" encoding="utf-8"?><Job><ImageFileName>$lotNo.image</ImageFileName></Job>""" | |||
| 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() | |||
| } | |||