需要向已具有数字签名的pdf文档添加新页面 [英] Need to add a new page to a pdf document that already has a digital signature

查看:928
本文介绍了需要向已具有数字签名的pdf文档添加新页面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在文档的最后一页上没有更多空间时添加一个新页面我已经看到了itext的数字签名书,它说我不能只使用insertPage()方法和这就是我现在的做法所以数字签名会被破坏,所以书中说。


注意:请注意'允许页面添加操作'并不意味着您可以使用insertPage()方法。此消息引用了Adobe Acrobat JavaScript参考手册中描述的页面模板实例化,这超出了本文的范围。


但是我找不到如何用javascript和itext添加新页面你们中的任何一个都有同样的问题可以帮助我我真的需要一个没有标志的新页面



找不到java脚本的代码并与itext集成我这个但是没有用:

  String js = var aTemplates = this.templates; 
+aTemplates [0] .spawn({nPage:0,bRename:true,bOverlay:false});;

var a = this.getTemplate(MyTemplate);
a.spawn(this.pageNums);

这一个

  //获取PDF的模板对象数组; 
var aTemplates = this.templates;
//从第一个模板创建一个新页面,将其放在PDF的末尾并重命名字段;
//重命名字段,不叠加;
aTemplates [0] .spawn({nPage:0,bRename:true,bOverlay:false});

然后我使用itext



这两个不同的使用方式javascript但不起作用,不在文档末尾添加新页面。


  1. PdfAction.javaScript( js,stamper.getWriter());


  2. stamper.addJavaScript(js);



解决方案

这个答案显示了90%的问题解决方案,直到我对原始问题的评论中提到的问题。



页面模板处理的助手类



多年前,当Adobe Reader开始考虑签名时只要将新内容的其他页面添加到PDF中,我就会尝试使用页面模板实例化。事实证明,该代码可以很容易地适应当前的5.5.x iText版本(以及Java泛型)。我还没有尝试过对iText 7的调整。



