首页 > 解决方案 > 使用 PDFBox 将 FormXobject 内容从资源添加到内容流?

问题描述

我的页面1->资源-> Xobjects-> Fm0,Fm1,Fm2 下有FormXobject ..

所以它不是直接内容流,在 content->contentstream 下不可用。所以我想将Fm0->Contentstream的内容流移动到page1->contents->contentstream。

当我们像这样移动内容流时,我们必须并行地将 Fm0 相关资源传输或复制到页面级资源。

如此处所示。

1.内容流需要在页面级内容下复制。

2.色彩空间对象需要在page1->Resource->Colorspace下复制。

3.ExtGState对象需要在page1->Resource->ExtGState下复制。

4.properties需要在page1->Resource下复制(这里需要完全创建)

我尝试了一些代码

private PDDocument parseFormXobject(PDDocument document) throws IOException {
PDDocument newdocument = new PDDocument();
for (int pg_ind = 0; pg_ind < document.getNumberOfPages(); pg_ind++) {
    List<Object> tokens1 = (List<Object>) (getTokens(document)).get(pg_ind);
    PDStream newContents = new PDStream(document);
    OutputStream out = newContents.createOutputStream(COSName.FLATE_DECODE);
    ContentStreamWriter writer = new ContentStreamWriter(out);

    PDPage pageinner = document.getPage(pg_ind);
    PDResources resources = pageinner.getResources();
    PDResources new_resources = new PDResources();
    new_resources = resources;

    COSDictionary fntdict = new COSDictionary();
    COSDictionary imgdict = new COSDictionary();
    COSDictionary extgsdict = new COSDictionary();
    COSDictionary colordict = new COSDictionary();
    int img_count = 0;
    for (COSName xObjectName : resources.getXObjectNames()) {
        PDXObject  xObject = resources.getXObject(xObjectName);
        if (xObject instanceof PDFormXObject) {

            PDFStreamParser parser = new PDFStreamParser(((PDFormXObject) xObject).getContentStream());
            parser.parse();
            List<Object>  tokens3 = parser.getTokens();
            int ind =0;
            System.out.println(xObjectName.getName());
                for (COSName colorname :((PDFormXObject) xObject).getResources().getColorSpaceNames())
                {
                    COSName new_name = COSName.getPDFName(colorname.getName()+"_Fm"+img_count);
                    PDColorSpace pdcolor = ((PDFormXObject) xObject).getResources().getColorSpace(colorname);
                    colordict.setItem(new_name,pdcolor);
                }
                for (COSName fontName :((PDFormXObject) xObject).getResources().getFontNames() )
                {
                    COSName new_name = COSName.getPDFName(fontName.getName()+"_Fm"+img_count);
                    PDFont font =((PDFormXObject) xObject).getResources().getFont(fontName);
                    font.getCOSObject().setItem(COSName.NAME, new_name);
                    fntdict.setItem(new_name,font);
                }
                for (COSName ExtGSName :((PDFormXObject) xObject).getResources().getExtGStateNames() )
                {
                    COSName new_name = COSName.getPDFName(ExtGSName.getName()+"_Fm"+img_count);
                    PDExtendedGraphicsState ExtGState =((PDFormXObject) xObject).getResources().getExtGState(ExtGSName);
                    ExtGState.getCOSObject().setItem(COSName.NAME, new_name);
                    extgsdict.setItem(new_name,ExtGState);
                }
                imgdict.setItem(xObjectName, xObject);
                for (COSName Imgname :((PDFormXObject) xObject).getResources().getXObjectNames() )
                {
                    COSName new_name = COSName.getPDFName(Imgname.getName()+"_Fm"+img_count);
                    xObject.getCOSObject().setItem(COSName.NAME, new_name);
                    PDXObject img =((PDFormXObject) xObject).getResources().getXObject(Imgname);
                    imgdict.setItem(new_name, img);
                }

                    for (int k=0; k< tokens1.size(); k++) {
                        if ( ((tokens1.get(k) instanceof Operator) && ((Operator)tokens1.get(k)).getName().toString().equals("Do"))
                                && ((COSName)tokens1.get(k-1)).getName().toString().equals(xObjectName.getName().toString()) ) {
                            System.out.println(tokens1.get(k).toString());
                            tokens1.remove(k-1);
                            tokens1.remove(k-1);
                            ind =k-1;
                            break;
                        }
                    }
                for (int k=0; k< tokens3.size(); k++) {
                    if ( (tokens3.size() > k+1) && (tokens3.get(k+1) instanceof Operator) && (((Operator)tokens3.get(k+1)).getName().toString().equals("Do")
                            || ((Operator)tokens3.get(k+1)).getName().toString().equals("gs")
                            || ((Operator)tokens3.get(k+1)).getName().toString().equals("cs")  ) ) {
                        COSName new_name = COSName.getPDFName( ((COSName) tokens3.get(k)).getName()+"_Fm"+img_count );
                        tokens1.add(ind+k, new_name );
                    }else if ( (tokens3.size() > k+2) && (tokens3.get(k+2) instanceof Operator)
                            && ((Operator)tokens3.get(k+2)).getName().toString().equals("Tf") ) {
                        COSName new_name = COSName.getPDFName( ((COSName) tokens3.get(k)).getName()+"_Fm"+img_count );
                        tokens1.add(ind+k, new_name );
                    }else
                        tokens1.add(ind+k,tokens3.get(k));
                }

                img_count +=1;
        }else
            imgdict.setItem(xObjectName, xObject);
    }
    for (COSName fontName :new_resources.getFontNames() )
    {
        PDFont font =new_resources.getFont(fontName);
        fntdict.setItem(fontName,font);
    }
    for (COSName ExtGSName :new_resources.getExtGStateNames() )
    {
        PDExtendedGraphicsState extg =new_resources.getExtGState(ExtGSName);
        extgsdict.setItem(ExtGSName,extg);
    }
    for (COSName colorname :new_resources.getColorSpaceNames() )
    {
        PDColorSpace color =new_resources.getColorSpace(colorname);
        colordict.setItem(colorname,color);
    }
    resources.getCOSObject().setItem(COSName.EXT_G_STATE,extgsdict);
    resources.getCOSObject().setItem(COSName.FONT,fntdict);
    resources.getCOSObject().setItem(COSName.XOBJECT,imgdict);
    resources.getCOSObject().setItem(COSName.COLORSPACE, colordict);

    writer.writeTokens(tokens1);
    out.close();
    document.getPage(pg_ind).setContents(newContents);
    document.getPage(pg_ind).setMediaBox(PDFUtils.Media_box);
    document.getPage(pg_ind).setResources(resources);
    newdocument.addPage(document.getPage(pg_ind));
}
newdocument.save("D:/Testfiles/stu.pdf");
return newdocument;
}

