Muthukumar R
Muthukumar R

Reputation: 13

Setting "overprint=true" for a specific ColorSpace on PDF (not the entire PDF Page)

I have a requirement to set overprint=true at ColorSpace level on a "PDF" (not for the entire PDF Page). I'm trying to solve this using PDFBox.

Again, I want to apply overprint only for a specific colorSpace (see If condition in the sample code below), but graphicsState.setStrokingOverprintControl(true); seems to be setting overprint for the entire PDF Page (all colorSpaces).

Here's the sample code. Anyone came across this problem? Am I missing something?

Sample code:

public static void fixPdfOverprint(String inputFilePath, String outputFilePath) throws IOException {
        final ByteArrayInputStream pdfStream = new ByteArrayInputStream(readFileIntoMemory(inputFilePath));
        try(PDDocument document = PDDocument.load(pdfStream)) {
            for (PDPage page : document.getDocumentCatalog().getPages()) {
                try(PDPageContentStream contentStream = createPageContentStream(document, page)) {
                    PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
                    PDResources pdResources = document.getDocumentCatalog().getPages().get(0).getResources();
                    for (COSName cosName : pdResources.getColorSpaceNames()) {
                         if(cosName.getName().equals("<my specific colorSpace>")) {
                           graphicsState.setStrokingOverprintControl(true); // Why this is setting for the entire page rathen than just this colorSpace. Btw - I confirmed that this if condition is correct.
                         }
                    }
                    contentStream.setGraphicsStateParameters(graphicsState);
                }
            }

            document.save(outputFilePath);
        }
    }

Upvotes: 1

Views: 858

Answers (1)

Tilman Hausherr
Tilman Hausherr

Reputation: 18916

Here's some code that will work only on the content stream of the page. It does not process xobject forms, patterns and whatever other content streams. It is based somewhat on the RemoveAllText.java example from the source code download, but inserts the setting of an ExtGState after the setting of a colorspace. Note that you must do at least one change where //TODO can be seen.

public static void main(String[] args) throws IOException
{
    if( args.length != 2 )
    {
        usage();
    }
    else
    {
        try (PDDocument document = PDDocument.load(new File(args[0])))
        {
            if (document.isEncrypted())
            {
                System.err.println(
                        "Error: Encrypted documents are not supported for this example.");
                System.exit(1);
            }
            for (PDPage page : document.getPages())
            {
                insertOverprint(page, document);
            }
            document.save(args[1]);
        }
    }
}

private static void insertOverprint(PDPage page, PDDocument document) throws IOException
{
    // non stroking overprint control true
    PDExtendedGraphicsState extGStateNonStrokingOverprintCtrlTrue = new PDExtendedGraphicsState();
    extGStateNonStrokingOverprintCtrlTrue.setNonStrokingOverprintControl(true);
    COSName nameExtGStateNonStrokingOverprintCtrlTrue = page.getResources().add(extGStateNonStrokingOverprintCtrlTrue);

    // stroking overprint control true
    PDExtendedGraphicsState extGStateStrokingOverprintCtrlTrue = new PDExtendedGraphicsState();
    extGStateStrokingOverprintCtrlTrue.setStrokingOverprintControl(true);
    COSName nameExtGStateStrokingOverprintCtrlTrue = page.getResources().add(extGStateStrokingOverprintCtrlTrue);

    // non stroking overprint control false
    PDExtendedGraphicsState extGStateNonStrokingOverprintCtrlFalse = new PDExtendedGraphicsState();
    extGStateNonStrokingOverprintCtrlFalse.setNonStrokingOverprintControl(false);
    COSName nameExtGStateNonStrokingOverprintCtrlFalse = page.getResources().add(extGStateNonStrokingOverprintCtrlFalse);

    // stroking overprint control false
    PDExtendedGraphicsState extGStateStrokingOverprintCtrlFalse = new PDExtendedGraphicsState();
    extGStateStrokingOverprintCtrlFalse.setStrokingOverprintControl(false);
    COSName nameExtGStateStrokingOverprintCtrlFalse = page.getResources().add(extGStateStrokingOverprintCtrlFalse);

    PDFStreamParser parser = new PDFStreamParser(page);
    List<Object> newTokens = new ArrayList<>();
    Object token = parser.parseNextToken();
    while (token != null)
    {
        if (token instanceof Operator && !newTokens.isEmpty())
        {
            String opname = ((Operator) token).getName();
            Object lastToken = newTokens.get(newTokens.size() - 1);
            // check whether this is an operator that sets colorspace and was preceded by a name
            if (lastToken instanceof COSName && !"Pattern".equals(((COSName) lastToken).getName()) && 
                    ("CS".equals(opname.toUpperCase()) || "SCN".equals(opname.toUpperCase())))
            {
                // get last item = argument = colorspace name
                COSName name = (COSName) lastToken;
                System.out.println(name.getName() + " " + opname);
                newTokens.add(token);

                if (true) // TODO !here! add code to check whether this is the correct colorspace name
                {
                    if (Character.isUpperCase(opname.charAt(0)))
                    {
                        // stroking
                        newTokens.add(nameExtGStateStrokingOverprintCtrlTrue);
                    }
                    else
                    {
                        // nonstroking
                        newTokens.add(nameExtGStateNonStrokingOverprintCtrlTrue);
                    }
                }
                else
                {
                    if (opname.contains("S"))
                    {
                        // stroking
                        newTokens.add(nameExtGStateStrokingOverprintCtrlFalse);
                    }
                    else
                    {
                        // nonstroking
                        newTokens.add(nameExtGStateNonStrokingOverprintCtrlFalse);
                    }
                }
                // Set parameters from graphics state parameter dictionary
                newTokens.add(Operator.getOperator("gs"));
                token = parser.parseNextToken();
                continue;
            }
            // check all operators that implicitely set a colorspace
            else if ("G".equals(opname.toUpperCase()) || "RG".equals(opname.toUpperCase()) || "K".equals(opname.toUpperCase()))
            {
                newTokens.add(token);
                if (Character.isUpperCase(opname.charAt(0)))
                {
                    // stroking
                    newTokens.add(nameExtGStateStrokingOverprintCtrlFalse);
                }
                else
                {
                    // nonstroking
                    newTokens.add(nameExtGStateNonStrokingOverprintCtrlFalse);
                }
                // Set parameters from graphics state parameter dictionary
                newTokens.add(Operator.getOperator("gs"));
                token = parser.parseNextToken();
                continue;
            }
        }
        newTokens.add(token);
        token = parser.parseNextToken();
    }

    PDStream newContents = new PDStream(document);
    try (OutputStream out = newContents.createOutputStream(COSName.FLATE_DECODE))
    {
        ContentStreamWriter writer = new ContentStreamWriter(out);
        writer.writeTokens(newTokens);
    }
    page.setContents(newContents);
}

/**
 * This will print the usage for this document.
 */
private static void usage()
{
    System.err.println("Usage: java " + InsertOverprint.class.getName() + " <input-pdf> <output-pdf>");
}

Some more explanations: sc, scn, SC and SCN are operators to set the stroking or non-stroking colors. The upper case operators are for stroking, the lower case operators for non stroking. In the content stream, the name of the colorspace is before the operator. I excluded the "Pattern" color because it isn't a real colorspace.

Update 25.7.2017: The RemoveAllTexts example (which I've used as a starting point for this question) has been improved, now it handles not just the page and xobject forms, but also patterns. To modify it for what is done here, have a look at createTokensWithoutText().

Upvotes: 1

Related Questions