Reading legacy Word forms checkboxes converted to PDF

独自空忆成欢 提交于 2020-01-17 05:45:21

问题


Our customers sends us orders as PDF forms which is generated from a Word document built with legacy forms.

Currently people at our customer center is punching the orders into our system, but we have decided to try and automate this task.

I'm able to read the content of the PDF with a simple PdfReader per page:

    public static string GetPdfText(string path)
    { 
        var text = string.Empty;
        using (var reader = new PdfReader(path))
        {
            for (var page = 1; page <= reader.NumberOfPages; page++)
            {
                text += PdfTextExtractor.GetTextFromPage(reader, page);
            }
        }
        return text;
    }

But not the checkboxes...

I am able to detect the checkboxes as dictionaries while running through every object in the PDF, but I'm unable to distinguish them from other objects or read the value...

    public static IEnumerable<PdfDictionary> ReadCheckboxes(string path)
    {
        using (var reader = new PdfReader(path))
        {
            var checkboxes = new List<PdfDictionary>();
            for (var i = 0; i < reader.XrefSize; i++)
            {
                var pdfObject = reader.GetPdfObject(i);
                checkboxes.Add((PdfDictionary) pdfObject);
            }
            return checkboxes;
        }
    }

What am I missing? I've also tried reading the AcroFields, but they're empty...

I have uploaded a sample PDF with legacy checkboxes here.

Currently there is not option to integrate between our systems or do any changes to the underlying PDF or Word document.


回答1:


The OP indicated in comments that a solution which returns an output like "checkbox at position x0, y0, checked; checkbox at position x1, y1, not checked; ..." would suffice, i.e. his "forms" are static enough so that these positions allow identification of the meaning of the respective checkboxes. Thus, here an implementation of this variant.

I just saw that the question is tagged c# while I have implemented the search using Java. This should not be too big a problem, the code should be easy to port. If there are problems porting, I'll add a C# version here.

As the checkboxes are drawn using vector graphics, the text extraction already used by the OP does not find them. Fortunately, though, the iText parsing framework can also be used to look for vector graphics.

Thus, we first need an ExtRenderListener (IExtRenderListener in iTextSharp) which collects the boxes. It only has non-trivial implementations of the interface methods modifyPath and renderPath:

@Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
    switch (renderInfo.getOperation())
    {
    case PathConstructionRenderInfo.RECT:
    {
        float x = renderInfo.getSegmentData().get(0);
        float y = renderInfo.getSegmentData().get(1);
        float w = renderInfo.getSegmentData().get(2);
        float h = renderInfo.getSegmentData().get(3);
        rectangle = new Rectangle(x, y, x+w, y+h);
    }
    case PathConstructionRenderInfo.MOVETO:
    {
        float x = renderInfo.getSegmentData().get(0);
        float y = renderInfo.getSegmentData().get(1);
        moveToVector = new Vector(x, y, 1);
        lineToVector = null;
        break;
    }
    case PathConstructionRenderInfo.LINETO:
    {
        if (moveToVector != null)
        {
            float x = renderInfo.getSegmentData().get(0);
            float y = renderInfo.getSegmentData().get(1);
            lineToVector = new Vector(x, y, 1);
        }
        break;
    }
    default:
        moveToVector = null;
        lineToVector = null;
    }
}

@Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
    if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
    {
        if (rectangle != null)
        {
            Vector a = new Vector(rectangle.getLeft(), rectangle.getBottom(), 1).cross(renderInfo.getCtm());
            Vector b = new Vector(rectangle.getRight(), rectangle.getBottom(), 1).cross(renderInfo.getCtm());
            Vector c = new Vector(rectangle.getRight(), rectangle.getTop(), 1).cross(renderInfo.getCtm());
            Vector d = new Vector(rectangle.getLeft(), rectangle.getTop(), 1).cross(renderInfo.getCtm());

            Box box = new Box(new LineSegment(a, c), new LineSegment(b, d));
            boxes.add(box);

        }
        if (moveToVector != null && lineToVector != null)
        {
            if (!boxes.isEmpty())
            {
                Vector from = moveToVector.cross(renderInfo.getCtm());
                Vector to = lineToVector.cross(renderInfo.getCtm());

                boxes.get(boxes.size() - 1).selectDiagonal(new LineSegment(from, to));
            }
        }
    }

    moveToVector = null;
    lineToVector = null;
    rectangle = null;
    return null;
}

Vector moveToVector = null;
Vector lineToVector = null;
Rectangle rectangle = null;

public Iterable<Box> getBoxes()
{
    return boxes;
}

final List<Box> boxes = new ArrayList<Box>();

(from CheckBoxExtractionStrategy.java)

It uses a helper class Box which models the checkboxes using their respective diagonals:

public class Box
{
    public LineSegment getDiagonal()
    {
        return diagonalA;
    }

    public boolean isChecked()
    {
        return selectedA && selectedB;
    }

    Box(LineSegment diagonalA, LineSegment diagonalB)
    {
        this.diagonalA = diagonalA;
        this.diagonalB = diagonalB;
    }

    void selectDiagonal(LineSegment diagonal)
    {
        if (approximatelyEquals(diagonal, diagonalA))
            selectedA = true;
        else if (approximatelyEquals(diagonal, diagonalB))
            selectedB = true;
    }

    boolean approximatelyEquals(LineSegment a, LineSegment b)
    {
        float permissiveness = a.getLength() / 10.0f;
        if (approximatelyEquals(a.getStartPoint(), b.getStartPoint(), permissiveness) &&
                approximatelyEquals(a.getEndPoint(), b.getEndPoint(), permissiveness))
            return true;
        if (approximatelyEquals(a.getStartPoint(), b.getEndPoint(), permissiveness) &&
                approximatelyEquals(a.getEndPoint(), b.getStartPoint(), permissiveness))
            return true;
        return false;
    }

    boolean approximatelyEquals(Vector a, Vector b, float permissiveness)
    {
        return a.subtract(b).length() < permissiveness;
    }

    boolean selectedA = false;
    boolean selectedB = false;
    final LineSegment diagonalA, diagonalB;
}

(Inner class in CheckBoxExtractionStrategy.java)

Applying it like this to the sample document:

for (int page = 1; page <= pdfReader.getNumberOfPages(); page++)
{
    System.out.printf("\nPage %s\n====\n", page);

    CheckBoxExtractionStrategy strategy = new CheckBoxExtractionStrategy();
    PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);
    parser.processContent(page, strategy);

    for (Box box : strategy.getBoxes())
    {
        Vector basePoint = box.getDiagonal().getStartPoint();
        System.out.printf("at %s, %s - %s\n", basePoint.get(Vector.I1), basePoint.get(Vector.I2),
                box.isChecked() ? "checked" : "unchecked");
    }
}

one gets the output

Page 1
====
at 73.104, 757.8 - checked
at 86.544, 757.8 - checked
at 99.984, 757.8 - unchecked

for the OP's document



来源:https://stackoverflow.com/questions/40549977/reading-legacy-word-forms-checkboxes-converted-to-pdf

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!