From 3c9095bff4259c617d6dd39678253dc6e0161b8e Mon Sep 17 00:00:00 2001 From: lifangliang Date: Mon, 23 Jun 2025 11:21:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=93=E5=8D=B0=E6=9C=BA?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../printserver/main/PrintController.java | 96 ++++- .../main/domain/bo/PrintOption.java | 54 ++- .../printserver/main/utils/PdfPrinter.java | 392 +++++++++++++++--- 3 files changed, 458 insertions(+), 84 deletions(-) diff --git a/src/main/java/com/goeing/printserver/main/PrintController.java b/src/main/java/com/goeing/printserver/main/PrintController.java index dd3e1a3..cce3ce0 100644 --- a/src/main/java/com/goeing/printserver/main/PrintController.java +++ b/src/main/java/com/goeing/printserver/main/PrintController.java @@ -9,8 +9,11 @@ import org.springframework.web.bind.annotation.*; import javax.print.PrintService; import java.awt.print.PrinterJob; +import java.io.File; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -19,33 +22,100 @@ import java.util.stream.Collectors; public class PrintController { private final String rootPath = "/Users/fl0919/work_space/goeingPrintServer/pdf"; + /** + * 获取所有可用打印机列表 + * + * @return 打印机名称列表 + */ @GetMapping("printerList") - public List printerList(){ + public List printerList() { PrintService[] printServices = PrinterJob.lookupPrintServices(); Set collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet()); return collect.stream().sorted().collect(Collectors.toList()); } + + /** + * 获取支持的打印选项 + * + * @return 包含支持的纸张尺寸和装订选项的Map + */ + @GetMapping("printOptions") + public Map getPrintOptions() { + Map options = new HashMap<>(); + options.put("paperSizes", PdfPrinter.getSupportedPaperSizes()); + options.put("bindingOptions", PdfPrinter.getSupportedBindingOptions()); + options.put("colorOptions", Arrays.asList("Full Color", "Black and White", "Cover Letter Color Only")); + options.put("sidesOptions", Arrays.asList("One-Sided", "Double-Sided", "Two-Sided")); + options.put("orientations", PdfPrinter.getSupportedOrientations()); + return options; + } @PostMapping("print") public String print(@RequestBody PrintRequest request) { - - System.out.println(JSONUtil.toJsonPrettyStr(request)); - + // 记录请求信息 + System.out.println("Received print request: " + JSONUtil.toJsonPrettyStr(request)); + + // 参数验证 + if (request == null) { + throw new IllegalArgumentException("Print request cannot be null"); + } + + if (request.getFileUrl() == null || request.getFileUrl().trim().isEmpty()) { + throw new IllegalArgumentException("File URL cannot be null or empty"); + } + + if (request.getPrinterName() == null || request.getPrinterName().trim().isEmpty()) { + throw new IllegalArgumentException("Printer name cannot be null or empty"); + } + + // 验证打印机是否存在 + if (!PdfPrinter.isPrinterExists(request.getPrinterName())) { + throw new IllegalArgumentException("Printer not found: " + request.getPrinterName()); + } + + if (request.getPrintOption() == null) { + throw new IllegalArgumentException("Print options cannot be null"); + } + + // 创建临时文件路径 + File pdfDir = new File(rootPath); + if (!pdfDir.exists()) { + pdfDir.mkdirs(); + } + String fileUrl = request.getFileUrl(); - String filePath = rootPath+"/"+IdUtil.getSnowflakeNextId() + ".pdf"; + String filePath = rootPath + "/" + IdUtil.getSnowflakeNextId() + ".pdf"; + File pdfFile = new File(filePath); + try { + // 下载文件 + System.out.println("Downloading file from: " + fileUrl); HttpUtil.downloadFile(fileUrl, filePath); - } catch (Exception e) { - System.err.println("There was an error downloading the print file!"); - throw new RuntimeException("There was an error downloading the print file!"); - } - try { + + if (!pdfFile.exists() || pdfFile.length() == 0) { + throw new RuntimeException("Downloaded file is empty or does not exist"); + } + + // 打印文件 + System.out.println("Printing file to printer: " + request.getPrinterName()); PdfPrinter.print(filePath, request.getPrinterName(), request.getPrintOption()); + + return "success"; } catch (Exception e) { - throw new RuntimeException(e); + System.err.println("Error during print process: " + e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("Print failed: " + e.getMessage(), e); + } finally { + // 清理临时文件 + if (pdfFile.exists()) { + try { + pdfFile.delete(); + System.out.println("Temporary file deleted: " + filePath); + } catch (Exception e) { + System.err.println("Failed to delete temporary file: " + filePath); + } + } } - - return "success"; } } diff --git a/src/main/java/com/goeing/printserver/main/domain/bo/PrintOption.java b/src/main/java/com/goeing/printserver/main/domain/bo/PrintOption.java index 03561b0..4593e90 100644 --- a/src/main/java/com/goeing/printserver/main/domain/bo/PrintOption.java +++ b/src/main/java/com/goeing/printserver/main/domain/bo/PrintOption.java @@ -2,21 +2,63 @@ package com.goeing.printserver.main.domain.bo; import lombok.Data; +/** + * 打印选项类,用于配置打印任务的各种参数 + */ @Data public class PrintOption { + /** + * 纸张尺寸,如"Letter", "A4", "Legal"等 + */ private String size; - private String paper; + + /** + * 打印颜色模式,如"Full Color", "Monochrome"等 + */ private String color; + + /** + * 打印面设置,如"One-Sided", "Double-Sided"等 + */ private String sides; + + /** + * 装订选项,如"Left", "Top", "Staple", "None"等 + */ private String bind; - private double margin; // in inches + + /** + * 页面边距,单位为英寸 + */ + private double margin = 0.5; // 默认0.5英寸 + + /** + * 打印位置 + */ private String position; + + /** + * 封面设置 + */ private String front; + + /** + * 封底设置 + */ private String back; + + /** + * 分节设置 + */ private String section; + + /** + * 页面方向,如"ORI_PORTRAIT", "ORI_LANDSCAPE"等 + */ private String orientation; - - private int qty; - - + + /** + * 打印份数 + */ + private int qty = 1; // 默认1份 } \ No newline at end of file diff --git a/src/main/java/com/goeing/printserver/main/utils/PdfPrinter.java b/src/main/java/com/goeing/printserver/main/utils/PdfPrinter.java index ac67190..57d3cb9 100644 --- a/src/main/java/com/goeing/printserver/main/utils/PdfPrinter.java +++ b/src/main/java/com/goeing/printserver/main/utils/PdfPrinter.java @@ -4,18 +4,26 @@ import cn.hutool.core.util.StrUtil; import com.goeing.printserver.main.domain.bo.PrintOption; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.printing.PDFPageable; +import org.apache.pdfbox.printing.Orientation; +import org.apache.pdfbox.printing.PDFPrintable; +import org.apache.pdfbox.printing.Scaling; import javax.print.PrintService; import javax.print.PrintServiceLookup; import javax.print.attribute.HashPrintRequestAttributeSet; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.standard.*; + +import java.awt.print.Book; import java.awt.print.PageFormat; import java.awt.print.Paper; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; public class PdfPrinter { @@ -23,20 +31,126 @@ public class PdfPrinter { private static final Map PAPER_SIZES = new HashMap<>(); static { - PAPER_SIZES.put("Letter", MediaSizeName.NA_LETTER);//8.5" × 11" - PAPER_SIZES.put("Legal", MediaSizeName.NA_LEGAL);//8.5" × 14" - PAPER_SIZES.put("Tabloid", MediaSizeName.TABLOID);//11" × 17" + PAPER_SIZES.put("Letter", MediaSizeName.NA_LETTER); // 8.5" × 11" + PAPER_SIZES.put("Legal", MediaSizeName.NA_LEGAL); // 8.5" × 14" + PAPER_SIZES.put("Tabloid", MediaSizeName.TABLOID); // 11" × 17" + PAPER_SIZES.put("A4", MediaSizeName.ISO_A4); // 8.27" × 11.69" + PAPER_SIZES.put("A3", MediaSizeName.ISO_A3); // 11.69" × 16.54" + PAPER_SIZES.put("Executive", MediaSizeName.EXECUTIVE); // 7.25" × 10.5" + } + + /** + * 获取所有支持的纸张尺寸 + * + * @return 纸张尺寸名称列表 + */ + public static List getSupportedPaperSizes() { + return new ArrayList<>(PAPER_SIZES.keySet()); + } + + /** + * 获取所有支持的打印方向 + * + * @return 打印方向名称列表 + */ + public static List getSupportedOrientations() { + return Arrays.asList("ORI_PORTRAIT", "ORI_LANDSCAPE"); } - public static PDDocument print(String pdfPath, String printerName, PrintOption option) throws Exception { + /** + * 打印PDF文件 + * + * @param pdfPath PDF文件路径 + * @param printerName 打印机名称 + * @param option 打印选项 + * @throws Exception 如果打印过程中出现错误 + */ + public static void print(String pdfPath, String printerName, PrintOption option) throws Exception { + // 参数验证 + validatePrintParameters(pdfPath, printerName, option); + + // 加载PDF文档并执行打印 + PDDocument document = null; + try { + document = PDDocument.load(new File(pdfPath)); + PrinterJob job = getPrintServiceByName(printerName); + setPageStyle(document, job, option); + } finally { + // 确保文档被关闭 + if (document != null) { + document.close(); + } + } + } + + /** + * 验证打印参数 + * + * @param pdfPath PDF文件路径 + * @param printerName 打印机名称 + * @param option 打印选项 + * @throws IllegalArgumentException 如果参数无效 + */ + private static void validatePrintParameters(String pdfPath, String printerName, PrintOption option) { + if (option == null) { + throw new IllegalArgumentException("PrintOption cannot be null"); + } + + if (StrUtil.isBlank(pdfPath)) { + throw new IllegalArgumentException("PDF path cannot be null or empty"); + } + File file = new File(pdfPath); - PDDocument document = PDDocument.load(file); - PrinterJob job = getPrintServiceByName(printerName); - setPageStyle(document, job, option); - return document; + if (!file.exists()) { + throw new IllegalArgumentException("PDF file does not exist: " + pdfPath); + } + + if (StrUtil.isBlank(printerName)) { + throw new IllegalArgumentException("Printer name cannot be null or empty"); + } + + if (!isPrinterExists(printerName)) { + throw new IllegalArgumentException("Printer not found: " + printerName); + } } + /** + * 检查指定名称的打印机是否存在 + * + * @param printerName 打印机名称 + * @return 如果打印机存在返回true,否则返回false + */ + public static boolean isPrinterExists(String printerName) { + if (StrUtil.isBlank(printerName)) { + return false; + } + + PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null); + if (services == null || services.length == 0) { + return false; + } + + for (PrintService service : services) { + if (service.getName().equalsIgnoreCase(printerName)) { + return true; + } + } + + return false; + } + + /** + * 根据打印机名称获取打印作业 + * + * @param printerName 打印机名称 + * @return 打印作业对象 + * @throws Exception 如果找不到打印机或出现其他错误 + */ public static PrinterJob getPrintServiceByName(String printerName) throws Exception { + if (StrUtil.isBlank(printerName)) { + throw new IllegalArgumentException("Printer name cannot be null or empty"); + } + PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null); if (services == null || services.length == 0) { @@ -44,108 +158,256 @@ public class PdfPrinter { } for (PrintService service : services) { - if (StrUtil.isNotBlank(printerName)&&service.getName().equalsIgnoreCase(printerName)) { + if (service.getName().equalsIgnoreCase(printerName)) { PrinterJob job = PrinterJob.getPrinterJob(); job.setPrintService(service); return job; } } - throw new RuntimeException("No name found [" + printerName + "] Printers"); + throw new RuntimeException("Printer not found: [" + printerName + "]"); } - private static void setPageStyle(PDDocument document, PrinterJob job, PrintOption option) { + /** + * 设置页面样式并执行打印任务 + * + * @param document PDF文档 + * @param job 打印作业 + * @param option 打印选项 + * @throws PrinterException 如果打印过程中出现错误 + */ + private static void setPageStyle(PDDocument document, PrinterJob job, PrintOption option) throws PrinterException { + // 创建打印请求属性集 + PrintRequestAttributeSet aset = createPrintRequestAttributeSet(option); + + // 设置页面方向 + Orientation pdfOrientation = getPdfOrientation(option.getOrientation()); + + // 获取纸张尺寸 + String size = option.getSize() != null ? option.getSize() : "Letter"; + double[] dimensions = getPaperDimensions(size); + + // 创建自定义Paper对象 + Paper paper = createPaper(dimensions[0], dimensions[1], option.getMargin()); + // 创建PageFormat对象 PageFormat pageFormat = new PageFormat(); - // 根据打印选项获取纸张大小 - Paper paper = getPdfPaper(option.getSize()); - - // 计算边距大小 - double marginPoints = option.getMargin() * 72; - // 计算可打印区域的宽度 - double imageableWidth = paper.getWidth() - 2 * marginPoints; - // 计算可打印区域的高度 - double imageableHeight = paper.getHeight() - 2 * marginPoints; - // 设置纸张的可打印区域 - paper.setImageableArea(marginPoints, marginPoints, imageableWidth, imageableHeight); - // 设置PageFormat对象的纸张 pageFormat.setPaper(paper); - - // 根据打印选项设置页面方向 - if ("ORI_LANDSCAPE".equalsIgnoreCase(option.getOrientation())) { + + // 设置页面方向 + if (pdfOrientation == Orientation.LANDSCAPE) { pageFormat.setOrientation(PageFormat.LANDSCAPE); } else { pageFormat.setOrientation(PageFormat.PORTRAIT); } - - // 创建PrintRequestAttributeSet对象 + + // 创建Book对象(替代PDFPageable) + Book book = new Book(); + + // 将所有页面添加到Book中,使用相同的PageFormat + // 根据页面方向选择适当的缩放模式 + Scaling scaling; + if (option.getSize() != null) { + // 如果用户指定了纸张大小,使用适应页面的缩放模式 + scaling = Scaling.SHRINK_TO_FIT; + } else { + // 如果用户没有指定纸张大小,使用实际大小 + scaling = Scaling.ACTUAL_SIZE; + } + + // 创建PDFPrintable对象,设置居中和显示页面边框 + PDFPrintable printable = new PDFPrintable(document, scaling, false, 0, true); + book.append(printable, pageFormat, document.getNumberOfPages()); + + // 应用自定义页面设置到打印作业 + job.setPageable(book); + + // 执行打印任务 + job.print(aset); + } + + /** + * 根据打印选项创建Paper对象 + * 这个方法可以在需要Paper对象的场景中使用,例如自定义打印布局 + * + * @param option 打印选项 + * @return 配置好的Paper对象 + */ + public static Paper createPaperFromOption(PrintOption option) { + // 获取纸张尺寸 + String size = option.getSize() != null ? option.getSize() : "Letter"; + double[] dimensions = getPaperDimensions(size); + + // 获取边距(默认为0.5英寸) + double marginInches = option.getMargin(); + + // 创建并返回Paper对象 + return createPaper(dimensions[0], dimensions[1], marginInches); + } + + /** + * 创建打印请求属性集 + * + * @param option 打印选项 + * @return 打印请求属性集 + */ + private static PrintRequestAttributeSet createPrintRequestAttributeSet(PrintOption option) { PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet(); - + // 设置纸张大小 - aset.add(PAPER_SIZES.getOrDefault(option.getSize(), MediaSizeName.NA_LETTER)); - - // 根据打印选项设置颜色模式 - if ("Full Color".equalsIgnoreCase(option.getColor()) || "Cover Letter Color Only".equalsIgnoreCase(option.getColor())) { + String size = option.getSize() != null ? option.getSize() : "Letter"; + aset.add(PAPER_SIZES.getOrDefault(size, MediaSizeName.NA_LETTER)); + + // 设置颜色模式 + setColorMode(aset, option.getColor()); + + // 设置打印面 + setPrintSides(aset, option.getSides()); + + // 设置打印份数 + int qty = option.getQty() <= 0 ? 1 : option.getQty(); + aset.add(new Copies(qty)); + + // 设置装订选项 + setBindingOption(aset, option.getBind()); + + return aset; + } + + /** + * 获取PDF页面方向 + * + * @param orientationStr 方向字符串 + * @return PDF页面方向 + */ + private static Orientation getPdfOrientation(String orientationStr) { + if (orientationStr != null && "ORI_LANDSCAPE".equalsIgnoreCase(orientationStr)) { + return Orientation.LANDSCAPE; + } + return Orientation.PORTRAIT; // 默认为纵向 + } + + /** + * 设置颜色模式 + * + * @param aset 打印请求属性集 + * @param color 颜色模式字符串 + */ + private static void setColorMode(PrintRequestAttributeSet aset, String color) { + if (color != null && ("Full Color".equalsIgnoreCase(color) || "Cover Letter Color Only".equalsIgnoreCase(color))) { aset.add(Chromaticity.COLOR); } else { aset.add(Chromaticity.MONOCHROME); } - - // 根据打印选项设置打印面 - if ("Double-Sided".equalsIgnoreCase(option.getSides()) || "Two-Sided".equalsIgnoreCase(option.getSides())) { + } + + /** + * 设置打印面 + * + * @param aset 打印请求属性集 + * @param sides 打印面字符串 + */ + private static void setPrintSides(PrintRequestAttributeSet aset, String sides) { + if (sides != null && ("Double-Sided".equalsIgnoreCase(sides) || "Two-Sided".equalsIgnoreCase(sides))) { aset.add(Sides.TWO_SIDED_LONG_EDGE); } else { aset.add(Sides.ONE_SIDED); } - - // 设置打印份数 - aset.add(new Copies(option.getQty())); - - // 设置装订选项 - setBindingOption(aset, option.getBind()); - - // 设置Pageable对象 - job.setPageable(new PDFPageable(document)); - try { - // 执行打印任务 - job.print(aset); - } catch (PrinterException e) { - // 打印异常处理 - e.printStackTrace(); - } } - private static Paper getPdfPaper(String size) { + /** + * 获取纸张尺寸的宽度和高度(以点为单位) + * + * @param size 纸张尺寸名称 + * @return 包含宽度和高度的数组,[0]为宽度,[1]为高度 + */ + private static double[] getPaperDimensions(String size) { + if (size == null) { + size = "Letter"; // 默认Letter尺寸 + } + + // 返回纸张尺寸的宽度和高度(以点为单位,1英寸=72点) switch (size) { case "Letter": - return createPaper(8.5 * 72, 11 * 72); + return new double[] {8.5 * 72, 11 * 72}; case "Legal": - return createPaper(8.5 * 72, 14 * 72); + return new double[] {8.5 * 72, 14 * 72}; case "Tabloid": - return createPaper(8.5 * 72, 16 * 72); + return new double[] {11 * 72, 17 * 72}; + case "A4": + return new double[] {8.27 * 72, 11.69 * 72}; + case "A3": + return new double[] {11.69 * 72, 16.54 * 72}; + case "Executive": + return new double[] {7.25 * 72, 10.5 * 72}; + case "B5": + return new double[] {6.93 * 72, 9.84 * 72}; + case "B4": + return new double[] {9.84 * 72, 13.90 * 72}; default: - return createPaper(8.5 * 72, 11 * 72); + return new double[] {8.5 * 72, 11 * 72}; // 默认Letter尺寸 } } - - private static Paper createPaper(double width, double height) { + + /** + * 创建纸张对象并设置可打印区域 + * + * @param width 纸张宽度(点) + * @param height 纸张高度(点) + * @param marginInches 边距(英寸) + * @return 配置好的Paper对象 + */ + private static Paper createPaper(double width, double height, double marginInches) { Paper paper = new Paper(); paper.setSize(width, height); + + // 将边距从英寸转换为点 + double marginPoints = marginInches * 72; + + // 设置可打印区域 + paper.setImageableArea( + marginPoints, + marginPoints, + width - 2 * marginPoints, + height - 2 * marginPoints + ); + return paper; } + /** + * 获取所有支持的装订选项 + * + * @return 装订选项名称列表 + */ + public static List getSupportedBindingOptions() { + return Arrays.asList("Left", "Top", "Staple", "None"); + } + + /** + * 设置装订选项 + * + * @param aset 打印请求属性集 + * @param bind 装订选项名称 + */ private static void setBindingOption(PrintRequestAttributeSet aset, String bind) { + if (bind == null) { + return; + } + switch (bind) { -// case "Left": -// aset.add(Binding.LEFT); -// break; -// case "Top": -// aset.add(Binding.TOP); -// break; + case "Left": + aset.add(Finishings.EDGE_STITCH_LEFT); + break; + case "Top": + aset.add(Finishings.EDGE_STITCH_TOP); + break; case "Staple": aset.add(Finishings.STAPLE); break; + case "None": default: + // 不添加任何装订选项 break; } }