Skip to content

Commit

Permalink
#480 Test case for link shapes returned by custom object drawer.
Browse files Browse the repository at this point in the history
As promised @rototor. Link shapes are working perfectly in Acrobat.
  • Loading branch information
danfickle committed May 18, 2020
1 parent f82c54b commit 6f3ab99
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Shape, String> drawObject(Element e, double x, double y, double width, double height,
OutputDevice outputDevice, RenderingContext ctx, int dotsPerPixel) {

Map<Shape, String> 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()); };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<html>
<head>
<style>
@page {
size: 700px 600px;
margin: 20px 102px 40px 15px;
@right-middle {
content: element(right-shapes);
min-width: 102px;
min-height: 102px;
}
}
object {
display: block;
width: 400px;
height: 400px;
border: 1px solid red;
}
</style>
</head>
<body>
<object type="shapes" style="position: running(right-shapes); width: 100px; height: 100px;"></object>

<object type="shapes"></object>

<div style="page-break-before: always;"></div>

<object type="shapes"></object>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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<float[]> 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<float[]> page0 = new ArrayList<>();
List<float[]> 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.
Expand Down

0 comments on commit 6f3ab99

Please sign in to comment.