Search code examples
javapdfpdfbox

How to adjust Operators and Operands from XObjects with PDFBox


Using the PDFContentStreamEditor.class it is possible to manipulate the operators and operands of the main PDF page contents.

It seems that XObjects are not edited by the editor. My question is if there are examples to also adjust operators and operands of XObjects ?

 PDResources pdResources = page.getResources();
        pdResources.getXObjectNames().forEach(propertyName -> {
                    PDXObject xObject = pdResources.getXObject(propertyName);
                    if(xObject instanceof PDFormXObject pdformxobject) {
                           //Is this the right place
                    }
            }

I tried something else overwriting the processTransparencyGroup method in the PDFContentStreamEditor.class. I thought i am getting nearer to the solution but with the following line i only add a Contents element inside the XObject and do not change the root objects operators and operands:

group.getCOSObject().setItem(COSName.CONTENTS, stream);

Here the complete method:

@Override
protected void processTransparencyGroup(PDTransparencyGroup group){
 PDStream stream = new PDStream(document, group.getContents());
        replacementForm = new ContentStreamWriter(replacementFormStream = stream.createOutputStream(COSName.FLATE_DECODE));// stream.createOutputStream(COSName.FLATE_DECODE));
        super.processTransparencyGroup(group);
        replacementFormStream.close();

        group.getCOSObject().setItem(COSName.CONTENTS, stream);
        replacementForm = null;
        replacementFormStream = null;
}

Solution

  • You can edit form XObjects content streams by adding this method to the PdfContentStreamEditor class from this old answer:

    public void processFormXObject(PDFormXObject formXObject, PDPage page) throws IOException {
        PDStream stream = new PDStream(document);
        replacement = new ContentStreamWriter(replacementStream = stream.createOutputStream(COSName.FLATE_DECODE));
        super.processChildStream(formXObject, page);
        replacementStream.close();
        try (OutputStream outputStream = formXObject.getCOSObject().createOutputStream()) {
            stream.createInputStream().transferTo(outputStream);
        } finally {
            replacement = null;
            replacementStream = null;
        }
    }
    

    (PdfContentStreamEditor method)

    (Form XObjects don't have their content streams in a Contents sub-entry, they themselves are the content streams. Thus, the replacement stream contents must be stored differently.)

    You can use it like this:

    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 (RGB_FILL_COLOR_OPERATORS.contains(operatorString))
                {
                    for (int i = 0; i < operands.size(); i++) {
                        COSBase number = operands.get(i);
                        if (number instanceof COSNumber) {
                            operands.set(i, new COSFloat(1.0f - ((COSNumber)number).floatValue()));
                        }
                    }
                }
    
                super.write(contentStreamWriter, operator, operands);
            }
    
            final List<String> RGB_FILL_COLOR_OPERATORS = Arrays.asList("rg", "sc", "scn");
        };
        PDResources resources = page.getResources();
        for (COSName name : resources.getXObjectNames()) {
            PDXObject xObject = resources.getXObject(name);
            if (xObject instanceof PDFormXObject) {
                System.out.printf("Editing form XObject %s.\n", name.toString());
                editor.processFormXObject((PDFormXObject) xObject, page);
            }
        }
    }
    

    (EditFormXObjectContent test testInvertColorsHighPioneerFallNewsletterADApdf_2)

    This example inverts some fill colors in the immediate form XObject page resources of the document.