【2023】Java print PDF (no need to call the browser to print directly and silently)

1. Jane

need

The reason for writing this is mainly because the printing function in the project at that time was when the user printed a label. Every time he clicked to print, the PDF file was downloaded to the client browser, and then he needed to click on the printer through the browser to print, which was very troublesome. The steps are very complicated every time, and the parameters must be reset every time. So I was thinking about how to call the printer by myself through Java. The user only needs to enter the parameters that need to be written to the PDF template, configure the printing parameters in advance, and then call the printing in the background without going through the browser to print individually.

The specific implementation is to write text, two-dimensional code, barcode, and picture into the pdf file through the template, and then print it to the printer

Implementation steps

  • First, let’s briefly introduce the content of this article. It mainly uses Adobe Acrobat DC (or other PDF template making apps) to create a PDF template, and then writes the data into the text itextpdffield corresponding to the template through the framework to realize the PDF file. Open it, and the written content can be displayed normally, which means that the pdf file creation is useless.
  • Then deploy a jar package that calls the local printer on the device that needs to be connected to the printer for printing. The jar mainly calls the pdfboxlocal printer through the framework, and successfully passes the pdf file that needs to be printed to the printer's print queue to achieve printing.
  • In the jar of the printer host, write the http interface that accepts the pdf printed by the server. It is used to receive the pdf file that needs to be printed and some printing parameters passed by the server (including specifying the printer, customizing the paper size, setting the printing parameters, and displaying the printing dialog box. box, etc.)

2. Code implementation

It mainly includes the server part (this only includes writing the pdf template to calling the printer client interface, the specific business can be modified according to your actual business) and the printer client.

0. Print template

Configure the corresponding text field through Adobe Acrobat DC. The specific configuration can be viewed on Baidu, so I won’t go into details here
Insert image description here

1. Server part (port: 8090)

The yml only sets the port and does not display it.

1.1, maven dependency

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
                <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.15</version>
        </dependency>
<!--pdf生成-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.23</version>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.48</version>
        </dependency>

1.2. Entities

1.2.1, interface return class

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    
    
    /**
     * 是否成功
     **/
    private Boolean success;
    /**
     * 错误信息
     **/
    private String message;
    /**
     * 请求状态 200-成功 400-失败
     **/
    private Integer code;
    /**
     * 当前时间戳
     **/
    private Long timestamp;
    /**
     * 返回结果
     **/
    private Object result;

    public static Result ok() {
    
    
        return new Result(true, null, 200, System.currentTimeMillis(),null);
    }

    public static Result ok(Object data) {
    
    
        return new Result(true, null, 200,System.currentTimeMillis(),data);
    }


    public static Result ok(List<?> data) {
    
    
        return new Result(true, null, 200,System.currentTimeMillis(),data);
    }

    public static Result error(String errorMsg) {
    
    
        return new Result(false, errorMsg, 400,System.currentTimeMillis(),null);
    }
}

1.2.2. Label page parameter class

Printed label paper page parameter class, add default value

/**
 * @author zhengfuping
 * @version 1.0
 * 110*65 的标签纸页面
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaperVo {
    
    
    /**宽*/
    private double width = 100*2.83;
    /**高*/
    private double height = 60*2.83;
    /** X 坐标*/
    private double x = 15;
    /** Y 坐标*/
    private double y = 10;
    /**
     * 打印页面方向:
     *      0:横向 从右向左,1:横向 从左向右,2:纵向。
     * */
    private Integer orientation = PageFormat.PORTRAIT;
    private String name;
}

1.2.3. PDF template parameter class

The text field name corresponding to the pdf template

/**
 * @author zhengfuping
 * @version 1.0
 * pdf模板参数
 */
@Data
@Builder
public class Template {
    
    
    private String time;

    private String code;

    private String qrCode;

    private String barCode;

    private String image;

    private String age;

    private String name;
}

1.3. Controller layer interface

@RequestMapping("/pdf")
@RestController
public class PDFController {
    
    
    @Autowired
    private Netty netty;
    @Autowired
    private HttpPdf httpPdf;