由于此处使用的iText API方法的可见性有限,因此必须将此类放入包中 com.itextpdf.text.pdf 。或者,可以更改此类以充分利用反射魔法。

  public class PdfStamperHelper 
{
public static final PdfName TEMPLATES = new PdfName(Templates);
public static final PdfName TEMPLATE = new PdfName(Template);
public static final PdfName TEMPLATE_INSTANTIATED = new PdfName(TemplateInstantiated);

/ **
*此方法命名给定页面。有问题的页面已经有
*存在于给定PdfStamper工作的原始文档中。
* /
public static void createTemplate(PdfStamper pdfStamper,String name,int page)抛出IOException,DocumentException
{
PdfDictionary pageDic = pdfStamper.stamper.reader.getPageNRelease(page) ;
if(pageDic!= null&& pageDic.getIndRef()!= null)
{
HashMap< String,PdfObject> namedPages = getNamedPages(pdfStamper);
namedPages.put(name,pageDic.getIndRef());
storeNamedPages(pdfStamper);
}
}

/ **
*此方法隐藏给定的可见命名页面。
* /
public static void hideTemplate(PdfStamper pdfStamper,String name)抛出IOException,DocumentException
{
HashMap< String,PdfObject> namedPages = getNamedPages(pdfStamper);
PdfObject object = namedPages.get(name);
if(object == null)
抛出新的DocumentException(Document包含无可见模板+ name +'。');

namedPages.remove(name);
storeNamedPages(pdfStamper);

if(removePage(pdfStamper,(PRIndirectReference)pdfStamper.stamper.reader.getCatalog()。get(PdfName.PAGES),(PRIndirectReference)object))
{
pdfStamper .stamper.reader.pageRefs.reReadPages();
// TODO:correctAcroFieldPages
}
PdfDictionary pageDict =(PdfDictionary)PdfReader.getPdfObject(object);
if(pageDict!= null)
{
pdfStamper.stamper.markUsed(pageDict);
pageDict.remove(PdfName.PARENT);
pageDict.remove(PdfName.B);
pageDict.put(PdfName.TYPE,TEMPLATE);
}

HashMap< String,PdfObject> templates = getNamedTemplates(pdfStamper);
templates.put(name,object);
storeNamedTemplates(pdfStamper);
}

/ **
*此方法返回模板字典。
* /
public static PdfDictionary getTemplate(PdfStamper pdfStamper,String name)抛出DocumentException
{
HashMap< String,PdfObject> namedTemplates = getNamedTemplates(pdfStamper);
PdfObject object =(PdfObject)namedTemplates.get(name);
if(object == null){
HashMap< String,PdfObject> namedPages = getNamedPages(pdfStamper);
object = namedPages.get(name);
}
return(PdfDictionary)PdfReader.getPdfObject(object);
}

/ **
*此方法生成一个模板,将其插入给定的页码。
* /
public static void spawnTemplate(PdfStamper pdfStamper,String name,int pageNumber)抛出DocumentException,IOException
{
PdfDictionary template = getTemplate(pdfStamper,name);
if(template == null)
抛出新的DocumentException(文档不包含模板+ name +'。');

PdfReader reader = pdfStamper.stamper.reader;

// contRef:对生成页面的内容流的引用;
//它只插入模板XObject
PRIndirectReference contRef = reader.addPdfObject(getTemplateStream(name,reader.getPageSize(template)));
// resRef:对包含/ XObject
//字典的资源字典的引用,其中包含模板XObject资源
//携带实际模板内容
PdfDictionary xobjDict = new PdfDictionary ();
xobjDict.put(new PdfName(name),reader.addPdfObject(getFormXObject(reader,template,pdfStamper.stamper.getCompressionLevel(),name)));
PdfDictionary resources = new PdfDictionary();
resources.put(PdfName.XOBJECT,xobjDict);
PRIndirectReference resRef = reader.addPdfObject(resources);

//页面:衍生模板页面的字典
PdfDictionary page = new PdfDictionary();
page.put(PdfName.TYPE,PdfName.PAGE); //不是PdfName.TEMPLATE!
page.put(TEMPLATE_INSTANTIATED,new PdfName(name));
page.put(PdfName.CONTENTS,contRef);
page.put(PdfName.RESOURCES,resRef);
page.mergeDifferent(template); //实际上有点太多了TODO:处理注释,因为它们应该被处理

PRIndirectReference pref = reader.addPdfObject(page);
PdfDictionary父母;
PRIndirectReference parentRef;
if(pageNumber> reader.getNumberOfPages()){
PdfDictionary lastPage = reader.getPageNRelease(reader.getNumberOfPages());
parentRef =(PRIndirectReference)lastPage.get(PdfName.PARENT);
parentRef = new PRIndirectReference(reader,parentRef.getNumber());
parent =(PdfDictionary)PdfReader.getPdfObject(parentRef);
PdfArray kids =(PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS),parent);
kids.add(pref);
pdfStamper.stamper.markUsed(孩子们);
reader.pageRefs.insertPage(pageNumber,pref);
}
else {
if(pageNumber< 1)
pageNumber = 1;
PdfDictionary firstPage = reader.getPageN(pageNumber);
PRIndirectReference firstPageRef = reader.getPageOrigRef(pageNumber);
reader.releasePage(pageNumber);
parentRef =(PRIndirectReference)firstPage.get(PdfName.PARENT);
parentRef = new PRIndirectReference(reader,parentRef.getNumber());
parent =(PdfDictionary)PdfReader.getPdfObject(parentRef);
PdfArray kids =(PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS),parent);
ArrayList< PdfObject> ar = kids.getArrayList();
int len = ar.size();
int num = firstPageRef.getNumber();
for(int k = 0; k< len; ++ k){
PRIndirectReference cur =(PRIndirectReference)ar.get(k);
if(num == cur.getNumber()){
ar.add(k,pref);
休息;
}
}
if(len == ar.size())
抛出新的RuntimeException(内部不一致。);
pdfStamper.stamper.markUsed(孩子们);
reader.pageRefs.insertPage(pageNumber,pref);
pdfStamper.stamper.correctAcroFieldPages(pageNumber);
}
page.put(PdfName.PARENT,parentRef);
while(parent!= null){
pdfStamper.stamper.markUsed(parent);
PdfNumber count =(PdfNumber)PdfReader.getPdfObjectRelease(parent.get(PdfName.COUNT));
parent.put(PdfName.COUNT,new PdfNumber(count.intValue()+ 1));
parent =(PdfDictionary)PdfReader.getPdfObject(parent.get(PdfName.PARENT));
}
}

//
//帮助方法
//
/ **
*此方法递归删除给定页面树中的给定页面。
* /
static boolean removePage(PdfStamper pdfStamper,PRIndirectReference pageTree,PRIndirectReference pageToRemove)
{
PdfDictionary pageDict =(PdfDictionary)PdfReader.getPdfObject(pageTree);
PdfArray kidsPR =(PdfArray)PdfReader.getPdfObject(pageDict.get(PdfName.KIDS));
if(kidsPR!= null){
ArrayList< PdfObject> kids = kidsPR.getArrayList();
boolean removed = false;
for(int k = 0; k< kids.size(); ++ k){
PRIndirectReference obj =(PRIndirectReference)kids.get(k);
if(pageToRemove.getNumber()== obj.getNumber()&& pageToRemove.getGeneration()== obj.getGeneration())
{
kids.remove(k) ;
pdfStamper.stamper.markUsed(pageTree);
removed = true;
休息;
}
else if(removePage(pdfStamper,(PRIndirectReference)obj,pageToRemove))
{
removed = true;
休息;
}
}
if(removed)
{
PdfNumber count =(PdfNumber)PdfReader.getPdfObjectRelease(pageDict.get(PdfName.COUNT));
pageDict.put(PdfName.COUNT,new PdfNumber(count.intValue()+ 1));
pdfStamper.stamper.markUsed(pageTree);
返回true;
}
}
返回false;
}

