1. 概述

iText 是一个用于创建和操作PDF文件的Java库。水印有助于保护机密信息

在这个教程中,我们将使用iText PDF库来创建包含水印的新PDF文件,并向现有PDF添加水印。

2. Maven依赖项

在本教程中,我们将使用Maven管理依赖关系。我们需要iText依赖以开始使用iText PDF库。此外,我们还需要AssertJ依赖进行测试。我们将这两个依赖添加到我们的pom.xml中:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext7-core</artifactId>
    <version>7.2.4</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.25.3</version>
    <scope>test</scope>
</dependency>

3. 水印

水印可以帮助在文档或图像文件上叠加或底层放置文本或logo。对于版权保护、数字产品的市场推广以及防止伪造等方面至关重要

在这个教程中,我们将为生成的PDF添加保密水印,防止未经授权的使用:

PDF上的水印

4. 使用iText生成PDF

在这篇文章中,我们将编写一个故事并使用iText PDF库将其转换为PDF格式。我们将创建一个简单的程序StoryTime。首先,我们声明两个String类型的变量,将故事存储在声明的变量中:

public class StoryTime {
    String aliceStory = "I am ...";
    String paulStory = "I am Paul ..";
}

为了简洁,我们将String值缩短。然后,声明一个String类型的变量,用于存储生成的PDF的输出路径:

public static final String OUTPUT_DIR = "output/alice.pdf";

最后,创建一个方法,包含程序逻辑。我们将创建一个PdfWriter实例,指定输出路径和名称。

接下来,我们将创建一个PdfDocument实例来处理我们的PDF文件。为了将String值添加到PDF文档中,我们将创建一个新的Document实例:

public void createPdf(String output) throws IOException {
    
    PdfWriter writer = new PdfWriter(output);
    PdfDocument pdf = new PdfDocument(writer);
    try (Document document = new Document(pdf, PageSize.A4, false)) {
        document.add(new Paragraph(aliceSpeech)
          .setFont(PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN)));
        document.add(new Paragraph(paulSpeech)
          .setFont(PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN)));
        document.close();
    }
}

我们的方法将生成一个新的PDF文件,并将其存储在OUTPUT_DIR中。

5. 在生成的PDF上添加水印

在上一节中,我们使用iText PDF库生成了一个PDF文件。首先生成PDF有助于了解页面大小、旋转和页数,这有助于有效地添加水印。现在,让我们在简单程序中添加更多逻辑。我们的程序将在生成的PDF上添加水印。

首先,创建一个方法来指定水印的属性。我们将设置水印的FontfontSizeOpacity

public Paragraph createWatermarkParagraph(String watermark) throws IOException {
    
    PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA);
    Text text = new Text(watermark);
    text.setFont(font);
    text.setFontSize(56);
    text.setOpacity(0.5f);
    return new Paragraph(text);
}

接下来,创建一个方法,包含将水印添加到PDF文档的逻辑。该方法将接受DocumentParagraphoffset作为参数。我们将计算水印段落的位置和旋转:

public void addWatermarkToGeneratedPDF(Document document, int pageIndex, 
  Paragraph paragraph, float verticalOffset) {
    
    PdfPage pdfPage = document.getPdfDocument().getPage(pageIndex);
    PageSize pageSize = (PageSize) pdfPage.getPageSizeWithRotation();
    float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
    float y = (pageSize.getTop() + pageSize.getBottom()) / 2;
    float xOffset = 100f / 2;
    float rotationInRadians = (float) (PI / 180 * 45f);
    document.showTextAligned(paragraph, x - xOffset, y + verticalOffset, 
      pageIndex, CENTER, TOP, rotationInRadians);
}

通过调用showTextAligned()方法,我们将水印段落添加到文档中。接下来,编写一个方法,它将生成新的PDF并添加水印。我们将调用createWatermarkParagraph()addWatermarkToGeneratedPDF()方法:

public void createNewPDF() throws IOException {
    
    StoryTime storyTime = new StoryTime();
    String waterMark = "CONFIDENTIAL";
    PdfWriter writer = new PdfWriter(storyTime.OUTPUT_FILE);
    PdfDocument pdf = new PdfDocument(writer);
        
    try (Document document = new Document(pdf)) {
        document.add(new Paragraph(storyTime.alice)
          .setFont(PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN)));
        document.add(new Paragraph(storyTime.paul));
        Paragrapgh paragraph = storyTime.createWatermarkParagraph(waterMark);
        for (int i = 1; i <= document.getPdfDocument().getNumberOfPages(); i++) {
            storyTime.addWatermarkToGeneratedPDF(document, i, paragraph, 0f);
        }
    }
}

