一、簡(jiǎn)
需求
寫這個(gè)的原因主要是因?yàn)楫?dāng)時(shí)項(xiàng)目中的打印功能是用戶打印標(biāo)簽時(shí),每次點(diǎn)擊打印是通過把PDF文件下載到客戶端瀏覽器,然后需要通過瀏覽器去點(diǎn)擊打印機(jī)實(shí)現(xiàn)打印,就非常麻煩,每次都步驟非常復(fù)雜,而且每次參數(shù)都要重新設(shè)置。于是就想著怎么通過java實(shí)現(xiàn)自己調(diào)用打印機(jī),用戶只需要輸入需要寫入pdf模板的參數(shù),提前配置好打印參數(shù),然后后臺(tái)自己去調(diào)用打印不需要通過瀏覽器去單個(gè)打印。
具體實(shí)現(xiàn)把文字、二維碼、條形碼、圖片實(shí)現(xiàn)通過模板寫入pdf文件,然后再到打印機(jī)打印處理
實(shí)現(xiàn)步驟
- 先大致介紹一下這篇文章的內(nèi)容,主要是通過 Adobe Acrobat DC(或者其他的PDF模板制作app),制作好PDF模板,然后通過
itextpdf
框架把數(shù)據(jù)寫入到模板對(duì)應(yīng)的文本域中,可以實(shí)現(xiàn)PDF文件打開,寫入的內(nèi)容可以正常顯示代表pdf文件制作沒用問題了。 - 然后在需要連接打印機(jī)打印的上部署一個(gè)調(diào)用本地打印機(jī)的jar包,jar主要通過
pdfbox
框架實(shí)現(xiàn)調(diào)用本地打印機(jī),成功把需要打印的pdf文件傳遞到打印機(jī)的打印隊(duì)列,實(shí)現(xiàn)打印。 - 在打印機(jī)主機(jī)的jar寫好接受服務(wù)器打印的pdf的http接口,用于接收服務(wù)器傳遞過來的需要打印的pdf文件以及一些打印參數(shù)(包括指定打印機(jī)、自定義紙張大小、設(shè)置打印參數(shù)、以及顯示打印對(duì)話框等)
二、代碼實(shí)現(xiàn)
主要包括服務(wù)器部分(這里只包括寫入pdf模板到調(diào)用打印機(jī)客戶端接口,具體業(yè)務(wù)根據(jù)自己實(shí)際業(yè)務(wù)修改就行)和打印機(jī)客戶端。
0、打印模板
通過 Adobe Acrobat DC配置對(duì)應(yīng)的文本域,具體配置可以百度查看,這里就不贅述了
1、服務(wù)器部分 (端口:8090)
yml只設(shè)置了端口,就不展示出來了
1.1、maven依賴
<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、實(shí)體
1.2.1、接口返回類
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
/**
* 是否成功
**/
private Boolean success;
/**
* 錯(cuò)誤信息
**/
private String message;
/**
* 請(qǐng)求狀態(tài) 200-成功 400-失敗
**/
private Integer code;
/**
* 當(dāng)前時(shí)間戳
**/
private Long timestamp;
/**
* 返回結(jié)果
**/
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、標(biāo)簽紙頁面參數(shù)類
打印的標(biāo)簽紙頁面參數(shù)類,添加默認(rèn)值
/**
* @author zhengfuping
* @version 1.0
* 110*65 的標(biāo)簽紙頁面
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaperVo {
/**寬*/
private double width = 100*2.83;
/**高*/
private double height = 60*2.83;
/** X 坐標(biāo)*/
private double x = 15;
/** Y 坐標(biāo)*/
private double y = 10;
/**
* 打印頁面方向:
* 0:橫向 從右向左,1:橫向 從左向右,2:縱向。
* */
private Integer orientation = PageFormat.PORTRAIT;
private String name;
}
1.2.3、PDF模板參數(shù)類
對(duì)應(yīng)pdf模板的文本域名稱
/**
* @author zhengfuping
* @version 1.0
* pdf模板參數(shù)
*/
@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層接口
@RequestMapping("/pdf")
@RestController
public class PDFController {
@Autowired
private Netty netty;
@Autowired
private HttpPdf httpPdf;
/**
* @author yingfeng
* @Param * @param params 包括兩個(gè)參數(shù) copies:打印張數(shù)、duplex:是否打印反面
* @param request
* @return * @return Result
*/
@PostMapping("/print")
public Result print(@RequestBody Map<String ,Object> params, HttpServletRequest request){
//因?yàn)闇y(cè)試原因,便于理解,所以參數(shù)直接添加
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();
//轉(zhuǎn)為map是因?yàn)樾枰h(huán)把值寫入對(duì)應(yīng)的文本域
Map<String, Object> map = BeanUtil.beanToMap(template);
//調(diào)用寫入文本域工具類,返回對(duì)應(yīng)的byte[]數(shù)據(jù)
byte[] pdf = PDFUtil.test(data);
params.put("pdf",pdf);
//用于調(diào)用客戶端的接口
Result result = httpPdf.doPostWith(params);
return result;
}
1.4、寫入pdf工具類
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
* 實(shí)現(xiàn)往打印機(jī)打印
*/
@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打印測(cè)試模板.pdf";
//讀取pdf
PdfReader reader = new PdfReader(fileName);
bos = new ByteArrayOutputStream();
//將要生成的目標(biāo)PDF文件名稱
ps = new PdfStamper(reader, bos);
// PdfContentByte under = ps.getUnderContent(1);
// 取出報(bào)表模板中的所有字段
AcroFields fields = ps.getAcroFields();
// 對(duì)表單數(shù)據(jù)進(jìn)行賦值
fillData(fields,ps,data);
ps.setFormFlattening(true);
ps.close();
fos = new FileOutputStream("D:/模板打印測(cè)試/a1.pdf");
fos.write(bos.toByteArray());
fos.flush();
fos.close(); //實(shí)際應(yīng)該finally在關(guān)閉一次
bos.close(); //注意,需要在得到 byte[]之前關(guān)閉流
// 執(zhí)行打印
byte[] bytes = bos.toByteArray();
return bytes;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 具體往模板的對(duì)應(yīng)文本域?qū)懭霐?shù)據(jù)
* @author zhengfuping
* @date 2023/8/9 15:55
* @param fields AcroFields對(duì)象
* @param ps PdfStamper對(duì)象
* @param data 數(shù)據(jù)
*/
public static void fillData(AcroFields fields, PdfStamper ps, Map<String, Object> data) throws IOException, DocumentException {
// 設(shè)置中文字體
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);
//循環(huán)遍歷集合中的文本域字段,根據(jù)名稱進(jìn)行不同處理
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();
// 根據(jù)路徑讀取圖片
Image image = Image.getInstance(imgpath);
// 獲取圖片頁面
PdfContentByte under = ps.getOverContent(pageNo);
// 圖片大小自適應(yīng)
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);
}
//條碼與數(shù)字間距
barcode128.setBaseline(8);
//條碼值
barcode128.setCode(value);
barcode128.setStartStopText(false);
barcode128.setExtended(true);
//繪制在第一頁
PdfContentByte cb = ps.getOverContent(1);
//生成條碼圖片
Image image128 = barcode128.createImageWithBarcode(cb, null, null);
//旋轉(zhuǎn)度數(shù)
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;
//設(shè)定容錯(cuò)性二維碼容錯(cuò)率用字母表示,容錯(cuò)能力等級(jí)分為:L、M、Q、H四級(jí):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");
設(shè)置文本大小
// if (partCode.length()<8 && key.equals("age")){
// fields.setFieldProperty(key,"textsize",(float)36,null);
//
// }else {
// fields.setFieldProperty(key,"textsize",(float)9,null);
// }
// 設(shè)置文本字體
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);
}
}
}
}
/**
* 獲取本機(jī)的字體文件
*
* @param fontName
*/
private static String getFontPath(String fontName) {
String fontPath = "C:\\Windows\\Fonts\\" + fontName;
// 判斷系統(tǒng)類型,加載字體文件
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、調(diào)用客戶端jar接口
/**
* @author zhengfuping
* @version 1.0
* 調(diào)用客戶端打印機(jī)的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、打印機(jī)客戶端jar (端口:9050)
2.1、依賴
<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、實(shí)體類
同上
2.2.1、紙張對(duì)象
@Data
public class PaperVo {
/**長*/
private double width = 216;
/**寬*/
private double height = 360;
/** X 坐標(biāo)*/
private double x = 5;
/** Y 坐標(biāo)*/
private double y = 100;
/**
* 打印頁面方向:
* 0:橫向 從右向左,1:橫向 從左向右,2:縱向。
* */
private Integer orientation = PageFormat.PORTRAIT;
}
2.2.2、統(tǒng)一返回對(duì)象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
/**
* 是否成功
**/
private Boolean success;
/**
* 錯(cuò)誤信息
**/
private String message;
/**
* 請(qǐng)求狀態(tài) 200-成功 400-失敗
**/
private Integer code;
/**
* 當(dāng)前時(shí)間戳
**/
private Long timestamp;
/**
* 返回結(jié)果
**/
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層,接收服務(wù)器調(diào)用打印請(qǐng)求
/**
* @author zhengfuping
* @version 1.0
* 接收調(diào)用打印請(qǐng)求
*/
@RequestMapping("/print")
@RestController
public class PrintController {
/**
* @Description: 默認(rèn)使用打印機(jī)的名稱??梢源嬖跀?shù)據(jù)庫里做持久化
*/
private static String printName = "SATO CL4NX Plus 305dpi";
/**
* @Description: 獲取打印機(jī)列表
* @Param: []
* @Return: com.wq.print.util.R
*/
@RequestMapping("/list")
public Result list() {
ArrayList<String> list = new ArrayList<>();
// 遍歷所有打印機(jī)的名稱
for (PrintService ps : PrinterJob.lookupPrintServices()) {
list.add(ps.getName());
}
if (list.size() != 0) {
return Result.ok(list);
}
return Result.error("暫無可用打印機(jī),請(qǐng)檢查系統(tǒng)打印機(jī)設(shè)置");
}
/**
* @Description: 設(shè)置使用的打印機(jī)
* @Param:
* @Return: com.wq.print.util.R
*/
@PostMapping("/setPrint")
public Result setPrint(@RequestParam("printName") String printName) {
PrintController.printName = printName;
return Result.ok("打印機(jī)設(shè)置成功");
}
@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字符串有誤或?yàn)榭?,?qǐng)檢查");
}
//因?yàn)閭鬏斶^程中會(huì)把 byte[]轉(zhuǎn)為 pdfBase64Str,需要重新轉(zhuǎn)回來
byte[] pdfByte = PrintUtil.base64ToFileByte(pdfBase64Str);
//設(shè)置參數(shù),沒設(shè)置也要給默認(rèn)值
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;
//如果服務(wù)器沒有傳對(duì)應(yīng)參數(shù),就用這邊的
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、配置打印參數(shù)調(diào)用打印機(jī)
配置配置參數(shù)調(diào)用客戶端打印機(jī)
public class PrintUtil {
/**
* 調(diào)用配置打印機(jī)
* @author zhengfuping
* @param pdfByte 數(shù)據(jù)
* @param printName 打印機(jī)名稱
* @param copies 打印張數(shù)
* @param duplex 是否打印反面
* @param paperVo 紙張參數(shù)
* @return Boolean
*/
public static Boolean print(byte[] pdfByte, String printName, int copies, boolean duplex , PaperVo paperVo) {
//加載pdf文件對(duì)象
try(PDDocument document = PDDocument.load(pdfByte)){
// 創(chuàng)建打印任務(wù)
PrinterJob job = PrinterJob.getPrinterJob();
// 遍歷所有打印機(jī)的名稱
for (PrintService ps : PrinterJob.lookupPrintServices()){
String psName = ps.getName();
if (psName.equals(printName)){
job.setPrintService(ps);
break;
}
}
job.setPageable(new PDFPageable(document));
// 紙張對(duì)象
Paper paper = new Paper();
// 設(shè)置打印紙張大小
paper.setSize(paperVo.getWidth(),paperVo.getHeight());
// 設(shè)置打印位置 坐標(biāo)
paper.setImageableArea(paperVo.getX(),paperVo.getY(),paper.getWidth(),paper.getHeight());
// 打印的頁面參數(shù)
PageFormat pageFormat = new PageFormat();
pageFormat.setPaper(paper);
pageFormat.setOrientation(paperVo.getOrientation()); //橫向 從右向左
Book book = new Book();
// 打印頁面對(duì)象--配置
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轉(zhuǎn)換編碼
* @Param * @param strBase64
*/
public static byte[] base64ToFileByte(String strBase64) {
return java.util.Base64.getDecoder().decode(strBase64);
}
三、測(cè)試
1、調(diào)用測(cè)試
使用postman調(diào)用服務(wù)器的打印接口
最終實(shí)現(xiàn)的打印文件因?yàn)闇y(cè)試我是直接寫入到本地的,實(shí)際項(xiàng)目中可能是通過瀏覽器調(diào)用,需要上載到瀏覽器,直接配置request就行,如果文件可以正常打開,并且客戶端有接收到參數(shù),pdf中有數(shù)據(jù),說明服務(wù)器端代碼沒問題了。
- 具體打印的pdf文件
2、實(shí)現(xiàn)結(jié)果
具體直接最后一句document.close();
調(diào)用打印機(jī)后,對(duì)應(yīng)的打印隊(duì)列中有任務(wù),則說明實(shí)現(xiàn)成功
3、查看客戶端打印機(jī)
總結(jié)
-
具體實(shí)現(xiàn)上需要注意文本域的名稱要對(duì)應(yīng)上;
-
局限是需要再客戶端服務(wù)器需要在同一個(gè)局域網(wǎng),如果不是的話,則客戶端的接口也需要映射出去用于給服務(wù)器調(diào)用。文章來源:http://www.zghlxwxcb.cn/news/detail-644215.html
-
客戶端的代碼實(shí)際上可以轉(zhuǎn)為jar后通過 exe4j 轉(zhuǎn)為.exe文件,再通過Inno Setup 把jdk綁定進(jìn)去。文章來源地址http://www.zghlxwxcb.cn/news/detail-644215.html
到了這里,關(guān)于java靜默打印PDF(可實(shí)現(xiàn)生產(chǎn)環(huán)境下服務(wù)器寫入PDF模板,然后調(diào)用客戶端打印機(jī)打?。┑奈恼戮徒榻B完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!