這周寫了一周的需求,是制作一個PDF生成功能,其中用到了Itext來制作PDF的視覺效果。其中一些功能不是很懂,僅作記錄,若要學習請仔細甄別正確與否。
開始之前,我還是想說,這傻福需求怎么想出來的,讓人手擼PDF,哥們一兩頁PDF寫了快1k行代碼,代碼量大并且感覺極難維護。這次遇見的難點主要有:
1:對于分頁的處理。
這個問題真的困擾了很久,現在也無法良好解決。比如在遇見我寫一個動態表格的時候,發生了分頁,但我一旦分頁我要先添加一些別的表頭信息再繼續我的分頁實現。這個時候我首先想到了用事件監聽之類的,在發生分頁時自動插入某些函數。
注冊事件監聽器:
pdfDoc.addEventHandler(PdfDocumentEvent.START_PAGE,new PageNumberEventHandler(font, boldFont));
具體實現(GPT實現)
private static class PageNumberEventHandler implements IEventHandler {private final PdfFont font;private final PdfFont boldFont;public PageNumberEventHandler(PdfFont font, PdfFont boldFont) {this.font = font;this.boldFont = boldFont;}@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent) event;PdfDocument pdfDoc = docEvent.getDocument();PdfPage page = docEvent.getPage();int pageNumber = pdfDoc.getPageNumber(page);int totalPages = pdfDoc.getNumberOfPages();// 更新靜態變量InvoiceGenerator.currentPage = pageNumber;InvoiceGenerator.totalPages = totalPages;Rectangle pageSize = page.getPageSize();PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamBefore(),page.getResources(), pdfDoc);// 定義繪制區域(關鍵修正)Rectangle rootArea = new Rectangle(36, 36,pageSize.getWidth() - 72,pageSize.getHeight() - 72);// 創建Canvas對象Canvas canvas = new Canvas(pdfCanvas, rootArea);// 如果是續頁(第2頁及以后),添加續篇表頭if (pageNumber > 1) {try {addContinuationHeader(canvas, font, boldFont, pageNumber);} catch (IOException e) {throw new RuntimeException(e);}}// 添加頁碼(右上角)Paragraph pageInfo = new Paragraph().add(String.format(PAGE_X_OF_Y_TEXT, pageNumber, totalPages)).setFont(font).setFontSize(8).setFixedPosition(pageSize.getRight() - 36,pageSize.getTop() - 20,100).setTextAlignment(TextAlignment.RIGHT);canvas.add(pageInfo);// 添加修訂信息(左下角)Paragraph revInfo = new Paragraph(REV_INFO).setFont(font).setFontSize(7).setFixedPosition(36, 20, 100);canvas.add(revInfo);canvas.close();pdfCanvas.release();}
我后面發現,handler實現出來要寫在一個canvas里面,而我開始是把所有內容繪制在一個document里面的,這就導致我把canvas放在document里面的時候document里面的內容無法正確識別出我加入的canvas,然后形成了內容的疊加。遂放棄了用監聽器。
手動計算分頁
比如計算一頁最多可以放多少數據,然后放了這么多條我就新建一頁
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));// 強制分頁
然后繼續在這一頁里面寫。這樣的缺點就是我要計算出準確的能放多少數據,如果數據的變化太多這個方法就不行了。其實后面就遇見了,因為后面插入的每條數據的寬度不一樣。然后我之前想的是。把寬度計算出來,結果我第一頁的寬度計算和后面的不一樣,縷了半小時思路還是有問題。
實際場景是:
插入items里面的每一行數據,按道理來說每一行setbond是(10),但是里面的詳情字段可能會超長,然后導致換行,我這里就把這個item當作了兩行,這樣確實可以做到準確分頁,但是會出現的問題是,如果分頁剛好發生在換行這個item,那么我這個item就應該到下面去,我設定的是第一頁只存12行,存過了就分頁。但這樣如果我第4個item的時候前面已經有10行了,那么我第4行就要超過了,所以第4行就應該放到下一個去。然后當時就是設置了一個總行數totalNum和一個真實到了哪一個item的數量currentNum。第一頁的邏輯就是。總行數totalNum就是每次calculateLineBreaks(commercialInvoiceDto.getItems().get(i).getDescription()
? ? ? ? ? ? ? ? ? ? , font
? ? ? ? ? ? ? ? ? ? , 8
? ? ? ? ? ? ? ? ? ? , 340)得到的行數,這個函數就是計算我這個詳情實際占多少行。
if(i+calculateLineBreaks(commercialInvoiceDto.getItems().get(i).getDescription(), font, 8, 340)<12)
那么在第二頁的邏輯的時候,我就傳一個currentNum去代表這一頁開始遍歷的item的index。這一頁可以存40行數據,那么我每遍歷一個currentNum進去,我的totalNum就加一次calculateLineBreaks,然后如果下一次+calculateLineBreaks會超過40我就提前分頁。這樣我感覺從邏輯上是能行通的。結果后面告訴我不想要這種換行的效果,要采用字體縮放來解決。
字體縮放
結果這才是最痛苦的,本來打算周五可以摸摸魚看看八股,在GPT和百度的掙扎下,最開始老板告訴我有一個自適應縮放的,找了半天沒找到。我就打算采用百度上通過計算我這個字段,計算出他會占多少行,然后按照比例縮小字體大小。結果他的計算方式是大致計算,不能準確的識別出里面的空格,就導致縮放的有可能會不夠小。然后我采用一個處理溢出操作的方法,結果和注釋所說一樣,我的自適應縮放只能縮放到7f大小就不能更小了。
Paragraph para = new Paragraph(Des).setFont(font).setFontSize(fontSize).setWidth(maxWidth);para.setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);//只能自適應縮放到字體大小大于7f的,看見有什么MIN_FONT_SIZE字段,但我用的7.2.5沒有,我看了好久源碼也沒找到。
2:布局問題
邊框重合
在生成過程中,我發現有的線框會變得很粗,這是因為兩個外邊框的重合會引起變粗的效果。因此可以通過setBorder來操控,比如說和上面的方框重合了,就把上面的setBorderBottom設置為(NO_Border)或者把下面的setBorderTop。
同時注意,外邊框和內部文字的邊框,如果只想呈現一個框框里面只有文字沒有分割線的效果就只在new Table的時候設置邊框,如果文字中間需要分割就在Cell里面設置。
對齊問題
一行有多少內容可以存放是由開始設置的分割布局來決定的,因此如果有兩行內容可以放在一個table里面,但是注意存放的數量。
3:常見概念
(這是在下周一寫的了,哥們明明功能都寫完了,結果組長告訴我周五說我現在用的包不能過甲方審核,heartbreak了。然后后面嘗試用openPdf做,我感覺太麻煩了,剛好老板說不用做了,但是都寫這么多了,再說點我這次 遇見的常用的一些內容吧)
一、表格 (Table) 操作
-
表格初始化
PdfPTable table = new PdfPTable(3);
:創建一個包含 3 列的表格。table.setWidthPercentage(100);
:設置表格寬度占頁面寬度的 100%。table.setTotalWidth(new float[]{30, 20, 50});
:自定義列寬比例,這里三列的寬度比例分別為 30、20、50。
-
表格樣式屬性
table.setSpacingBefore(10f);
:設置表格上方的間距為 10 個單位。table.setSpacingAfter(10f);
:設置表格下方的間距為 10 個單位。table.setLockedWidth(true);
:鎖定列寬,防止列寬在后續操作中自動調整。table.getDefaultCell().setBorder(0);
:設置表格默認單元格無邊框。
二、單元格 (Cell) 操作
-
基礎單元格
PdfPCell cell = new PdfPCell(new Paragraph("內容"));
:創建一個包含文本內容的單元格。cell.setBackgroundColor(BaseColor.ORANGE);
:設置單元格的背景顏色為橙色。cell.setBorderColor(BaseColor.BLUE);
:設置單元格的邊框顏色為藍色。
-
高級屬性
cell.setColspan(2);
:使單元格橫向合并 2 列。cell.setRowspan(2);
:使單元格縱向合并 2 行。cell.setHorizontalAlignment(Element.ALIGN_CENTER);
:設置單元格內容水平居中對齊。cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
:設置單元格內容垂直居中對齊。
三、文本與段落
-
段落樣式
Paragraph p = new Paragraph("文本", FontFactory.getFont("SIMHEI", 12));
:創建一個包含文本的段落,并設置中文字體為 “黑體”,字號為 12。p.setAlignment(Element.ALIGN_CENTER);
:設置段落內容居中對齊。p.setSpacingBefore(5f);
:設置段落前的間距為 5 個單位。
四、圖形與圖像
-
插入圖片
Image img = Image.getInstance("logo.png");
:從指定路徑(這里是 “logo.png”)獲取圖片。PdfPCell imgCell = new PdfPCell(img, true);
:創建一個包含圖片的單元格,true
表示圖片將被縮放以適應單元格大小。
五、文檔元數據
document.addTitle("標題");
:設置文檔的標題。document.addKeywords("關鍵詞");
:添加文檔的關鍵詞。document.addCreator("創建者");
:設置文檔的創建者信息。