由于某些原因,首页上的背景颜色不正确

时间:2019-02-11 18:46:52

标签: java pdf pdfbox

文件示例:here 问题:我正在尝试确定文本是否在页面上可见。为此,对于每个 Fill 命令,我保存其路径和颜色,如下所示:

    public class FillNonZeroRule extends OperatorProcessor {
        @Override
        public final void process(Operator operator, List<COSBase> operands) throws IOException {
            PDGraphicsState gs = getGraphicsState();    
            linePath.setWindingRule(GeneralPath.WIND_NON_ZERO);
            addFillPath(gs.getNonStrokingColor());
            linePath.reset();
        }

        @Override
        public String getName() {
            return "f";
        }
    }

    void addFillPath(PDColor color) {
        filledPaths.put((GeneralPath)linePath.clone(), color);
    }

而且,这就是我以后为每个角色获取背景的方法:

private PDColor getCharacterBackgroundColor(TextPosition text) {
        PDColor color = null;           
        for (Map.Entry<GeneralPath, PDColor> filledPath : filledPaths.entrySet()) {
            Vector center = getTextPositionCenterPoint(text);
            if (filledPath.getKey().contains(lowerLeftX + center.getX(), lowerLeftY + center.getY())) {
                color = filledPath.getValue();                  
            }
        }

        return color;
    }

也请为每个文本位置保存颜色。 然后,我尝试确定该背景色是否与字符色相同。 有趣的是,对于首页的背景颜色和标题的文本颜色(顶部带有背景的行)均为2301728(int RGB值)-这是不正确的,但是,对于第二页,文本颜色为2301728,背景颜色为14145754(正确!)。 所以我的问题是什么导致首页上的背景不正确... 预先感谢!

整个课程如下:

public class PdfToTextInfoConverter extends PDFTextStripper {

    private int rotation = 0;

    private float lowerLeftX = 0;

    private float lowerLeftY = 0;

    private PDPage page = null;

    private GeneralPath linePath;

    private Map<GeneralPath, PDColor> filledPaths;

    private Map<TextPosition, PDColor> nonStrokingColors;

    public PdfToTextInfoConverter(PDDocument pddfDoc) throws IOException {
        addOperator(new SetStrokingColorSpace());
        addOperator(new SetNonStrokingColorSpace());
        addOperator(new SetNonStrokingColorN());
        addOperator(new SetStrokingColor());
        addOperator(new SetNonStrokingColor());
        addOperator(new SetStrokingDeviceGrayColor());
        addOperator(new SetNonStrokingDeviceGrayColor());
        addOperator(new SetStrokingDeviceRGBColor());
        addOperator(new SetNonStrokingDeviceRGBColor());
        addOperator(new SetStrokingDeviceCMYKColor());
        addOperator(new SetNonStrokingDeviceCMYKColor());

        addOperator(new AppendRectangleToPath());
        addOperator(new ClipEvenOddRule());
        addOperator(new ClipNonZeroRule());
        addOperator(new ClosePath());
        addOperator(new CurveTo());
        addOperator(new CurveToReplicateFinalPoint());
        addOperator(new CurveToReplicateInitialPoint());
        addOperator(new EndPath());
        addOperator(new FillEvenOddAndStrokePath());
        addOperator(new FillEvenOddRule());
        addOperator(new FillNonZeroAndStrokePath());
        addOperator(new FillNonZeroRule());
        addOperator(new LineTo());
        addOperator(new MoveTo());
        addOperator(new StrokePath());
        document = pddfDoc;
    }

