diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/base/BaseImagePrint.java b/src/main/java/com/dpkj/modules/autoReplyPrint/base/BaseImagePrint.java new file mode 100644 index 0000000..ea9a96e --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/base/BaseImagePrint.java @@ -0,0 +1,89 @@ +package com.dpkj.modules.autoReplyPrint.base; + +import com.dpkj.common.exception.RRException; +import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint; +import com.dpkj.modules.autoReplyPrint.utils.ImageUtils; +import com.sun.jna.Pointer; +import org.springframework.web.multipart.MultipartFile; + + +/** + * 图片打印基础实现 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-17 14:37:23 + */ +public abstract class BaseImagePrint { + + /** + * 必须要实现这个,获取句柄 + * @param devName 设备名称/串口名称 + * @return 窗口句柄 + */ + public abstract Pointer getHandle(String devName); + + + public void printFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + Pointer handle = getHandle(devName); + try { + // 开始打印图片 + boolean printTag = AutoReplyPrint.INSTANCE.CP_Pos_PrintRasterImageFromFile(handle, dstw, dsth, pszFile, binaryzation_method, compression_method); + if( !printTag ){ + throw new RRException("打印图片失败"); + } + + // 切纸 + AutoReplyPrint.INSTANCE.CP_BlackMark_SetBlackMarkPaperCutPosition(handle, 0); + boolean cutPaper = AutoReplyPrint.INSTANCE.CP_Pos_FeedAndHalfCutPaper(handle); + if ( !cutPaper){ + throw new RRException("图片裁剪失败,请自行裁剪"); + } + }catch (Exception e){ + e.printStackTrace(); + throw new RRException(e); + }finally { + if ( handle != null) AutoReplyPrint.INSTANCE.CP_Port_Close(handle); + } + } + + + public void printFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + // 获取图片 + byte[] data = ImageUtils.imageToBytes(pszFile); + this.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method); + } + + + public void printFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) { + Pointer handle = getHandle(devName); + try { + // 开始打印图片 + boolean printTag = AutoReplyPrint.INSTANCE.CP_Pos_PrintRasterImageFromData(handle, dstw, dsth, data, data.length, binaryzation_method, compression_method); + if( !printTag ){ + throw new RRException("打印图片失败"); + } + + // 切纸 + AutoReplyPrint.INSTANCE.CP_BlackMark_SetBlackMarkPaperCutPosition(handle, 0); + boolean cutPaper = AutoReplyPrint.INSTANCE.CP_Pos_FeedAndHalfCutPaper(handle); + if ( !cutPaper){ + throw new RRException("图片裁剪失败,请自行裁剪"); + } + }catch (Exception e){ + e.printStackTrace(); + throw new RRException(e); + }finally { + if ( handle != null) AutoReplyPrint.INSTANCE.CP_Port_Close(handle); + } + } + + + public void printFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method){ + byte[] data = ImageUtils.getByteToMultipartFile(file); + this.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method); + } + + +} + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/controller/ImagePrintController.java b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/ImagePrintController.java new file mode 100644 index 0000000..9c97d2e --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/ImagePrintController.java @@ -0,0 +1,78 @@ +package com.dpkj.modules.autoReplyPrint.controller; + +import com.dpkj.common.vo.Result; +import com.dpkj.modules.autoReplyPrint.service.ImagePrintService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +/** + * 图片打印控制层 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-17 13:57:11 + */ +@RequestMapping("/autoReplyPrint") +public class ImagePrintController { + + @Resource(name = "USBImagePrint") + private ImagePrintService usbImagePrintService; + + /** + * 图片打印,通过路径 + */ + @GetMapping("/usb/imagePrint/path") + Result usbImagePrintFromPath(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName, + @RequestParam(defaultValue = "500") Integer width, + @RequestParam(defaultValue = "500") Integer height, + @RequestParam String path, Integer binaryMethod, Integer compressionMethod){ + this.usbImagePrintService.imagePrintFromPath(usbName, width, height, path, binaryMethod, compressionMethod); + return Result.ok("图片打印成功"); + } + + + /** + * 图片打印,通过byte[] + */ + @GetMapping("/usb/imagePrint/pathData") + Result usbImagePrintFromData(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName, + @RequestParam(defaultValue = "500") Integer width, + @RequestParam(defaultValue = "500") Integer height, + @RequestParam String path, Integer binaryMethod, Integer compressionMethod){ + this.usbImagePrintService.imagePrintFromPathData(usbName, width, height, path, binaryMethod, compressionMethod); + return Result.ok("图片打印成功"); + } + + + /** + * 图片打印,通过multipartFile文件 + */ + @GetMapping("/usb/imagePrint/multipartFile") + Result usbImagePrintFromData(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName, + @RequestParam(defaultValue = "500") Integer width, + @RequestParam(defaultValue = "500") Integer height, + @RequestParam MultipartFile file, Integer binaryMethod, Integer compressionMethod){ + this.usbImagePrintService.imagePrintFromMultipartFile(usbName, width, height, file, binaryMethod, compressionMethod); + return Result.ok("图片打印成功"); + } + + + /** + * 图片打印,通过byte[] + */ + @GetMapping("/usb/imagePrint/data") + Result usbImagePrintFromData(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName, + @RequestParam(defaultValue = "500") Integer width, + @RequestParam(defaultValue = "500") Integer height, + @RequestParam byte[] data, Integer binaryMethod, Integer compressionMethod){ + this.usbImagePrintService.imagePrintFromData(usbName, width, height, data, binaryMethod, compressionMethod); + return Result.ok("图片打印成功"); + } + + +} + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java new file mode 100644 index 0000000..509f16d --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java @@ -0,0 +1,54 @@ +package com.dpkj.modules.autoReplyPrint.controller; + +import com.alibaba.fastjson.JSONObject; +import com.dpkj.common.vo.Result; +import com.dpkj.modules.autoReplyPrint.service.impl.TemplateService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 打印模板控制层 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-17 15:25:03 + */ +@RestController +@RequestMapping("/autoReplyPrint") +public class TemplateController { + + @Resource + private TemplateService templateService; + + + @GetMapping("/template/{templateName}") + public Result testTemplate(@RequestParam String jsonData, + @RequestParam Integer width, + @RequestParam Integer height, + @PathVariable String templateName, + @RequestParam(defaultValue = "E:\\images") String saveDir) { + this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), templateName, width, height, saveDir); + return Result.ok("模板生成成功"); + } + + @GetMapping("/template") + public Result testTemplate(@RequestParam String jsonData, + @RequestParam Integer width, + @RequestParam Integer height, @RequestParam(defaultValue = "E:\\images") String saveDir) { + String html = "\n" + + "\n" + + "Test\n" + + "\n" + + "

你好,我是一个好的模板

\n" + + "

\n" + + "\n" + + ""; + this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), html, width, height, saveDir); + return Result.ok("模板生成成功"); + } + +} + + + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/ImagePrintService.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/ImagePrintService.java new file mode 100644 index 0000000..e823ceb --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/ImagePrintService.java @@ -0,0 +1,59 @@ +package com.dpkj.modules.autoReplyPrint.service; + +import org.springframework.web.multipart.MultipartFile; + +/** + * 图片打印服务接口 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-17 11:38:36 + */ +public interface ImagePrintService { + + /** + * 图片打印 + * @param devName 设备名称/usb/tcp/com + * @param dstw 要打印的宽度 + * @param dsth 要打印的高度 + * @param pszFile 图片的路径 + * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。 + * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。 + */ + void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method); + + /** + * 图片打印,内部直接读取转换为byte进行打印 + * @param devName 设备名称/usb/tcp/com + * @param dstw 要打印的宽度 + * @param dsth 要打印的高度 + * @param pszFile 图片的路径 + * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。 + * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。 + */ + void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method); + + /** + * 直接传输数据进行打印 + * @param devName 设备名称/usb/tcp/com + * @param dstw 要打印的宽度 + * @param dsth 要打印的高度 + * @param data 图片数据 + * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。 + * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。 + */ + void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method); + + /** + * 直接传的MultipartFile文件过来 + * @param devName 设备名称/usb/tcp/com + * @param dstw 要打印的宽度 + * @param dsth 要打印的高度 + * @param file 图片数据 + * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。 + * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。 + */ + void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method); + + +} diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/COMImagePrintServiceImpl.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/COMImagePrintServiceImpl.java new file mode 100644 index 0000000..1b2f776 --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/COMImagePrintServiceImpl.java @@ -0,0 +1,59 @@ +package com.dpkj.modules.autoReplyPrint.service.impl; + +import com.dpkj.common.exception.RRException; +import com.dpkj.modules.autoReplyPrint.base.BaseImagePrint; +import com.dpkj.modules.autoReplyPrint.service.ImagePrintService; +import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint; +import com.sun.jna.Pointer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * 串口图片打印 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-20 14:55:37 + */ +@Service("COMImagePrint") +@Slf4j +public class COMImagePrintServiceImpl extends BaseImagePrint implements ImagePrintService { + + @Override + public Pointer getHandle(String devName) { + try { + Pointer pointer = AutoReplyPrint.INSTANCE.CP_Port_OpenLpt(devName); + if ( pointer == null ){ + throw new RRException(); + } + return pointer; + }catch (Exception e){ + e.printStackTrace(); + throw new RRException("获取COM串口句柄失败"); + } + } + + @Override + public void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + super.printFromPath(devName, dstw, dsth, pszFile, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + super.printFromPathData(devName, dstw, dsth, pszFile, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) { + super.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method) { + super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method); + } + + +} + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TCPImagePrintServiceImpl.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TCPImagePrintServiceImpl.java new file mode 100644 index 0000000..cf00081 --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TCPImagePrintServiceImpl.java @@ -0,0 +1,63 @@ +package com.dpkj.modules.autoReplyPrint.service.impl; + +import com.dpkj.common.exception.RRException; +import com.dpkj.modules.autoReplyPrint.base.BaseImagePrint; +import com.dpkj.modules.autoReplyPrint.service.ImagePrintService; +import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint; +import com.sun.jna.Pointer; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * tcp类型句柄的图片打印 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-20 14:46:06 + */ +@Data +@Service("TCPImagePrint") +@Slf4j +public class TCPImagePrintServiceImpl extends BaseImagePrint implements ImagePrintService { + + private String destIP; // 目标ip + private Short destPort; // 目标端口 + + @Override + public Pointer getHandle(String devName) { + try { + Pointer pointer = AutoReplyPrint.INSTANCE.CP_Port_OpenTcp("0", destIP, destPort, 300000, 1); + if ( pointer == null ){ + throw new RRException(); + } + return pointer; + }catch (Exception e){ + e.printStackTrace(); + throw new RRException("获取TCP句柄失败"); + } + } + + @Override + public void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + super.printFromPath(devName, dstw, dsth, pszFile, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + super.printFromPathData(devName, dstw, dsth, pszFile, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) { + super.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method) { + super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method); + } + +} + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java new file mode 100644 index 0000000..834f51f --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java @@ -0,0 +1,334 @@ +package com.dpkj.modules.autoReplyPrint.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.dpkj.common.exception.RRException; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import lombok.extern.slf4j.Slf4j; +import org.jsoup.Jsoup; +import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.StringTemplateResolver; +import org.w3c.dom.Document; +import org.xhtmlrenderer.layout.SharedContext; +import org.xhtmlrenderer.simple.Graphics2DRenderer; + +import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.thymeleaf.context.Context; + +/** + * 模板服务类 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-18 15:33:51 + */ +@Service +@Slf4j +public class TemplateService { + + + /** + * 生成小票图片 + * + * @param data json数据,用来填充模板 + * @param template 模板(html字符串或者模板名称) + * @param width 图片宽度 + * @param height 图片高度 + * @param saveDir 图片的保存路径,如果为空,那么不进行图片的保存 + * @return 图片字节数组 + */ + public byte[] generateReceiptImage(JSONObject data, String template, int width, int height, String saveDir) { + try { + // 获取模板上下文 + Context context = this.getContext(data); + + TemplateEngine templateEngine = new TemplateEngine(); + if (checkIsHtml(template)) { + StringTemplateResolver resolver = new StringTemplateResolver(); + resolver.setTemplateMode("HTML"); + // 设置模板引擎使用这个自定义的模板解析器 + templateEngine.setTemplateResolver(resolver); + }else { + SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); + resolver.setPrefix("classpath:/templates/"); + resolver.setSuffix(".html"); + resolver.setTemplateMode(TemplateMode.HTML); + resolver.setCacheable(true); + resolver.setApplicationContext(new AnnotationConfigReactiveWebApplicationContext()); + templateEngine.setTemplateResolver(resolver); + } + + // 渲染模板 + String html = templateEngine.process(template, context); + + BufferedImage image = this.generate(html, width, height); + + // 保存图片 + if (saveDir != null && !"".equals(saveDir)) { + String outputPath = saveDir + "\\genera_image_" + System.currentTimeMillis() + ".png"; + ImageIO.write(image, "PNG", new File(outputPath)); + } + + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + ImageIO.write(image, "PNG", byteOutputStream); + return byteOutputStream.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + throw new RRException("图片打印失败"); + } + } + + + /** + * 生成图片 + * @param html html内容 + */ + private BufferedImage generate(String html, int width, int height){ + try { + // 转换为xhtml + String xhtml = this.htmlToXhtml(html); + + // 转换为document + Document document = this.xhtmlToDocument(xhtml); + + // 生成图片 + return createImageToDocument(document, width, height); + } catch (Exception e) { + e.printStackTrace(); + throw new RRException("图片打印失败"); + } + } + + + /** + * 通过JsonObject进行context内容填充 + * @param data jsonObject + * @return context + */ + private Context getContext(JSONObject data) { + // 创建Thymeleaf上下文 + Context context = new Context(); + Set keys = data.keySet(); + for (String key : keys) { + // 判单是否有图片生成,统一后面采用的是_2base64Type + String[] split = key.split("_"); + if (split.length > 1 && split[1].equals("2base64Type")) { + int width = split.length > 2 ? Integer.parseInt(split[2]) : 100; + int height = split.length > 3 ? Integer.parseInt(split[3]) : 100; + // 如果是图片类型,需要进行base64转换 + String base64 = this.generateQRCode(String.valueOf(data.get(key)), width, height); + context.setVariable(split[0], "data:image/jpeg;base64," + base64); + } else { + // 普通字段直接设置 + context.setVariable(key, data.get(key)); + } + } + + return context; + } + + + /** + * 根据内容生成二维码 + * @param content 转换内容 + */ + private String generateQRCode(String content, int width, int height) { + try { + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符编码为 UTF-8 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 设置纠错级别 + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints); + + BufferedImage qrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + qrImage.createGraphics(); + + Graphics2D graphics = (Graphics2D) qrImage.getGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, width, height); + graphics.setColor(Color.BLACK); + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + if (bitMatrix.get(i, j)) { + graphics.fillRect(i, j, 1, 1); + } + } + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(qrImage, "png", baos); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + }catch (Exception e){ + log.error("二维码生成失败"); + throw new RRException("二维码生成失败"); + } + } + + + /** + * xhtml 转换为 Document + * @param xhtml xhtml + * @return document + * @throws Exception e + */ + private Document xhtmlToDocument(String xhtml) throws Exception { + // 创建DocumentBuilder + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + DocumentBuilder builder = factory.newDocumentBuilder(); + + // 解析XHTML字符串为Document + return builder.parse(new ByteArrayInputStream(xhtml.getBytes(StandardCharsets.UTF_8))); + } + + + /** + * 转换,将html转换为xhtml + * @param html html内容 + * @return xhtml + */ + private String htmlToXhtml(String html) { + org.jsoup.nodes.Document doc = Jsoup.parse(html); + doc.outputSettings() + .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml) + .charset(StandardCharsets.UTF_8); + + // 不使用外部DTD + return "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + doc.body().html() + + "\n\n" + + ""; + } + + + /** + * 通过document转换为图片 + * @param document doc + * @param width 图片的宽度 + * @param height 图片的高度 + * @return bufferedImage + */ + private BufferedImage createImageToDocument(Document document, int width, int height) { + try { + // 创建Dimension对象 + Dimension dimension = new Dimension(width, height); + + // 创建图片 + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + Graphics2D graphics = (Graphics2D) image.getGraphics(); + + // 设置白色背景 + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, width, height); + + // 设置渲染提示 + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + // 创建渲染器 + Graphics2DRenderer renderer = new Graphics2DRenderer(); + renderer.setDocument(document, null); + + // 设置渲染参数 + SharedContext sharedContext = renderer.getSharedContext(); + sharedContext.setInteractive(false); + sharedContext.setDPI(256f); + + // 使用系统默认字体,假设系统默认字体支持UTF - 8 + Font font = new Font(Font.DIALOG, Font.PLAIN, 20); + graphics.setFont(font); + + // 使用Dimension对象进行布局 + renderer.layout(graphics, dimension); + renderer.render(graphics); + + graphics.dispose(); + return image; + } catch (Exception e) { + throw new RRException("渲染图片失败", e); + } + } + + + /** + * 校验这个模板内容是不是html字符串,而非模板名称 + * @param template template + * @return 是否是html字符串 + */ + private boolean checkIsHtml(String template){ + try { + String pattern = "<(\"[^\"]*\"|'[^']*'|[^'\">])*>"; + Pattern r = Pattern.compile(pattern); + return r.matcher(template).find(); + }catch (Exception e) { + return false; + } + } + + + /** + * 将document对象转换为字符串 + * @param doc document + * @return document转换为的字符串 + */ + private String documentToString(Document doc) { + try { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + return writer.getBuffer().toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + +} + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/USBImagePrintServiceImpl.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/USBImagePrintServiceImpl.java new file mode 100644 index 0000000..b9f2038 --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/USBImagePrintServiceImpl.java @@ -0,0 +1,60 @@ +package com.dpkj.modules.autoReplyPrint.service.impl; + +import com.dpkj.common.exception.RRException; +import com.dpkj.modules.autoReplyPrint.base.BaseImagePrint; +import com.dpkj.modules.autoReplyPrint.service.ImagePrintService; +import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint; +import com.sun.jna.Pointer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + + +/** + * 图片打印服务 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-17 11:38:36 + */ +@Slf4j +@Service("USBImagePrint") +public class USBImagePrintServiceImpl extends BaseImagePrint implements ImagePrintService { + + @Override + public Pointer getHandle(String devName) { + try { + Pointer pointer = AutoReplyPrint.INSTANCE.CP_Port_OpenUsb(devName, 1); + if ( pointer == null ){ + throw new RRException(); + } + return pointer; + }catch (Exception e){ + e.printStackTrace(); + throw new RRException("获取USB句柄失败"); + } + } + + @Override + public void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + super.printFromPath(devName, dstw, dsth, pszFile, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) { + super.printFromPathData(devName, dstw, dsth, pszFile, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) { + super.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method); + } + + @Override + public void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method) { + super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method); + } + + +} + diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/utils/ImageUtils.java b/src/main/java/com/dpkj/modules/autoReplyPrint/utils/ImageUtils.java new file mode 100644 index 0000000..f358ea2 --- /dev/null +++ b/src/main/java/com/dpkj/modules/autoReplyPrint/utils/ImageUtils.java @@ -0,0 +1,85 @@ +package com.dpkj.modules.autoReplyPrint.utils; + +import com.dpkj.common.exception.RRException; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +/** + * 图片转换工具 + * + * @author 石头人 + * @version 1.0 + * @since 2025-01-17 14:06:09 + */ +public class ImageUtils { + + /** + * 读取图片并转换为byte数组 + * @param imagePath 图片路径 + * @return byte数组 + */ + public static byte[] imageToBytes(String imagePath) { + try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(Paths.get(imagePath))); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = bis.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + + return baos.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + throw new RRException("读取图片信息失败"); + } + } + + + /** + * 获取文件byte[],通过 MultipartFile + * @param file 文件信息 + * @return byte数组 + */ + public static byte[] getByteToMultipartFile(MultipartFile file){ + // 验证文件是否为空 + if (file.isEmpty()) { + throw new RRException("文件内容为空"); + } + + // 验证文件类型 + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + throw new RRException("只支持图片文件"); + } + + // 获取文件扩展名 + String fileName = file.getOriginalFilename(); + String fileExtension = fileName != null ? + fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : ""; + + // 验证扩展名 + List allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif"); + if (!allowedExtensions.contains(fileExtension)) { + throw new RRException("不支持的文件格式"); + } + + try { + // 转换为字节数组 + return file.getBytes(); + }catch (Exception e){ + e.printStackTrace(); + throw new RRException("获取文件字节失败"); + } + } + + + +} +