使用 PDFBox 加水印

Posted

技术标签:

【中文标题】使用 PDFBox 加水印【英文标题】:Watermarking with PDFBox 【发布时间】:2012-02-14 08:12:53 【问题描述】:

我正在尝试使用 PDFBox 为 PDF 添加水印。我已经能够让图像出现在每一页上,但它失去了背景透明度,因为它看起来好像 PDJpeg 将其转换为 JPG。也许有一种方法可以使用 PDXObjectImage。

这是我迄今为止所写的:

public static void watermarkPDF(PDDocument pdf) throws IOException

    // Load watermark
    BufferedImage buffered = ImageIO.read(new File("C:\\PDF_Test\\watermark.png"));
    PDJpeg watermark = new PDJpeg(pdf, buffered);

    // Loop through pages in PDF
    List pages = pdf.getDocumentCatalog().getAllPages();
    Iterator iter = pages.iterator();
    while(iter.hasNext())
    
        PDPage page = (PDPage)iter.next();

        // Add watermark to individual page
        PDPageContentStream stream = new PDPageContentStream(pdf, page, true, false);
        stream.drawImage(watermark, 100, 0);
        stream.close();
    

    try 
    
        pdf.save("C:\\PDF_Test\\watermarktest.pdf");
     
    catch (COSVisitorException e) 
    
        e.printStackTrace();
    

【问题讨论】:

以下答案的问题是,如果页面尺寸不同,则定位无法正常工作(左上对齐)。我需要在 PDF 文档的每一页顶部添加一个文本水印,而上面的解决方案正是我所需要的,所以我要改进这个解决方案。 【参考方案1】:

看看这个方法,使用 PDFBOX 库在 pdf 源中添加水印图像

/**
     * Coloca una imagen como marca de agua en un pdf en una posición especifica
     * 
     * @param buffer
     *            flujo de bytes que contiene el pdf original
     * 
     * @param imageFileName
     *            nombre del archivo de la imagen a colocar
     * 
     * @param x
     *            posición x de la imagen en el pdf
     * 
     * @param y
     *            posición y de la imagen en el pdf
     * 
     * @param under 
     * determina si la marca se pone encima o por debajo de la factura
     * 
     * @return flujo de bytes resultante que contiene el pdf modificado
     * 
     * @throws IOException
     * @throws DocumentException
     */
    public static byte[] addWaterMarkImageToPDF(byte[] buffer,
            String imageFileName, int x, int y, boolean under) throws IOException,
            DocumentException 
        logger.debug("Agregando marca de agua:"+imageFileName);
        PdfReader reader = new PdfReader(buffer);
        ByteArrayOutputStream arrayOutput = new ByteArrayOutputStream();
        OutputStream output = new BufferedOutputStream(arrayOutput);
        PdfStamper stamper = new PdfStamper(reader, output);
        com.lowagie.text.Image img = com.lowagie.text.Image
                .getInstance(imageFileName);
        img.setAbsolutePosition(x, y);
        img.scalePercent(SCALE_PER);
        PdfContentByte pdfContent;
        int total = reader.getNumberOfPages() + 1;
        for (int i = 1; i < total; i++) 
            pdfContent = (under)?stamper.getUnderContent(i):
                stamper.getOverContent(i);
            pdfContent.addImage(img);
        
        stamper.close();
        output.flush();
        output.close();
        return arrayOutput.toByteArray();
    

【讨论】:

你会如何使用 Rails 来做到这一点?【参考方案2】:

更新的答案(更好的版本,简单的水印方法,感谢下面的评论员和@okok 提供了他的答案)

如果您使用的是 PDFBox 1.8.10 或更高版本,您可以轻松地为您的 PDF 文档添加水印,更好地控制哪些页面需要添加水印。假设您有一个包含水印图像的单页 PDF 文档,您可以将其覆盖在要添加水印的文档上,如下所示。

使用 1.8.10 的示例代码

import java.util.HashMap;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.util.Overlay;

