pdfbox - 尽管删除了 PDAnnotation,文本仍显示为蓝色

标签 pdfbox

我们有一个要求,需要删除某些匹配条件检查的注释。当我执行 allPageAnnotationsList.remove(annotationTobeRemoved) 语句时,PDAnnotation 被删除。

但相应的文本仍然仅以蓝色显示。如何将文本颜色更新为正常(黑色)?

最佳答案

最初我以为您要求将页面上的所有非黑色文本更改为黑色。这导致了我原来的答案,现在是第一部分“将所有文本更新为黑色”。然后您澄清说您只想将已删除注释区域中的文本设为黑色。这在第二部分“将区域中的文本更新为黑色”中显示。

将所有文本更新为黑色

首先,正如 Tilman 在评论中所述,删除链接注释通常只会删除该链接的交互性,但链接注释区域中的文本保持原样。因此,如果您想将文本颜色更新为正常(黑色),则必须添加第二个步骤并操作静态页面内容中的颜色。

静态页面内容由改变图形状态或绘制某些内容的指令流定义。用于绘图的颜色是图形状态的一部分,由显式颜色设置指令设置。因此,人们可能会认为您可以通过选择正常(黑色)的指令来简单地替换所有颜色设置指令。

不幸的是,这并不容易,因为颜色也可能会改变以绘制其他东西。例如。在你的文档的开头,整个页面都被白色填充;如果您在填充指令之前替换了颜色设置指令,则整个页面将是黑色的。不完全是您想要的。

要将文本颜色更新为正常(黑色)但不更改其他颜色,因此,您必须考虑要更改的指令的上下文。

PDFBox 解析框架可以在这方面为您提供帮助,迭代内容流并跟踪图形状态。

此外,基于该框架,在 this answer 中创建了一个通用内容流编辑器帮助程序类。 ,PdfContentStreamEditor 。 (有关详细信息和示例用途,请参阅该答案。)现在您只需根据您的用例对其进行自定义,例如像这样:

PDDocument document = ...;
for (PDPage page : document.getDocumentCatalog().getPages()) {
    PdfContentStreamEditor editor = new PdfContentStreamEditor(document, page) {
        @Override
        protected void write(ContentStreamWriter contentStreamWriter, Operator operator, List<COSBase> operands) throws IOException {
            String operatorString = operator.getName();

            if (TEXT_SHOWING_OPERATORS.contains(operatorString)) {
                if (currentlyReplacedColor == null)
                {
                    PDColor currentFillColor = getGraphicsState().getNonStrokingColor();
                    if (!isBlack(currentFillColor))
                    {
                        currentlyReplacedColor = currentFillColor;
                        super.write(contentStreamWriter, SET_NON_STROKING_GRAY, GRAY_BLACK_VALUES);
                    }
                }
            } else if (currentlyReplacedColor != null) {
                PDColorSpace replacedColorSpace = currentlyReplacedColor.getColorSpace();
                List<COSBase> replacedColorValues = new ArrayList<>();
                for (float f : currentlyReplacedColor.getComponents())
                    replacedColorValues.add(new COSFloat(f));
                if (replacedColorSpace instanceof PDDeviceCMYK)
                    super.write(contentStreamWriter, SET_NON_STROKING_CMYK, replacedColorValues);
                else if (replacedColorSpace instanceof PDDeviceGray)
                    super.write(contentStreamWriter, SET_NON_STROKING_GRAY, replacedColorValues);
                else if (replacedColorSpace instanceof PDDeviceRGB)
                    super.write(contentStreamWriter, SET_NON_STROKING_RGB, replacedColorValues);
                else {
                    //TODO
                }
                currentlyReplacedColor = null;
            }

            super.write(contentStreamWriter, operator, operands);
        }

        PDColor currentlyReplacedColor = null;

        final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
        final Operator SET_NON_STROKING_CMYK = Operator.getOperator("k");
        final Operator SET_NON_STROKING_RGB = Operator.getOperator("rg");
        final Operator SET_NON_STROKING_GRAY = Operator.getOperator("g");
        final List<COSBase> GRAY_BLACK_VALUES = Arrays.asList(COSInteger.ZERO);
    };
    editor.processPage(page);
}
document.save("withBlackText.pdf");

( ChangeTextColor 测试 testMakeTextBlackTestAfterRemovingAnnotation)

这里我们检查当前指令是否是文本绘制指令。如果是,并且当前颜色尚未被替换,我们检查当前颜色是否已经是黑色。如果它不是黑色,我们将其存储并添加一条指令以将当前填充颜色替换为黑色。

否则,即如果当前指令不是文本绘制指令,我们检查当前颜色是否已替换为黑色。如果有,我们恢复原来的颜色。

要检查给定颜色是否为黑色,我们使用以下辅助方法。