    public void stripPage(int pageNum, int resolution) throws IOException {
        this.setStartPage(pageNum + 1);
        this.setEndPage(pageNum + 1);
        page = document.getPage(pageNum);
        rotation = page.getRotation();
        linePath = new GeneralPath();
        filledPaths = new LinkedHashMap<>();
        nonStrokingColors = new HashMap<>();    
        Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream());
        writeText(document, dummy); // This call starts the parsing process and calls writeString repeatedly.
    }

    @Override
    public void processPage(PDPage page) throws IOException {
        PDRectangle pageSize = page.getCropBox();

        lowerLeftX = pageSize.getLowerLeftX();
        lowerLeftY = pageSize.getLowerLeftY();

        super.processPage(page);
    }

    private Integer getCharacterBackgroundColor(TextPosition text) {
        Integer fillColorRgb = null;
        try {           
            for (Map.Entry<GeneralPath, PDColor> filledPath : filledPaths.entrySet()) {
                Vector center = getTextPositionCenterPoint(text);
                if (filledPath.getKey().contains(lowerLeftX + center.getX(), lowerLeftY + center.getY())) {
                    fillColorRgb = filledPath.getValue().toRGB();                   
                }
            }
        } catch (IOException e) {
            logger.error("Could not convert color to RGB", e);
        }
        return fillColorRgb;
    }

    private int getCharacterColor(TextPosition text) {
        int colorRgb = 0; // assume it's black even if we could not convert to RGB
        try {
            colorRgb = nonStrokingColors.get(text).toRGB();         
        } catch (IOException e) {
            logger.error("Could not convert color to RGB", e);
        }
        return colorRgb;
    }

    @Override
    protected void processTextPosition(TextPosition text) {
        PDGraphicsState gs = getGraphicsState();
        // check opacity for stroke and fill text 
        if (gs.getAlphaConstant() < Constants.EPSILON && gs.getNonStrokeAlphaConstant() < Constants.EPSILON) {
            return;
        }                       

        Vector center = getTextPositionCenterPoint(text);
        Area area = gs.getCurrentClippingPath();
        if (area == null || area.contains(lowerLeftX + center.getX(), lowerLeftY + center.getY())) {            
            nonStrokingColors.put(text, gs.getNonStrokingColor());
            super.processTextPosition(text);
        }
    }

    @Override
    protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
        for (TextPosition text : textPositions) {           
            Integer characterColor = getCharacterColor(text);
            Integer characterBackgroundColor = getCharacterBackgroundColor(text);
        }
    }

    private Vector getTextPositionCenterPoint(TextPosition text) {
        Matrix textMatrix = text.getTextMatrix();
        Vector start = textMatrix.transform(new Vector(0, 0));
        Vector center = null;
        switch (rotation) {
        case 0:
            center = new Vector(start.getX() + text.getWidth()/2, start.getY()); 
            break;
        case 90:
            center = new Vector(start.getX(), start.getY() + text.getWidth()/2);
            break;
        case 180:
            center = new Vector(start.getX() - text.getWidth()/2, start.getY());
            break;
        case 270:
            center = new Vector(start.getX(), start.getY() - text.getWidth()/2);
            break;
        default:
            center = new Vector(start.getX() + text.getWidth()/2, start.getY());
            break;
        }

        return center;
    }

    void addFillPath(PDColor color) {
        filledPaths.put((GeneralPath)linePath.clone(), color);
    }
}

1 个答案:

答案 0 :(得分:2)

这是PDFBox中的错误。

(嗯,您的代码中还有一个问题,但是当前问题的原因是基于PDFBox的。)

错误

问题是PDColor.toRGB()的呼叫

fillColorRgb = filledPath.getValue().toRGB();

损坏相关特定颜色的颜色值本身!

有问题的颜色空间是分色颜色空间。因此,PDColor.toRGB()使用其PDSeparation.toRGB(float[])成员作为参数来调用components

如果给定参数的RGB值尚未在色彩空间中缓存,则PDSeparation.toRGB(float[])将为给定参数评估tintTransform。对于所讨论的色彩空间,色调转换是PDFunctionType0实例。因此,称为PDFunctionType0.eval(float[])

不幸的是,PDFunctionType0.eval(float[])假定它可以出于自身目的使用数组参数input

input[i] = clipToRange(input[i], domain.getMin(), domain.getMax());
input[i] = interpolate(input[i], domain.getMin(), domain.getMax(), 
        encodeValues.getMin(), encodeValues.getMax());
input[i] = clipToRange(input[i], 0, sizeValues[i] - 1);

但是此数组是原始PDColor成员components。因此,此评估将颜色对象的单一成分从0.172更改为43.688。

后来toRGB调用该颜色时,发现其43.688(或由于其他不必要的更改而导致的其他值)远远超过最大值1.0,因此他们将其裁剪为1.0并从那里进行变换。但是组件1.0的颜色空间中的颜色恰好是用于前景文本的颜色。因此,您的代码认为背景和前景是相同的。

解决方法

要解决此问题,方法PDFunctionType0.eval(float[])应该重写为不要写入其参数数组。一种快速的方法是添加

input = input.clone();

该方法PDFunctionType0.eval(float[])的顶部。

您代码中的问题

您的方法getTextPositionCenterPoint使用页面旋转来确定给定TextPosition的基线中心。但是,这仅对于页面旋转后垂直绘制的文本是正确的。但是,对于您的文档,情况并非如此。因此,您无需分析页面旋转,而是需要分析文本矩阵以了解文本的实际方向。

但是,这对您而言并没有太大区别,因为您用作字符宽度的TextPosition.getWidth()值也是基于页面旋转来计算的。由于相关页面未旋转,但文本方向旋转了90°,因此TextPosition.getWidth()始终返回0 ...您可能想使用getWidthDirAdj()来代替...