diff --git a/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java b/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java index 9a30b5af0..f572cdff1 100644 --- a/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java +++ b/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/visualtest/TestSupport.java @@ -1,21 +1,35 @@ package com.openhtmltopdf.visualtest; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Files; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.w3c.dom.Element; + import com.openhtmltopdf.bidi.support.ICUBidiReorderer; import com.openhtmltopdf.bidi.support.ICUBidiSplitter; import com.openhtmltopdf.bidi.support.ICUBreakers; +import com.openhtmltopdf.extend.FSObjectDrawer; +import com.openhtmltopdf.extend.FSObjectDrawerFactory; import com.openhtmltopdf.extend.FSTextBreaker; +import com.openhtmltopdf.extend.OutputDevice; import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.TextDirection; +import com.openhtmltopdf.render.RenderingContext; import com.openhtmltopdf.svgsupport.BatikSVGDrawer; import com.openhtmltopdf.util.XRLogger; import com.openhtmltopdf.visualtest.Java2DVisualTester.Java2DBuilderConfig; @@ -158,4 +172,56 @@ public void setText(String newText) { * Configures the builder to use SVG drawer but not font. */ public static final BuilderConfig WITH_SVG = (builder) -> builder.useSVGDrawer(new BatikSVGDrawer()); + + public static class ShapesObjectDrawer implements FSObjectDrawer { + public Map drawObject(Element e, double x, double y, double width, double height, + OutputDevice outputDevice, RenderingContext ctx, int dotsPerPixel) { + + Map shapes = new HashMap<>(); + + outputDevice.drawWithGraphics((float) x, (float) y, (float) width / dotsPerPixel, + (float) height / dotsPerPixel, (Graphics2D g2d) -> { + + double realWidth = width / dotsPerPixel; + double realHeight = height / dotsPerPixel; + + Rectangle2D rectUpperLeft = new Rectangle2D.Double(0, 0, realWidth / 4d, realHeight / 4d); + Rectangle2D rectLowerRight = new Rectangle2D.Double(realWidth * (3d/4d), realHeight * (3d/4d), realWidth / 4d, realHeight / 4d); + + int[] xpoints = new int[] { (int) (realWidth / 2d), (int) (realWidth * (1d/4d)), (int) (realWidth * (3d/4d)), (int) (realWidth / 2d) }; + int[] ypoints = new int[] { (int) (realHeight * (1d/4d)), (int) (realHeight * (3d/4d)), (int) (realHeight * (3d/4d)), (int) (realHeight * (1d/4d)) }; + Polygon centreTriangle = new Polygon(xpoints, ypoints, xpoints.length); + + g2d.setColor(Color.CYAN); + + g2d.draw(rectUpperLeft); + g2d.draw(rectLowerRight); + g2d.draw(centreTriangle); + + AffineTransform scale = AffineTransform.getScaleInstance(dotsPerPixel, dotsPerPixel); + + shapes.put(scale.createTransformedShape(rectUpperLeft), "http://example.com/1"); + shapes.put(scale.createTransformedShape(rectLowerRight), "http://example.com/2"); + shapes.put(scale.createTransformedShape(centreTriangle), "http://example.com/3"); + }); + + return shapes; + } + } + + public static class ShapesObjectDrawerFactory implements FSObjectDrawerFactory { + public FSObjectDrawer createDrawer(Element e) { + if (!isReplacedObject(e)) { + return null; + } + + return new ShapesObjectDrawer(); + } + + public boolean isReplacedObject(Element e) { + return e.getAttribute("type").equals("shapes"); + } + } + + public static final BuilderConfig WITH_SHAPES_DRAWER = (builder) -> { builder.useObjectDrawerFactory(new ShapesObjectDrawerFactory()); }; } diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/pr-480-link-shapes.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/pr-480-link-shapes.html new file mode 100644 index 000000000..bdf547df5 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/pr-480-link-shapes.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+ + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java index 0703b2c71..e827c60b6 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/nonvisualregressiontests/NonVisualRegressionTest.java @@ -8,6 +8,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.pdfbox.io.IOUtils; @@ -26,11 +28,13 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDTextField; import org.apache.pdfbox.util.Charsets; import org.hamcrest.CustomTypeSafeMatcher; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.testcases.TestcaseRunner; +import com.openhtmltopdf.visualtest.TestSupport; import com.openhtmltopdf.visualtest.VisualTester.BuilderConfig; public class NonVisualRegressionTest { @@ -717,7 +721,106 @@ public void testInputWithoutNameAttribute() throws IOException { assertEquals(0, form.getFields().size()); remove("input-without-name-attribute", doc); } - + + private static float[] getQuadPoints(PDDocument doc, int pg, int linkIndex) throws IOException { + return ((PDAnnotationLink) doc.getPage(pg).getAnnotations().get(linkIndex)).getQuadPoints(); + } + + private static String print(float[] floats) { + StringBuilder sb = new StringBuilder(); + + sb.append("new float[] { "); + for (float floater : floats) { + sb.append(floater); + sb.append("f, "); + } + + sb.deleteCharAt(sb.length() - 2); + sb.append("}"); + + return sb.toString(); + } + + private static final float QUAD_DELTA = 0.5f; + private static boolean qAssert(List expectedList, float[] actual, StringBuilder sb, int pg, int linkIndex) { + sb.append("PAGE: " + pg + ", LINK: " + linkIndex + "\n"); + sb.append(" ACT(" + actual.length + "): " + print(actual) + "\n"); + + // NOTE: The shapes are returned as a map and are therefore placed on + // the page in a non-determined order. So we just searh the expected + // list for a match. + ALL_EXPECTED: + for (float[] expected : expectedList) { + if (expected.length != actual.length) { + continue; + } + + for (int i = 0; i < expected.length; i++) { + float diff = Math.abs(expected[i] - actual[i]); + + if (diff > QUAD_DELTA) { + continue ALL_EXPECTED; + } + } + + return false; + } + + sb.append(" !FAILED!"); + sb.append("\n\n"); + return true; + } + + /** + * Tests the shaped links support for custom object drawers + * in the main document area and in the page margin on multiple + * pages. + */ + @Test + public void testPR480LinkShapes() throws IOException { + try (PDDocument doc = run("pr-480-link-shapes", TestSupport.WITH_SHAPES_DRAWER)) { + StringBuilder sb = new StringBuilder(); + List page0 = new ArrayList<>(); + List page1 = new ArrayList<>(); + boolean failure = false; + + page0.add(new float[] { 486.75f, 251.25f, 468.0f, 213.75f, 486.75f, 213.75f, 505.5f, 213.75f, 486.75f, 251.25f, 505.5f, 213.75f, 496.125f, 232.5f, 486.75f, 251.25f }); + page0.add(new float[] { 449.25f, 270.0f, 449.25f, 251.25f, 458.625f, 251.25f, 468.0f, 251.25f, 449.25f, 270.0f, 468.0f, 251.25f, 468.0f, 260.625f, 468.0f, 270.0f }); + page0.add(new float[] { 505.5f, 213.75f, 505.5f, 195.0f, 514.875f, 195.0f, 524.25f, 195.0f, 505.5f, 213.75f, 524.25f, 195.0f, 524.25f, 204.375f, 524.25f, 213.75f }); + page0.add(new float[] { 243.0f, 203.25f, 243.0f, 128.25f, 280.5f, 128.25f, 318.0f, 128.25f, 243.0f, 203.25f, 318.0f, 128.25f, 318.0f, 165.75f, 318.0f, 203.25f }); + page0.add(new float[] { 168.0f, 353.25f, 93.0f, 203.25f, 168.0f, 203.25f, 243.0f, 203.25f, 168.0f, 353.25f, 243.0f, 203.25f, 205.5f, 278.25f, 168.0f, 353.25f }); + page0.add(new float[] { 18.0f, 428.25f, 18.0f, 353.25f, 55.5f, 353.25f, 93.0f, 353.25f, 18.0f, 428.25f, 93.0f, 353.25f, 93.0f, 390.75f, 93.0f, 428.25f }); + + failure |= qAssert(page0, getQuadPoints(doc, 0, 0), sb, 0, 0); + failure |= qAssert(page0, getQuadPoints(doc, 0, 1), sb, 0, 1); + failure |= qAssert(page0, getQuadPoints(doc, 0, 2), sb, 0, 2); + failure |= qAssert(page0, getQuadPoints(doc, 0, 3), sb, 0, 3); + failure |= qAssert(page0, getQuadPoints(doc, 0, 4), sb, 0, 4); + failure |= qAssert(page0, getQuadPoints(doc, 0, 5), sb, 0, 5); + + page1.add(new float[] { 486.75f, 251.25f, 468.0f, 213.75f, 486.75f, 213.75f, 505.5f, 213.75f, 486.75f, 251.25f, 505.5f, 213.75f, 496.125f, 232.5f, 486.75f, 251.25f }); + page1.add(new float[] { 449.25f, 270.0f, 449.25f, 251.25f, 458.625f, 251.25f, 468.0f, 251.25f, 449.25f, 270.0f, 468.0f, 251.25f, 468.0f, 260.625f, 468.0f, 270.0f }); + page1.add(new float[] { 505.5f, 213.75f, 505.5f, 195.0f, 514.875f, 195.0f, 524.25f, 195.0f, 505.5f, 213.75f, 524.25f, 195.0f, 524.25f, 204.375f, 524.25f, 213.75f }); + page1.add(new float[] { 243.0f, 209.25f, 243.0f, 134.25f, 280.5f, 134.25f, 318.0f, 134.25f, 243.0f, 209.25f, 318.0f, 134.25f, 318.0f, 171.75f, 318.0f, 209.25f }); + page1.add(new float[] { 168.0f, 359.25f, 93.0f, 209.25f, 168.0f, 209.25f, 243.0f, 209.25f, 168.0f, 359.25f, 243.0f, 209.25f, 205.5f, 284.25f, 168.0f, 359.25f }); + page1.add(new float[] { 18.0f, 434.25f, 18.0f, 359.25f, 55.5f, 359.25f, 93.0f, 359.25f, 18.0f, 434.25f, 93.0f, 359.25f, 93.0f, 396.75f, 93.0f, 434.25f }); + + failure |= qAssert(page1, getQuadPoints(doc, 1, 0), sb, 1, 0); + failure |= qAssert(page1, getQuadPoints(doc, 1, 1), sb, 1, 1); + failure |= qAssert(page1, getQuadPoints(doc, 1, 2), sb, 1, 2); + failure |= qAssert(page1, getQuadPoints(doc, 1, 3), sb, 1, 3); + failure |= qAssert(page1, getQuadPoints(doc, 1, 4), sb, 1, 4); + failure |= qAssert(page1, getQuadPoints(doc, 1, 5), sb, 1, 5); + + if (failure) { + System.out.print(sb.toString()); + Assert.fail("Quad points were not correct"); + } + + remove("pr-480-link-shapes", doc); + } + } + // TODO: // + More form controls. // + Custom meta info.