How to check if a text is transparent with pdfbox

后端 未结 1 1623
刺人心
刺人心 2021-01-28 02:59

I subclassed PDFStreamEngine and overloaded processTextPosition, I am now able to reconstruct the text like PDFTextStripper but I don\'t w

1条回答
  •  隐瞒了意图╮
    2021-01-28 03:41

    As turned out the transparent text actually was not transparent at all but instead merely covered by an image: In 201103 Key Smoking Statistic for SA 2010 FINAL.pdf the text "Key Smoking Statistics for SA --- 2004" has been covered by an image showing a TC logo.

    The following shows a proof of concept of a text stripper class ignoring text covered by images.

    public class VisibleTextStripper extends PDFTextStripper
    {
        public VisibleTextStripper() throws IOException
        {
            super();
            registerOperatorProcessor("Do", new Invoke());
        }
    
        //
        // Hiding operations
        //
        void hide(String name)
        {
            Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
            float x = ctm.getXPosition();
            float y = ctm.getYPosition();
            float scaledWidth = ctm.getXScale();
            float scaledHeight = ctm.getYScale();
    
            for(List characters : charactersByArticle)
            {
                Collection toRemove = new ArrayList();
                for (TextPosition character : characters)
                {
                    Matrix matrix = character.getTextPos();
                    float cx = matrix.getXPosition();
                    float cy = matrix.getYPosition();
                    float cw = character.getWidth();
                    float ch = character.getHeight();
                    if (overlaps(x, scaledWidth, cx, cw) && overlaps(y, scaledHeight, cy, cw))
                    {
                        System.out.printf("Hidden by '%s': X: %f; Y: %f; Width: %f; Height: %f; Char: '%s'\n", name, cx, cy, cw, ch, character.getCharacter());
                        toRemove.add(character);
                    }
                }
                characters.removeAll(toRemove);
            }
        }
    
        private boolean overlaps(float start1, float width1, float start2, float width2)
        {
            if (width1 < 0)
            {
                start1 += width1;
                width1 = -width1;
            }
    
            if (width2 < 0)
            {
                start2 += width2;
                width2 = -width2;
            }
    
            if (start1 < start2)
            {
                return start1 + width1 >= start2;
            }
            else
            {
                return start2 + width2 >= start1;
            }
        }
    
        //
        // operator processors
        //
        public static class Invoke extends OperatorProcessor
        {
            /**
             * Log instance.
             */
            private static final Log LOG = LogFactory.getLog(Invoke.class);
    
            /**
             * process : Do : Paint the specified XObject (section 4.7).
             * @param operator The operator that is being executed.
             * @param arguments List
             * @throws IOException If there is an error invoking the sub object.
             */
            public void process(PDFOperator operator, List arguments) throws IOException
            {
                VisibleTextStripper drawer = (VisibleTextStripper)context;
                COSName objectName = (COSName)arguments.get( 0 );
                Map xobjects = drawer.getResources().getXObjects();
                PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
                if ( xobject == null )
                {
                    LOG.warn("Can't find the XObject for '"+objectName.getName()+"'");
                }
                else if( xobject instanceof PDXObjectImage )
                {
                    drawer.hide(objectName.getName());
                }
                else if(xobject instanceof PDXObjectForm)
                {
                    PDXObjectForm form = (PDXObjectForm)xobject;
                    COSStream formContentstream = form.getCOSStream();
                    // if there is an optional form matrix, we have to map the form space to the user space
                    Matrix matrix = form.getMatrix();
                    if (matrix != null) 
                    {
                        Matrix xobjectCTM = matrix.multiply( context.getGraphicsState().getCurrentTransformationMatrix());
                        context.getGraphicsState().setCurrentTransformationMatrix(xobjectCTM);
                    }
                    // find some optional resources, instead of using the current resources
                    PDResources pdResources = form.getResources();
                    context.processSubStream( context.getCurrentPage(), pdResources, formContentstream );
                }
            }
        }
    }
    

    It works well with your sample document.

    The check

    if (overlaps(x, scaledWidth, cx, cw) && overlaps(y, scaledHeight, cy, cw))
    

    unfortunately assumes that there are no rotations (all transformations aggregated) involved, neither of the text nor of the image.

    For a generic solution you have to change this test to something that checks whether the 1x1 square transformed by the Matrix ctm = getGraphicsState().getCurrentTransformationMatrix() overlaps the character box transformed by the Matrix matrix = character.getTextPos() with fixed width and height cw = character.getWidth() and ch = character.getHeight(). And maybe simple overlapping does not suffice, you might want the character box to be covered sufficiently.

    Furthermore this test ignores image masks, i.e. transparency of the image.

    0 讨论(0)
提交回复
热议问题