/ **
*此方法返回内容PDF对象的未压缩字节。
* /
static byte [] pageContentsToArray(PdfReader reader,PdfObject contents,RandomAccessFileOrArray file)抛出IOException {
if(contents == null)
返回新字节[0];
if(file == null)
file = reader.getSafeFile();
ByteArrayOutputStream bout = null;
if(contents.isStream()){
返回PdfReader.getStreamBytes((PRStream)内容,文件);
}
else if(contents.isArray()){
PdfArray array =(PdfArray)contents;
ArrayList< PdfObject> list = array.getArrayList();
bout = new ByteArrayOutputStream();
for(int k = 0; k< list.size(); ++ k){
PdfObject item = PdfReader.getPdfObjectRelease(list.get(k));
if(item == null ||!item.isStream())
continue;
byte [] b = PdfReader.getStreamBytes((PRStream)item,file);
bout.write(b);
if(k!= list.size() - 1)
bout.write('\ n');
}
返回bout.toByteArray();
}
else
返回新字节[0];
}

/ **
*此方法返回一个PDF流对象,其中包含具有给定名称的给定模板页面的
*内容的副本。峰; br>
*为了在检查
*签名有效性时使Acrobat 9对此模板XObject感到满意,必须将/ Size更改为将由此生成的流的大小
*产生给定模板时,Acrobat本身
*。
* /
静态PdfStream getFormXObject(PdfReader reader,PdfDictionary页面,int compressionLevel,String name)抛出IOException {
Rectangle pageSize = reader.getPageSize(page);
final PdfLiteral MATRIX = new PdfLiteral([1 0 0 1+ -getXOffset(pageSize)++ -getYOffset(pageSize)+]);
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.RESOURCES,PdfReader.getPdfObjectRelease(page.get(PdfName.RESOURCES)));
dic.put(PdfName.TYPE,PdfName.XOBJECT);
dic.put(PdfName.SUBTYPE,PdfName.FORM);
dic.put(PdfName.BBOX,page.get(PdfName.MEDIABOX));
dic.put(PdfName.MATRIX,MATRIX);
dic.put(PdfName.FORMTYPE,PdfReaderInstance.ONE);
dic.put(PdfName.NAME,new PdfName(name));

PdfStream流;
PdfObject contents = PdfReader.getPdfObjectRelease(page.get(PdfName.CONTENTS));
byte bout [] = null;
if(contents!= null)
bout = pageContentsToArray(reader,contents,reader.getSafeFile());
else
bout = new byte [0];
byte [] embedded = new byte [bout.length + 4];
System.arraycopy(bout,0,embedded,2,bout.length);
embedded [0] ='q';
embedded [1] = 10;嵌入
[embedded.length - 2] ='Q';嵌入
[embedded.length - 1] = 10;
stream = new PdfStream(embedded);
stream.putAll(dic);
stream.flateCompress(compressionLevel);
PdfObject filter = stream.get(PdfName.FILTER);
if(filter!= null&&!(filter instanceof PdfArray))
stream.put(PdfName.FILTER,new PdfArray(filter));
返回流;
}

/ **
*此方法返回生成的
*模板的内容流对象。
* /
static PdfStream getTemplateStream(String name,Rectangle pageSize)
{
int x = getXOffset(pageSize);
int y = getYOffset(pageSize);
String content =q 1 0 0 1+ x ++ y +cm /+ name +Do Q;
返回新的PdfStream(PdfEncodings.convertToBytes(content,null));
}

/ **
*此方法返回给定页面矩形的中心x偏移量。
* /
static int getXOffset(Rectangle pageSize)
{
return Math.round((pageSize.getLeft()+ pageSize.getRight())/ 2);
}

/ **
*此方法返回给定页面矩形的中心y偏移量。
* /
static int getYOffset(Rectangle pageSize)
{
return Math.round((pageSize.getTop()+ pageSize.getBottom())/ 2);
}