但我无法获得精确页面图形。我正在失去一些东西。

输入pdf

输出pdf

标签: javapdfpdfbox

解决方案


有多个问题,一些在细节上,一些在概念上。

包装在保存图形状态/恢复图形状态信封中

绘制 XObject 时,该 XObject 中的图形状态更改不会更改您当前的图形状态。为了确保在将 XObject 指令复制到页面内容流后仍然如此,您必须将该块包装到保存图形状态/恢复图形状态信封 ( q ... Q ) 中。您可以通过添加这两行来做到这一点

tokens1.add(ind++, Operator.getOperator("q"));
tokens1.add(ind, Operator.getOperator("Q"));

在你的指令复制循环之前

for (int k=0; k< tokens3.size(); k++) {
    ...
}

坐标系

您假设 XObject 中的坐标系等于页面的坐标系。不一定。XObjects 可能有一个矩阵条目,表示要应用的转换。

边界框

您不限制 XObject 指令绘制的区域。但是 XObjects 有一个BBox条目,表示要将输出剪辑到的框。

可选内容

XObjects 也可能有一个OC条目,表示它们的可选内容成员资格。这种成员资格需要转换为等效的可选内容标记。

标记内容,结构树

XObjects 也可以通过它们的StructParentStructParents条目来引用结构父树。为了保持文档的结构完整性,您可能需要大量更新结构树。

