当前位置: 首页 > 知识库问答 >
问题:

使用PDFBox删除未启用的可选内容组

尉迟栋
2023-03-14

我使用的是来自java的ApachePDFBox,我有一个带有多个可选内容组的源PDF。我想做的是导出一个PDF版本,其中只包含标准内容和启用的可选内容组。为了我的目的,我保留原作的任何动态方面是很重要的。。。。因此,文本字段是静态文本字段,矢量图像是静态矢量图像,等等。之所以需要这样做,是因为我打算最终使用一个pdf表单编辑器程序,它不知道如何处理可选内容,并且会盲目地渲染所有内容,所以我想预处理源pdf,并在不太混乱的目标pdf上使用表单编辑程序。

我一直在试图找到一些东西,可以给我任何关于如何用谷歌做到这一点的提示,但没有用。我不知道我是否只是使用了错误的搜索词,或者这只是PDFBox API设计的目的之外的东西。我倒希望不是后者。此处显示的信息似乎不起作用(将C#代码转换为java),因为尽管我尝试导入的pdf具有可选内容,但在检查每个页面上的标记时,似乎没有任何OC资源。

    for(PDPage page:pages) {
        PDResources resources = page.getResources();            
        PDFStreamParser parser = new PDFStreamParser(page);
        parser.parse();
        Collection tokens = parser.getTokens();
        ...
    }

我真的很抱歉没有更多的代码来展示我到目前为止所做的尝试,但是我已经仔细研究了大约8个小时的JavaAPI文档,试图找出我可能需要做什么,只是还没有弄清楚。

我知道如何做的是添加文本行和图像到一个新的PDPage但我不知道如何检索该信息从一个给定的源页面复制它也不知道如何告诉哪个可选的内容组这样的信息是一部分的(如有的话)。我也不知道如何将源pdf中的表单字段复制到目标,也不知道如何将字体信息复制到目标。

老实说,如果有一个网页是我用谷歌搜索时找不到的,我很乐意读到更多关于它的信息,但我真的被困在这里了,我个人不知道有谁知道这个图书馆。

请帮忙。

编辑:尝试从下面的建议中理解,我编写了一个循环来检查页面上的每个XObject,如下所示:

PDResources resources = pdPage.getResources();
Iterable<COSName> names = resources.getXObjectNames();
for(COSName name:names) {
    PDXObject xobj = resources.getXObject(name);
    PDFStreamParser parser = new PDFStreamParser(xobj.getStream().toByteArray());
    parser.parse();
    Object [] tokens = parser.getTokens().toArray();
    for(int i = 0;i<tokens.length-1;i++) {
        Object obj = tokens[i];
        if (obj instanceof COSName && obj.equals(COSName.OC)) {
            i++;
            Object obj = tokens[i];
            if (obj instanceof COSName) {
                PDPropertyList props = resources.getProperties((COSName)obj);
                if (props != null) {
...

但是,在OC键之后,tokens数组中的下一个条目始终是标记为“BMC”的运算符。我从指定的可选内容组中找不到任何可以识别的信息。


共有2个答案

龙晟睿
2023-03-14

可选内容组用BDC和EMC标记。您必须浏览从解析器返回的所有标记,并从数组中删除“部分”。下面是一些不久前发布的C#代码-[1]:如何使用pdfbox从pdf中删除可选内容组及其内容?

我调查了一下(转换成Java),但没能让它像预期的那样工作。我设法删除BDC和EMC之间的内容,然后使用与示例相同的技术保存结果,但PDF已损坏。也许这是我缺乏C#知识(与元组等相关)。)

这是我想出的,正如我所说的,它不起作用,也许你或其他人(mkl,蒂尔曼·豪斯赫尔)可以发现这个缺陷。

    OCGDelete (PDDocument doc, int pageNum, String OCName) {
      PDPage pdPage = (PDPage) doc.getDocumentCatalog().getPages().get(pageNum);
      PDResources pdResources = pdPage.getResources();
      PDFStreamParser pdParser = new PDFStreamParser(pdPage);

      int ocgStart
      int ocgLength

      Collection tokens = pdParser.getTokens();
      Object[] newTokens = tokens.toArray()

      try {
        for (int index = 0; index < newTokens.length; index++) {
            obj = newTokens[index]
            if (obj instanceof COSName && obj.equals(COSName.OC)) {
                // println "Found COSName at "+index   /// Found Optional Content
                startIndex = index
                index++
                if (index < newTokens.size()) {
                    obj = newTokens[index]
                    if (obj instanceof COSName) {
                        prop = pdRes.getProperties(obj)
                        if (prop != null && prop instanceof PDOptionalContentGroup) {
                            if ((prop.getName()).equals(delLayer)) {
                                println "Found the Layer to be deleted"
                                println "prop Name was " + prop.getName()

                                index++

                                if (index < newTokens.size()) {
                                    obj = newTokens[index]

                                    if ((obj.getName()).equals("BDC")) {
                                        ocgStart = index
                                        println("OCG Start " + ocgStart)
                                        ocgLength = -1
                                        index++

                                        while (index < newTokens.size()) {
                                            ocgLength++
                                            obj = newTokens[index]
                                            println " Loop through relevant OCG Tokens " + obj
                                            if (obj instanceof Operator && (obj.getName()).equals("EMC")) {

                                                println "the next obj was " + obj
                                                println "after that " + newTokens[index + 1] + "and then " + newTokens[index + 2]
                                                println("OCG End " + ocgLength++)
                                                break

                                            }

                                            index++
                                        }
                                        if (endIndex > 0) {
                                            println "End Index was something " + (startIndex + ocgLength)

                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    catch (Exception ex){
        println ex.message()
    }

    for (int i = ocgStart; i < ocgStart+ ocgLength; i++){
        newTokens.removeAt(i)
    }


    PDStream newContents = new PDStream(doc);
    OutputStream output = newContents.createOutputStream(COSName.FLATE_DECODE);
    ContentStreamWriter writer = new ContentStreamWriter(output);
    writer.writeTokens(newTokens);
    output.close();
    pdPage.setContents(newContents);

  }
麹繁
2023-03-14

这里有一个强大的解决方案,用于删除标记的内容块(如果任何人发现任何不正常的内容块,都可以接受反馈)。您应该能够调整OC块。。。

这段代码正确地处理了资源的嵌套和移除(xobject、图形状态和字体-如果需要,可以很容易地添加其他内容)。

public class MarkedContentRemover {

    private final MarkedContentMatcher matcher;
    
    /**
     * 
     */
    public MarkedContentRemover(MarkedContentMatcher matcher) {
        this.matcher = matcher;
    }
    
    public int removeMarkedContent(PDDocument doc, PDPage page) throws IOException {
        ResourceSuppressionTracker resourceSuppressionTracker = new ResourceSuppressionTracker();
        
        PDResources pdResources = page.getResources();

        PDFStreamParser pdParser = new PDFStreamParser(page);
        
        
        PDStream newContents = new PDStream(doc);
        OutputStream newContentOutput = newContents.createOutputStream(COSName.FLATE_DECODE);
        ContentStreamWriter newContentWriter = new ContentStreamWriter(newContentOutput);
        
        List<Object> operands = new ArrayList<>();
        Operator operator = null;
        Object token;
        int suppressDepth = 0;
        boolean resumeOutputOnNextOperator = false;
        int removedCount = 0;
        
        while (true) {

            operands.clear();
            token = pdParser.parseNextToken();
            while(token != null && !(token instanceof Operator)) {
                operands.add(token);
                token = pdParser.parseNextToken();
            }
            operator = (Operator)token;
            
            if (operator == null) break;
            
            if (resumeOutputOnNextOperator) {
                resumeOutputOnNextOperator = false;
                suppressDepth--;
                if (suppressDepth == 0)
                    removedCount++;
            }
            
            if (OperatorName.BEGIN_MARKED_CONTENT_SEQ.equals(operator.getName())
                    || OperatorName.BEGIN_MARKED_CONTENT.equals(operator.getName())) {
                
                COSName contentId = (COSName)operands.get(0);

                final COSDictionary properties;
                if (operands.size() > 1) {
                    Object propsOperand = operands.get(1);
                    
                    if (propsOperand instanceof COSDictionary) {
                        properties = (COSDictionary) propsOperand;
    
                    } else if (propsOperand instanceof COSName) {
                        properties = pdResources.getProperties((COSName)propsOperand).getCOSObject();
                    } else {
                        properties = new COSDictionary();
                    }
                } else {
                    properties = new COSDictionary();
                }
                
                if (matcher.matches(contentId, properties)) {
                    suppressDepth++;
                }
                
            }
        
            if (OperatorName.END_MARKED_CONTENT.equals(operator.getName())) {
                if (suppressDepth > 0)
                    resumeOutputOnNextOperator = true;
            }

            else if (OperatorName.SET_GRAPHICS_STATE_PARAMS.equals(operator.getName())) {
                resourceSuppressionTracker.markForOperator(COSName.EXT_G_STATE, operands.get(0), suppressDepth == 0);
            }

            else if (OperatorName.DRAW_OBJECT.equals(operator.getName())) {
                resourceSuppressionTracker.markForOperator(COSName.XOBJECT, operands.get(0), suppressDepth == 0);
            }
            
            else if (OperatorName.SET_FONT_AND_SIZE.equals(operator.getName())) {
                resourceSuppressionTracker.markForOperator(COSName.FONT, operands.get(0), suppressDepth == 0);
            }
            
            

            if (suppressDepth == 0) {
                newContentWriter.writeTokens(operands);
                newContentWriter.writeTokens(operator);
            }

        }
        
        if (resumeOutputOnNextOperator)
            removedCount++;

        

        newContentOutput.close();

        page.setContents(newContents);
        
        resourceSuppressionTracker.updateResources(pdResources);
        
        return removedCount;
    }

    
    private static class ResourceSuppressionTracker{
        // if the boolean is TRUE, then the resource should be removed.  If the boolean is FALSE, the resource should not be removed
        private final Map<COSName, Map<COSName, Boolean>> tracker = new HashMap<>();
        
        public void markForOperator(COSName resourceType, Object resourceNameOperand, boolean preserve) {
            if (!(resourceNameOperand instanceof COSName)) return;
            if (preserve) {
                markForPreservation(resourceType, (COSName)resourceNameOperand);
            } else {
                markForRemoval(resourceType, (COSName)resourceNameOperand);
            }
        }
        
        public void markForRemoval(COSName resourceType, COSName refId) {
            if (!resourceIsPreserved(resourceType, refId)) {
                getResourceTracker(resourceType).put(refId, Boolean.TRUE);
            }
        }

        public void markForPreservation(COSName resourceType, COSName refId) {
            getResourceTracker(resourceType).put(refId, Boolean.FALSE);
        }
        
        public void updateResources(PDResources pdResources) {
            for (Map.Entry<COSName, Map<COSName, Boolean>> resourceEntry : tracker.entrySet()) {
                for(Map.Entry<COSName, Boolean> refEntry : resourceEntry.getValue().entrySet()) {
                    if (refEntry.getValue().equals(Boolean.TRUE)) {
                        pdResources.getCOSObject().getCOSDictionary(COSName.XOBJECT).removeItem(refEntry.getKey());
                    }
                }
            }
        }
        
        private boolean resourceIsPreserved(COSName resourceType, COSName refId) {
            return getResourceTracker(resourceType).getOrDefault(refId, Boolean.FALSE);
        }
        
        private Map<COSName, Boolean> getResourceTracker(COSName resourceType){
            if (!tracker.containsKey(resourceType)) {
                tracker.put(resourceType, new HashMap<>());
            }
            
            return tracker.get(resourceType);
            
        }
    }
    
}

助手类:

public interface MarkedContentMatcher {
    public boolean matches(COSName contentId, COSDictionary props);
}
 类似资料:
  • 我已经实现了从pdf中删除图层的功能,但问题是,我在图层上绘制的内容无法删除。下面是我用来删除图层的代码:

  • -我发现shiftRows函数存在一个导致excel中断的错误。https://bz.apache.org/bugzilla/show_bug.cgi?id=57423我不确定这是否已经修复。我已经在bugzilla上添加了一个评论来进一步了解这一点。 如果满足特定的单元格值条件,我正尝试从excel文件中删除行(而不是删除的内容)。 我成功地实现了这一点,但问题是它破坏了我的excel并且当我重

  • 有没有一种简单的方法来删除Angular中div的内容, 我有<代码>

  • 问题内容: 链接到pdf 当我尝试从上面的pdf中提取文本时,我混合了在evince查看器中不可见的文本和可见的文本。此外,某些所需的文本缺少查看器中未缺少的字符,例如“ FALCONS”中的“ S”和许多缺少的“ 1/2”字符。我认为这是由于来自不可见文本的干扰,因为在查看器中突出显示pdf时,可以看到不可见文本与可见文本重叠。 有没有办法删除不可见的文字?还是有其他解决方案? 码: 输出(粗体

  • 我正在尝试用Java中的PDFBox编辑pdf的一些内容。问题是,每当我编辑pdf中的任何字符串,并尝试使用Adobe Reader打开它时,最后一行不会出现在新呈现的pdf中。 当我尝试直接从浏览器顶部打开渲染的pdf时,我能够看到最后一行。但是,它以不同的格式编码。我正在使用以下代码编辑pdf的内容: 编辑pdf会删除“有问题?...”这一行。这里有什么问题?我做错了什么吗? 谢谢。

  • 链接到pdf 当我尝试从上面的pdf中提取文本时,我得到了在evince viewer中不可见的文本和可见的文本的混合。此外,一些所需的文本缺少查看器中没有缺少的字符,例如,“FALCONS”中的“S”和许多缺少的“½”字符。我认为这是由于不可见文本的干扰,因为在查看器中突出显示pdf时,可以看到不可见文本与可见文本重叠。 有没有办法去掉不可见的文字?还是有别的解决办法? 代码: 输出(粗体文本为