优化打印机参数

This commit is contained in:
lifangliang 2025-06-23 11:21:53 +08:00
parent d04c487851
commit 3c9095bff4
3 changed files with 458 additions and 84 deletions

View File

@ -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<String> printerList(){
public List<String> printerList() {
PrintService[] printServices = PrinterJob.lookupPrintServices();
Set<String> collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet());
return collect.stream().sorted().collect(Collectors.toList());
}
/**
* 获取支持的打印选项
*
* @return 包含支持的纸张尺寸和装订选项的Map
*/
@GetMapping("printOptions")
public Map<String, Object> getPrintOptions() {
Map<String, Object> 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";
}
}

View File

@ -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份
}

View File

@ -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<String, MediaSizeName> 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<String> getSupportedPaperSizes() {
return new ArrayList<>(PAPER_SIZES.keySet());
}
/**
* 获取所有支持的打印方向
*
* @return 打印方向名称列表
*/
public static List<String> 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<String> 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;
}
}