Search code examples
javaannotationshighlightitext7appearance

Set appearance stream on highlight annotation with iText7


I have a PDF viewer that doesn't show highlights if they do not have an appearance stream. I'm trying out iText 7 core in java to try and add highlight annotations to PDFs but these annotations do not have appearance streams, and thus I'm looking to try and add them myself when writing the annotations to the PDF file.

I've come across this old answer, but it's for C# and iText 5, and I can't seem to figure out how to replicate it in iText 7 with a successful result.

So my question is thus: how do you set appearance streams on the highlighting annotations in iText 7 core that are working?

The furthest I've gotten with the code is shown below. I'm using the RegexBasedLocationExtrationStrategy class to find the locations of all search words in the pdf.

RegexBasedLocationExtractionStrategy evntlstnr = new RegexBasedLocationExtractionStrategy(pattern);
for (int pIdx = 0; pIdx < pdfDoc.getNumberOfPages(); ++pIdx) {
    final PdfPage page = pdfDoc.getPage(pIdx + 1);
    new PdfCanvasProcessor(evntlstnr).processPageContent(page);
    Collection<IPdfTextLocation> locations =  evntlstnr.getResultantLocations();
    for (IPdfTextLocation location : locations) {
        Rectangle rect = location.getRectangle();
        // Specify quad points in Z-like order
        // [0,1] x1,y1   [2,3] x2,y2
        // [4,5] x3,y3   [6,7] x4,y4
        float[] quads = new float[8];
        quads[0] = rect.getX();
        quads[1] = rect.getY() + rect.getHeight();
        quads[2] = rect.getX() + rect.getWidth();
        quads[3] = quads[1];
        quads[4] = quads[0];
        quads[5] = rect.getY();
        quads[6] = quads[2];
        quads[7] = quads[5];

        Color highlightColor = new DeviceRgb(0f, 0f, 1f);
        PdfTextMarkupAnnotation highlight = PdfTextMarkupAnnotation.createHighLight(rect, quads);
        highlight.setColor(highlightColor);


        Rectangle appearRect = new Rectangle(0f, 0f, rect.getWidth(), rect.getHeight());                            
        PdfFormXObject appearObj = new PdfFormXObject(appearRect);                                                        
        final PdfResources appearRes = appearObj.getResources();

        PdfExtGState extGState = new PdfExtGState();
        extGState.setBlendMode(PdfExtGState.BM_MULTIPLY);
        appearRes.addExtGState(extGState);
        appearObj.setBBox(new PdfArray(new float[] {0f, 0f, rect.getWidth(), rect.getHeight()}));

        PdfShading appearShading = new PdfShading.Axial(highlightColor.getColorSpace(), 0f, 0f, highlightColor.getColorValue(), 1f, 1f, highlightColor.getColorValue());                                                     
        appearRes.addShading(appearShading);

        appearRes.addColorSpace(highlightColor.getColorSpace());                            

        PdfAnnotationAppearance appearance = new PdfAnnotationAppearance(appearObj.getPdfObject());
        highlight.setNormalAppearance(appearance);
        highlight.setFlag(PdfAnnotation.PRINT);
        page.addAnnotation(highlight);
    }
}

Solution

  • Using Samuel's answer I stumbled my way to a working answer.

    I'm no expect in the PDF standard and this framework (iText), but my hypothesis, based on my working example below, is that the rectangle I was trying to write for the highlight is a crude fall-back method for "faking" the highlight rectangle when the viewer cannot render the annotations (since they have no appearance stream). Realizing this that the two operations are not linked, I came to the working example shown below. Hope this helps others in the future.

    RegexBasedLocationExtractionStrategy evntlstnr = new RegexBasedLocationExtractionStrategy(pattern);
    for (int pIdx = 0; pIdx < pdfDoc.getNumberOfPages(); ++pIdx) {
        final PdfPage page = pdfDoc.getPage(pIdx + 1);
        new PdfCanvasProcessor(evntlstnr).processPageContent(page);
        Collection<IPdfTextLocation> locations =  evntlstnr.getResultantLocations();
        for (IPdfTextLocation location : locations) {
            Rectangle rect = location.getRectangle();
            // Specify quad points in Z-like order
            // [0,1] x1,y1   [2,3] x2,y2
            // [4,5] x3,y3   [6,7] x4,y4
            float[] quads = new float[8];
            quads[0] = rect.getX();
            quads[1] = rect.getY() + rect.getHeight();
            quads[2] = rect.getX() + rect.getWidth();
            quads[3] = quads[1];
            quads[4] = quads[0];
            quads[5] = rect.getY();
            quads[6] = quads[2];
            quads[7] = quads[5];
    
            Color highlightColor = new DeviceRgb(1f, 1f, 0f);                                                                                                                           
    
            PdfTextMarkupAnnotation highlight = PdfTextMarkupAnnotation.createHighLight(rect, quads);
            highlight.setColor(highlightColor); 
            highlight.setFlag(PdfAnnotation.PRINT);
            page.addAnnotation(highlight);                            
    
            PdfCanvas canvas = new PdfCanvas(page);
            PdfExtGState extGState = new PdfExtGState();
            extGState.setBlendMode(PdfExtGState.BM_MULTIPLY);
            canvas.setExtGState(extGState);
            canvas.rectangle(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
            canvas.setFillColor(highlightColor);
            canvas.fill();                            
            canvas.release();                                                        
        }
    }