/ **
*此方法返回文档的/ Names名称字典;如果
*该文档还没有,则生成一个。< br>
*小心!如果文档包含名称字典作为间接
*对象,则字典应写入但只能一次;这个/包含/
*由{@link PdfStamper}写。
* /
static PdfDictionary getNameDictionary(PdfStamper pdfStamper)
{
PdfDictionary catalog = pdfStamper.stamper.reader.getCatalog();
PdfDictionary names =(PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.NAMES),catalog);
if(names == null){
names = new PdfDictionary();
catalog.put(PdfName.NAMES,names);
pdfStamper.stamper.markUsed(catalog);
}
返回姓名;
}

final static Map< PdfStamper,HashMap< String,PdfObject>> namedPagesByStamper = new HashMap<>();

static HashMap< String,PdfObject> getNamedPages(PdfStamper pdfStamper)抛出DocumentException
{
if(namedPagesByStamper.containsKey(pdfStamper))
返回namedPagesByStamper.get(pdfStamper);

final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
PdfObject pagesObject = PdfReader.getPdfObjectRelease(nameDictionary.get(PdfName.PAGES));
if(pagesObject!= null&&!(pagesObject instanceof PdfDictionary))
抛出新的DocumentException(Pages name dictionary既不是PdfDictionary也不是null);
HashMap< String,PdfObject> namesMap = PdfNameTree.readTree((PdfDictionary)pagesObject);
namedPagesByStamper.put(pdfStamper,namesMap);
返回namesMap;
}

static void storeNamedPages(PdfStamper pdfStamper)抛出IOException
{
if(namedPagesByStamper.containsKey(pdfStamper))
{
final HashMap< String,PdfObject> pages = namedPagesByStamper.get(pdfStamper);
final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
pdfStamper.stamper.markUsed(nameDictionary);
if(pages.isEmpty())
nameDictionary.remove(PdfName.PAGES);
else {
final PdfDictionary tree = PdfNameTree.writeTree(pages,pdfStamper.stamper);
nameDictionary.put(PdfName.PAGES,pdfStamper.stamper.addToBody(tree).getIndirectReference());
}
}
}

final static Map< PdfStamper,HashMap< String,PdfObject>> namedTemplatesByStamper = new HashMap<>();

static HashMap< String,PdfObject> getNamedTemplates(PdfStamper pdfStamper)抛出DocumentException
{
if(namedTemplatesByStamper.containsKey(pdfStamper))
返回namedTemplatesByStamper.get(pdfStamper);

final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
PdfObject templatesObject = PdfReader.getPdfObjectRelease(nameDictionary.get(TEMPLATES));
if(templatesObject!= null&&!(templatesObject instanceof PdfDictionary))
抛出新的DocumentException(模板名称字典既不是PdfDictionary也不是null);
HashMap< String,PdfObject> templatesMap = PdfNameTree.readTree((PdfDictionary)templatesObject);
namedTemplatesByStamper.put(pdfStamper,templatesMap);
返回templatesMap;
}

static void storeNamedTemplates(PdfStamper pdfStamper)抛出IOException
{
if(namedTemplatesByStamper.containsKey(pdfStamper))
{
final HashMap< String,PdfObject> templates = namedTemplatesByStamper.get(pdfStamper);
final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
pdfStamper.stamper.markUsed(nameDictionary);
if(templates.isEmpty())
nameDictionary.remove(TEMPLATES);
else {
final PdfDictionary tree = PdfNameTree.writeTree(templates,pdfStamper.stamper);
nameDictionary.put(TEMPLATES,pdfStamper.stamper.addToBody(tree).getIndirectReference());
}
}
}
}



按下计算修改列表后,我的旧版Acrobat Pro 9.5甚至意识到只有页面模板已实例化,但仍然调用签名INVALID:








实验显示Adobe Acrobat Reader执行根据PDF规范,一个测试没有任何意义:它希望页面模板形式xobject在压缩(!!)之后具有相同的 Size 条目值,就像它被压缩一样读者本身。由于deflate压缩的不同实现可能导致不同的流大小(iText的实现在手头的情况下创建了3个字节的流更短),我还不知道如何一般地通过这个测试。



将上面生成的PDF中特定流的大小条目从159修补为162后,Adobe Acrobat Reader会显示:





(有效期未知,因为未及时添加吊销信息。)


i need to add a new page when there is no more space on the last page of the document i have been seen the digital signare book of itext and it says the i can't be just using the insertPage() method and that's how i do now so the digital signatures get broken so the book says.

NOTE: Be aware that ‘page adding actions are allowed’ doesn’t mean you can use the insertPage() method. This message refers to page template instantiation as described in the Adobe Acrobat JavaScript Reference Manual, which is out of scope in this paper.

but i can't find how to add the new page with javascript and itext any of you has the same problem that can help me i really need a new page without signs get broken

can't find the code of java script and integrate with itext I foud this but is not working:

String js = "var aTemplates = this.templates;"
          + "aTemplates[0].spawn({nPage: 0, bRename: true, bOverlay: false});";