public class TestPDF 
    public static void main(String[] args) throws Exception
            PDDocument realDoc = PDDocument.load("originaldocument.pdf"); 
            //the above is the document you want to watermark                   

            //for all the pages, you can add overlay guide, indicating watermark the original pages with the watermark document.
            HashMap<Integer, String> overlayGuide = new HashMap<Integer, String>();
            for(int i=0; i<realDoc.getPageCount(); i++)
                overlayGuide.put(i+1, "watermark.pdf");
                //watermark.pdf is the document which is a one page PDF with your watermark image in it. Notice here that you can skip pages from being watermarked.
            
            Overlay overlay = new Overlay();
            overlay.setInputPDF(realDoc);
            overlay.setOutputFile("final.pdf");
            overlay.setOverlayPosition(Overlay.Position.BACKGROUND);
            overlay.overlay(overlayGuide,false);
           //final.pdf will have the original PDF with watermarks.

使用 PDFBox 2.0.0 候选版本的示例

import java.io.File;
import java.util.HashMap;
import org.apache.pdfbox.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument;

public class TestPDF 

    public static void main(String[] args) throws Exception        
        PDDocument realDoc = PDDocument.load(new File("originaldocument.pdf"));
        //the above is the document you want to watermark
        //for all the pages, you can add overlay guide, indicating watermark the original pages with the watermark document.

        HashMap<Integer, String> overlayGuide = new HashMap<Integer, String>();
        for(int i=0; i<realDoc.getNumberOfPages(); i++)
            overlayGuide.put(i+1, "watermark.pdf");
            //watermark.pdf is the document which is a one page PDF with your watermark image in it. 
            //Notice here, you can skip pages from being watermarked.
        
        Overlay overlay = new Overlay();
        overlay.setInputPDF(realDoc);
        overlay.setOutputFile("final.pdf");
        overlay.setOverlayPosition(Overlay.Position.BACKGROUND);
        overlay.overlay(overlayGuide);      
    

如果您想使用新包 org.apache.pdfbox.tools.OverlayPDF 进行叠加,您可以这样做。 (感谢下面的海报)

String[] overlayArgs = "C:/Examples/foreground.pdf", "C:/Examples/background.pdf", "C:/Examples/resulting.pdf";
OverlayPDF.main(overlayArgs);
System.out.println("Overlay finished.");

老答案效率低下,不推荐。

好吧,OP问如何在PDFBox中做到这一点,第一个答案看起来像一个使用iText的例子。在 PDFBox 中创建水印非常简单。诀窍是,您应该有一个带有水印图像的空 PDF 文档。然后,您所要做的就是将此水印文档覆盖在要添加水印的文档上。

PDDocument watermarkDoc = PDDocument.load("watermark.pdf");
//Assuming your empty document with watermark image in it.

PDDocument realDoc = PDDocument.load("document-to-be-watermarked.pdf");
//Let's say this is your document that you want to watermark. For example sake, I am opening a new one, you would already have a reference to PDDocument if you are creating one

Overlay overlay = new Overlay();
overlay.overlay(realDoc,watermarkDoc);
watermarkDoc.save("document-now-watermarked.pdf");

注意:您应该确保两个文档中的页数相匹配。否则,您最终会得到一个页数与页数最少的文档相匹配的文档。您可以操作水印文档并复制页面以匹配您的文档。

希望这会有所帮助!

【讨论】:

抱歉回复晚了(项目被搁置)。我尝试实现此代码,但遇到以下两个问题之一:(1)我使用了 1 页 PDF 和 1 页水印 PDF,但水印是唯一显示在最终文档上的东西(可能是某种透明度问题?)(2)我尝试了带有 2 页水印 PDF 的 2 页 PDF,并不断收到“当前不支持 COSArray 的布局页面”。我找不到任何文档或如何将多页文档更改为更合适的格式。 在玩了这个之后,它似乎工作得很好,但仅限于特定场景。如果水印不是文本,则水印似乎无法正常工作(也无法获得透明度)。叠加层也不能包含 COSArray(我仍然不确定它来自哪里,可能与书签或文本有关)。 嗨 - 是否有解决方案可以为 PDF 上的特定页面而不是所有页面加水印? @Joey Ezekiel,我不确定是否有更简单的方法。可能当您创建将覆盖在最终文档上的水印文档时,您可以确保在水印文档上为您不想添加水印的页面提供匹配的空白页面。没试过,只是一个想法。 不要混淆 org.apache.pdfbox.util.Overlayorg.apache.pdfbox.Overlay 包。后者是正确的。【参考方案3】:

