org.geoserver.wms.map.PDFGetMapTest.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.wms.map.PDFGetMapTest.java

Source

/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.wms.map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.awt.geom.Point2D;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
import org.apache.pdfbox.contentstream.PDFStreamEngine;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.color.SetNonStrokingColorN;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPattern;
import org.geoserver.catalog.Catalog;
import org.geoserver.data.test.MockData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.wms.WMSTestSupport;
import org.junit.After;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;

public class PDFGetMapTest extends WMSTestSupport {

    String bbox = "-1.5,-0.5,1.5,1.5";

    String layers = getLayerId(MockData.BASIC_POLYGONS);

    String requestBase = "wms?bbox=" + bbox + "&layers=" + layers + "&Format=application/pdf&request=GetMap"
            + "&width=300&height=300&srs=EPSG:4326";

    static boolean tilingPatterDefault = PDFMapResponse.ENCODE_TILING_PATTERNS;

    @After
    public void setupTilingPatternEncoding() {
        PDFMapResponse.ENCODE_TILING_PATTERNS = tilingPatterDefault;
    }

    @Override
    protected void onSetUp(SystemTestData testData) throws Exception {
        super.onSetUp(testData);

        Catalog catalog = getCatalog();
        testData.addStyle("burg-fill", "burg-fill.sld", PDFGetMapTest.class, catalog);
        testData.addStyle("triangle-fill", "triangle-fill.sld", PDFGetMapTest.class, catalog);
        testData.addStyle("hatch-fill", "hatch-fill.sld", PDFGetMapTest.class, catalog);

        // copy over the svg icon
        File styles = new File(testData.getDataDirectoryRoot(), "styles");
        File burg = new File(styles, "burg02.svg");
        try (InputStream is = getClass().getResourceAsStream("burg02.svg");
                FileOutputStream fos = new FileOutputStream(burg)) {
            IOUtils.copy(is, fos);
        }
    }

    @Test
    public void testBasicPolygonMap() throws Exception {
        MockHttpServletResponse response = getAsServletResponse(requestBase + "&styles=");
        assertEquals("application/pdf", response.getContentType());
        PDTilingPattern tilingPattern = getTilingPattern(response.getContentAsByteArray());
        assertNull(tilingPattern);
    }

    @Test
    public void testSvgFillOptimization() throws Exception {
        // get a single polygon to ease testing
        MockHttpServletResponse response = getAsServletResponse(
                requestBase + "&styles=burg-fill&featureId=BasicPolygons.1107531493630");
        assertEquals("application/pdf", response.getContentType());

        PDTilingPattern tilingPattern = getTilingPattern(response.getContentAsByteArray());
        assertNotNull(tilingPattern);
        assertEquals(20, tilingPattern.getXStep(), 0d);
        assertEquals(20, tilingPattern.getYStep(), 0d);
    }

    @Test
    public void testSvgFillOptimizationDisabled() throws Exception {
        PDFMapResponse.ENCODE_TILING_PATTERNS = false;

        // get a single polygon to ease testing
        MockHttpServletResponse response = getAsServletResponse(
                requestBase + "&styles=burg-fill&featureId=BasicPolygons.1107531493630");
        assertEquals("application/pdf", response.getContentType());

        // the tiling pattern encoding has been disabled
        PDTilingPattern tilingPattern = getTilingPattern(response.getContentAsByteArray());
        assertNull(tilingPattern);
    }

    @Test
    public void testTriangleFillOptimization() throws Exception {
        MockHttpServletResponse response = getAsServletResponse(
                requestBase + "&styles=triangle-fill&featureId=BasicPolygons.1107531493630");
        assertEquals("application/pdf", response.getContentType());

        File file = new File("./target/test.pdf");
        org.apache.commons.io.FileUtils.writeByteArrayToFile(file, response.getContentAsByteArray());

        PDTilingPattern tilingPattern = getTilingPattern(response.getContentAsByteArray());
        assertNotNull(tilingPattern);
        assertEquals(20, tilingPattern.getXStep(), 0d);
        assertEquals(20, tilingPattern.getYStep(), 0d);
    }

    @Test
    public void testHatchFillOptimization() throws Exception {
        MockHttpServletResponse response = getAsServletResponse(
                requestBase + "&styles=hatch-fill&featureId=BasicPolygons.1107531493630");
        assertEquals("application/pdf", response.getContentType());

        // for hatches we keep the existing "set of parallel lines" optimization approach, need to determine
        // if we want to remove it or not yet
        PDTilingPattern tilingPattern = getTilingPattern(response.getContentAsByteArray());
        assertNull(tilingPattern);
    }

    /**
     * Returns the last tiling pattern found during a render of the PDF document. Can be used to extract
     * one tiling pattern that gets actually used to render shapes (meant to be used against a document
     * that only has a single tiling pattern)
     * 
     * @param pdfDocument
     * @return
     * @throws InvalidPasswordException
     * @throws IOException
     */
    PDTilingPattern getTilingPattern(byte[] pdfDocument) throws InvalidPasswordException, IOException {
        // load the document using PDFBOX (iText is no good for parsing tiling patterns, mostly works
        // well for text and image extraction, spent a few hours trying to use it with no results)
        PDDocument doc = PDDocument.load(pdfDocument);
        PDPage page = doc.getPage(0);

        // use a graphics stream engine, it's the only thing I could find that parses the PDF
        // deep enough to allow catching the tiling pattern in parsed form 
        AtomicReference<PDTilingPattern> pattern = new AtomicReference<>();
        PDFStreamEngine engine = new PDFGraphicsStreamEngine(page) {

            @Override
            public void strokePath() throws IOException {
            }

            @Override
            public void shadingFill(COSName shadingName) throws IOException {
            }

            @Override
            public void moveTo(float x, float y) throws IOException {
            }

            @Override
            public void lineTo(float x, float y) throws IOException {
            }

            @Override
            public Point2D getCurrentPoint() throws IOException {
                return null;
            }

            @Override
            public void fillPath(int windingRule) throws IOException {
            }

            @Override
            public void fillAndStrokePath(int windingRule) throws IOException {
            }

            @Override
            public void endPath() throws IOException {
            }

            @Override
            public void drawImage(PDImage pdImage) throws IOException {
            }

            @Override
            public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
            }

            @Override
            public void closePath() throws IOException {
            }

            @Override
            public void clip(int windingRule) throws IOException {
            }

            @Override
            public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {
            }
        };

        // setup the tiling pattern trap
        engine.addOperator(new SetNonStrokingColorN() {

            @Override
            public void process(Operator operator, List<COSBase> arguments) throws IOException {
                super.process(operator, arguments);

                PDColor color = context.getGraphicsState().getNonStrokingColor();
                if (context.getGraphicsState().getNonStrokingColorSpace() instanceof PDPattern) {
                    PDPattern colorSpace = (PDPattern) context.getGraphicsState().getNonStrokingColorSpace();
                    PDAbstractPattern ap = colorSpace.getPattern(color);
                    if (ap instanceof PDTilingPattern) {
                        pattern.set((PDTilingPattern) ap);
                    }
                }
            }
        });
        // run it
        engine.processPage(page);

        return pattern.get();
    }
}