var a = this.getTemplate("MyTemplate");
a.spawn (this.pageNums);

and this one

//get the array of the template object for the PDF;
var aTemplates = this.templates;
// create a new page from the first template placing it at the end of the PDF and renaming the fields;
// rename the fields, do not overlay;
aTemplates[0].spawn({nPage: 0, bRename: true, bOverlay: false});

then i use itext

these two different ways of use javascript but it's not working, not adding new page at the end of the document.

  1. PdfAction.javaScript(js, stamper.getWriter());

  2. stamper.addJavaScript(js);

解决方案

This answer shows 90% of a solution of the problem up to the problem mentioned in my comments to the original question.

Helper class for page template handling

Years ago, when Adobe Reader started to consider a signature broken as soon as additional pages with new content were added to the PDF, I experimented with page template instantiation. As it turns out, that code could easily be adapted to the current 5.5.x iText versions (and additionally to Java generics). I have not tried adaption to iText 7 yet.

Due to limited visibility of iText API methods used here, this class has to be put into the package com.itextpdf.text.pdf. Alternatively this class may be changed to make considerable use of reflection magic.

public class PdfStamperHelper
{
    public static final PdfName TEMPLATES = new PdfName("Templates");
    public static final PdfName TEMPLATE = new PdfName("Template"); 
    public static final PdfName TEMPLATE_INSTANTIATED = new PdfName("TemplateInstantiated"); 

    /**
     * This method names a given page. The page in question already has
     * to exist in the original document the given PdfStamper works on.
     */
    public static void createTemplate(PdfStamper pdfStamper, String name, int page) throws IOException, DocumentException
    {
        PdfDictionary pageDic = pdfStamper.stamper.reader.getPageNRelease(page);
        if (pageDic != null && pageDic.getIndRef() != null)
        {
            HashMap<String, PdfObject> namedPages = getNamedPages(pdfStamper);
            namedPages.put(name, pageDic.getIndRef());
            storeNamedPages(pdfStamper);
        }
    }

    /**
     * This method hides a given visible named page.
     */
    public static void hideTemplate(PdfStamper pdfStamper, String name) throws IOException, DocumentException
    {
        HashMap<String, PdfObject> namedPages = getNamedPages(pdfStamper);
        PdfObject object = namedPages.get(name);
        if (object == null)
            throw new DocumentException("Document contains no visible template " + name + '.');

        namedPages.remove(name);
        storeNamedPages(pdfStamper);

        if (removePage(pdfStamper, (PRIndirectReference)pdfStamper.stamper.reader.getCatalog().get(PdfName.PAGES), (PRIndirectReference) object))
        {
            pdfStamper.stamper.reader.pageRefs.reReadPages();
            // TODO: correctAcroFieldPages 
        }
        PdfDictionary pageDict = (PdfDictionary)PdfReader.getPdfObject(object);
        if (pageDict != null)
        {
            pdfStamper.stamper.markUsed(pageDict);
            pageDict.remove(PdfName.PARENT);
            pageDict.remove(PdfName.B);
            pageDict.put(PdfName.TYPE, TEMPLATE);
        }

        HashMap<String, PdfObject> templates = getNamedTemplates(pdfStamper);
        templates.put(name, object);
        storeNamedTemplates(pdfStamper);
    }

    /**
     * This method returns a template dictionary.
     */
    public static PdfDictionary getTemplate(PdfStamper pdfStamper, String name) throws DocumentException
    {
        HashMap<String, PdfObject> namedTemplates = getNamedTemplates(pdfStamper);
        PdfObject object = (PdfObject) namedTemplates.get(name);
        if (object == null) {
            HashMap<String, PdfObject> namedPages = getNamedPages(pdfStamper);
            object = namedPages.get(name);
        }
        return (PdfDictionary)PdfReader.getPdfObject(object);
    }