刚刚编写了这段代码,用于使用 pdfbox 将(透明)图像(jpg、png、gif)添加到 pdf 页面:

/**
 * Draw an image to the specified coordinates onto a single page. <br>
 * Also scaled the image with the specified factor.
 * 
 * @author Nick Russler
 * @param document PDF document the image should be written to.
 * @param pdfpage Page number of the page in which the image should be written to.
 * @param x X coordinate on the page where the left bottom corner of the image should be located. Regard that 0 is the left bottom of the pdf page.
 * @param y Y coordinate on the page where the left bottom corner of the image should be located.
 * @param scale Factor used to resize the image.
 * @param imageFilePath Filepath of the image that is written to the PDF.
 * @throws IOException
 */
public static void addImageToPage(PDDocument document, int pdfpage, int x, int y, float scale, String imageFilePath) throws IOException    
    // Convert the image to TYPE_4BYTE_ABGR so PDFBox won't throw exceptions (e.g. for transparent png's).
    BufferedImage tmp_image = ImageIO.read(new File(imageFilePath));
    BufferedImage image = new BufferedImage(tmp_image.getWidth(), tmp_image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);        
    image.createGraphics().drawRenderedImage(tmp_image, null);

    PDXObjectImage ximage = new PDPixelMap(document, image);

    PDPage page = (PDPage)document.getDocumentCatalog().getAllPages().get(pdfpage);

    PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true);
    contentStream.drawXObject(ximage, x, y, ximage.getWidth()*scale, ximage.getHeight()*scale);
    contentStream.close();

例子:

public static void main(String[] args) throws Exception 
    String pdfFilePath = "C:/Users/Nick/Desktop/pdf-test.pdf";
    String signatureImagePath = "C:/Users/Nick/Desktop/signature.png";
    int page = 0;

    PDDocument document = PDDocument.load(pdfFilePath);

    addImageToPage(document, page, 0, 0, 0.5f, signatureImagePath);

    document.save("C:/Users/Nick/Desktop/pdf-test-neu.pdf");

这对我来说适用于 jdk 1.7 和 bcmail-jdk16-140.jar、bcprov-jdk16-140.jar、commons-logging-1.1.3.jar、fontbox-1.8.3.jar、jempbox-1.8.3 .jar 和 pdfbox-1.8.3.jar。

【讨论】:

如果使用不支持 BufferedImage 的 android 和 PdfBox-Android 版本 1.8.9.1 还没有 PDPixelMap,可以从位图创建 PDXObjectImage: PDImageXObject ximage = LosslessFactory.createFromImage(文档,位图);【参考方案4】:

在 util 包中还有另一个 Overlay 类,它使您不必创建与源文档页数相同的 pdf,然后再进行覆盖。

要了解其用法,请查看 pdfbox 源代码,特别是 OverlayPDF 类。

【讨论】:

【参考方案5】:

@Androidman : 除了https://***.com/a/9382212/7802973

似乎每个版本的 PDFBox 都删除了许多方法。所以该代码在 PDFBox 2.0.7 上不起作用。

Overlay overlay = new Overlay();
overlay.setInputPDF(realDoc);
// ** The method setOutputFile(String) is undefined for the type Overlay ** 
overlay.setOutputFile("final.pdf")

相反,使用void org.apache.pdfbox.pdmodel.PDDocument.save(String fileName),我认为:

PDDocument realDoc = PDDocument.load(new File("originaldocument.pdf"));
    //the above is the document you want to watermark
    //for all the pages, you can add overlay guide, indicating watermark the original pages with the watermark document.

