Java使用PDFBox渲染生成pdf文檔的代碼詳解
使用PDFBox可以渲染生成pdf文檔,并且自定義程度高,只是比較麻煩,pdf的內(nèi)容位置都需要手動設置x(橫向)和y(縱向)絕對位置,但是每個企業(yè)的單據(jù)都是不一樣的,一般來說都會設置一個模板,然后內(nèi)容再填充到適當位置,所以這個功能還是有用的
實際效果
填充數(shù)據(jù)后效果
實現(xiàn)代碼
以下代碼基于PDFBox依賴版本-2.0.23
public class Demo01 { public static void main(String[] args) throws Exception{ // 設定中文字體 File fontFile = new File("C:\\Windows\\Fonts\\simHei.ttf"); try (PDDocument document = new PDDocument()) { PDType0Font load = PDType0Font.load(document, fontFile); PDPage page; for (int i = 0; i < 1; i++) { page = new PDPage(); document.addPage(page); // 對具體PDPage設定內(nèi)容 try(PDPageContentStream contentStream = new PDPageContentStream(document, page)) { contentStream.setFont(load, 25); contentStream.beginText(); // newLineAtOffset方法 contentStream.newLineAtOffset(220, 750); contentStream.showText("借用出庫打印單"); contentStream.setFont(load, 12); contentStream.endText(); // 倉庫和會員渲染位置 contentStream.beginText(); contentStream.newLineAtOffset(80, 700); // 80,700 contentStream.showText("倉庫:"); contentStream.newLineAtOffset(300, 0); //380,700 contentStream.showText("會員:"); contentStream.endText(); // 銷售員和操作人渲染位置 contentStream.beginText(); contentStream.newLineAtOffset(80, 675); // 80,675 contentStream.showText("銷售員:"); contentStream.newLineAtOffset(300, 0); //380,675 contentStream.showText("操作人:"); contentStream.endText(); // 操作時間位置 contentStream.beginText(); contentStream.newLineAtOffset(80, 650); // 80,650 contentStream.showText("操作時間:"); contentStream.endText(); // ----------------實際內(nèi)容----------------------- // 表頭 contentStream.beginText(); contentStream.newLineAtOffset(80, 625); //80,625 contentStream.showText("序號"); contentStream.newLineAtOffset(40, 0); //120,625 contentStream.showText("商品編號"); contentStream.newLineAtOffset(80, 0); //200,625 contentStream.showText("商品名稱"); contentStream.newLineAtOffset(70, 0); //270,625 contentStream.showText("單位"); contentStream.newLineAtOffset(40, 0); //310,625 contentStream.showText("借出數(shù)量"); contentStream.newLineAtOffset(70, 0); //380,625 contentStream.showText("備注"); contentStream.newLineAtOffset(100, 0); //480,625 contentStream.showText("零售價"); contentStream.endText(); Map<String, String> contentMap = new HashMap<>(); contentMap.put("序號", "1"); contentMap.put("商品編號", "000212130023"); contentMap.put("商品名稱", "洗地機124123"); contentMap.put("單位", "個"); contentMap.put("借出數(shù)量", "13"); contentMap.put("備注", "我是備注我是備注"); contentMap.put("零售價", "1123300.34"); fillContent(contentStream, contentMap, load); // 結(jié)尾結(jié)構渲染 // 合計位置 contentStream.beginText(); contentStream.newLineAtOffset(80, 150); // 80,150 contentStream.showText("合計"); contentStream.endText(); // 出庫數(shù)量和總金額位置 contentStream.beginText(); contentStream.newLineAtOffset(110, 125); // 110,125 contentStream.showText("出庫數(shù)量:"); contentStream.newLineAtOffset(270, 0); // 380,125 contentStream.showText("總金額:"); contentStream.endText(); // 簽名位置 contentStream.beginText(); contentStream.newLineAtOffset(80, 50); // 110,125 contentStream.showText("簽名:_______"); contentStream.endText(); // 模擬填充模板 Map<String, String> map = new HashMap<>(); map.put("倉庫", "上海倉"); map.put("會員", "小明"); map.put("銷售員", "銷售員01"); map.put("操作人", "系統(tǒng)管理員"); map.put("操作時間", "2025年4月1日23點07分"); map.put("出庫數(shù)量", "1455"); map.put("總金額", "285743835.45"); fillTemplate(contentStream, map); } } document.save("demo01.pdf"); System.out.println("PDF created successfully!"); } catch (IOException e) { throw new RuntimeException(e); } } // 填充固定模板方法 該方法不填充中間詳細內(nèi)容 public static void fillTemplate(PDPageContentStream contentStream, Map<String, String> map) { try { contentStream.beginText(); contentStream.newLineAtOffset(130, 700); contentStream.showText(map.get("倉庫")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(430, 700); contentStream.showText(map.get("會員")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(130, 675); contentStream.showText(map.get("銷售員")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(430, 675); contentStream.showText(map.get("操作人")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(150, 650); contentStream.showText(map.get("操作時間")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(180, 125); contentStream.showText(map.get("出庫數(shù)量")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(430, 125); contentStream.showText(map.get("總金額")); contentStream.endText(); } catch (IOException e) { throw new RuntimeException(e); } } public static void fillContent(PDPageContentStream contentStream, Map<String, String> map, PDType0Font font) { try { contentStream.setFont(font, 10); contentStream.beginText(); contentStream.newLineAtOffset(80, 600); contentStream.showText(map.get("序號")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(120, 600); contentStream.showText(map.get("商品編號")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(200, 600); contentStream.showText(map.get("商品名稱")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(270, 600); contentStream.showText(map.get("單位")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(310, 600); contentStream.showText(map.get("借出數(shù)量")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(380, 600); contentStream.showText(map.get("備注")); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(480, 600); contentStream.showText(map.get("零售價")); contentStream.endText(); contentStream.setFont(font, 12); } catch (IOException e) { throw new RuntimeException(e); } } }
上述代碼看著確實是挺繁瑣,每個內(nèi)容的位置都需要設置x和y值,但是沒辦法
PDF文件本質(zhì)是「坐標畫布」
- PDF的渲染模型基于絕對坐標系(原點在頁面左下角),所有元素(文字、圖形)必須明確指定位置(x,y)。
- 無布局引擎:PDF規(guī)范未定義“自動換行”或“文檔流”等高級排版概念,開發(fā)者需自行計算坐標。
但是這樣設計的好處就是自定義程度高,你可以任意設計一個PDF文檔的模板應該是什么樣子,內(nèi)容該如何填充全部由你自由設定,就像低代碼平臺一樣,市面上成熟開源的低代碼平臺有許多,但是邏輯都是一開始就定好的,如果你想加上許多符合自己公司需求的功能但是平臺沒有那么都得自行開發(fā),并且自行開發(fā)的代碼融合進已有的系統(tǒng)不是一件容易的事情,甚至比自行開發(fā)一套系統(tǒng)都麻煩。
所以如果你有這樣的需求可以看下上述代碼實現(xiàn),上述代碼只是一個簡單的demo,我只是進行記錄方便自己以后用到。
tips:關于一些方法的解釋
contentStream.beginText(); contentStream.newLineAtOffset(80, 700); // 80,700 --絕對定位 contentStream.showText("倉庫:"); contentStream.newLineAtOffset(300, 0); //380,700 --相對定位(以'倉庫:'的位置為準) contentStream.showText("會員:"); contentStream.endText(); contentStream.beginText(); contentStream.newLineAtOffset(80, 675); // 80,675 --絕對定位 contentStream.showText("銷售員:"); contentStream.newLineAtOffset(300, 0); //380,675 --相對定位(以'銷售員'的位置為準) contentStream.showText("操作人:"); contentStream.endText();
上述代碼可以看到在渲染內(nèi)容時是被包裹在beginText()和endText()方法中間的,這樣當你調(diào)用newLineAtOffset(x, y)方法時參數(shù)中的x和y才從坐標系的絕對位置(絕對位置為畫布的左下角0,0)進行定位。如果你在定位時沒有重新開啟beginText()和endText()時,調(diào)用newLineAtOffset(x, y)方法則是參照上一個文本的位置進行相對定位的,相對定位對于需要在同一行的不同位置渲染內(nèi)容會比較方便。
newLineAtOffset(x, y)方法的官方注釋有問題,官方說法是移動到下一行的開頭,從當前行的開頭進行偏移 (x, y),實測不對,并不會移動到下一行的開頭,并且在相對定位時參考的位置也是你上一次的位置的起始點。
如果你需要像寫文章那樣一段一段的文字進行渲染,那么可以考慮使用另外一個方法
contentStream.beginText(); contentStream.newLineAtOffset(80, 500); // 設定絕對位置的起點 contentStream.setLeading(20); // 文本行距 contentStream.showText("XXXXX"); //渲染內(nèi)容 contentStream.newLine(); //開啟新行 contentStream.showText("XXXXX"); //渲染內(nèi)容 contentStream.newLine(); //開啟新行 contentStream.showText("XXXXX"); //渲染內(nèi)容 contentStream.newLine(); //開啟新行 contentStream.endText();
這個方法更適合大段連貫的文字渲染,你只要設定好固定行距之后就可以直接開啟新行,新行的位置會成功進入到下一行的開頭并且行距就是你設定的值,這樣你就不用每次都自行定位了,效果如下
到此這篇關于Java使用PDFBox渲染生成pdf文檔的代碼詳解的文章就介紹到這了,更多相關Java PDFBox渲染pdf內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
maven+阿里云創(chuàng)建國內(nèi)鏡像的中央倉庫(親測可用)
本篇文章主要介紹了maven+阿里云創(chuàng)建國內(nèi)鏡像的中央倉庫(親測可用),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12解決SpringBoot jar包中的文件讀取問題實現(xiàn)
這篇文章主要介紹了解決SpringBoot jar包中的文件讀取問題實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08java如何根據(jù)IP獲取當前區(qū)域天氣信息詳解
根據(jù)IP自動獲取當?shù)氐奶鞖忸A報信息這個功能大家應該都遇到過,天氣預報信息用途非常廣泛,篇文章主要給大家介紹了關于java如何根據(jù)IP獲取當前區(qū)域天氣信息的相關資料,需要的朋友可以參考下2021-08-08Java多線程編程中使用Condition類操作鎖的方法詳解
Condition是java.util.concurrent.locks包下的類,提供了對線程鎖的更精細的控制方法,下面我們就來看一下Java多線程編程中使用Condition類操作鎖的方法詳解2016-07-07解決mybatis-plus動態(tài)數(shù)據(jù)源切換不生效的問題
本文主要介紹了解決mybatis-plus動態(tài)數(shù)據(jù)源切換不生效的問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-01-01Spring Cloud應用實現(xiàn)配置自動刷新過程詳解
這篇文章主要介紹了Spring Cloud應用實現(xiàn)配置自動刷新過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12idea自帶database連接mysql失敗問題的解決辦法
在IDEA?帶的數(shù)據(jù)庫連接?具中,可以連接MySQL數(shù)據(jù)庫,但是有的時候連接出現(xiàn)錯誤,連接不上數(shù)據(jù)庫,下面這篇文章主要給大家介紹了關于idea自帶database連接mysql失敗問題的解決辦法,需要的朋友可以參考下2023-06-06