    /**
     * This method spawns a template inserting it at the given page number.
     */
    public static void spawnTemplate(PdfStamper pdfStamper, String name, int pageNumber) throws DocumentException, IOException
    {
        PdfDictionary template = getTemplate(pdfStamper, name);
        if (template == null)
            throw new DocumentException("Document contains no template " + name + '.');

        PdfReader reader = pdfStamper.stamper.reader;

        // contRef: reference to the content stream of the spawned page;
        // it only inserts the template XObject
        PRIndirectReference contRef = reader.addPdfObject(getTemplateStream(name, reader.getPageSize(template)));
        // resRef: reference to resources dictionary containing a /XObject
        // dictionary in turn containing the template XObject resource
        // carrying the actual template content
        PdfDictionary xobjDict = new PdfDictionary();
        xobjDict.put(new PdfName(name), reader.addPdfObject(getFormXObject(reader, template, pdfStamper.stamper.getCompressionLevel(), name)));
        PdfDictionary resources = new PdfDictionary();
        resources.put(PdfName.XOBJECT, xobjDict);
        PRIndirectReference resRef = reader.addPdfObject(resources);

        // page: dictionary of the spawned template page
        PdfDictionary page = new PdfDictionary();
        page.put(PdfName.TYPE, PdfName.PAGE); // not PdfName.TEMPLATE!
        page.put(TEMPLATE_INSTANTIATED, new PdfName(name));
        page.put(PdfName.CONTENTS, contRef);
        page.put(PdfName.RESOURCES, resRef);
        page.mergeDifferent(template); // actually a bit too much. TODO: treat annotations as they should be treated

        PRIndirectReference pref = reader.addPdfObject(page);
        PdfDictionary parent;
        PRIndirectReference parentRef;
        if (pageNumber > reader.getNumberOfPages()) {
            PdfDictionary lastPage = reader.getPageNRelease(reader.getNumberOfPages());
            parentRef = (PRIndirectReference)lastPage.get(PdfName.PARENT);
            parentRef = new PRIndirectReference(reader, parentRef.getNumber());
            parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
            PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
            kids.add(pref);
            pdfStamper.stamper.markUsed(kids);
            reader.pageRefs.insertPage(pageNumber, pref);
        }
        else {
            if (pageNumber < 1)
                pageNumber = 1;
            PdfDictionary firstPage = reader.getPageN(pageNumber);
            PRIndirectReference firstPageRef = reader.getPageOrigRef(pageNumber);
            reader.releasePage(pageNumber);
            parentRef = (PRIndirectReference)firstPage.get(PdfName.PARENT);
            parentRef = new PRIndirectReference(reader, parentRef.getNumber());
            parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
            PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
            ArrayList<PdfObject> ar = kids.getArrayList();
            int len = ar.size();
            int num = firstPageRef.getNumber();
            for (int k = 0; k < len; ++k) {
                PRIndirectReference cur = (PRIndirectReference)ar.get(k);
                if (num == cur.getNumber()) {
                    ar.add(k, pref);
                    break;
                }
            }
            if (len == ar.size())
                throw new RuntimeException("Internal inconsistence.");
            pdfStamper.stamper.markUsed(kids);
            reader.pageRefs.insertPage(pageNumber, pref);
            pdfStamper.stamper.correctAcroFieldPages(pageNumber);
        }
        page.put(PdfName.PARENT, parentRef);
        while (parent != null) {
            pdfStamper.stamper.markUsed(parent);
            PdfNumber count = (PdfNumber)PdfReader.getPdfObjectRelease(parent.get(PdfName.COUNT));
            parent.put(PdfName.COUNT, new PdfNumber(count.intValue() + 1));
            parent = (PdfDictionary)PdfReader.getPdfObject(parent.get(PdfName.PARENT));
        }
    }

    //
    // helper methods
    //
    /**
     * This method recursively removes a given page from the given page tree.
     */
    static boolean removePage(PdfStamper pdfStamper, PRIndirectReference pageTree, PRIndirectReference pageToRemove)
    {
        PdfDictionary pageDict = (PdfDictionary)PdfReader.getPdfObject(pageTree);
        PdfArray kidsPR = (PdfArray)PdfReader.getPdfObject(pageDict.get(PdfName.KIDS));
        if (kidsPR != null) {
            ArrayList<PdfObject> kids = kidsPR.getArrayList();
            boolean removed = false;
            for (int k = 0; k < kids.size(); ++k){
                PRIndirectReference obj = (PRIndirectReference)kids.get(k);
                if (pageToRemove.getNumber() == obj.getNumber() && pageToRemove.getGeneration() == obj.getGeneration())
                {
                    kids.remove(k);
                    pdfStamper.stamper.markUsed(pageTree);
                    removed = true;
                    break;
                }
                else if (removePage(pdfStamper, (PRIndirectReference)obj, pageToRemove))
                {
                    removed = true;
                    break;
                }
            }
            if (removed)
            {
                PdfNumber count = (PdfNumber) PdfReader.getPdfObjectRelease(pageDict.get(PdfName.COUNT));
                pageDict.put(PdfName.COUNT, new PdfNumber(count.intValue() + 1));
                pdfStamper.stamper.markUsed(pageTree);
                return true;
            }
        }
        return false;
    }