最后,编写一个单元测试来验证水印的存在:

@Test
public void givenNewTexts_whenGeneratingNewPDFWithIText() throws IOException {
 
    StoryTime storyTime = new StoryTime();
    String waterMark = "CONFIDENTIAL";
    LocationTextExtractionStrategy extStrategy = new LocationTextExtractionStrategy();
    try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(storyTime.OUTPUT_FILE))) {
        for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
            String textFromPage = getTextFromPage(pdfDocument.getPage(i), extStrategy);
            assertThat(textFromPage).contains(waterMark);
        }
    }
}

我们的测试验证了生成PDF中的水印存在。

6. 向现有PDF添加水印

iText PDF库使得向现有PDF添加水印变得容易。首先,我们需要将PDF文档加载到程序中,并使用iText库操纵现有的PDF。

首先,我们需要创建一个方法来添加水印段落。因为我们已经在上一节创建过,所以这里也可以使用。

接下来,创建一个方法,其中包含逻辑,帮助我们在现有PDF上添加水印。该方法将接受DocumentParagraphPdfExtGStatepageIndexoffSet作为参数。在方法中,我们将创建一个PdfCanvas实例,用于向PDF内容流写入数据。

然后,我们将在PDF上计算水印的位置和旋转。我们将刷新文档并释放状态以提高性能:

public void addWatermarkToExistingPDF(Document document, int pageIndex,
  Paragraph paragraph, PdfExtGState graphicState, float verticalOffset) {
    
    PdfDocument pdfDocument = document.getPdfDocument();
    PdfPage pdfPage = pdfDocument.getPage(pageIndex);
    PageSize pageSize = (PageSize) pdfPage.getPageSizeWithRotation();
    float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
    float y = (pageSize.getTop() + pageSize.getBottom()) / 2;
    
    PdfCanvas over = new PdfCanvas(pdfDocument.getPage(pageIndex));
    over.saveState();
    over.setExtGState(graphicState);
    float xOffset = 14 / 2;
    float rotationInRadians = (float) (PI / 180 * 45f);
    
    document.showTextAligned(paragraph, x - xOffset, y + verticalOffset, 
      pageIndex, CENTER, TOP, rotationInRadians);
    document.flush();
    over.restoreState();
    over.release();
}

最后,编写一个方法来向现有PDF添加水印。我们将调用createWatermarkParagraph()来添加水印段落,并调用addWatermarkToExistingPDF()来处理添加水印到页面的任务:

public void addWatermarkToExistingPdf() throws IOException {
    
    StoryTime storyTime = new StoryTime();
    String outputPdf = "output/aliceNew.pdf";
    String watermark = "CONFIDENTIAL";
    
    try (PdfDocument pdfDocument = new PdfDocument(new PdfReader("output/alice.pdf"), 
      new PdfWriter(outputPdf))) {
        Document document = new Document(pdfDocument);
        Paragraph paragraph = storyTime.createWatermarkParagraph(watermark);
        PdfExtGState transparentGraphicState = new PdfExtGState().setFillOpacity(0.5f);
        for (int i = 1; i <= document.getPdfDocument().getNumberOfPages(); i++) {
            storyTime.addWatermarkToExistingPage(document, i, paragraph, 
              transparentGraphicState, 0f);
        }
    }
}

编写一个单元测试来验证水印的存在:

@Test
public void givenAnExistingPDF_whenManipulatedPDFWithITextmark() throws IOException {
    StoryTime storyTime = new StoryTime();
    String outputPdf = "output/aliceupdated.pdf";
    String watermark = "CONFIDENTIAL";
        
    LocationTextExtractionStrategy extStrategy 
      = new LocationTextExtractionStrategy();
    try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(outputPdf))) {
        for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
            String textFromPage = getTextFromPage(pdfDocument.getPage(i), extStrategy);
            assertThat(textFromPage).contains(watermark);
        }
    }
}

我们的测试验证了现有PDF中的水印存在。

7. 结论

在这篇教程中,我们探索了iText PDF库,生成了新的PDF,并向生成的PDF和现有PDF添加了水印。iText库在操作PDF方面显得非常强大。完整的代码可以在GitHub上找到。