OpaquePDFRenderer.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pdfbox.examples.printing;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import javax.print.PrintServiceLookup;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
import org.apache.pdfbox.contentstream.PDFStreamEngine;
import org.apache.pdfbox.contentstream.operator.MissingOperandException;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.OperatorName;
import org.apache.pdfbox.contentstream.operator.OperatorProcessor;
import org.apache.pdfbox.contentstream.operator.graphics.GraphicsOperatorProcessor;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.RandomAccessReadBuffer;
import org.apache.pdfbox.pdmodel.MissingResourceException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.printing.PDFPrintable;
import org.apache.pdfbox.printing.Scaling;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.rendering.PageDrawer;
import org.apache.pdfbox.rendering.PageDrawerParameters;
/**
* PDF documents with transparency are sometimes printed slowly and in poor quality, see
* <a href="https://issues.apache.org/jira/browse/PDFBOX-4123">PDFBOX-4123</a> and
* <a href="https://issues.apache.org/jira/browse/PDFBOX-5605">PDFBOX-5605</a>. If the transparency
* isn't really needed (e.g. for most labels), we can use a custom PDFRenderer / PageDrawer that
* uses a custom DrawObject class which doesn't call showTransparencyGroup() but only showForm() and
* sets ca and CA to 1.
* <p>
* This OpaquePDFRenderer class object can be passed to the "long" constructor of
* {@link PDFPrintable#PDFPrintable(org.apache.pdfbox.pdmodel.PDDocument, org.apache.pdfbox.printing.Scaling, boolean, float, boolean, org.apache.pdfbox.rendering.PDFRenderer)}.
*
* @author Tilman Hausherr
*/
public class OpaquePDFRenderer extends PDFRenderer
{
@SuppressWarnings("java:S1075")
public static void main(String[] args) throws IOException, PrinterException, URISyntaxException
{
// PDF from the QZ Tray project, who reported this problem.
// Also test with
// https://github.com/qzind/tray/files/11432463/sample_file-1.pdf
// (second page)
try (PDDocument doc = Loader.loadPDF(RandomAccessReadBuffer.createBufferFromStream(
new URI("https://github.com/qzind/tray/files/1749977/test.pdf")
.toURL().openStream())))
{
PDFRenderer renderer = new OpaquePDFRenderer(doc);
Printable printable = new PDFPrintable(doc, Scaling.SCALE_TO_FIT, false, 0, true, renderer);
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintService(PrintServiceLookup.lookupDefaultPrintService());
job.setPrintable(printable);
if (job.printDialog())
{
job.print();
}
}
}
public OpaquePDFRenderer(PDDocument document)
{
super(document);
}
@Override
protected PageDrawer createPageDrawer(PageDrawerParameters parameters) throws IOException
{
return new OpaquePageDrawer(parameters);
}
private static class OpaquePageDrawer extends PageDrawer
{
OpaquePageDrawer(PageDrawerParameters parameters) throws IOException
{
super(parameters);
addOperator(new OpaqueDrawObject(this));
addOperator(new OpaqueSetGraphicsStateParameters(this));
}
}
// copied from org.apache.pdfbox.contentstream.operator.graphics.DrawObject()
// but doesn't call showTransparencyGroup
private static class OpaqueDrawObject extends GraphicsOperatorProcessor
{
OpaqueDrawObject(PDFGraphicsStreamEngine context)
{
super(context);
}
private static final Logger LOG = LogManager.getLogger(OpaqueDrawObject.class);
@Override
public void process(Operator operator, List<COSBase> operands) throws IOException
{
if (operands.isEmpty())
{
throw new MissingOperandException(operator, operands);
}
COSBase base0 = operands.get(0);
if (!(base0 instanceof COSName))
{
return;
}
COSName objectName = (COSName) base0;
PDFGraphicsStreamEngine context = getGraphicsContext();
PDXObject xobject = context.getResources().getXObject(objectName);
if (xobject == null)
{
throw new MissingResourceException("Missing XObject: " + objectName.getName());
}
else if (xobject instanceof PDImageXObject)
{
PDImageXObject image = (PDImageXObject) xobject;
context.drawImage(image);
}
else if (xobject instanceof PDFormXObject)
{
try
{
context.increaseLevel();
if (context.getLevel() > 50)
{
LOG.error("recursion is too deep, skipping form XObject");
return;
}
context.showForm((PDFormXObject) xobject);
}
finally
{
context.decreaseLevel();
}
}
}
@Override
public String getName()
{
return OperatorName.DRAW_OBJECT;
}
}
// copied from org.apache.pdfbox.contentstream.operator.state.SetGraphicsStateParameters()
// but resets ca and CA
private static class OpaqueSetGraphicsStateParameters extends OperatorProcessor
{
private static final Logger LOG = LogManager.getLogger(OpaqueSetGraphicsStateParameters.class);
OpaqueSetGraphicsStateParameters(PDFStreamEngine context)
{
super(context);
}
@Override
public void process(Operator operator, List<COSBase> arguments) throws IOException
{
if (arguments.isEmpty())
{
throw new MissingOperandException(operator, arguments);
}
COSBase base0 = arguments.get(0);
if (!(base0 instanceof COSName))
{
return;
}
// set parameters from graphics state parameter dictionary
COSName graphicsName = (COSName) base0;
PDFStreamEngine context = getContext();
PDExtendedGraphicsState gs = context.getResources().getExtGState(graphicsName);
if (gs == null)
{
LOG.error("name for 'gs' operator not found in resources: /{}",
graphicsName.getName());
return;
}
gs.setNonStrokingAlphaConstant(1f);
gs.setStrokingAlphaConstant(1f);
gs.copyIntoGraphicsState(context.getGraphicsState());
}
@Override
public String getName()
{
return OperatorName.SET_GRAPHICS_STATE_PARAMS;
}
}
}