    /**
     * This method returns the uncompressed bytes of a content PDF object.
     */
    static byte[] pageContentsToArray(PdfReader reader, PdfObject contents, RandomAccessFileOrArray file) throws IOException{
        if (contents == null)
            return new byte[0];
        if (file == null)
            file = reader.getSafeFile();
        ByteArrayOutputStream bout = null;
        if (contents.isStream()) {
            return PdfReader.getStreamBytes((PRStream)contents, file);
        }
        else if (contents.isArray()) {
            PdfArray array = (PdfArray)contents;
            ArrayList<PdfObject> list = array.getArrayList();
            bout = new ByteArrayOutputStream();
            for (int k = 0; k < list.size(); ++k) {
                PdfObject item = PdfReader.getPdfObjectRelease(list.get(k));
                if (item == null || !item.isStream())
                    continue;
                byte[] b = PdfReader.getStreamBytes((PRStream)item, file);
                bout.write(b);
                if (k != list.size() - 1)
                    bout.write('\n');
            }
            return bout.toByteArray();
        }
        else
            return new byte[0];
    }

    /**
     * This method returns a PDF stream object containing a copy of the
     * contents of the given template page with the given name.<br>
     * To make Acrobat 9 happy with this template XObject when checking
     * for signature validity, the /Size has to be changed to be the size
     * of the stream that would have been generated by Acrobat itself
     * when spawning the given template.
     */
    static PdfStream getFormXObject(PdfReader reader, PdfDictionary page, int compressionLevel, String name) throws IOException {
        Rectangle pageSize = reader.getPageSize(page);
        final PdfLiteral MATRIX = new PdfLiteral("[1 0 0 1 " + -getXOffset(pageSize) + " " + -getYOffset(pageSize) + "]");
        PdfDictionary dic = new PdfDictionary();
        dic.put(PdfName.RESOURCES, PdfReader.getPdfObjectRelease(page.get(PdfName.RESOURCES)));
        dic.put(PdfName.TYPE, PdfName.XOBJECT);
        dic.put(PdfName.SUBTYPE, PdfName.FORM);
        dic.put(PdfName.BBOX, page.get(PdfName.MEDIABOX));
        dic.put(PdfName.MATRIX, MATRIX);
        dic.put(PdfName.FORMTYPE, PdfReaderInstance.ONE);
        dic.put(PdfName.NAME, new PdfName(name));

        PdfStream stream;
        PdfObject contents = PdfReader.getPdfObjectRelease(page.get(PdfName.CONTENTS));
        byte bout[] = null;
        if (contents != null)
            bout = pageContentsToArray(reader, contents, reader.getSafeFile());
        else
            bout = new byte[0];
        byte[] embedded = new byte[bout.length + 4];
        System.arraycopy(bout, 0, embedded, 2, bout.length);
        embedded[0] = 'q';
        embedded[1] = 10;
        embedded[embedded.length - 2] = 'Q';
        embedded[embedded.length - 1] = 10;
        stream = new PdfStream(embedded);
        stream.putAll(dic);
        stream.flateCompress(compressionLevel);
        PdfObject filter = stream.get(PdfName.FILTER);
        if (filter != null && !(filter instanceof PdfArray))
            stream.put(PdfName.FILTER, new PdfArray(filter));
        return stream;
    }

    /**
     * This method returns the content stream object for a spawned
     * template.
     */
    static PdfStream getTemplateStream(String name, Rectangle pageSize)
    {
        int x = getXOffset(pageSize);
        int y = getYOffset(pageSize);
        String content = "q 1 0 0 1 " + x + " " + y + " cm /" + name + " Do Q";
        return new PdfStream(PdfEncodings.convertToBytes(content, null));
    }

    /**
     * This method returns the center x offset for the given page rectangle.
     */
    static int getXOffset(Rectangle pageSize)
    {
        return Math.round((pageSize.getLeft() + pageSize.getRight()) / 2);
    }

    /**
     * This method returns the center y offset for the given page rectangle.
     */
    static int getYOffset(Rectangle pageSize)
    {
        return Math.round((pageSize.getTop() + pageSize.getBottom()) / 2);
    }

    /**
     * This method returns the /Names name dictionary of the document; if
     * the document does not have one yet, it generates one.<br>
     * Beware! If the document contains a name dictionary as an indirect
     * object, the dictionary shall be written to but once; this /includes/
     * writes by the {@link PdfStamper}.
     */
    static PdfDictionary getNameDictionary(PdfStamper pdfStamper)
    {
        PdfDictionary catalog = pdfStamper.stamper.reader.getCatalog();
        PdfDictionary names = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
        if (names == null) {
            names = new PdfDictionary();
            catalog.put(PdfName.NAMES, names);
            pdfStamper.stamper.markUsed(catalog);
        }
        return names;
    }

    final static Map<PdfStamper, HashMap<String, PdfObject>> namedPagesByStamper = new HashMap<>();