分组

XObjects 可能包含一个Group条目,指示其内容应被视为一个组。特别是在透明度组的情况下,这会导致透明度相关功能的行为与复制到页面内容中的相同指令的行为不同。

除非您完全分析以某种透明度绘制的每一位内容的效果,并逐个重写绘制它的指令,否则将指令从 XObject 复制到页面内容流将导致显示的内容存在显着差异。

用法

您的代码假定 XObject 在页面内容流中仅使用一次。不必如此,它也可以更频繁地使用或根本不使用。


参考

在评论中,您要求提供参考。实际上,这一切都在 PDF 规范 ISO 32000 中,已经在公开的 ISO 32000-1 中:

8.10 表单 XObjects

表单 XObject是 PDF 内容流,它是任何图形对象序列(包括路径对象、文本对象和采样图像)的自包含描述。一个表单 XObject 可以被绘制多次——在几个页面上或在同一页面上的多个位置——并且每次都产生相同的结果,只受调用时的图形状态的影响。

因此,给定页面上的任意数量的使用都是可能的

Do运算符应用于表单 XObject 时,符合标准的阅读器应执行以下任务:

a) 保存当前图形状态,就像调用q运算符一样(参见 8.4.4,“图形状态运算符”)

b) 将表单字典的Matrix条目中的矩阵与当前变换矩阵 (CTM)连接起来

c) 根据表单字典的BBox条目进行剪辑

d) 绘制在表单内容流中指定的图形对象

e) 恢复保存的图形状态,就像调用Q运算符一样(参见 8.4.4,“图形状态运算符”)

因此,当复制到页面内容流中时,您应该等效地使用q / Q信封并尊重MatrixBBox条目。

8.11.3.3 XObjects 和注解中的可选内容

除了内容流中的标记内容外,表单 XObjects 和图像 XObjects(参见 8.8,“外部对象”)和注释(参见 12.5,“注释”)可能包含一个OC条目,该条目应为可选内容组或可选内容会员词典。

表单或图像 XObject 的可见性应由组的状态或成员字典引用的组的状态及其P(或VE)条目以及 XObject 所在的上下文中的当前可见性状态决定被调用(即,对象在执行 Do操作的位置的内容流中是否可见)。

因此,在复制到页面内容时,请尊重此可选内容信息。

11.6.6 透明组 XObject

透明度组在 PDF 中表示为一种特殊类型的组 XObject(请参阅“组 XObjects”),称为透明度组 XObject。一个组 XObject 又是一种表单 XObject,其区别在于其表单字典中存在一个条目(请参阅“表单字典”)。此条目的值是定义组属性的子组属性字典。字典内容的格式和含义应由其组子类型确定,该组子类型由字典的S条目指定。透明度组(子类型Transparency)的条目显示在表 147 中。

...

附件 L

图 L.16 和 L.17

因此,从透明度组复制可能会显着改变外观。

14.7.4.3 PDF 对象作为内容项

当结构元素的内容包括与页面相关但不直接包含在页面内容流中的整个 PDF 对象(例如 XObject 或注释)时,该对象应在结构元素的K条目中由对象引用标识字典(见表 325)。

...

14.7.4.4 从内容项中查找结构元素

...

为了定位相关的父树条目,树中表示的每个对象或内容流都应包含一个特殊的字典条目,StructParentStructParents(见表 326)。根据内容项的类型,此条目可能出现在包含标记内容序列的页面的页面对象中、表单或图像 XObject 的流字典中、注释字典或任何其他类型的对象字典中作为内容项包含在结构元素中。

来自同一章节的这些信息和更多信息应该清楚地表明,从 XObject 复制到页面内容后的结构信息必须进行大修。


推荐阅读