HashMap<Integer, String> overlayGuide = new HashMap<Integer, String>();
    for(int i=0; i<realDoc.getNumberOfPages(); i++)
        overlayGuide.put(i+1, "watermark.pdf");
        //watermark.pdf is the document which is a one page PDF with your watermark image in it. 
        //Notice here, you can skip pages from being watermarked.
    
Overlay overlay = new Overlay();
overlay.setInputPDF(realDoc);
overlay.overlay(overlayGuide).save("final.pdf");
overlay.close();

编辑: 我现在使用org.apache.pdfbox.tools.OverlayPDF 进行叠加,它工作得很好。代码如下所示:

String[] overlayArgs = "C:/Examples/foreground.pdf", "C:/Examples/background.pdf", "C:/Examples/resulting.pdf";
OverlayPDF.main(overlayArgs);
System.out.println("Overlay finished.");

由于我没有足够的声誉来添加评论,我认为在新答案中添加它是合适的。

【讨论】:

【参考方案6】:

这就是我在 C# 中使用 PdfBox 2.0.x 添加带有日期的文本水印的方法。它将水印置于页面顶部的中心。

public static void AddWatermark(string fileName)
        
            StringBuilder sb = new StringBuilder();
            sb.Append("watermark_text  ");
            sb.Append(DateTime.Now.ToString());

            string waterMarkText = sb.ToString();

            PDDocument origDoc = PDDocument.load(new java.io.File(fileName));
            PDPageTree allPages = origDoc.getPages();
            PDFont font = PDType1Font.HELVETICA_BOLD;
            for (int i = 0, len = allPages.getCount(); i < len; ++i)
            
                PDPage pg = (PDPage)allPages.get(i);

                AddWatermarkText(origDoc, pg, font, waterMarkText);
            

            origDoc.save(fileName);
            origDoc.close();
        

static void AddWatermarkText(PDDocument doc, PDPage page, PDFont font, string text)
        
            using (PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true))
            
                float fontHeight = 30;
                float width = page.getMediaBox().getWidth();
                float height = page.getMediaBox().getHeight();
                float stringWidth = font.getStringWidth(text) / 1000 * fontHeight;

                float x = (width / 2) - (stringWidth / 2);
                float y = height - 25;

                cs.setFont(font, fontHeight);

                PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
                gs.setNonStrokingAlphaConstant(new java.lang.Float(0.2f));
                gs.setStrokingAlphaConstant(new java.lang.Float(0.2f));
                gs.setBlendMode(BlendMode.MULTIPLY);
                gs.setLineWidth(new java.lang.Float(3f));
                cs.setGraphicsStateParameters(gs);

                cs.setNonStrokingColor(Color.red);
                cs.setStrokingColor(Color.red);

                cs.beginText();
                cs.newLineAtOffset(x, y);
                cs.showText(text);
                cs.endText();
            
        

pdfboxc#watermarkpdf

【讨论】:

【参考方案7】:

添加@Droidman 的答案如果您使用2.0.21 及更高版本,您可以直接覆盖在PDDocument 上。示例代码如下。

PDDocument realDoc = pdfGenerator.getDocument(); 
HashMap<Integer, PDDocument> overlayGuide = new HashMap<>(); 
for (int i = 0; i < realDoc.getNumberOfPages(); i++)  
   overlayGuide.put(i + 1, document); 
 
Overlay overlay = new Overlay(); 
overlay.setInputPDF(realDoc); 
overlay.setOverlayPosition(Overlay.Position.BACKGROUND); 
overlay.overlayDocuments(overlayGuide);

【讨论】:

以上是关于使用 PDFBox 加水印的主要内容,如果未能解决你的问题,请参考以下文章

干货来袭!几行代码实现pdf添加水印和去除水印

干货来袭!几行代码实现pdf添加水印和去除水印

手机用水印相机加水印后图片变大了,要怎么弄?

绝了! 2 行代码可以加水印文件对比以及利好抓包

火狐浏览器打印加粗字体

用Photoshop 处理图片,加水印后,图片占用空间大小增大了好多倍,不方便储存和发送,怎么解决这个问题?