    static HashMap<String, PdfObject> getNamedPages(PdfStamper pdfStamper) throws DocumentException
    {
        if (namedPagesByStamper.containsKey(pdfStamper))
            return namedPagesByStamper.get(pdfStamper);

        final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
        PdfObject pagesObject = PdfReader.getPdfObjectRelease(nameDictionary.get(PdfName.PAGES));
        if (pagesObject != null && !(pagesObject instanceof PdfDictionary))
            throw new DocumentException("Pages name dictionary is neither a PdfDictionary nor null");
        HashMap<String, PdfObject> namesMap = PdfNameTree.readTree((PdfDictionary)pagesObject);
        namedPagesByStamper.put(pdfStamper, namesMap);
        return namesMap;
    }

    static void storeNamedPages(PdfStamper pdfStamper) throws IOException
    {
        if (namedPagesByStamper.containsKey(pdfStamper))
        {
            final HashMap<String, PdfObject> pages = namedPagesByStamper.get(pdfStamper);
            final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
            pdfStamper.stamper.markUsed(nameDictionary);
            if (pages.isEmpty())
                nameDictionary.remove(PdfName.PAGES);
            else {
                final PdfDictionary tree = PdfNameTree.writeTree(pages, pdfStamper.stamper);
                nameDictionary.put(PdfName.PAGES, pdfStamper.stamper.addToBody(tree).getIndirectReference());
            }
        }
    }

    final static Map<PdfStamper, HashMap<String, PdfObject>> namedTemplatesByStamper = new HashMap<>();

    static HashMap<String, PdfObject> getNamedTemplates(PdfStamper pdfStamper) throws DocumentException
    {
        if (namedTemplatesByStamper.containsKey(pdfStamper))
            return namedTemplatesByStamper.get(pdfStamper);

        final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
        PdfObject templatesObject = PdfReader.getPdfObjectRelease(nameDictionary.get(TEMPLATES));
        if (templatesObject != null && !(templatesObject instanceof PdfDictionary))
            throw new DocumentException("Templates name dictionary is neither a PdfDictionary nor null");
        HashMap<String, PdfObject> templatesMap = PdfNameTree.readTree((PdfDictionary)templatesObject);
        namedTemplatesByStamper.put(pdfStamper, templatesMap);
        return templatesMap;
    }

    static void storeNamedTemplates(PdfStamper pdfStamper) throws IOException
    {
        if (namedTemplatesByStamper.containsKey(pdfStamper))
        {
            final HashMap<String, PdfObject> templates = namedTemplatesByStamper.get(pdfStamper);
            final PdfDictionary nameDictionary = getNameDictionary(pdfStamper);
            pdfStamper.stamper.markUsed(nameDictionary);
            if (templates.isEmpty())
                nameDictionary.remove(TEMPLATES);
            else {
                final PdfDictionary tree = PdfNameTree.writeTree(templates, pdfStamper.stamper);
                nameDictionary.put(TEMPLATES, pdfStamper.stamper.addToBody(tree).getIndirectReference());
            }
        }
    }
}

(PdfStamperHelper.java)

Using the helper class

The helper class assumes you already have a PDF and want to make some page in it a named page template or instantiate an existing named template.

You can make an existing page a named template like this:

PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, target, '\0', true);
PdfStamperHelper.createTemplate(pdfStamper, "template", 1);
pdfStamper.close();

(BasicTemplating.java test testNameTest)

The page does remain visible. If you don't want that, hide it using PdfStamperHelper.hideTemplate after naming.

You can spawn an existing template like this:

pdfReader = new PdfReader(...);
pdfStamper = new PdfStamper(pdfReader, target, '\0', true);
PdfStamperHelper.spawnTemplate(pdfStamper, "template", 1);
pdfStamper.close();

(BasicTemplating.java test testNameSpawnTest)

Issue in concert with Adobe Reader

I took a PDF, created a named page template in it, and signed that PDF.

Then I spawned the named template using the code above, cf. BasicTemplating.java test testSpawnPdfaNamedSigned, and then inspected the result in Adobe Acrobat Reader DC, I unfortunately saw

My older Acrobat Pro 9.5 after pressing "Compute Modifications List" is even aware that only a page template has been instantiated but still calls the signature INVALID:


Experiments showed that Adobe Acrobat Reader executes one test that does not make any sense in the light of the PDF specification: It expects the page template form xobject to have the same Size entry value after compression (!!) as if it was compressed by the Reader itself. As different implementations of the deflate compression can result in different stream sizes (iText's implementation in the case at hand created a stream 3 bytes shorter), I have no idea yet how to generically pass this test.

After patching the specific stream's Size entry in the PDF generated above from 159 to 162, Adobe Acrobat Reader shows:

(The validity is unknown because revocation information had not been added in time.)

这篇关于需要向已具有数字签名的pdf文档添加新页面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