Skip to content

Commit

Permalink
[GEOS-10363] Switch from itextpdf to openpdf for PDF map rendering (#…
Browse files Browse the repository at this point in the history
…5607)

* [GEOS-10363] Switch from itextpdf to openpdf for PDF map rendering

* Update pom.xml
  • Loading branch information
bradh committed Jan 27, 2022
1 parent 265475e commit 6addab6
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 152 deletions.
2 changes: 1 addition & 1 deletion src/community/gsr/NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ the creators of the libraries we rely on, GeoServer would certainly not
be possible without them. There are also several LGPL libraries that do
not require us to cite them, but we'd like to thank GeoTools -
http://geotools.org, JTS - http://www.vividsolutions.com/jts/jtshome.htm
WKB4J http://wkb4j.sourceforge.net iText - http://www.lowagie.com/iText/
WKB4J http://wkb4j.sourceforge.net, OpenPDF - https://github.com/LibrePDF/OpenPDF,
and J. David Eisenberg's PNG encoder http://www.catcode.com/pngencoder/

### NeuQuant Neural-Net Quantization Algorithm
Expand Down
7 changes: 3 additions & 4 deletions src/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1026,9 +1026,9 @@
<version>1.2.17.norce</version>
</dependency>
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.5</version>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.26</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
Expand Down Expand Up @@ -2078,7 +2078,6 @@
-link http://www.jump-project.org/docs/jts/1.7/api
-link http://www.geotools.fr/javadoc/2.2/
-link http://xmlgraphics.apache.org/batik/javadoc
-link http://itextdocs.lowagie.com/docs
-tag todo:tfmc:TODO:
-tag task:tfmc:TODO:
-tag revisit:tfmc:TODO:
Expand Down
2 changes: 1 addition & 1 deletion src/release/NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ the creators of the libraries we rely on, GeoServer would certainly not
be possible without them. There are also several LGPL libraries that do
not require us to cite them, but we'd like to thank GeoTools -
http://geotools.org, JTS - http://www.vividsolutions.com/jts/jtshome.htm
WKB4J http://wkb4j.sourceforge.net iText - http://www.lowagie.com/iText/
WKB4J http://wkb4j.sourceforge.net, OpenPDF - https://github.com/LibrePDF/OpenPDF,
and J. David Eisenberg's PNG encoder http://www.catcode.com/pngencoder/

### NeuQuant Neural-Net Quantization Algorithm
Expand Down
12 changes: 6 additions & 6 deletions src/wms/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@
</dependency>
<!-- end of SVG rendering markers support -->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<exclusions>
<exclusion>
<artifactId>bcmail-jdk14</artifactId>
<groupId>bouncycastle</groupId>
<artifactId>bcmail-jdk15on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcprov-jdk14</artifactId>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
</exclusions>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
import org.geoserver.wms.decoration.MapDecorationLayout;