    /**
     * @author yingfeng
     * @Param * @param params 包括两个参数 copies:打印张数、duplex:是否打印反面
     * @param request
     * @return * @return Result
     */
    @PostMapping("/print")
    public Result print(@RequestBody Map<String ,Object> params, HttpServletRequest request){
    
    
        //因为测试原因,便于理解,所以参数直接添加
        String Code = "43504277201002308221C0100C010145006";
        String barCode = "43504277201002308221C0100C010145006-bar";
        String time = DateUtil.format(new Date(), "yyyyMMdd");
        String qrCode = "https://blog.csdn.net/weixin_52315708";
        String name = "张三";
        String image = "D:/1zheng/dai/excel/exportexcel/a1.jpg";
        Template template = Template.builder()
                .qrCode(qrCode)
                .code(Code)
                .time(time)
                .barCode(barCode)
                .image(image)
                .name(name)
                .age("18").build();
        //转为map是因为需要循环把值写入对应的文本域
        Map<String, Object> map = BeanUtil.beanToMap(template);
		//调用写入文本域工具类,返回对应的byte[]数据
        byte[] pdf = PDFUtil.test(data);
        params.put("pdf",pdf);
        //用于调用客户端的接口
        Result result = httpPdf.doPostWith(params);
        return result;
    }

1.4, write pdf tool class

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.qrcode.EncodeHintType;
import com.itextpdf.text.pdf.qrcode.ErrorCorrectionLevel;
import com.zheng.exceltest.pdf.entity.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author zhengfuping
 * @version 1.0
 * 实现往打印机打印
 */
@Slf4j
public class PDFUtil {
    
    

