itext7史上最全實戰總結

1. itext7史上最全實戰總結

1.1. 前言

最近有個需求須要我用Java手動寫一份PDF報告,通過考察幾種pdf開源代碼,最終選取了itext7,此版本爲7.1.11,因爲發現網上關於該工具的博文比較少,特別是實戰博文幾乎沒有,在我踩完各類坑,最終把PDF成型後,打算把經驗分享出來,本文經過摘錄解釋來講明,內容來自本人GitHub itext-pdfcss

1.2. 配置文件

項目採用了Spring Cloud config因此配置在git上,僅僅研究itext7不須要用到數據庫等功能,請直接運行PdfMain類的main方法,便可生成模擬的PDF報告html

1.3. 版本POM

itext7相關pom前端

<properties>
    <itext.version>7.1.11</itext.version>
</properties>
<dependencies>
    <!-- itext7 -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>kernel</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>io</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>layout</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>forms</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>pdfa</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>pdftest</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>font-asian</artifactId>
        <version>${itext.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.18</version>
    </dependency>

    <!--itext7 html轉pdf用到的包-->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>html2pdf</artifactId>
        <version>3.0.0</version>
    </dependency>
</dependencies>

1.4. 乾貨

itext7語義自己和前端css很像,因此有點前端基礎仍是比較容易掌握的java

1.4.1. 添加圖片

  1. 讀取項目中圖片文件
  2. 設置邊距
  3. 設置寬高擴大縮小
Image indexImage = new Image(ImageDataFactory.create(GenoReportBuilder.class.getClassLoader().getResource("image/gene.png")));
indexImage.setMargins(-50, -60, -60, -60);
indexImage.scale(1, 1.05f);

1.4.2. 添加指定空白頁

  1. 添加第2頁爲空白頁,當即刷新後再繼續添加
pdf.addNewPage(2).flush();

1.4.3. Div、Paragraph

Div div = new Div();
    div.setWidth(UnitValue.createPercentValue(100));
    div.setHeight(UnitValue.createPercentValue(100));
    div.setHorizontalAlignment(HorizontalAlignment.CENTER);
    Paragraph p1 = new Paragraph();
    p1.setHorizontalAlignment(HorizontalAlignment.CENTER);
    p1.setMaxWidth(UnitValue.createPercentValue(75));
    p1.setMarginTop(180f);
    p1.setCharacterSpacing(0.4f);
    Style large = new Style();
    large.setFontSize(22);
    large.setFontColor(GenoColor.getThemeColor());
    p1.add(new Text("尊敬的 ").addStyle(large));
    ...
    Paragraph p2 = new Paragraph();
    ...
    div.add(p1);
    div.add(p2);
  1. 整塊的內容用Div包裹,這裏整塊包裹的好處是什麼?一方面排版分明成體系,另外一方面若需求是整塊的內容必須在同一個版面,你能夠對Div設置div.setKeepTogether(true);,儘可能保證若整塊的內容超出了一頁,那這塊內容會自動整塊出如今下一頁,上一頁剩下的就留白了
  2. 能夠看到DivParagraph能夠設置不少屬性,實際上咱們經常使用的組件除了這兩種,還有TableCellList,他們大部分的屬性都是同樣的,只是部分屬性只在部分組件起效果,因此當你設置某個屬性沒起效果也不用奇怪
  3. Paragraph須要特別注意的一點,想要段落文字居中,不要用setHorizontalAlignment(HorizontalAlignment.CENTER);這是組件的居中對段落無效,甚至對段落裏你放Text也無效,須要改用setTextAlignment(TextAlignment.CENTER);
  4. Paragraph段落的行距也是個高頻問題,這裏給出官方我看到的解釋,參考https://itextpdf.com/en/resources/books/itext-7-building-blocks/chapter-4-adding-abstractelement-objects-part-1,搜關鍵字setFixedLeading,個人理解該方法設值行高絕對值,官方解釋是兩行文字中間基線之間的距離
  5. 若是想了解詳細的什麼屬性哪裏能起做用哪裏不行,請訪問該地址

UTOOLS1590980957507.png

1.4.4. Table

  1. useAllAvailableWidth表示頁面有多寬,我就有多寬
  2. table.startNewRow();表示新起一行,table每畫一行都要新起一行
  3. 一樣table內容須要居中,和段落同樣,請設置new Cell().setTextAlignment(TextAlignment.CENTER)
  4. 每一個table中cell都有默認高度,會比實際輸入字體高些,此時設置setHeight,若更大沒有問題,若高度小於或接近字體大小文字可能就消失了,若想讓Cell高度更接近文字高度,請設置Cellpadding,即cell.setPadding(-2),設置負值便可

1.4.5. Tab,\t

  1. itext7中若是要表示段落前的空格,不能使用\t,但換行可使用\ngit

  2. 若要實現Tab效果能夠有多個方法github

    1. \u00a0符號,大概七、8個該符號可表示tab,可能不是很準確
    p1.add(new Text("\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0壹基因衷心祝願您身體健康、享受品質生活!"));
    1. p1.setFirstLineIndent(24),表示段落前留多少空,須要知道一個字多大,設置成兩倍就行
    2. Tab也是集成AbstractElement的組件,經過如下方式也可實現相同的效果
    p2.add(new Tab());
      p2.addTabStops(new TabStop(20, TabAlignment.LEFT));

1.4.6. 換頁

我經常使用的換頁方法爲以下,該方法可保證當即換頁數據庫

doc.add(new AreaBreak(AreaBreakType.NEXT_PAGE));

固然PdfDocumentaddNewPage其實也能夠用,但有時候你沒把握好刷新時間可能致使某些混亂ide

1.4.7. 畫圖或畫文字

能畫出多麼複雜的圖形看是誰畫了,在個人PDF中,我畫的最複雜的圖形以下工具

UTOOLS1590982696170.png

該圖形由多個弧形區域加線段加文字組成,包括數字上的小箭頭也是畫出來的,畫這個的代碼過多,想要了解詳細的能夠自行下載研究,這裏介紹API功能字體

  1. lineTo畫線段
  2. roundRectangle可用來畫角是弧形的方形,也能夠用來畫圓
  3. showText用來畫文字

以上幾種結合填充便可把三角形,多邊形畫出來了

PdfPage page = pdf.getPage(pdf.getNumberOfPages());
    pageSize = pdf.getDefaultPageSize();
    PdfCanvas pdfCanvas = new PdfCanvas(page);

    pdfCanvas.saveState().moveTo(pageSize.getWidth() / 2 - 100 + i * 40, yOffset - 203)
                    .lineTo(pageSize.getWidth() / 2 - 100 + i * 40, yOffset - 208)
                    .stroke().restoreState();

    pdfCanvas.setLineWidth(2);
        pdfCanvas.setStrokeColor(color);
        pdfCanvas.roundRectangle(pageSize.getWidth() / 2 - 3 + posXOffset, yOffset - 188, 6, 6, 3)
                .stroke();

    pdfCanvas.beginText()
                .setFontAndSize(font, 12)
                .moveText(pageSize.getWidth() / 2 - text.length() * 12 / 2, yOffset - 45);
        pdfCanvas.showText(text);
        pdfCanvas.endText();

1.4.8. Html段落轉Pdf段落

咱們可能遇到把一段Html文本轉換成itext7的段落放進來,此時須要用到它的htmlToPdf模塊,該模塊對應POM

<!--itext7 html轉pdf用到的包-->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>html2pdf</artifactId>
        <version>3.0.0</version>
    </dependency>

至於使用,設置好配置屬性,使用也很簡單,一般咱們須要支持中文,全部配置以下,字體能夠本身換

ConverterProperties proper = new ConverterProperties();
    //字體設置,解決中文不顯示問題
    FontSet fontSet = new FontSet();
    fontSet.addFont(GenoReportBuilder.class.getClassLoader().getResource("font/SourceHanSansCN-Regular.ttf").getPath(), PdfEncodings.IDENTITY_H);

    FontProvider fontProvider = new FontProvider(fontSet);
    proper.setFontProvider(fontProvider);

    String content = "html內容";
    List<IElement> elements = HtmlConverter.convertToElements(content, proper);

轉換的內容是IElement集合,而IElement是什麼呢?給張圖就瞭解了

UTOOLS1590990939918.png

也就是說只要你的html內容是<div></div>包裹的,你直接把元素轉成itext7的Div而後adddocument就能夠實現html內容的添加了,固然你也能夠用instanceof判斷不一樣內容不一樣處理

以下是個人處理例子供參考,我把輸入html內容樣式進行了必定修改後轉成itext7組件,這裏特別提心,html轉過來的itext7組件可能會不支持部分樣式的修改,因此須要在html中進行css樣式的添加,這裏我就把字體和高度統一用css設值了

Div overall = new Div();
    java.util.List<IElement> iElements = getFixContent(value);
    for (IElement iElement : iElements) {
        Style style = new Style();
        style.setFontSize(10);
        style.setCharacterSpacing(0.7f);
        if (iElement instanceof Div) {
            Div div = (Div) iElement;
            java.util.List<IElement> children = div.getChildren();
            // 所有段落改爲相一樣式
            this.addParagraphStyleCircle(style, children);
            overall.add(div);
        } else if (iElement instanceof Paragraph) {
            Paragraph element = (Paragraph) iElement;
            overall.add(element.addStyle(style));
        }
    }
    doc.add(overall);
  • getFixContent
private java.util.List<IElement> getFixContent(String content) {
        if (content.startsWith("<div>")) {
            content = content.replaceAll("<div>", "<div style='line-height:18pt;font-size:16px;'>");
        } else {
            content = "<div style='line-height:18pt;font-size:16px;'>" + content + "</div>";
        }
        return HtmlConverter.convertToElements(content, proper);
    }
  • addParagraphStyleCircle
private void addParagraphStyleCircle(Style style, java.util.List<IElement> children) {
        for (IElement child : children) {
            if (child instanceof Paragraph) {
                Paragraph element = (Paragraph) child;
                element.addStyle(style);
                java.util.List<IElement> children1 = element.getChildren();
                this.addParagraphStyleCircle(style, children1);
            }
            if (child instanceof Div) {
                Div div = (Div) child;
                java.util.List<IElement> children1 = div.getChildren();
                this.addParagraphStyleCircle(style, children1);
            }
            if (child instanceof Text) {
                Text text = (Text) child;
                text.addStyle(style);
            }
        }
    }

1.4.9. 監聽事件

在編寫pdf的時候,好比一篇總體的文章,咱們須要在頁眉位置添加關於這篇文章的固定文本或者圖形,相似於打個標籤,表示你翻了這麼多頁一直在看這篇文章,當第二篇文章的時候就換一個,舉個例子

  • 第一頁

UTOOLS1590992499119.png

  • 第二頁

UTOOLS1590992555997.png

這種需求咱們如何實現呢?思路分析發現,咱們須要知道何時文章內容一頁寫不起了,換了一頁的時候咱們須要添加一個一樣的頁眉。這樣咱們就須要知道頁是什麼時候添加的,監聽事件就是處理這種問題的

  • pdf是PdfDocument,可添加的事件有START_PAGEINSERT_PAGEREMOVE_PAGEEND_PAGE共四個,如上需求咱們須要監聽START_PAGE事件,在事件處理中作相應的處理,我在事件中使用PdfCanvas畫了頭部內容
HeaderTextEvent headerTextEvent = new HeaderTextEvent(title, font);
pdf.addEventHandler(PdfDocumentEvent.START_PAGE, headerTextEvent);
  • HeaderTextEvent類,Painting僅僅是封裝了PdfCanvas
public class HeaderTextEvent implements IEventHandler {

    private String text;
    private PdfFont font;

    public HeaderTextEvent(String text,PdfFont font) {
        this.text = text;
        this.font = font;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdfDoc = docEvent.getDocument();
        Painting painting = new Painting(pdfDoc, font);
        painting.drawHeader();
        painting.drawHeaderText(text);
        painting.close();
    }
}

在添加內容前添加相應事件,同時須要記得在不須要的時候移除

// 移除監聽器
pdf.removeEventHandler(PdfDocumentEvent.START_PAGE, headerTextEvent);

1.4.10. 添加目錄

我沒有找到itext7原生是否有目錄添加,根據我本身的需求,我用Table組件來實現了自定義目錄,因爲個人PDF是用來打印的,因此我並無給目錄添加Link,也就是頁面跳轉,不過當你完全理解了個人項目,我想這個需求實現也不難

  • 實現效果以下,隨着內容的增加,目錄自動增加

UTOOLS1590993651033.png

先說下遇到的困難,目錄顧明思意,必需要有內容纔會有目錄,因此實際上目錄是最後添加的,但若是咱們添加內容到最後再跳轉到前面的頁面來添加目錄,有三個問題:

  1. 目錄有幾頁如何知道?
  2. 目錄有幾頁不知道,如何知道內容在第幾頁?
  3. 因爲目錄不肯定,因此後續內容的頁碼其實也是不肯定的,也就是說頁碼也不是一頁頁能夠添加過去的

而通過實踐你會發現,咱們不可以回到前幾頁去修改已存在的頁面,由於會提示你已經flush了,不能修改。

這時我看到了movePage這個方法,也就是能夠經過移動頁面,把目錄在內容以後生成,後再移動到前幾頁,可是頁碼仍是不能修改,發現腦殼不夠想了只能用上屁股,靈光一閃,不能一遍生成爲何不能二次渲染呢?因而研究讀取原pdf在原pdf上修改,二次渲染的時候填上頁碼及移動頁面,主要代碼以下,包括了讀取中間文件,移動目錄,添加每頁頁碼

PdfReader reader = null;
PdfWriter writer = null;
String inPath = getInPath();
try {
    reader = new PdfReader(new File(inPath));
    writer = new PdfWriter(new File(outPath));
} catch (IOException e) {
    e.printStackTrace();
}
PdfDocument pdf = new PdfDocument(reader, writer);
Document doc = new Document(pdf);
int startPage = 7;
int numberOfPages = pdf.getNumberOfPages();
for (int i = 0; i < catalogSize; i++) {
    pdf.movePage(numberOfPages, startPage);
}
String forbidPage = properties.getProperty("forbidPage");
for (int pageNumber = 1; pageNumber < numberOfPages + 1; pageNumber++) {

    if (pageNumber > 6 + catalogSize && pageNumber != 8 + catalogSize) {
        if (forbidPage != null && (pageNumber - catalogSize) >= Integer.parseInt(forbidPage)) {
            continue;
        }
        PageSize pageSize = pdf.getDefaultPageSize();
        doc.showTextAligned(new Paragraph(String.format("- %d -", pageNumber)), pageSize.getWidth() / 2, 30, pageNumber, TextAlignment.CENTER, VerticalAlignment.MIDDLE, 0);
    }
}

1.5. 總結

通過上述總結,我基本上把項目中的大多基本點和難點都歸納進去了,初次用itext7寫PDF的同窗基本會遇到的問題基本都在上述這些,不理解的就把項目下下來運行Main方法慢慢調試,理解透我這個項目,還有其它問題那基本只能翻官網

項目Github: https://github.com/tzxylao/onegeno-itext-pdf
itext7官網:https://itextpdf.com/

相關文章
相關標籤/搜索