/**
* Handles a GetMap request that spects a map in PDF format.
* Handles a GetMap request that expects a map in PDF format.
*
* @author Pierre-Emmanuel Balageas, ALCER (http://www.alcer.com)
* @author Simone Giannecchini - GeoSolutions
* @author Gabriel Roldan
* @version $Id$
*/
public class PDFMapOutputFormat extends AbstractMapOutputFormat {
public PDFMapOutputFormat() {
Expand Down
280 changes: 142 additions & 138 deletions src/wms/src/main/java/org/geoserver/wms/map/PDFMapResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@
import org.springframework.util.Assert;

/**
* Handles a GetMap request that spects a map in PDF format.
* Handles a GetMap request that expects a map in PDF format.
*
* @author Pierre-Emmanuel Balageas, ALCER (http://www.alcer.com)
* @author Simone Giannecchini - GeoSolutions
* @author Gabriel Roldan
* @version $Id$
*/
public class PDFMapResponse extends AbstractMapResponse {

/** A logger for this class. */
private static final Logger LOGGER =
org.geotools.util.logging.Logging.getLogger("org.vfny.geoserver.responses.wms.map.pdf");
Expand Down Expand Up @@ -117,153 +118,155 @@ public void write(Object value, OutputStream output, Operation operation)
// height of document-object is height*72 inches
com.lowagie.text.Rectangle pageSize = new com.lowagie.text.Rectangle(width, height);

Document document = new Document(pageSize);
document.setMargins(0, 0, 0, 0);

// step 2: creation of the writer
PdfWriter writer = PdfWriter.getInstance(document, output);

// step 3: we open the document
document.open();

// step 4: we grab the ContentByte and do some stuff with it
try (Document document = new Document(pageSize)) {
document.setMargins(0, 0, 0, 0);

// step 2: creation of the writer
PdfWriter writer = PdfWriter.getInstance(document, output);

// step 3: we open the document
document.open();

// step 4: we grab the ContentByte and do some stuff with it
// we create a fontMapper and read all the fonts in the font
// directory
DefaultFontMapper mapper = new DefaultFontMapper();
FontFactory.registerDirectories();

// we create a template and a Graphics2D object that corresponds
// with it
PdfContentByte cb = writer.getDirectContent();
PdfTemplate tp = cb.createTemplate(width, height);
PdfGraphics2D graphic = (PdfGraphics2D) tp.createGraphics(width, height, mapper);

// we set graphics options
if (!mapContent.isTransparent()) {
graphic.setColor(mapContent.getBgColor());
graphic.fillRect(0, 0, width, height);
} else {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("setting to transparent");
}

// we create a fontMapper and read all the fonts in the font
// directory
DefaultFontMapper mapper = new DefaultFontMapper();
FontFactory.registerDirectories();
int type = AlphaComposite.SRC;
graphic.setComposite(AlphaComposite.getInstance(type));

Color c =
new Color(
mapContent.getBgColor().getRed(),
mapContent.getBgColor().getGreen(),
mapContent.getBgColor().getBlue(),
0);
graphic.setBackground(mapContent.getBgColor());
graphic.setColor(c);
graphic.fillRect(0, 0, width, height);

type = AlphaComposite.SRC_OVER;
graphic.setComposite(AlphaComposite.getInstance(type));
}

// we create a template and a Graphics2D object that corresponds
// with it
PdfContentByte cb = writer.getDirectContent();
PdfTemplate tp = cb.createTemplate(width, height);
PdfGraphics2D graphic = (PdfGraphics2D) tp.createGraphics(width, height, mapper);
Rectangle paintArea = new Rectangle(width, height);

// we set graphics options
if (!mapContent.isTransparent()) {
graphic.setColor(mapContent.getBgColor());
graphic.fillRect(0, 0, width, height);
} else {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("setting to transparent");
StreamingRenderer renderer;
if (ENCODE_TILING_PATTERNS) {
renderer = new PDFStreamingRenderer();
} else {
renderer = new StreamingRenderer();
}
renderer.setMapContent(mapContent);
// TODO: expose the generalization distance as a param
// ((StreamingRenderer) renderer).setGeneralizationDistance(0);

RenderingHints hints =
new RenderingHints(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
renderer.setJava2DHints(hints);

// we already do everything that the optimized data loading does...
// if we set it to true then it does it all twice...
Map<String, Object> rendererParams = new HashMap<>();
rendererParams.put("optimizedDataLoadingEnabled", Boolean.TRUE);
rendererParams.put("renderingBuffer", Integer.valueOf(mapContent.getBuffer()));
// we need the renderer to draw everything on the batik provided graphics object
rendererParams.put(StreamingRenderer.OPTIMIZE_FTS_RENDERING_KEY, Boolean.FALSE);
// render everything in vector form if possible
rendererParams.put(StreamingRenderer.VECTOR_RENDERING_KEY, Boolean.TRUE);
if (DefaultWebMapService.isLineWidthOptimizationEnabled()) {
rendererParams.put(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY, true);
}
rendererParams.put(
StreamingRenderer.SCALE_COMPUTATION_METHOD_KEY,
mapContent.getRendererScaleMethod());

renderer.setRendererHints(rendererParams);

// enforce no more than x rendering errors
int maxErrors = wms.getMaxRenderingErrors();
MaxErrorEnforcer errorChecker = new MaxErrorEnforcer(renderer, maxErrors);

// Add a render listener that ignores well known rendering exceptions and reports
// back
// non
// ignorable ones
final RenderExceptionStrategy nonIgnorableExceptionListener =
new RenderExceptionStrategy(renderer);
renderer.addRenderListener(nonIgnorableExceptionListener);

// enforce max memory usage
int maxMemory = wms.getMaxRequestMemory() * KB;
PDFMaxSizeEnforcer memoryChecker =
new PDFMaxSizeEnforcer(renderer, graphic, maxMemory);

// render the map
renderer.paint(
graphic,
paintArea,
mapContent.getRenderingArea(),
mapContent.getRenderingTransform());

// render the watermark
MapDecorationLayout.Block watermark =
RenderedImageMapOutputFormat.getWatermark(wms.getServiceInfo());

if (pdfMap.layout != null) {
pdfMap.layout.paint(graphic, paintArea, mapContent);
} else if (watermark != null) {
MapDecorationLayout layout = new MapDecorationLayout();
layout.paint(graphic, paintArea, mapContent);
}

int type = AlphaComposite.SRC;
graphic.setComposite(AlphaComposite.getInstance(type));

Color c =
new Color(
mapContent.getBgColor().getRed(),
mapContent.getBgColor().getGreen(),
mapContent.getBgColor().getBlue(),
0);
graphic.setBackground(mapContent.getBgColor());
graphic.setColor(c);
graphic.fillRect(0, 0, width, height);

type = AlphaComposite.SRC_OVER;
graphic.setComposite(AlphaComposite.getInstance(type));
}

Rectangle paintArea = new Rectangle(width, height);
// check if a non ignorable error occurred
if (nonIgnorableExceptionListener.exceptionOccurred()) {
Exception renderError = nonIgnorableExceptionListener.getException();
throw new ServiceException(
"Rendering process failed", renderError, "internalError");
}

StreamingRenderer renderer;
if (ENCODE_TILING_PATTERNS) {
renderer = new PDFStreamingRenderer();
} else {
renderer = new StreamingRenderer();
}
renderer.setMapContent(mapContent);
// TODO: expose the generalization distance as a param
// ((StreamingRenderer) renderer).setGeneralizationDistance(0);

RenderingHints hints =
new RenderingHints(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
renderer.setJava2DHints(hints);

// we already do everything that the optimized data loading does...
// if we set it to true then it does it all twice...
Map<String, Object> rendererParams = new HashMap<>();
rendererParams.put("optimizedDataLoadingEnabled", Boolean.TRUE);
rendererParams.put("renderingBuffer", Integer.valueOf(mapContent.getBuffer()));
// we need the renderer to draw everything on the batik provided graphics object
rendererParams.put(StreamingRenderer.OPTIMIZE_FTS_RENDERING_KEY, Boolean.FALSE);
// render everything in vector form if possible
rendererParams.put(StreamingRenderer.VECTOR_RENDERING_KEY, Boolean.TRUE);
if (DefaultWebMapService.isLineWidthOptimizationEnabled()) {
rendererParams.put(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY, true);
}
rendererParams.put(
StreamingRenderer.SCALE_COMPUTATION_METHOD_KEY,
mapContent.getRendererScaleMethod());

renderer.setRendererHints(rendererParams);

// enforce no more than x rendering errors
int maxErrors = wms.getMaxRenderingErrors();
MaxErrorEnforcer errorChecker = new MaxErrorEnforcer(renderer, maxErrors);

// Add a render listener that ignores well known rendering exceptions and reports back
// non
// ignorable ones
final RenderExceptionStrategy nonIgnorableExceptionListener =
new RenderExceptionStrategy(renderer);
renderer.addRenderListener(nonIgnorableExceptionListener);

// enforce max memory usage
int maxMemory = wms.getMaxRequestMemory() * KB;
PDFMaxSizeEnforcer memoryChecker = new PDFMaxSizeEnforcer(renderer, graphic, maxMemory);

// render the map
renderer.paint(
graphic,
paintArea,
mapContent.getRenderingArea(),
mapContent.getRenderingTransform());

// render the watermark
MapDecorationLayout.Block watermark =
RenderedImageMapOutputFormat.getWatermark(wms.getServiceInfo());

if (pdfMap.layout != null) {
pdfMap.layout.paint(graphic, paintArea, mapContent);
} else if (watermark != null) {
MapDecorationLayout layout = new MapDecorationLayout();
layout.paint(graphic, paintArea, mapContent);
}
// check if too many errors occurred
if (errorChecker.exceedsMaxErrors()) {
throw new ServiceException(
"More than " + maxErrors + " rendering errors occurred, bailing out",
errorChecker.getLastException(),
"internalError");
}

// check if a non ignorable error occurred
if (nonIgnorableExceptionListener.exceptionOccurred()) {
Exception renderError = nonIgnorableExceptionListener.getException();
throw new ServiceException(
"Rendering process failed", renderError, "internalError");
}
// check we did not use too much memory
if (memoryChecker.exceedsMaxSize()) {
long kbMax = maxMemory / KB;
throw new ServiceException(
"Rendering request used more memory than the maximum allowed:"
+ kbMax
+ "KB");
}

// check if too many errors occurred
if (errorChecker.exceedsMaxErrors()) {
throw new ServiceException(
"More than " + maxErrors + " rendering errors occurred, bailing out",
errorChecker.getLastException(),
"internalError");
}
graphic.dispose();
cb.addTemplate(tp, 0, 0);

// check we did not use too much memory
if (memoryChecker.exceedsMaxSize()) {
long kbMax = maxMemory / KB;
throw new ServiceException(
"Rendering request used more memory than the maximum allowed:"
+ kbMax
+ "KB");
// step 5: we clean up
document.close();
writer.flush();
writer.close();
}

graphic.dispose();
cb.addTemplate(tp, 0, 0);

// step 5: we close the document
document.close();
writer.flush();
writer.close();
} catch (DocumentException t) {
throw new ServiceException("Error setting up the PDF", t, "internalError");
}
Expand Down Expand Up @@ -421,6 +424,7 @@ private PdfPatternPainter buildPattern(
// this should not happen given the arguments passed to the lite shape
throw new RuntimeException(e);
}
patternGraphic.dispose();
} else {
throw new IllegalArgumentException("Unsupported style " + graphicFill);
}
Expand Down

0 comments on commit 6addab6

Please sign in to comment.