 public static byte[] test(Map<String, Object> data)  {
    
    
        BASE64Encoder encoder = new BASE64Encoder();
        BufferedInputStream bin = null;
        ByteArrayOutputStream bos = null;
        PdfStamper ps = null;
        OutputStream fos = null;
        try {
    
    
            // pdf模板
            String fileName = "exportexcel/PDF打印测试模板.pdf";
            //读取pdf
            PdfReader reader  = new PdfReader(fileName);
            bos = new ByteArrayOutputStream();
            //将要生成的目标PDF文件名称
            ps = new PdfStamper(reader, bos);
//        PdfContentByte under = ps.getUnderContent(1);
//        取出报表模板中的所有字段
            AcroFields fields = ps.getAcroFields();

//        对表单数据进行赋值
            fillData(fields,ps,data);
            
            ps.setFormFlattening(true);
            ps.close();
            fos = new FileOutputStream("D:/模板打印测试/a1.pdf");
            fos.write(bos.toByteArray());
            fos.flush();
            fos.close();   //实际应该finally在关闭一次
            bos.close();  //注意,需要在得到 byte[]之前关闭流

//            执行打印
            byte[] bytes = bos.toByteArray();
            return bytes;
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 具体往模板的对应文本域写入数据
     * @author zhengfuping
     * @date 2023/8/9 15:55
     * @param fields AcroFields对象
     * @param ps PdfStamper对象
     * @param data 数据
     */
	public static void fillData(AcroFields fields, PdfStamper ps, Map<String, Object> data) throws IOException, DocumentException {
    
    
//        设置中文字体
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        ArrayList<BaseFont> fonts = new ArrayList<>();
        Font font = FontFactory.getFont(getFontPath("SimHei.ttf"), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED,(short)14);
        fonts.add(font.getBaseFont());
        fonts.add(bf);
        fields.setSubstitutionFonts(fonts);
	//循环遍历集合中的文本域字段,根据名称进行不同处理
        for (String key : data.keySet()) {
    
    
            System.out.println(key+":"+data.get(key));
            if (data.get(key)==null)continue;
            if (key.equals("image")) {
    
          //      生成图片
                String value = data.get(key).toString();
                String imgpath = value;
                int pageNo = fields.getFieldPositions(key).get(0).page;
                Rectangle signRect = fields.getFieldPositions(key).get(0).position;
                float x = signRect.getLeft();
                float y = signRect.getBottom();
                // 根据路径读取图片
                Image image = Image.getInstance(imgpath);
                // 获取图片页面
                PdfContentByte under = ps.getOverContent(pageNo);
                // 图片大小自适应
                image.scaleToFit(signRect.getWidth(), signRect.getHeight());
                // 添加图片
                image.setAbsolutePosition(x, y);
                under.addImage(image);
            } else if (key.equals("barCode")) {
    
         //生成条形码
                //遍历条码字段
                String value = data.get(key).toString();
//              获取位置(左上右下)
                AcroFields.FieldPosition fieldPosition = fields.getFieldPositions(key).get(0);
//                  ?null
                PdfNumber rNum = fields.getFieldItem(key).getWidget(0).getAsDict(PdfName.AP).getAsNumber(PdfName.R);
                if (rNum == null) {
    
    
                    fieldPosition.position.setRotation(0);
                } else {
    
    
                    fieldPosition.position.setRotation(rNum.intValue());
                }
                //绘制条码
                Barcode128 barcode128 = new Barcode128();
                barcode128.setSize(8);
                if (fieldPosition.position.getRotation() == 90 || fieldPosition.position.getRotation() == 270) {
    
    
                    barcode128.setBarHeight(25);
                    barcode128.setX(0.82f);
                } else {
    
    
                    //条码宽高
//                    barcode128.setBarHeight(fieldPosition.position.getHeight() - 40);
//                    barcode128.setX(fieldPosition.position.getWidth() / 150);
                    barcode128.setBarHeight(25);
                    barcode128.setX(0.5f);
                }
                //条码与数字间距
                barcode128.setBaseline(8);
                //条码值
                barcode128.setCode(value);
                barcode128.setStartStopText(false);
                barcode128.setExtended(true);
                //绘制在第一页
                PdfContentByte cb = ps.getOverContent(1);
                //生成条码图片
                Image image128 = barcode128.createImageWithBarcode(cb, null, null);
                //旋转度数
                image128.setRotationDegrees(fieldPosition.position.getRotation());

                //左边距(居中处理)
                float marginLeft = (fieldPosition.position.getRight() - fieldPosition.position.getLeft() - image128.getWidth()) / 2;
                //条码位置
                image128.setAbsolutePosition(fieldPosition.position.getLeft() + marginLeft, fieldPosition.position.getBottom());
                //加入条码
                cb.addImage(image128);
            }else if ("qrCode".equals(key)){
    
            //生成二维码
                //           遍历二维码字段
                String value = data.get(key).toString();
                // 获取属性的类型
                if (value != null ) {
    
    
                    //获取位置(左上右下)
                    AcroFields.FieldPosition fieldPosition = fields.getFieldPositions(key).get(0);
                    //绘制二维码
                    float width = fieldPosition.position.getRight()/2 - fieldPosition.position.getLeft()/2;
                    //设定容错性二维码容错率用字母表示,容错能力等级分为:L、M、Q、H四级:L :7%;M:15%;Q:25%;H:30%
                    Map<EncodeHintType, Object> hints = new HashMap<>();
                    hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
                    BarcodeQRCode pdf417 = new BarcodeQRCode(value.toString(), (int) width, (int) width, hints);
                    //生成二维码图像
                    Image image128 = pdf417.getImage();
                    //绘制在第一页
                    PdfContentByte cb = ps.getOverContent(1);
                    //左边距(居中处理)
                    float marginLeft = (fieldPosition.position.getRight() - fieldPosition.position.getLeft() - image128.getWidth()) / 2;
                    //条码位置
                    image128.setAbsolutePosition(fieldPosition.position.getLeft() + marginLeft, fieldPosition.position.getBottom()-3f);
                    //加入条码
                    cb.addImage(image128);
                }
            }else{
    
              //生成文字
                String value = data.get(key).toString();
//                String partCode = (String) data.get("name");
                设置文本大小
//                if (partCode.length()<8 && key.equals("age")){
    
    
//                    fields.setFieldProperty(key,"textsize",(float)36,null);
//
//                }else {
    
    
//                    fields.setFieldProperty(key,"textsize",(float)9,null);
//                }
//                设置文本字体
                if (Pattern.compile("[\u4E00-\u9FA5]").matcher(key).find()){
    
    
                    fields.setFieldProperty(key,"textfont",bf,null);    //中文
                    fields.setField(key, value);
                }else {
    
    
                    fields.setFieldProperty(key,"textfont",font.getBaseFont(),null);  //英文
                    fields.setField(key, value);
                }
            }
        }
    }

    /**
     * 获取本机的字体文件
     *
     * @param fontName
     */
    private static String getFontPath(String fontName) {
    
    
        String fontPath = "C:\\Windows\\Fonts\\" + fontName;
        // 判断系统类型,加载字体文件
        java.util.Properties prop = System.getProperties();
        String osName = prop.getProperty("os.name").toLowerCase();
        if (osName.indexOf("linux") > -1) {
    
    
            fontPath = "/usr/share/fonts/" + fontName;
        }
        log.info(osName + "-------------------" + fontPath);
        return fontPath;
    }

1.5. Call the client jar interface

/**
 * @author zhengfuping
 * @version 1.0
 * 调用客户端打印机的jar
 * @date 2023/4/14 14:35
 */
@Service
public class HttpPdf {
    
    
    @Autowired
    private RestTemplate restTemplate;

	//接口路径
    final String url = "http://127.0.0.1:9050/print/print";


    public Result doPostWith(Map<String ,Object> params){
    
    
        PaperVo paperVo = new PaperVo();
        System.out.println();
        params.put("paper",paperVo);
        Result result = restTemplate.postForObject(url, params, Result.class);
        return result;
    }

2. Printer client jar (port: 9050)

2.1. Dependency

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.23</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.0</version>
        </dependency>
    </dependencies>

2.2. Entity class

Same as above

2.2.1. Paper object

@Data
public class PaperVo {
    
    
    /**长*/
    private double width = 216;
    /**宽*/
    private double height = 360;
    /** X 坐标*/
    private double x = 5;
    /** Y 坐标*/
    private double y = 100;
    /**
     * 打印页面方向:
     *      0:横向 从右向左,1:横向 从左向右,2:纵向。
     * */
    private Integer orientation = PageFormat.PORTRAIT;
}

2.2.2. Return objects uniformly

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    
    
    /**
     * 是否成功
     **/
    private Boolean success;
    /**
     * 错误信息
     **/
    private String message;
    /**
     * 请求状态 200-成功 400-失败
     **/
    private Integer code;
    /**
     * 当前时间戳
     **/
    private Long timestamp;
    /**
     * 返回结果
     **/
    private Object result;

    public static Result ok() {
    
    
        return new Result(true, null, 200, System.currentTimeMillis(),null);
    }

    public static Result ok(Object data) {
    
    
        return new Result(true, null, 200,System.currentTimeMillis(),data);
    }


    public static Result ok(List<?> data) {
    
    
        return new Result(true, null, 200,System.currentTimeMillis(),data);
    }

    public static Result error(String errorMsg) {
    
    
        return new Result(false, errorMsg, 400,System.currentTimeMillis(),null);
    }
}

2.3, Controller layer, receiving server call printing request

/**
 * @author zhengfuping
 * @version 1.0
 * 接收调用打印请求
 */
@RequestMapping("/print")
@RestController
public class PrintController {
    
    
    /**
     * @Description: 默认使用打印机的名称。可以存在数据库里做持久化
     */
    private static String printName = "SATO CL4NX Plus 305dpi";


    /**
     * @Description: 获取打印机列表
     * @Param: []
     * @Return: com.wq.print.util.R
     */
    @RequestMapping("/list")
    public Result list() {
    
    
        ArrayList<String> list = new ArrayList<>();
        // 遍历所有打印机的名称
        for (PrintService ps : PrinterJob.lookupPrintServices()) {
    
    
            list.add(ps.getName());
        }
        if (list.size() != 0) {
    
    
            return Result.ok(list);
        }
        return Result.error("暂无可用打印机,请检查系统打印机设置");
    }
    /**
     * @Description: 设置使用的打印机
     * @Param:
     * @Return: com.wq.print.util.R
     */
    @PostMapping("/setPrint")
    public Result setPrint(@RequestParam("printName") String printName) {
    
    
        PrintController.printName = printName;
        return Result.ok("打印机设置成功");
    }

    @PostMapping("/print")
    public Result print(@RequestBody Map<String ,Object> params, HttpServletRequest request){
    
    
        try {
    
    
            String pdfBase64Str = String.valueOf(params.get("pdf"));
            if (StrUtil.isEmptyIfStr(pdfBase64Str)) {
    
    
                return Result.error("pdf的Base64字符串有误或为空,请检查");
            }
            //因为传输过程中会把 byte[]转为 pdfBase64Str,需要重新转回来
            byte[] pdfByte = PrintUtil.base64ToFileByte(pdfBase64Str);
				//设置参数,没设置也要给默认值
            int copies = params.get("copies") == null ?1 : Integer.parseInt(params.get("copies").toString());
            boolean duplex = params.get("duplex") != null && Boolean.parseBoolean(params.get("duplex").toString());
            Map<String,Object> pdf = (Map<String,Object>) params.get("paper");
            PaperVo paperVo = null;
            //如果服务器没有传对应参数,就用这边的
            if (pdf==null){
    
    
                paperVo = new PaperVo();
            }else {
    
    
                paperVo = BeanUtil.mapToBean(pdf, PaperVo.class, false, new CopyOptions());
            }
            Boolean print = PrintUtil.print(pdfByte, PrintController.printName, copies, duplex,paperVo);

            if (print){
    
    
                return Result.ok("打印完成");
            }else {
    
    
                return Result.ok("打印失败");
            }
        } catch (NumberFormatException e) {
    
    
            e.printStackTrace();
            return Result.ok("打印失败"+e.getMessage());
        }
    }
}

2.4. Configure printing parameters and call the printer

Configure configuration parameters to call the client printer

public class PrintUtil {
    
    

    /** 
     * 调用配置打印机
     * @author zhengfuping
     * @param pdfByte 数据
     * @param printName  打印机名称
     * @param copies 打印张数
     * @param duplex 是否打印反面
     * @param paperVo 纸张参数
     * @return Boolean 
     */
    public static Boolean print(byte[] pdfByte, String printName, int copies, boolean duplex , PaperVo paperVo) {
    
    
        //加载pdf文件对象
        try(PDDocument document = PDDocument.load(pdfByte)){
    
    
            //            创建打印任务
            PrinterJob job = PrinterJob.getPrinterJob();
            //            遍历所有打印机的名称
            for (PrintService ps : PrinterJob.lookupPrintServices()){
    
    
                String psName = ps.getName();
                if (psName.equals(printName)){
    
    
                    job.setPrintService(ps);
                    break;
                }
            }

            job.setPageable(new PDFPageable(document));
//            纸张对象
            Paper paper = new Paper();
            // 设置打印纸张大小
            paper.setSize(paperVo.getWidth(),paperVo.getHeight());
            // 设置打印位置 坐标
            paper.setImageableArea(paperVo.getX(),paperVo.getY(),paper.getWidth(),paper.getHeight());

//            打印的页面参数
            PageFormat pageFormat = new PageFormat();
            pageFormat.setPaper(paper);
            pageFormat.setOrientation(paperVo.getOrientation()); //横向 从右向左

            Book book = new Book();
            //            打印页面对象--配置
            PDFPrintable pdfPrintable = new PDFPrintable(document, Scaling.SHRINK_TO_FIT, true, 0, true);
            book.append(pdfPrintable,pageFormat,1);
            job.setPageable(book);
//            打印份额
            job.setCopies(copies);

            if (duplex){
    
    
                HashPrintRequestAttributeSet printSet = new HashPrintRequestAttributeSet();
                printSet.add(Sides.DUPLEX);
                job.print(printSet);
            }else {
    
    
                job.print();
            }
            document.close();
            return true;

        }catch (Exception e){
    
    
            e.printStackTrace();
            return false;
        }
    }

    /** 
     * Base64转换编码
     * @Param * @param strBase64
     */
    
    public static byte[] base64ToFileByte(String strBase64) {
    
    
        return java.util.Base64.getDecoder().decode(strBase64);
    }

3. Test

1. Call the test

Use postman to call the server’s printing interface
Insert image description here

The final print file was written directly to the local area for testing. In the actual project, it may be called through a browser and needs to be uploaded to the browser. Just configure the request directly. If the file can be opened normally and the client receives the parameters, , there is data in the pdf, indicating that there is no problem with the server-side code.

Insert image description here

  • Specific printed pdf file
    Insert image description here

2. Achieve results

Specifically, the last sentence directly document.close();. After calling the printer, if there is a task in the corresponding print queue, it means that the implementation is successful.
Insert image description here

3. Check the client printer

Insert image description here

Summarize

  • In terms of specific implementation, it is necessary to pay attention to the name of the text field to be corresponding;

  • The limitation is that the client server needs to be in the same local area network. If not, the client interface also needs to be mapped out for calling the server.

  • The client code can actually be converted into a jar and then converted into an .exe file through exe4j, and then the jdk can be bound into it through Inno Setup.

Guess you like

Origin blog.csdn.net/weixin_52315708/article/details/132188505