static boolean isBlack(PDColor pdColor) {
    PDColorSpace pdColorSpace = pdColor.getColorSpace();
    float[] components = pdColor.getComponents();
    if (pdColorSpace instanceof PDDeviceCMYK)
        return (components[0] > .9f && components[1] > .9f && components[2] > .9f) || components[3] > .9f;
    else if (pdColorSpace instanceof PDDeviceGray)
        return components[0] < .1f;
    else if (pdColorSpace instanceof PDDeviceRGB)
        return components[0] < .1f && components[1] < .1f && components[2] < .1f;
    else
        return false;
}

(ChangeTextColor辅助方法)

将区域中的文本更新为黑色

您在评论中澄清,您只想将已删除注释区域中的文本变为黑色。

为此,您必须收集删除的注释的矩形,然后在切换颜色之前检查位置是否位于这些矩形之一内。

这可以通过如下扩展上面的代码来完成。在这里,我仅删除所有其他注释并收集它们的矩形以供稍后检查。此外,我还重写了 PDFStreamEngine 方法 showText(byte[]) 来存储当前文本绘制指令中显示的文本的位置。

PDDocument document = ...;
for (PDPage page : document.getDocumentCatalog().getPages()) {
    List<PDRectangle> areas = new ArrayList<>();
    // Remove every other annotation, collect their areas
    List<PDAnnotation> annotations = new ArrayList<>();
    boolean remove = true;
    for (PDAnnotation annotation : page.getAnnotations()) {
        if (remove)
            areas.add(annotation.getRectangle());
        else
            annotations.add(annotation);
        remove = !remove;
    }
    page.setAnnotations(annotations);

    PdfContentStreamEditor editor = new PdfContentStreamEditor(document, page) {
        @Override
        protected void write(ContentStreamWriter contentStreamWriter, Operator operator, List<COSBase> operands) throws IOException {
            String operatorString = operator.getName();

            if (TEXT_SHOWING_OPERATORS.contains(operatorString) && isInAreas()) {
                if (currentlyReplacedColor == null)
                {
                    PDColor currentFillColor = getGraphicsState().getNonStrokingColor();
                    if (!isBlack(currentFillColor))
                    {
                        currentlyReplacedColor = currentFillColor;
                        super.write(contentStreamWriter, SET_NON_STROKING_GRAY, GRAY_BLACK_VALUES);
                    }
                }
            } else if (currentlyReplacedColor != null) {
                PDColorSpace replacedColorSpace = currentlyReplacedColor.getColorSpace();
                List<COSBase> replacedColorValues = new ArrayList<>();
                for (float f : currentlyReplacedColor.getComponents())
                    replacedColorValues.add(new COSFloat(f));
                if (replacedColorSpace instanceof PDDeviceCMYK)
                    super.write(contentStreamWriter, SET_NON_STROKING_CMYK, replacedColorValues);
                else if (replacedColorSpace instanceof PDDeviceGray)
                    super.write(contentStreamWriter, SET_NON_STROKING_GRAY, replacedColorValues);
                else if (replacedColorSpace instanceof PDDeviceRGB)
                    super.write(contentStreamWriter, SET_NON_STROKING_RGB, replacedColorValues);
                else {
                    //TODO
                }
                currentlyReplacedColor = null;
            }

            super.write(contentStreamWriter, operator, operands);

            before = null;
            after = null;
        }

        PDColor currentlyReplacedColor = null;

        final List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
        final Operator SET_NON_STROKING_CMYK = Operator.getOperator("k");
        final Operator SET_NON_STROKING_RGB = Operator.getOperator("rg");
        final Operator SET_NON_STROKING_GRAY = Operator.getOperator("g");
        final List<COSBase> GRAY_BLACK_VALUES = Arrays.asList(COSInteger.ZERO);

        @Override
        protected void showText(byte[] string) throws IOException {
            Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
            if (before == null)
                before = getTextMatrix().multiply(ctm);
            super.showText(string);
            after = getTextMatrix().multiply(ctm);
        }

        Matrix before = null;
        Matrix after = null;

        boolean isInAreas() {
            return isInAreas(before) || isInAreas(after);
        }
        boolean isInAreas(Matrix m) {
            return m != null && areas.stream().anyMatch(rect -> rect.contains(m.getTranslateX(), m.getTranslateY()));
        }
    };
    editor.processPage(page);
}
document.save("WithoutSomeAnnotation-withBlackTextThere.pdf");

关于pdfbox - 尽管删除了 PDAnnotation,文本仍显示为蓝色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68299177/

相关文章:

java - 创建仅在使用 PDFBox 打印时显示的水印(pdf 可选内容)

java - Mac 上的 PDFBox 在静默打印时出现严重错误

java - 使用 PDFBox 裁剪页面并用白色填充矩形外部

java - 计算pdf文件java中每个单词的出现次数

java - 如何更改 PDF 文件中图像的过滤器

java - 使用 pdfbox 从输出文本中删除新行

java - pdfbox 请求后获取 ava.lang.ClassNotFoundException : org. apache.pdfbox.io.RandomAccessRead 控制台错误

android - 适用于 Android 的 PDF 库 - PDFBox?

java - 使用 PDPageContentStream.drawLine 时出现 pdfbox 错误

pdfbox - 了解 PDFBox 2.0 中字体的加载