From ab9a1f91d73b5fe969120e6e0bde2fccdc68f5b3 Mon Sep 17 00:00:00 2001 From: danfickle Date: Thu, 2 May 2019 00:34:15 +1000 Subject: [PATCH 01/12] #350 #344 - Refactors sizing for replaced elements such as img and svg. This work moves sizing of replaced objects form externally in each replaced object implementation to internally in the core code. This should reduce duplication and mean all sizing properties are available on each replaced object type. For example, now images have min-width and min-height as well as a border-box implementation. This work is published initially in a new branch because while there are good tests for images we need to test the new sizing code for all the other replaced object options such as SVG, PDF, custom, etc. --- .../com/openhtmltopdf/render/BlockBox.java | 151 ++++++++++++++---- .../expected/replaced-img-in-table-cell-2.pdf | Bin 0 -> 12773 bytes .../expected/replaced-sizing-img.pdf | Bin 0 -> 14817 bytes .../html/replaced-img-in-table-cell-2.html | 22 +++ .../visualtest/html/replaced-sizing-img.html | 43 +++++ .../VisualRegressionTest.java | 18 +++ .../java2d/Java2DReplacedElementFactory.java | 7 +- .../pdfboxout/PdfBoxImageElement.java | 13 +- .../PdfBoxReplacedElementFactory.java | 44 ----- 9 files changed, 218 insertions(+), 80 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-img-in-table-cell-2.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-img.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-img-in-table-cell-2.html create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-img.html diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java index dca818e1a..3a03568fa 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java @@ -25,7 +25,6 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; - import org.w3c.dom.Element; import com.openhtmltopdf.css.constants.CSSName; @@ -703,6 +702,111 @@ private int calcEffPageRelativeWidth(LayoutContext c) { } } + /** + * Creates the replaced element as required. This method should be idempotent. + */ + private void createReplaced(LayoutContext c) { + ReplacedElement re = getReplacedElement(); + + if (re == null) { + int cssWidth = getCSSWidth(c); + int cssHeight = getCSSHeight(c); + + re = c.getReplacedElementFactory().createReplacedElement( + c, this, c.getUac(), cssWidth, cssHeight); + + if (re != null) { + setReplacedElement(re); + sizeReplacedElement(c, re); + } + } + } + + /** + * Size a replaced element taking into account size properties including min/max, + * border-box/content-box and the natural size/aspect ratio of the replaced object. + * + * This method may be called multiple times so must be idempotent. + */ + private void sizeReplacedElement(LayoutContext c, ReplacedElement re) { + int cssWidth = getCSSWidth(c); + int cssHeight = getCSSHeight(c); + + boolean haveExactDims = cssWidth >= 0 && cssHeight >= 0; + + int intrinsicWidth = re.getIntrinsicWidth(); + int intrinsicHeight = re.getIntrinsicHeight(); + + cssWidth = !getStyle().isMaxWidthNone() && intrinsicWidth > getCSSMaxWidth(c) ? + getCSSMaxWidth(c) : cssWidth; + cssWidth = getCSSMinWidth(c) > 0 && cssWidth < getCSSMinWidth(c) ? + getCSSMinWidth(c) : cssWidth; + + cssHeight = !getStyle().isMaxHeightNone() && intrinsicHeight > getCSSMaxHeight(c) ? + getCSSMaxHeight(c) : cssHeight; + cssHeight = getCSSMinHeight(c) > 0 && cssHeight < getCSSMinHeight(c) ? + getCSSMinHeight(c) : cssHeight; + + int nw; + int nh; + + if (cssWidth > 0 && cssHeight > 0) { + if (haveExactDims) { + // We only warp the aspect ratio if we have explicit width and height values. + nw = cssWidth; + nh = cssHeight; + } else if (intrinsicWidth > cssWidth || intrinsicHeight > cssHeight) { + // Too large, so reduce respecting the aspect ratio. + double rw = (double) intrinsicWidth / (double) cssWidth; + double rh = (double) intrinsicHeight / (double) cssHeight; + + if (rw > rh) { + nw = cssWidth; + nh = intrinsicHeight; + } else { + nw = intrinsicWidth; + nh = cssHeight; + } + } else { + // Too small. + double rw = (double) intrinsicWidth / (double) cssWidth; + double rh = (double) intrinsicHeight / (double) cssHeight; + + if (rw > rh) { + nw = cssWidth; + nh = ((int) (intrinsicHeight / rw)); + } else { + nw = ((int) (intrinsicWidth / rh)); + nh = cssHeight; + } + } + } else if (cssWidth > 0) { + // Explicit min/max/width with auto height so keep aspect ratio. + nw = cssWidth; + nh = ((int) (((double) cssWidth / (double) intrinsicWidth) * intrinsicHeight)); + } else if (cssHeight > 0) { + // Explicit min/max/height with auto width. + nh = cssHeight; + nw = ((int) (((double) cssHeight / (double) intrinsicHeight) * intrinsicWidth)); + } else if (cssWidth == 0 || cssHeight == 0) { + // Empty. + nw = cssWidth; + nh = cssHeight; + } else { + // Auto width and height so use the natural dimensions of the replaced object. + nw = intrinsicWidth; + nh = intrinsicHeight; + } + + if (getStyle().isBorderBox()) { + setBorderBoxWidth(c, nw); + setBorderBoxHeight(c, nh); + } else { + setContentWidth(nw); + setHeight(nh); + } + } + public void calcDimensions(LayoutContext c) { calcDimensions(c, getCSSWidth(c)); } @@ -728,12 +832,18 @@ protected void calcDimensions(LayoutContext c, int cssWidth) { setLeftMBP((int) margin.left() + (int) border.left() + (int) padding.left()); setRightMBP((int) padding.right() + (int) border.right() + (int) margin.right()); + createReplaced(c); + if (isReplaced()) { + setDimensionsCalculated(true); + return; + } + if (c.isPrint() && getStyle().isDynamicAutoWidth()) { setContentWidth(calcEffPageRelativeWidth(c)); } else { setContentWidth((getContainingBlockWidth() - getLeftMBP() - getRightMBP())); } - + setHeight(0); if (! isAnonymous() || (isFromCaptionedTable() && isFloated())) { @@ -761,19 +871,10 @@ protected void calcDimensions(LayoutContext c, int cssWidth) { } } - //check if replaced ReplacedElement re = getReplacedElement(); - if (re == null) { - re = c.getReplacedElementFactory().createReplacedElement( - c, this, c.getUac(), cssWidth, cssHeight); - if (re != null){ - re = fitReplacedElement(c, re); - } - } + if (re != null) { - setContentWidth(re.getIntrinsicWidth()); - setHeight(re.getIntrinsicHeight()); - setReplacedElement(re); + } else if (cssWidth == -1 && pinnedContentWidth == -1 && style.isCanBeShrunkToFit()) { setNeedShrinkToFitCalculatation(true); @@ -870,6 +971,7 @@ public void layout(LayoutContext c, int contentStart) { c.getRootLayer().addPageSequence(this); } + createReplaced(c); calcDimensions(c); calcShrinkToFitWidthIfNeeded(c); collapseMargins(c); @@ -1578,22 +1680,13 @@ public void calcMinMaxWidth(LayoutContext c) { int width = getCSSWidth(c, true); - if (width == -1) { - if (getReplacedElement() != null) { - width = getReplacedElement().getIntrinsicWidth(); - } else { - int height = getCSSHeight(c); - ReplacedElement re = c.getReplacedElementFactory().createReplacedElement( - c, this, c.getUac(), width, height); - if (re != null) { - re = fitReplacedElement(c, re); - setReplacedElement(re); - width = getReplacedElement().getIntrinsicWidth(); - } - } + createReplaced(c); + if (isReplaced() && width == -1) { + // FIXME: We need to special case this for issue 313. + width = getContentWidth(); } - - if (isReplaced() || (width != -1 && ! isFixedWidthAdvisoryOnly())) { + + if (width != -1 && !isFixedWidthAdvisoryOnly()) { _minWidth = _maxWidth = (int) margin.left() + (int) border.left() + (int) padding.left() + width + @@ -1643,11 +1736,11 @@ public void calcMinMaxWidth(LayoutContext c) { if (! isReplaced()) { calcMinMaxCSSMinMaxWidth(c, margin, border, padding); } - setMinMaxCalculated(true); } } + @Deprecated private ReplacedElement fitReplacedElement(LayoutContext c, ReplacedElement re) { diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-img-in-table-cell-2.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-img-in-table-cell-2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..66a3e620d7a2b1b45afe3bba8fded6cf511799da GIT binary patch literal 12773 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 literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-img.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-img.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7395742492cfd91d701131ed3908901c431e2950 GIT binary patch literal 14817 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 aznhl}4IG?q6a_({ykIOQCTV3Etp5WjnvWF# literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-img-in-table-cell-2.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-img-in-table-cell-2.html new file mode 100644 index 000000000..b03943e2d --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-img-in-table-cell-2.html @@ -0,0 +1,22 @@ + + + + + + + +
+ + + +
+ + diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-img.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-img.html new file mode 100644 index 000000000..ca53e905c --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-img.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 707c31880..6fe7c3d35 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -670,6 +670,15 @@ public void testReplacedImgInTableCell() throws IOException { assertTrue(vt.runTest("replaced-img-in-table-cell")); } + /** + * 1. Tests that an img (with percentage max-width) shows up in an absolute width table cell. + * 2. Tests that an img (with absolute max-width) shows up correctly sized in an auto width table cell. + */ + @Test + public void testReplacedImgInTableCell2() throws IOException { + assertTrue(vt.runTest("replaced-img-in-table-cell-2")); + } + /** * Tests that a fixed position element correctly resizes to the sum of its child boxes * using border-box sizing. @@ -726,6 +735,15 @@ public void testReplacedImgDisplayBlock() throws IOException { assertTrue(vt.runTest("replaced-img-display-block")); } + /** + * Tests various sizing properties for replaced images including box-sizing, + * min/max, etc. + */ + @Test + public void testReplacedSizingImg() throws IOException { + assertTrue(vt.runTest("replaced-sizing-img")); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DReplacedElementFactory.java b/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DReplacedElementFactory.java index bd9624190..7eed10789 100644 --- a/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DReplacedElementFactory.java +++ b/openhtmltopdf-java2d/src/main/java/com/openhtmltopdf/java2d/Java2DReplacedElementFactory.java @@ -45,12 +45,15 @@ public ReplacedElement createReplacedElement(LayoutContext context, BlockBox box if (srcAttr != null && srcAttr.endsWith(".svg")) { return new Java2DSVGReplacedElement(uac.getXMLResource(srcAttr).getDocument().getDocumentElement(), _svgImpl, cssWidth, cssHeight, box, context); } - } + }else if (context.getNamespaceHandler().isImageElement(e)) { + return replaceImage(uac, context, e, cssWidth, cssHeight); + } + return null; // We no longer handle form controls. /* * Default: Just let the base class handle everything */ - return super.createReplacedElement(context, box, uac, cssWidth, cssHeight); + //return super.createReplacedElement(context, box, uac, cssWidth, cssHeight); } @Override diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxImageElement.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxImageElement.java index c6f0fe83d..e4b7ff4ef 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxImageElement.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxImageElement.java @@ -78,12 +78,15 @@ public boolean isRequiresInteractivePaint() { return false; } - public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice, - BlockBox box) { - Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), - box.getAbsY(), c); + @Override + public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice, BlockBox box) { + Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), c); ReplacedElement element = box.getReplacedElement(); - outputDevice.drawImage(((PdfBoxImageElement) element).getImage(), + + FSImage img = ((PdfBoxImageElement) element).getImage(); + img.scale(contentBounds.width, contentBounds.height); + + outputDevice.drawImage(img, contentBounds.x, contentBounds.y, interpolate); } diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java index 2aea07a54..e6689e219 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java @@ -19,8 +19,6 @@ */ package com.openhtmltopdf.pdfboxout; -import com.openhtmltopdf.css.constants.CSSName; -import com.openhtmltopdf.css.style.CalculatedStyle; import com.openhtmltopdf.extend.*; import com.openhtmltopdf.layout.LayoutContext; import com.openhtmltopdf.render.BlockBox; @@ -64,48 +62,6 @@ public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, FSImage fsImage = uac.getImageResource(srcAttr).getImage(); if (fsImage != null) { - boolean hasMaxHeight = !box.getStyle().isMaxHeightNone(); - boolean hasMaxWidth = !box.getStyle().isMaxWidthNone(); - boolean hasMaxProperty = hasMaxWidth || hasMaxHeight; - if (cssWidth == -1 && cssHeight == -1) { - if (hasMaxProperty) { - long maxWidth = CalculatedStyle.getCSSMaxWidth(c, box); - long maxHeight = CalculatedStyle.getCSSMaxHeight(c, box); - int intrinsicHeight = fsImage.getHeight(); - int intrinsicWidth = fsImage.getWidth(); - - if (hasMaxWidth && hasMaxHeight) { - if (intrinsicWidth > maxWidth || intrinsicHeight > maxHeight) { - double rw = (double) intrinsicWidth / (double) maxWidth; - double rh = (double) intrinsicHeight / (double) maxHeight; - - if (rw > rh) { - fsImage.scale((int) maxWidth, -1); - } else { - fsImage.scale(-1, (int) maxHeight); - } - } - } else if (hasMaxWidth && intrinsicWidth > maxWidth) { - fsImage.scale((int) maxWidth, -1); - } else if (hasMaxHeight && intrinsicHeight > maxHeight) { - fsImage.scale(-1, (int) maxHeight); - } - } - } else { - if (hasMaxProperty) { - long maxWidth = box.getStyle().asLength(c, CSSName.MAX_WIDTH).value(); - long maxHeight = box.getStyle().asLength(c, CSSName.MAX_HEIGHT).value(); - if (cssHeight > maxHeight && cssHeight >= cssWidth) { - fsImage.scale(-1, (int) maxHeight); - } else if (cssWidth > maxWidth) { - fsImage.scale((int) maxWidth, -1); - } else { - fsImage.scale(cssWidth, cssHeight); - } - } else { - fsImage.scale(cssWidth, cssHeight); - } - } return new PdfBoxImageElement(e,fsImage,c.getSharedContext(), box.getStyle().isImageRenderingInterpolate()); } } From 965e7d48e276c628092359749e53461eda865861 Mon Sep 17 00:00:00 2001 From: danfickle Date: Mon, 6 May 2019 15:53:01 +1000 Subject: [PATCH 02/12] #350 - Much improved sizing support for SVG images. Including min-width/min-height and border-box sizing. With test. --- .../com/openhtmltopdf/render/BlockBox.java | 22 +++--- .../expected/replaced-sizing-img.pdf | Bin 14817 -> 14781 bytes .../expected/replaced-sizing-svg.pdf | Bin 0 -> 9229 bytes .../visualtest/html/replaced-sizing-svg.html | 69 +++++++++++++++++ .../VisualRegressionTest.java | 9 +++ .../svgsupport/BatikSVGDrawer.java | 2 +- .../svgsupport/BatikSVGImage.java | 5 +- .../svgsupport/PDFTranscoder.java | 73 +++++++++++++++++- 8 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-svg.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-svg.html diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java index 3a03568fa..65c89b379 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java @@ -737,16 +737,25 @@ private void sizeReplacedElement(LayoutContext c, ReplacedElement re) { int intrinsicWidth = re.getIntrinsicWidth(); int intrinsicHeight = re.getIntrinsicHeight(); - cssWidth = !getStyle().isMaxWidthNone() && intrinsicWidth > getCSSMaxWidth(c) ? + cssWidth = !getStyle().isMaxWidthNone() && + (intrinsicWidth > getCSSMaxWidth(c) || cssWidth > getCSSMaxWidth(c)) ? getCSSMaxWidth(c) : cssWidth; cssWidth = getCSSMinWidth(c) > 0 && cssWidth < getCSSMinWidth(c) ? getCSSMinWidth(c) : cssWidth; - cssHeight = !getStyle().isMaxHeightNone() && intrinsicHeight > getCSSMaxHeight(c) ? + cssHeight = !getStyle().isMaxHeightNone() && + (intrinsicHeight > getCSSMaxHeight(c) || cssHeight > getCSSMaxHeight(c)) ? getCSSMaxHeight(c) : cssHeight; cssHeight = getCSSMinHeight(c) > 0 && cssHeight < getCSSMinHeight(c) ? getCSSMinHeight(c) : cssHeight; + if (getStyle().isBorderBox()) { + BorderPropertySet border = getBorder(c); + RectPropertySet padding = getPadding(c); + cssWidth = (int) Math.max(0, cssWidth - border.width() - padding.width()); + cssHeight = (int) Math.max(0, cssHeight - border.height() - padding.height()); + } + int nw; int nh; @@ -798,13 +807,8 @@ private void sizeReplacedElement(LayoutContext c, ReplacedElement re) { nh = intrinsicHeight; } - if (getStyle().isBorderBox()) { - setBorderBoxWidth(c, nw); - setBorderBoxHeight(c, nh); - } else { - setContentWidth(nw); - setHeight(nh); - } + setContentWidth(nw); + setHeight(nh); } public void calcDimensions(LayoutContext c) { diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-img.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-img.pdf index 7395742492cfd91d701131ed3908901c431e2950..bee4193863186c2f7066cdcd180f24707fe0df37 100644 GIT binary patch delta 605 zcmah`J5Iwu5LGA$-iQuS0L3<@V>LTF`;$(S73z< zoq|ci0e)l;9lJbEuHB9rTT%lj0-?jS42vV~Ru7hy%j9y~_Erq3&qNhv(c_*f>xThe zy|MGrnTBXp^AJQ4g)bp*%g;%Ag0{|QtMAFh%~UH6@$jcpR$oBLfQB_mQrUwVN?k6z q%ACq_K<&ft%sqfJIx`k>XBTsi*372-_CHGb?ynLwEKT#f1^Wfj+<%Gy delta 627 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 F{{X_o35);$ literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-svg.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-svg.html new file mode 100644 index 000000000..1bf5f4ddf --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-svg.html @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 6fe7c3d35..5c95b5d89 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -744,6 +744,15 @@ public void testReplacedSizingImg() throws IOException { assertTrue(vt.runTest("replaced-sizing-img")); } + /** + * Tests various sizing properties for replaced SVG images including box-sizing, + * min/max, etc. + */ + @Test + public void testReplacedSizingSvg() throws IOException { + assertTrue(vt.runTest("replaced-sizing-svg", WITH_SVG)); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java index 5d0798430..41eeb0772 100644 --- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java +++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java @@ -30,7 +30,7 @@ public SVGImage buildSVGImage(Element svgElement, Box box, CssContext c, double cssMaxWidth = CalculatedStyle.getCSSMaxWidth(c, box); double cssMaxHeight = CalculatedStyle.getCSSMaxHeight(c, box); - BatikSVGImage img = new BatikSVGImage(svgElement, cssWidth, cssHeight, + BatikSVGImage img = new BatikSVGImage(svgElement, box, cssWidth, cssHeight, cssMaxWidth, cssMaxHeight, dotsPerPixel); img.setFontResolver(fontResolver); return img; diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java index bea5134d7..10a84cd1a 100644 --- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java +++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java @@ -14,6 +14,7 @@ import com.openhtmltopdf.extend.OutputDevice; import com.openhtmltopdf.extend.SVGDrawer.SVGImage; +import com.openhtmltopdf.render.Box; import com.openhtmltopdf.render.RenderingContext; import com.openhtmltopdf.svgsupport.PDFTranscoder.OpenHtmlFontResolver; import com.openhtmltopdf.util.XRLog; @@ -27,12 +28,12 @@ public class BatikSVGImage implements SVGImage { private PDFTranscoder pdfTranscoder; - public BatikSVGImage(Element svgElement, double cssWidth, double cssHeight, + public BatikSVGImage(Element svgElement, Box box, double cssWidth, double cssHeight, double cssMaxWidth, double cssMaxHeight, double dotsPerPixel) { this.svgElement = svgElement; this.dotsPerPixel = dotsPerPixel; - this.pdfTranscoder = new PDFTranscoder(cssWidth, cssHeight); + this.pdfTranscoder = new PDFTranscoder(box, dotsPerPixel, cssWidth, cssHeight); if (cssWidth >= 0) { this.pdfTranscoder.addTranscodingHint( SVGAbstractTranscoder.KEY_WIDTH, diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java index 53d8640be..c638e8dd3 100644 --- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java +++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java @@ -8,6 +8,7 @@ import com.openhtmltopdf.extend.OutputDevice; import com.openhtmltopdf.extend.OutputDeviceGraphicsDrawer; import com.openhtmltopdf.layout.SharedContext; +import com.openhtmltopdf.render.Box; import com.openhtmltopdf.render.RenderingContext; import com.openhtmltopdf.util.XRLog; import org.apache.batik.bridge.FontFace; @@ -21,6 +22,8 @@ import java.awt.*; import java.awt.font.TextAttribute; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; import java.io.InputStream; import java.util.HashMap; import java.util.List; @@ -31,17 +34,22 @@ public class PDFTranscoder extends SVGAbstractTranscoder { private OutputDevice outputDevice; private double x; private double y; + private final Box box; + private RenderingContext ctx; + private final double dotsPerPixel; - public PDFTranscoder(double width, double height ) { + public PDFTranscoder(Box box, double dotsPerPixel, double width, double height) { + this.box = box; this.width = (float)width; this.height = (float)height; + this.dotsPerPixel = dotsPerPixel; } public void setRenderingParameters(OutputDevice od, RenderingContext ctx, double x, double y, OpenHtmlFontResolver fontResolver) { this.x = x; this.y = y; this.outputDevice = od; - + this.ctx = ctx; this.fontResolver = fontResolver; } @@ -199,14 +207,71 @@ protected void transcode(Document svg, String uri, TranscoderOutput out) throws // is called before our constructor is called in the super constructor. this.userAgent = new OpenHtmlUserAgent(this.fontResolver); super.transcode(svg, uri, out); - - outputDevice.drawWithGraphics((float)x, (float)y, width, height, new OutputDeviceGraphicsDrawer() { + + int intrinsicWidth = (int) width; + int intrinsicHeight = (int) height; + + Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), ctx); + + int desiredWidth = (int) (contentBounds.width / this.dotsPerPixel); + int desiredHeight = (int) (contentBounds.height / this.dotsPerPixel); + + boolean transformed = false; + AffineTransform scale = null; + + if (width == 0 || height == 0) { + // Do nothing... + } + else if (desiredWidth > intrinsicWidth && + desiredHeight > intrinsicHeight) { + + double rw = (double) desiredWidth / width; + double rh = (double) desiredHeight / height; + + double factor = Math.min(rw, rh); + scale = AffineTransform.getScaleInstance(factor, factor); + transformed = true; + } else if (desiredWidth < intrinsicWidth && + desiredHeight < intrinsicHeight) { + double rw = (double) desiredWidth / width; + double rh = (double) desiredHeight / height; + + double factor = Math.max(rw, rh); + scale = AffineTransform.getScaleInstance(factor, factor); + transformed = true; + } + + AffineTransform inverseScale = null; + try { + if (transformed) { + inverseScale = scale.createInverse(); + } + } catch (NoninvertibleTransformException e) { + transformed = false; + } + final AffineTransform scale2 = scale; + final AffineTransform inverse2 = inverseScale; + final boolean transformed2 = transformed; + + outputDevice.drawWithGraphics( + (float) x, + (float) y, + (float) (contentBounds.width / this.dotsPerPixel), + (float) (contentBounds.height / this.dotsPerPixel), + new OutputDeviceGraphicsDrawer() { @Override public void render(Graphics2D graphics2D) { + if (transformed2) { + graphics2D.transform(scale2); + } /* * Do the real paint */ PDFTranscoder.this.root.paint(graphics2D); + + if (transformed2) { + graphics2D.transform(inverse2); + } } }); } From d9cdd6a6e2c297294bf54791dd7e1305349fdb16 Mon Sep 17 00:00:00 2001 From: danfickle Date: Thu, 13 Jun 2019 22:45:31 +1000 Subject: [PATCH 03/12] #350 #358 Implement correct non-css sizing for SVG images. We can now use unit values such as mm in the width/height attribute of SVG images. With tests. --- .../com/openhtmltopdf/render/BlockBox.java | 15 +++ .../simple/extend/XhtmlNamespaceHandler.java | 88 ++++++++++++++++-- .../expected/replaced-sizing-svg-non-css.pdf | Bin 0 -> 6594 bytes .../expected/replaced-sizing-svg.pdf | Bin 9229 -> 9225 bytes .../html/replaced-sizing-svg-non-css.html | 54 +++++++++++ .../VisualRegressionTest.java | 10 ++ .../svgsupport/BatikSVGImage.java | 71 ++++++-------- 7 files changed, 183 insertions(+), 55 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-svg-non-css.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-svg-non-css.html diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java index 65c89b379..540a3a93c 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java @@ -712,6 +712,21 @@ private void createReplaced(LayoutContext c) { int cssWidth = getCSSWidth(c); int cssHeight = getCSSHeight(c); + // Since the interface doesn't allow us to pass min-width/height + // we implement it here. + int minWidth = getCSSMinWidth(c); + int minHeight = getCSSMinHeight(c); + + if (minWidth > cssWidth && + minWidth > 0) { + cssWidth = minWidth; + } + + if (minHeight > cssHeight && + minHeight > 0) { + cssHeight = minHeight; + } + re = c.getReplacedElementFactory().createReplacedElement( c, this, c.getUac(), cssWidth, cssHeight); diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/XhtmlNamespaceHandler.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/XhtmlNamespaceHandler.java index 2e4e80fa1..2291f76fa 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/XhtmlNamespaceHandler.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/XhtmlNamespaceHandler.java @@ -18,9 +18,14 @@ */ package com.openhtmltopdf.simple.extend; +import java.awt.Point; +import java.util.logging.Level; + import org.w3c.dom.Element; import org.w3c.dom.Node; +import com.openhtmltopdf.util.XRLog; + /** * Handles xhtml documents, including presentational html attributes (see css 2.1 spec, 6.4.4). @@ -30,6 +35,8 @@ * @author Torbjoern Gannholm */ public class XhtmlNamespaceHandler extends XhtmlCssOnlyNamespaceHandler { + private static final String DEFAULT_SVG_DIMS = ""; + /** * {@inheritDoc} */ @@ -53,25 +60,86 @@ public String getImageSourceURI(Element e) { } public String getNonCssStyling(Element e) { - if (e.getNodeName().equals("table")) { - return applyTableStyles(e); - } else if (e.getNodeName().equals("td") || e.getNodeName().equals("th")) { + switch(e.getNodeName()) { + case "table": + return applyTableStyles(e); + case "td": /* FALL-THRU */ + case "th": return applyTableCellStyles(e); - } else if (e.getNodeName().equals("tr")) { + case "tr": return applyTableRowStyles(e); - } else if (e.getNodeName().equals("img")) { + case "img": return applyImgStyles(e); - } else if (e.getNodeName().equals("p") || e.getNodeName().equals("div")) { + case "p": /* FALL-THRU */ + case "div": return applyBlockAlign(e); - } else if (e.getNodeName().equals("textarea")) { - return applyTextareaStyles(e); - } else if (e.getNodeName().equals("input")) { - return applyInputStyles(e); + case "textarea": + return applyTextareaStyles(e); + case "input": + return applyInputStyles(e); + case "svg": + return applySvgStyles(e); } return ""; } + private String applySvgStyles(Element e) { + String w = e.getAttribute("width"); + String h = e.getAttribute("height"); + + if (!w.isEmpty() || !h.isEmpty()) { + StringBuilder sb = new StringBuilder(); + + if (!w.isEmpty()) { + sb.append("width: "); + sb.append(w); + if (isInteger(w)) { + sb.append("px"); + } + sb.append(';'); + } + + if (!h.isEmpty()) { + sb.append("height: "); + sb.append(h); + if (isInteger(h)) { + sb.append("px"); + } + sb.append(';'); + } + + return sb.toString(); + } + + String viewBoxAttr = e.getAttribute("viewBox"); + String[] splitViewBox = viewBoxAttr.split("\\s+"); + + if (splitViewBox.length != 4) { + return DEFAULT_SVG_DIMS; + } + try { + int viewBoxWidth = Integer.parseInt(splitViewBox[2]); + int viewBoxHeight = Integer.parseInt(splitViewBox[3]); + + StringBuilder sb = new StringBuilder(); + + sb.append("width: "); + sb.append(viewBoxWidth); + sb.append("px;"); + + sb.append("height: "); + sb.append(viewBoxHeight); + sb.append("px;"); + } catch (NumberFormatException ex) { + XRLog.general(Level.WARNING, + "Invalid integer passed in viewBox attribute for SVG: " + viewBoxAttr); + /* FALL-THRU */ + } + + return DEFAULT_SVG_DIMS; + } + private String applyInputStyles(Element e) { StringBuilder sb = new StringBuilder(); diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-svg-non-css.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-svg-non-css.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0de049dff0fcf066ab828aca64a2684ee87d5b69 GIT binary patch literal 6594 zcmb_h2~<R5#%f)`ZBTKkiVH5|GQIb`yxf~S&S~|)IfVQ#-}f)y{oj3m zE`f=KgUMW z7!iIVClbndqrs#fYMx4_QVNQMzpR=EuQbhoKfqO@-eLs4#2SI)92+pLBo8AI%4DO} zoNq}7BB+E5NCXIw0>&A0GHjDbhF0+~C94gHlHHX6(LgYA3c-*u1Vv^NG)2Y}spA0C zK;(fdFbOi7fFR**mhe`faT+oUAn^Bg$Rr|#$Pt#1A}tvNtq{m8_>)bjwaCgM)EelP z0ghPd%_L%?9Yk@wKoR(&A>b%Z4G54bzJm-5lZ^+&9D@i5kEbCeC51qqQF>Sm4pc$Y zYK0d1<3J`ftx|B%U_iR|48^wsOMq$_wSw1bwHinUFl0l5meDFG;Zw;QenpCnIQA07 z@IqZuXc-SBDrJq_v=+>z7!+G&&rom!GL~%U1g?{o;p`QP6(P&60;%CN$Om8`8!|K; z=nRJj&?A0D$^&sE9Z?=oM-6TdV{5lj^{u$}2;8s2uCbQXneGR=NVJpMv z01qx%fgS;s;Bj3mLwSIG(O!wDD$7e(L{(XEH%R>sd}0>#VdqfNJt0=ZI&47?ns(>` z*M-GwM;;D_-2;?5{8{J?ue2w3>&`w}T3LT<&f($2nMO^uZS0i7_|rdZKBV>C zm#}=}N{_J~=T;W)y|ed1{?4C5eQF9eO`3aduqsA%>E7jKi$1M+7SZ88*JFW?(sHKZ ze9hGh{X3-oEr0F)X{Q64UU%BnCx&lj+2LBcYOdCNu;$`K+=}Mlu>v%QuWhAyd}6)V zA*yrHqm<^4Ja_*i=exR^j4QJPPMu{oe_vd4Fq_@^hR$;D-qDRx%) zv3CQmG7pmjdY3I)=Enym6g2c*Uw8ZZuc_)$F}4#iyy5Fz57YPh_xjJGC3`kqOs*(# zyHyo+WygHO!WqAi>JvebI-b-U>shS^#XAKp8z;`6Ex7T@vh!?oez6Q^w(K0YxT zi0`@Qb@v$&y%w*iOqo}+>p@8;UB$5W>-+mW3M%Narr&QSGyWqi2i!(BbUfa3x_e&L z_^}?#*lj}^Zuqu5$bTF5cJMWYMp4!K^P@8_m&SGa)q@_L)xG^{doMD!gs4OF(!x!% z{f~X`RZ*u3x-e$%rp!Md`(1iVY*OjKt)XA1%g&Y0bsT7dw`liZEz02n|7u^h=-)-~eDYcdBF0LtQ3jW+@c=-73p53Nf@66uub3#hMmg^5U zJ|DAvajrGMDPZ-f;5dD^=WqJP2US$Rda8aEv-@C2 z^(C*+A#aMORG#0$`ES^zoO&STUj4~E`x~e4UzJyG>3V}ZUl4aB!TWNcw<7c9fhY4W zhGz87yXINrzASvpcrWjMe;He{`}E6^AD<{Xx7@cRn(sA(Z+WDd*JJsdKkHL3A~kT#H7e3Z+j7W=wQl}=3ZIz)QDeH2_+l09nU)B3WP zTVFS~GbH$OV}=*ZsAoZ*?v~~~1$1?-(A6vE zXADgvr`O@ZrgZxxJ=eB$73oV~jn_lnx;^J`M30=I8eY~W&YRkw+%g?%%dixFjy;+L{SvuQDq-t~;04d+VxQOWzg#yW0Ds^ij8&!zppTm&R7@SnqrF*@ zcHgeiK5k8I{G2YsN`o^qX;`!$F4FkHnt%|8OQ5KPz~@;3;TSk21U_yG2*|AhuIw8x5on?X} zJ2X7J2MzD!_n_fR@*XsNo!*0nFW!65@D=_4X;Ur6iQp8K(GZm5hg9)A%aaqq`KH^_9@urtTw&HCUh>Z(NIKS K;II)<#D4)bA~y^G literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-svg.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-svg.pdf index a0403a71f654fed6d782b1da39dfa649edf22f4a..c0181f963dcdad8a78a3c5f28e82941b025382a1 100644 GIT binary patch delta 634 zcmaixu}T9$5QbYlFc57NEDl5ZnVs30T@m3f=aJ60NENiOvGQsQLD(k|OPjoo_!63= zFy=4ZYWetP|C!mB`Sbks{?J%LP-1<19F5EB^s=nR%d$B8c>g+n8V!t}(c|qnKmP9rCN>m9zz2g{d6>wI*t;fcrr5U)_g&LE bDFrr}xk~5P_0@k$`J9{|a5bIY-1gNsChC9d delta 833 zcmeD5==GS8&uMC4W@u_+U~IUtf=`gi+<3C0faTOI=*@nBLxExP{`xu0y7LujSLMi#7xaiF~ls5&Ctcnj14i= znVDHwpsO&fE~w4s%nm9regzW^QhQX^DjiMmU;VSYoQP1O@_H99S3_V5qY&Fhf@73f5v_ zi0L~ELvv(>5OqdIm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 5c95b5d89..306d4551f 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -753,6 +753,16 @@ public void testReplacedSizingSvg() throws IOException { assertTrue(vt.runTest("replaced-sizing-svg", WITH_SVG)); } + /** + * Tests that non-css sizing for SVG works. For example width/height + * attributes or if not present the last two values of viewBox attribute. + * Finally, if neither is present, it should default to 400px x 400px. + */ + @Test + public void testReplacedSizingSvgNonCss() throws IOException { + assertTrue(vt.runTest("replaced-sizing-svg-non-css", WITH_SVG)); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java index 10a84cd1a..f6d9906d0 100644 --- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java +++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java @@ -20,7 +20,9 @@ import com.openhtmltopdf.util.XRLog; public class BatikSVGImage implements SVGImage { - private final static Point DEFAULT_DIMENSIONS = new Point(400, 400); + private final static int DEFAULT_SVG_WIDTH = 400; + private final static int DEFAULT_SVG_HEIGHT = 400; + private final static Point DEFAULT_DIMENSIONS = new Point(DEFAULT_SVG_WIDTH, DEFAULT_SVG_HEIGHT); private final Element svgElement; private final double dotsPerPixel; @@ -56,18 +58,31 @@ public BatikSVGImage(Element svgElement, Box box, double cssWidth, double cssHei } Point dimensions = parseDimensions(svgElement); + double w; + double h; - if (dimensions == DEFAULT_DIMENSIONS && - cssWidth >= 0 && cssHeight >= 0) { - svgElement.setAttribute("width", Integer.toString((int) (cssWidth / dotsPerPixel))); - svgElement.setAttribute("height", Integer.toString((int) (cssHeight / dotsPerPixel))); - this.pdfTranscoder.setImageSize((float) (cssWidth / dotsPerPixel), (float) (cssHeight / dotsPerPixel)); + if (dimensions == DEFAULT_DIMENSIONS) { + if (cssWidth >= 0 && cssHeight >= 0) { + w = (cssWidth / dotsPerPixel); + h = (cssHeight / dotsPerPixel); + } else if (cssWidth >= 0) { + w = (cssWidth / dotsPerPixel); + h = DEFAULT_SVG_HEIGHT; + } else if (cssHeight >= 0) { + w = DEFAULT_SVG_WIDTH; + h = (cssHeight / dotsPerPixel); + } else { + w = DEFAULT_SVG_WIDTH; + h = DEFAULT_SVG_HEIGHT; + } } else { - svgElement.setAttribute("width", Integer.toString(dimensions.x)); - svgElement.setAttribute("height", Integer.toString(dimensions.y)); - this.pdfTranscoder.setImageSize((float) dimensions.x, - (float) dimensions.y); + w = dimensions.x; + h = dimensions.y; } + + svgElement.setAttribute("width", Integer.toString((int) w)); + svgElement.setAttribute("height", Integer.toString((int) h)); + this.pdfTranscoder.setImageSize((float) w, (float) h); } @Override @@ -84,31 +99,7 @@ public void setFontResolver(OpenHtmlFontResolver fontResolver) { this.fontResolver = fontResolver; } - public Integer parseLength(String attrValue) { - // TODO read length with units and convert to dots. - // length ::= number (~"em" | ~"ex" | ~"px" | ~"in" | ~"cm" | ~"mm" | - // ~"pt" | ~"pc")? - try { - return Integer.valueOf(attrValue); - } catch (NumberFormatException e) { - XRLog.general(Level.WARNING, - "Invalid integer passed as dimension for SVG: " - + attrValue); - return null; - } - } - public Point parseDimensions(Element e) { - String widthAttr = e.getAttribute("width"); - Integer width = widthAttr.isEmpty() ? null : parseLength(widthAttr); - - String heightAttr = e.getAttribute("height"); - Integer height = heightAttr.isEmpty() ? null : parseLength(heightAttr); - - if (width != null && height != null) { - return new Point(width, height); - } - String viewBoxAttr = e.getAttribute("viewBox"); String[] splitViewBox = viewBoxAttr.split("\\s+"); if (splitViewBox.length != 4) { @@ -118,17 +109,7 @@ public Point parseDimensions(Element e) { int viewBoxWidth = Integer.parseInt(splitViewBox[2]); int viewBoxHeight = Integer.parseInt(splitViewBox[3]); - if (width == null && height == null) { - width = viewBoxWidth; - height = viewBoxHeight; - } else if (width == null) { - width = (int) Math.round(((double) height) - * ((double) viewBoxWidth) / ((double) viewBoxHeight)); - } else if (height == null) { - height = (int) Math.round(((double) width) - * ((double) viewBoxHeight) / ((double) viewBoxWidth)); - } - return new Point(width, height); + return new Point(viewBoxWidth, viewBoxHeight); } catch (NumberFormatException ex) { return DEFAULT_DIMENSIONS; } From 3b0292859974ef20d70a9886c104f7b62bc6a057 Mon Sep 17 00:00:00 2001 From: danfickle Date: Thu, 27 Jun 2019 15:06:22 +1000 Subject: [PATCH 04/12] #161 Improved sizing and tests for MathML elements. Now, always keep the correct aspect ratio. --- .../extend/ReplacedElementScaleHelper.java | 58 +++++ .../expected/replaced-sizing-mathml.pdf | Bin 0 -> 20438 bytes .../html/replaced-sizing-mathml.html | 206 ++++++++++++++++++ .../VisualRegressionTest.java | 11 + .../mathmlsupport/MathMLDrawer.java | 2 +- .../mathmlsupport/MathMLImage.java | 120 +++------- .../svgsupport/PDFTranscoder.java | 51 +---- 7 files changed, 314 insertions(+), 134 deletions(-) create mode 100644 openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-mathml.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-mathml.html diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java new file mode 100644 index 000000000..3cd490f3f --- /dev/null +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java @@ -0,0 +1,58 @@ +package com.openhtmltopdf.simple.extend; + +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; + +public class ReplacedElementScaleHelper { + /** + * Creates a scale AffineTransform to scale a given replaced element to the desired size. + * @param dotsPerPixel + * @param contentBounds the desired size + * @param width the intrinsic width + * @param height the intrinsic height + * @return AffineTransform or null if not available. + */ + public static AffineTransform createScaleTransform(double dotsPerPixel, Rectangle contentBounds, float width, float height) { + int intrinsicWidth = (int) width; + int intrinsicHeight = (int) height; + + int desiredWidth = (int) (contentBounds.width / dotsPerPixel); + int desiredHeight = (int) (contentBounds.height / dotsPerPixel); + + AffineTransform scale = null; + + if (width == 0 || height == 0) { + // Do nothing... + } + else if (desiredWidth > intrinsicWidth && + desiredHeight > intrinsicHeight) { + + double rw = (double) desiredWidth / width; + double rh = (double) desiredHeight / height; + + double factor = Math.min(rw, rh); + scale = AffineTransform.getScaleInstance(factor, factor); + } else if (desiredWidth < intrinsicWidth && + desiredHeight < intrinsicHeight) { + double rw = (double) desiredWidth / width; + double rh = (double) desiredHeight / height; + + double factor = Math.max(rw, rh); + scale = AffineTransform.getScaleInstance(factor, factor); + } + + return scale; + } + + public static AffineTransform inverseOrNull(AffineTransform in) { + if (in == null) { + return null; + } + try { + return in.createInverse(); + } catch (NoninvertibleTransformException e) { + return null; + } + } +} diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-mathml.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-mathml.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c8d54165ffc8315a7b2a0f6d11df2f854f0a104d GIT binary patch literal 20438 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 literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-mathml.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-mathml.html new file mode 100644 index 000000000..49a45fdb7 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-mathml.html @@ -0,0 +1,206 @@ + + + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + + + + + + a + + + b + + + 2 + + + + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 306d4551f..c6ec4b968 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -8,6 +8,7 @@ import org.junit.Ignore; import org.junit.Test; +import com.openhtmltopdf.mathmlsupport.MathMLDrawer; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.svgsupport.BatikSVGDrawer; import com.openhtmltopdf.visualtest.VisualTester; @@ -763,6 +764,16 @@ public void testReplacedSizingSvgNonCss() throws IOException { assertTrue(vt.runTest("replaced-sizing-svg-non-css", WITH_SVG)); } + /** + * Tests all the CSS sizing properties for MathML elements. + */ + @Test + public void testReplacedSizingMathMl() throws IOException { + assertTrue(vt.runTest("replaced-sizing-mathml", (builder) -> { + builder.useMathMLDrawer(new MathMLDrawer()); + })); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLDrawer.java b/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLDrawer.java index cd411f4b3..032b98b77 100644 --- a/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLDrawer.java +++ b/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLDrawer.java @@ -117,7 +117,7 @@ public SVGImage buildSVGImage(Element mathMlElement, Box box, CssContext c, doub double cssMaxHeight = CalculatedStyle.getCSSMaxHeight(c, box); List fontList = Arrays.asList(fonts); - MathMLImage img = new MathMLImage(mathMlElement, cssWidth, cssHeight, cssMaxWidth, cssMaxHeight, dotsPerPixel, fontList); + MathMLImage img = new MathMLImage(mathMlElement, box, cssWidth, cssHeight, cssMaxWidth, cssMaxHeight, dotsPerPixel, fontList); return img; } diff --git a/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLImage.java b/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLImage.java index 67e562b03..1eac2d1c4 100644 --- a/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLImage.java +++ b/openhtmltopdf-mathml-support/src/main/java/com/openhtmltopdf/mathmlsupport/MathMLImage.java @@ -1,6 +1,8 @@ package com.openhtmltopdf.mathmlsupport; import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; import java.util.List; import net.sourceforge.jeuclid.DOMBuilder; @@ -14,37 +16,25 @@ import com.openhtmltopdf.extend.OutputDevice; import com.openhtmltopdf.extend.OutputDeviceGraphicsDrawer; import com.openhtmltopdf.extend.SVGDrawer.SVGImage; +import com.openhtmltopdf.render.Box; import com.openhtmltopdf.render.RenderingContext; +import com.openhtmltopdf.simple.extend.ReplacedElementScaleHelper; public class MathMLImage implements SVGImage { private final JEuclidView _view; private final DocumentElement _mathDoc; private final double _dotsPerPixel; + private final Box _box; private final MathLayoutContext _context = new MathLayoutContext(); - private final double _scaledWidthInOutputDeviceDots; - private final double _scaledHeightInOutputDeviceDots; - private double _sx = 1; - private double _sy = 1; public static class MathLayoutContext extends LayoutContextImpl { private static final long serialVersionUID = 1; } - - private boolean haveValue(double val) { - return val >= 0; - } - - private double minIgnoringNoValues(double spec, double max) { - if (haveValue(spec) && haveValue(max)) { - return Math.min(spec, max); - } else { - return haveValue(spec) ? spec : max; - } - } - - public MathMLImage(Element mathMlElement, double cssWidth, + + public MathMLImage(Element mathMlElement, Box box, double cssWidth, double cssHeight, double cssMaxWidth, double cssMaxHeight, double dotsPerPixel, List fonts) { + this._box = box; this._dotsPerPixel = dotsPerPixel; this._mathDoc = DOMBuilder.getInstance().createJeuclidDom(mathMlElement); @@ -58,65 +48,6 @@ public MathMLImage(Element mathMlElement, double cssWidth, this._context.setParameter(Parameter.MATHSIZE, 16f); // TODO: Proper font size pickup from CSS. this._view = new JEuclidView(this._mathDoc, this._context, null); - - if (this.getViewWidthInOutputDeviceDots() <= 0 || this.getViewHeightInOutputDeviceDots() <= 0) { - this._scaledWidthInOutputDeviceDots = 0; - this._scaledHeightInOutputDeviceDots = 0; - return; - } - - boolean haveBoth = haveValue(cssWidth) && haveValue(cssHeight); - boolean haveNone = !haveValue(cssWidth) && !haveValue(cssHeight); - boolean haveOne = !haveBoth && !haveNone; - - double w = -1f; - double h = -1f; - - if (haveBoth) { - // Have both from CSS, use them constrained by max-xxx values, don't keep aspect ratio. - w = minIgnoringNoValues(cssWidth, cssMaxWidth); - h = minIgnoringNoValues(cssHeight, cssMaxHeight); - } else if (haveNone) { - // Use rendered view size with max-xxx constraints, keeping aspect ratio. - double prelimW = minIgnoringNoValues(this.getViewWidthInOutputDeviceDots(), cssMaxWidth); - double prelimH = minIgnoringNoValues(this.getViewHeightInOutputDeviceDots(), cssMaxHeight); - - double prelimScaleX = prelimW / this.getViewWidthInOutputDeviceDots(); - double prelimScaleY = prelimH / this.getViewHeightInOutputDeviceDots(); - - double scale = Math.min(prelimScaleX, prelimScaleY); - - w = scale * this.getViewWidthInOutputDeviceDots(); - h = scale * this.getViewHeightInOutputDeviceDots(); - } else if (haveOne) { - if (haveValue(cssWidth)) { - // Keep aspect ratio, if we have both a width and a conflicting max-height, the max-height wins out. - double prelimW = minIgnoringNoValues(cssWidth, cssMaxWidth); - double prelimScale = prelimW / this.getViewWidthInOutputDeviceDots(); - double prelimH = this.getViewHeightInOutputDeviceDots() * prelimScale; - - double scale = (haveValue(cssMaxHeight) && prelimH > cssMaxHeight) ? cssMaxHeight / this.getViewHeightInOutputDeviceDots() : prelimScale; - - w = scale * this.getViewWidthInOutputDeviceDots(); - h = scale * this.getViewHeightInOutputDeviceDots(); - } else if (haveValue(cssHeight)) { - // Keep aspect ratio, if we have both a height and a conflicting max-width, the max-width wins out. - double prelimH = minIgnoringNoValues(cssHeight, cssMaxHeight); - double prelimScale = prelimH / this.getViewHeightInOutputDeviceDots(); - double prelimW = this.getViewWidthInOutputDeviceDots() * prelimScale; - - double scale = (haveValue(cssMaxWidth) && prelimW > cssMaxWidth) ? cssMaxWidth / this.getViewWidthInOutputDeviceDots() : prelimScale; - - w = scale * this.getViewWidthInOutputDeviceDots(); - h = scale * this.getViewHeightInOutputDeviceDots(); - } - } - - _sx = w / this.getViewWidthInOutputDeviceDots(); - _sy = h / this.getViewHeightInOutputDeviceDots(); - - this._scaledWidthInOutputDeviceDots = w; - this._scaledHeightInOutputDeviceDots = h; } private double getViewWidthInOutputDeviceDots() { @@ -129,23 +60,36 @@ private double getViewHeightInOutputDeviceDots() { @Override public int getIntrinsicWidth() { - return (int) this._scaledWidthInOutputDeviceDots; + return (int) this.getViewWidthInOutputDeviceDots(); } @Override public int getIntrinsicHeight() { - return (int) this._scaledHeightInOutputDeviceDots; + return (int) this.getViewHeightInOutputDeviceDots(); } - @Override - public void drawSVG(OutputDevice outputDevice, RenderingContext ctx, - double x, double y) { - outputDevice.drawWithGraphics((float) x, (float) y, (float) this._scaledWidthInOutputDeviceDots, (float) this._scaledHeightInOutputDeviceDots, new OutputDeviceGraphicsDrawer() { - @Override - public void render(Graphics2D g2d) { - g2d.scale(_sx, _sy); - _view.draw(g2d, 0, _view.getAscentHeight()); - } + @Override + public void drawSVG(OutputDevice outputDevice, RenderingContext ctx, double x, double y) { + Rectangle contentBounds = _box.getContentAreaEdge(_box.getAbsX(), _box.getAbsY(), ctx); + + final AffineTransform scale2 = ReplacedElementScaleHelper.createScaleTransform(_dotsPerPixel, contentBounds, this._view.getWidth(), this._view.getAscentHeight() + this._view.getDescentHeight()); + final AffineTransform inverse2 = ReplacedElementScaleHelper.inverseOrNull(scale2); + final boolean transformed2 = scale2 != null && inverse2 != null; + + outputDevice.drawWithGraphics((float) x, (float) y, + (float) (contentBounds.width / _dotsPerPixel), + (float) (contentBounds.height / _dotsPerPixel), + new OutputDeviceGraphicsDrawer() { + @Override + public void render(Graphics2D g2d) { + if (transformed2) { + g2d.transform(scale2); + } + _view.draw(g2d, 0, _view.getAscentHeight()); + if (transformed2) { + g2d.transform(inverse2); + } + } }); } diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java index c638e8dd3..22ddb7bab 100644 --- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java +++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java @@ -10,6 +10,7 @@ import com.openhtmltopdf.layout.SharedContext; import com.openhtmltopdf.render.Box; import com.openhtmltopdf.render.RenderingContext; +import com.openhtmltopdf.simple.extend.ReplacedElementScaleHelper; import com.openhtmltopdf.util.XRLog; import org.apache.batik.bridge.FontFace; import org.apache.batik.bridge.FontFamilyResolver; @@ -23,7 +24,6 @@ import java.awt.*; import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; -import java.awt.geom.NoninvertibleTransformException; import java.io.InputStream; import java.util.HashMap; import java.util.List; @@ -168,7 +168,7 @@ public void importFontFaces(List fontFaces, SharedContext ctx) { continue; } - byte[] font1 = ctx.getUac().getBinaryResource(src.asString()); + byte[] font1 = ctx.getUserAgentCallback().getBinaryResource(src.asString()); if (font1 == null) { XRLog.exception("Could not load font " + src.asString()); continue; @@ -208,50 +208,11 @@ protected void transcode(Document svg, String uri, TranscoderOutput out) throws this.userAgent = new OpenHtmlUserAgent(this.fontResolver); super.transcode(svg, uri, out); - int intrinsicWidth = (int) width; - int intrinsicHeight = (int) height; - Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), ctx); - - int desiredWidth = (int) (contentBounds.width / this.dotsPerPixel); - int desiredHeight = (int) (contentBounds.height / this.dotsPerPixel); - - boolean transformed = false; - AffineTransform scale = null; - - if (width == 0 || height == 0) { - // Do nothing... - } - else if (desiredWidth > intrinsicWidth && - desiredHeight > intrinsicHeight) { - - double rw = (double) desiredWidth / width; - double rh = (double) desiredHeight / height; - - double factor = Math.min(rw, rh); - scale = AffineTransform.getScaleInstance(factor, factor); - transformed = true; - } else if (desiredWidth < intrinsicWidth && - desiredHeight < intrinsicHeight) { - double rw = (double) desiredWidth / width; - double rh = (double) desiredHeight / height; - - double factor = Math.max(rw, rh); - scale = AffineTransform.getScaleInstance(factor, factor); - transformed = true; - } - - AffineTransform inverseScale = null; - try { - if (transformed) { - inverseScale = scale.createInverse(); - } - } catch (NoninvertibleTransformException e) { - transformed = false; - } - final AffineTransform scale2 = scale; - final AffineTransform inverse2 = inverseScale; - final boolean transformed2 = transformed; + + final AffineTransform scale2 = ReplacedElementScaleHelper.createScaleTransform(this.dotsPerPixel, contentBounds, width, height); + final AffineTransform inverse2 = ReplacedElementScaleHelper.inverseOrNull(scale2); + final boolean transformed2 = scale2 != null && inverse2 != null; outputDevice.drawWithGraphics( (float) x, From 0eca9043d06b759bdf66f3879d28d72b68ec06b4 Mon Sep 17 00:00:00 2001 From: danfickle Date: Fri, 28 Jun 2019 15:42:51 +1000 Subject: [PATCH 05/12] #177 Visual regression test for Latex rendering support. --- .../expected/replaced-plugin-latex.pdf | Bin 0 -> 19697 bytes .../html/replaced-plugin-latex.html | 103 ++++++++++++++++++ .../VisualRegressionTest.java | 13 +++ openhtmltopdf-latex-support/pom.xml | 2 +- 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-plugin-latex.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex.html diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-plugin-latex.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-plugin-latex.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1cdfc31782a819e74f7d2e7da29c0ac13f721e13 GIT binary patch literal 19697 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 u+c9W%wsZ=IVoB<|3D_rlg)aqn$GD + + + + + + + + + + + + + + + + + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index c6ec4b968..65931bff9 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -8,6 +8,7 @@ import org.junit.Ignore; import org.junit.Test; +import com.openhtmltopdf.latexsupport.LaTeXDOMMutator; import com.openhtmltopdf.mathmlsupport.MathMLDrawer; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import com.openhtmltopdf.svgsupport.BatikSVGDrawer; @@ -774,6 +775,18 @@ public void testReplacedSizingMathMl() throws IOException { })); } + /** + * Tests the Latex support plugin including maths which are interpreted with + * the MathML plugin. + */ + @Test + public void testReplacedPluginLatex() throws IOException { + assertTrue(vt.runTest("replaced-plugin-latex", (builder) -> { + builder.addDOMMutator(LaTeXDOMMutator.INSTANCE); + builder.useMathMLDrawer(new MathMLDrawer()); + })); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-latex-support/pom.xml b/openhtmltopdf-latex-support/pom.xml index 6f8fe3df4..1cbd75cbc 100644 --- a/openhtmltopdf-latex-support/pom.xml +++ b/openhtmltopdf-latex-support/pom.xml @@ -14,7 +14,7 @@ jar Openhtmltopdf Latex Math Support - Open HTML to PDF is a CSS 2.1 renderer written in Java. This artifact supports drawing MathML graphics on to PDFs using JEuclid. + Open HTML to PDF is a CSS 2.1 renderer written in Java. This artifact supports rendering Latex markup (including math) on to PDFs using SnuggleTex. From 02b43e8684366bf3ff05e78788a5af837d206b18 Mon Sep 17 00:00:00 2001 From: danfickle Date: Fri, 28 Jun 2019 21:08:48 +1000 Subject: [PATCH 06/12] #161 Remove automatic visual regression testing for MathML. The MathML renderer (JEuclid) produces slightly different results on JDK8 vs JDK11 so is not suitable for auto testing. --- .../replaced-plugin-latex-with-math.pdf | Bin 0 -> 19697 bytes .../expected/replaced-plugin-latex.pdf | Bin 19697 -> 16869 bytes .../html/replaced-plugin-latex-with-math.html | 103 ++++++++++++++++++ .../html/replaced-plugin-latex.html | 9 +- .../VisualRegressionTest.java | 19 +++- 5 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-plugin-latex-with-math.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex-with-math.html diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-plugin-latex-with-math.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-plugin-latex-with-math.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1cdfc31782a819e74f7d2e7da29c0ac13f721e13 GIT binary patch literal 19697 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 delta 8653 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 V7$^LH7@%RYaFB?Irm+_2e*jit^yUBn diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex-with-math.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex-with-math.html new file mode 100644 index 000000000..b26122810 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex-with-math.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex.html index b26122810..306f3ba9a 100644 --- a/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex.html +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-plugin-latex.html @@ -78,7 +78,7 @@ \end{tabular} \end{center} - The full possibilities of $L^AT_EX$: + The full possibilities of Latex: \begin{tabular}{|r|l|} \hline @@ -92,12 +92,5 @@ ]]> - - - diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 65931bff9..c5893a616 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -769,6 +769,8 @@ public void testReplacedSizingSvgNonCss() throws IOException { * Tests all the CSS sizing properties for MathML elements. */ @Test + @Ignore // MathML renderer produces slightly different results on JDK11 vs JDK8 + // so can only be run manually. public void testReplacedSizingMathMl() throws IOException { assertTrue(vt.runTest("replaced-sizing-mathml", (builder) -> { builder.useMathMLDrawer(new MathMLDrawer()); @@ -780,10 +782,24 @@ public void testReplacedSizingMathMl() throws IOException { * the MathML plugin. */ @Test + @Ignore // MathML renderer produces slightly different results on JDK11 vs JDK8 + // so can only be run manually. + public void testReplacedPluginLatexWithMath() throws IOException { + assertTrue(vt.runTest("replaced-plugin-latex-with-math", (builder) -> { + builder.addDOMMutator(LaTeXDOMMutator.INSTANCE); + builder.useMathMLDrawer(new MathMLDrawer()); + })); + } + + /** + * Tests Latex rendering without math. Separate test because we can not test + * Latex with math automatically because of the MathML rendering issue on + * different JDKs (see above). + */ + @Test public void testReplacedPluginLatex() throws IOException { assertTrue(vt.runTest("replaced-plugin-latex", (builder) -> { builder.addDOMMutator(LaTeXDOMMutator.INSTANCE); - builder.useMathMLDrawer(new MathMLDrawer()); })); } @@ -791,7 +807,6 @@ public void testReplacedPluginLatex() throws IOException { // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) // + Inline layers. - // + Replaced elements. // + vertical page overflow, page-break-inside, etc. // + CSS columns. } From 6af18a0d208e6cb3d185af3dff9f6f55836296f7 Mon Sep 17 00:00:00 2001 From: danfickle Date: Thu, 4 Jul 2019 21:16:52 +1000 Subject: [PATCH 07/12] #350 #358 - Use width/height attribute of external SVGs linked with img tag. This is mostly a revert of d9cdd6a6 I forgot about external SVGs rather than inline SVGs where the width/height attribute are converted to CSS earlier in the render process. --- .../VisualRegressionTest.java | 1 - .../svgsupport/BatikSVGImage.java | 32 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index e1198457e..f5d3d95f6 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -834,7 +834,6 @@ public void testReplacedPluginLatex() throws IOException { * does not shutdown rendering altogether. Issue 353. */ @Test - @Ignore // Sizing is now all wrong for linked SVGs. public void testSvgLinkedFromImgTag() throws IOException { assertTrue(vt.runTest("svg-linked-from-img-tag", WITH_SVG)); } diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java index bc4b9e2df..cab3cf8cb 100644 --- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java +++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java @@ -102,12 +102,40 @@ public void setSecurityOptions(boolean allowScripts, boolean allowExternalResour this.pdfTranscoder.setSecurityOptions(allowScripts, allowExternalResources); this.pdfTranscoder.addTranscodingHint(SVGAbstractTranscoder.KEY_EXECUTE_ONLOAD, allowScripts); } + + public Integer parseLength(String attrValue) { + // TODO read length with units and convert to dots. + // length ::= number (~"em" | ~"ex" | ~"px" | ~"in" | ~"cm" | ~"mm" | + // ~"pt" | ~"pc")? + try { + return Integer.valueOf(attrValue); + } catch (NumberFormatException e) { + XRLog.general(Level.WARNING, + "Invalid integer passed as dimension for SVG: " + + attrValue); + return null; + } + } + + public Point parseWidthHeightAttributes(Element e) { + String widthAttr = e.getAttribute("width"); + Integer width = widthAttr.isEmpty() ? null : parseLength(widthAttr); + + String heightAttr = e.getAttribute("height"); + Integer height = heightAttr.isEmpty() ? null : parseLength(heightAttr); + + if (width != null && height != null) { + return new Point(width, height); + } + + return DEFAULT_DIMENSIONS; + } public Point parseDimensions(Element e) { String viewBoxAttr = e.getAttribute("viewBox"); String[] splitViewBox = viewBoxAttr.split("\\s+"); if (splitViewBox.length != 4) { - return DEFAULT_DIMENSIONS; + return parseWidthHeightAttributes(e); } try { int viewBoxWidth = Integer.parseInt(splitViewBox[2]); @@ -115,7 +143,7 @@ public Point parseDimensions(Element e) { return new Point(viewBoxWidth, viewBoxHeight); } catch (NumberFormatException ex) { - return DEFAULT_DIMENSIONS; + return parseWidthHeightAttributes(e); } } From 0887f7f765e83bc04e68cab9782d7ac063cb2d3c Mon Sep 17 00:00:00 2001 From: danfickle Date: Fri, 5 Jul 2019 01:39:59 +1000 Subject: [PATCH 08/12] #344 - Embed PDF page via an img tag. (WIP) Nearly there, the only reason I havent committed the test is that it is a couple of pixels off on width, which is noticeable when there is a border. --- .../com/openhtmltopdf/render/BlockBox.java | 10 +- .../extend/ReplacedElementScaleHelper.java | 8 +- .../src/main/resources/demos/images/hello.pdf | Bin 0 -> 5862 bytes .../html/pdf-linked-from-img-tag.html | 51 ++++++ .../VisualRegressionTest.java | 6 + .../pdfboxout/PdfBoxFastOutputDevice.java | 29 ++++ .../pdfboxout/PdfBoxOutputDevice.java | 6 +- .../pdfboxout/PdfBoxPDFReplacedElement.java | 149 ++++++++++++++++++ .../PdfBoxReplacedElementFactory.java | 10 ++ .../pdfboxout/PdfBoxSlowOutputDevice.java | 5 + .../pdfboxout/PdfContentStreamAdapter.java | 8 + 11 files changed, 272 insertions(+), 10 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/demos/images/hello.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/pdf-linked-from-img-tag.html create mode 100644 openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java index 540a3a93c..3e9abacd2 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java @@ -755,22 +755,22 @@ private void sizeReplacedElement(LayoutContext c, ReplacedElement re) { cssWidth = !getStyle().isMaxWidthNone() && (intrinsicWidth > getCSSMaxWidth(c) || cssWidth > getCSSMaxWidth(c)) ? getCSSMaxWidth(c) : cssWidth; - cssWidth = getCSSMinWidth(c) > 0 && cssWidth < getCSSMinWidth(c) ? + cssWidth = cssWidth >= 0 && getCSSMinWidth(c) > 0 && cssWidth < getCSSMinWidth(c) ? getCSSMinWidth(c) : cssWidth; cssHeight = !getStyle().isMaxHeightNone() && (intrinsicHeight > getCSSMaxHeight(c) || cssHeight > getCSSMaxHeight(c)) ? getCSSMaxHeight(c) : cssHeight; - cssHeight = getCSSMinHeight(c) > 0 && cssHeight < getCSSMinHeight(c) ? + cssHeight = cssHeight >= 0 && getCSSMinHeight(c) > 0 && cssHeight < getCSSMinHeight(c) ? getCSSMinHeight(c) : cssHeight; if (getStyle().isBorderBox()) { BorderPropertySet border = getBorder(c); RectPropertySet padding = getPadding(c); - cssWidth = (int) Math.max(0, cssWidth - border.width() - padding.width()); - cssHeight = (int) Math.max(0, cssHeight - border.height() - padding.height()); + cssWidth = cssWidth < 0 ? cssWidth : (int) Math.max(0, cssWidth - border.width() - padding.width()); + cssHeight = cssHeight < 0 ? cssHeight : (int) Math.max(0, cssHeight - border.height() - padding.height()); } - + int nw; int nh; diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java index 3cd490f3f..b7f33d859 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java @@ -17,15 +17,15 @@ public static AffineTransform createScaleTransform(double dotsPerPixel, Rectangl int intrinsicWidth = (int) width; int intrinsicHeight = (int) height; - int desiredWidth = (int) (contentBounds.width / dotsPerPixel); - int desiredHeight = (int) (contentBounds.height / dotsPerPixel); + int desiredWidth = (int) (contentBounds.getWidth() / dotsPerPixel); + int desiredHeight = (int) (contentBounds.getHeight() / dotsPerPixel); AffineTransform scale = null; - + if (width == 0 || height == 0) { // Do nothing... } - else if (desiredWidth > intrinsicWidth && + else if (desiredWidth > intrinsicWidth || desiredHeight > intrinsicHeight) { double rw = (double) desiredWidth / width; diff --git a/openhtmltopdf-examples/src/main/resources/demos/images/hello.pdf b/openhtmltopdf-examples/src/main/resources/demos/images/hello.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e07de7ee35e975931b4eb9363a2c67837953bd73 GIT binary patch literal 5862 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 literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/pdf-linked-from-img-tag.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/pdf-linked-from-img-tag.html new file mode 100644 index 000000000..8537155d1 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/pdf-linked-from-img-tag.html @@ -0,0 +1,51 @@ + + + + + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF + + +PDF +PDF + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index f5d3d95f6..72a918222 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -838,6 +838,12 @@ public void testSvgLinkedFromImgTag() throws IOException { assertTrue(vt.runTest("svg-linked-from-img-tag", WITH_SVG)); } + @Test + @Ignore // Still a couple of pixels off with width (noticeable when there is a border). + public void testPdfLinkedFromImgTag() throws IOException { + assertTrue(vt.runTest("pdf-linked-from-img-tag")); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java index 52d5a9cf7..70d6868d5 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java @@ -39,6 +39,7 @@ import com.openhtmltopdf.pdfboxout.PdfBoxSlowOutputDevice.FontRun; import com.openhtmltopdf.pdfboxout.PdfBoxSlowOutputDevice.Metadata; import com.openhtmltopdf.render.*; +import com.openhtmltopdf.simple.extend.ReplacedElementScaleHelper; import com.openhtmltopdf.util.ArrayUtil; import com.openhtmltopdf.util.XRLog; import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; @@ -842,6 +843,34 @@ public void drawImage(FSImage fsImage, int x, int y, boolean interpolate) { _cp.drawImage(xobject, (float) mx[4], (float) mx[5], (float) mx[0], (float) mx[3]); } + + @Override + public void drawPdfAsImage(PDFormXObject _srcObject, Rectangle contentBounds, float intrinsicWidth, float intrinsicHeight) { + // We start with the page margins... + AffineTransform af = AffineTransform.getTranslateInstance( + getTransform().getTranslateX(), + (_pageHeight) - getTransform().getTranslateY()); + + // Then the x and y of this object... + af.translate(contentBounds.getX() / _dotsPerPoint, -(contentBounds.getY() / _dotsPerPoint)); + + float conversion = 96f / 72f; + + // Scale to the desired height and width... + AffineTransform scale = ReplacedElementScaleHelper.createScaleTransform(_dotsPerPoint, contentBounds, intrinsicWidth / _dotsPerPoint * conversion, intrinsicHeight / _dotsPerPoint * conversion); + if (scale != null) { + af.concatenate(scale); + } + + // And take into account the height of the drawn feature... + // And yes these transforms were all determined by trial and error! + af.translate(0, -((intrinsicHeight / _dotsPerPoint) * conversion)); + + _cp.saveGraphics(); + _cp.applyPdfMatrix(af); + _cp.drawXForm(_srcObject); + _cp.restoreGraphics(); + } public float getDotsPerPoint() { return _dotsPerPoint; diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java index 1bc5104b6..ead654683 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxOutputDevice.java @@ -1,6 +1,7 @@ package com.openhtmltopdf.pdfboxout; import java.awt.Paint; +import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.RenderingHints.Key; @@ -11,6 +12,7 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.w3c.dom.Document; import com.openhtmltopdf.bidi.BidiReorderer; @@ -214,4 +216,6 @@ void drawWithGraphics(float x, float y, float width, float height, List getMetadata(); -} \ No newline at end of file + void drawPdfAsImage(PDFormXObject _src, Rectangle contentBounds, float intrinsicWidth, float intrinsicHeight); + +} diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java new file mode 100644 index 000000000..f9f580f9c --- /dev/null +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java @@ -0,0 +1,149 @@ +/* + * {{{ header & license + * Copyright (c) 2006 Wisconsin Court System + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * }}} + */ +package com.openhtmltopdf.pdfboxout; + +import com.openhtmltopdf.css.style.CssContext; +import com.openhtmltopdf.layout.LayoutContext; +import com.openhtmltopdf.layout.SharedContext; +import com.openhtmltopdf.pdfboxout.PdfBoxLinkManager.IPdfBoxElementWithShapedLinks; +import com.openhtmltopdf.render.BlockBox; +import com.openhtmltopdf.render.Box; +import com.openhtmltopdf.render.RenderingContext; +import com.openhtmltopdf.swing.ImageMapParser; +import com.openhtmltopdf.util.XRLog; + +import org.apache.pdfbox.multipdf.LayerUtility; +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.form.PDFormXObject; +import org.w3c.dom.Element; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.io.IOException; +import java.util.Map; +import java.util.logging.Level; + +public class PdfBoxPDFReplacedElement implements PdfBoxReplacedElement, IPdfBoxElementWithShapedLinks { + private final PDFormXObject _srcFormObject; + private final float _width; + private final float _height; + private final Map _imageMap; + private Point _location = new Point(0, 0); + + private PdfBoxPDFReplacedElement(PDFormXObject srcForm, Element e, Box box, CssContext ctx, SharedContext shared, float w, float h) { + this._srcFormObject = srcForm; + this._width = w; + this._height = h; + this._imageMap = ImageMapParser.findAndParseMap(e, shared); + } + + private static int parsePage(Element e) { + if (e.getAttribute("page").isEmpty()) { + return 0; + } + + try { + return Integer.parseInt(e.getAttribute("page")) - 1; + } catch (NumberFormatException e1) { + XRLog.exception("Unable to parse page of img tag with PDF!", e1); + } + + return 0; + } + + public static PdfBoxPDFReplacedElement create(PDDocument target, byte[] pdfBytes, Element e, Box box, CssContext ctx, SharedContext shared) { + try (PDDocument srcDocument = PDDocument.load(pdfBytes)){ + int pageNo = parsePage(e); + if (pageNo >= srcDocument.getNumberOfPages()) { + XRLog.load(Level.WARNING, "Page does not exist for pdf in img tag. Ignoring!"); + return null; + } + + PDPage page = srcDocument.getPage(pageNo); + float width = page.getMediaBox().getWidth() * shared.getDotsPerPixel(); + float height = page.getMediaBox().getHeight() * shared.getDotsPerPixel(); + + LayerUtility util = new LayerUtility(target); + PDFormXObject formXObject = util.importPageAsForm(srcDocument, page); + + return new PdfBoxPDFReplacedElement(formXObject, e, box, ctx, shared, width, height); + } catch (InvalidPasswordException e1) { + XRLog.exception("Tried to open a password protected document as src for an img!", e1); + } catch (IOException e1) { + XRLog.exception("Could not read pdf passed as src for img element!", e1); + } + + return null; + } + + @Override + public int getIntrinsicWidth() { + return (int) _width; + } + + @Override + public int getIntrinsicHeight() { + return (int) _height; + } + + @Override + public Point getLocation() { + return _location; + } + + @Override + public void setLocation(int x, int y) { + _location = new Point(x, y); + } + + @Override + public Map getLinkMap() { + return _imageMap; + } + + @Override + public void detach(LayoutContext c) { + } + + @Override + public boolean isRequiresInteractivePaint() { + // N/A + return false; + } + + @Override + public void paint(RenderingContext c, PdfBoxOutputDevice outputDevice, BlockBox box) { + Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), c); + outputDevice.drawPdfAsImage(_srcFormObject, contentBounds, getIntrinsicWidth(), getIntrinsicHeight()); + } + + @Override + public int getBaseline() { + return 0; + } + + @Override + public boolean hasBaseline() { + return false; + } +} diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java index f0ee336a2..b8263c266 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxReplacedElementFactory.java @@ -30,8 +30,10 @@ public class PdfBoxReplacedElementFactory implements ReplacedElementFactory { private final SVGDrawer _svgImpl; private final SVGDrawer _mathmlImpl; private final FSObjectDrawerFactory _objectDrawerFactory; + private final PdfBoxOutputDevice _outputDevice; public PdfBoxReplacedElementFactory(PdfBoxOutputDevice outputDevice, SVGDrawer svgImpl, FSObjectDrawerFactory objectDrawerFactory, SVGDrawer mathmlImpl) { + _outputDevice = outputDevice; _svgImpl = svgImpl; _objectDrawerFactory = objectDrawerFactory; _mathmlImpl = mathmlImpl; @@ -63,6 +65,14 @@ public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, return new PdfBoxSVGReplacedElement(xml.getDocument().getDocumentElement(), _svgImpl, cssWidth, cssHeight, box, c, c.getSharedContext()); } + return null; + } else if (srcAttr.endsWith(".pdf")) { + byte[] pdfBytes = uac.getBinaryResource(srcAttr); + + if (pdfBytes != null) { + return PdfBoxPDFReplacedElement.create(_outputDevice.getWriter(), pdfBytes, e, box, c, c.getSharedContext()); + } + return null; } diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java index 6fd1b790a..09671a263 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxSlowOutputDevice.java @@ -856,6 +856,11 @@ public void drawImage(FSImage fsImage, int x, int y, boolean interpolate) { _cp.drawImage(xobject, (float) mx[4], (float) mx[5], (float) mx[0], (float) mx[3]); } + + @Override + public void drawPdfAsImage(PDFormXObject _src, Rectangle contentBounds, float intrinsicWidth, float intrinsicHeight) { + throw new UnsupportedOperationException("Use the fast mode!"); + } /* private void drawPDFAsImage(PDFAsImage image, int x, int y) { URI uri = image.getURI(); diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfContentStreamAdapter.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfContentStreamAdapter.java index 4b1b80890..6c9ef5319 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfContentStreamAdapter.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfContentStreamAdapter.java @@ -298,6 +298,14 @@ public void drawImage(PDImageXObject xobject, float x, float y, float w, logAndThrow("drawImage", e); } } + + public void drawXForm(PDFormXObject xObject) { + try { + cs.drawForm(xObject); + } catch (IOException e) { + logAndThrow("drawXForm", e); + } + } public void setMiterLimit(float miterLimit) { try { From f3f5c8101f85cb54ddfc4f53115f6b8eacd1106f Mon Sep 17 00:00:00 2001 From: danfickle Date: Tue, 16 Jul 2019 15:15:23 +1000 Subject: [PATCH 09/12] #344 - Pixel perfect sizing for PDF pages embedded from img tag. Turns out the problem was prematurely casting our width and height to ints and thus getting incorrect scale factors. Test now passing. --- .../extend/ReplacedElementScaleHelper.java | 16 ++++++++-------- .../expected/pdf-linked-from-img-tag.pdf | Bin 0 -> 52173 bytes .../html/pdf-linked-from-img-tag.html | 8 ++++++-- .../VisualRegressionTest.java | 5 ++++- .../pdfboxout/PdfBoxFastOutputDevice.java | 7 +++---- .../pdfboxout/PdfBoxPDFReplacedElement.java | 5 +++-- 6 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/pdf-linked-from-img-tag.pdf diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java index b7f33d859..b5937de0f 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/simple/extend/ReplacedElementScaleHelper.java @@ -14,11 +14,11 @@ public class ReplacedElementScaleHelper { * @return AffineTransform or null if not available. */ public static AffineTransform createScaleTransform(double dotsPerPixel, Rectangle contentBounds, float width, float height) { - int intrinsicWidth = (int) width; - int intrinsicHeight = (int) height; + double intrinsicWidth = width; + double intrinsicHeight = height; - int desiredWidth = (int) (contentBounds.getWidth() / dotsPerPixel); - int desiredHeight = (int) (contentBounds.getHeight() / dotsPerPixel); + double desiredWidth = (contentBounds.getWidth() / dotsPerPixel); + double desiredHeight = (contentBounds.getHeight() / dotsPerPixel); AffineTransform scale = null; @@ -28,15 +28,15 @@ public static AffineTransform createScaleTransform(double dotsPerPixel, Rectangl else if (desiredWidth > intrinsicWidth || desiredHeight > intrinsicHeight) { - double rw = (double) desiredWidth / width; - double rh = (double) desiredHeight / height; + double rw = desiredWidth / width; + double rh = desiredHeight / height; double factor = Math.min(rw, rh); scale = AffineTransform.getScaleInstance(factor, factor); } else if (desiredWidth < intrinsicWidth && desiredHeight < intrinsicHeight) { - double rw = (double) desiredWidth / width; - double rh = (double) desiredHeight / height; + double rw = desiredWidth / width; + double rh = desiredHeight / height; double factor = Math.max(rw, rh); scale = AffineTransform.getScaleInstance(factor, factor); diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/pdf-linked-from-img-tag.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/pdf-linked-from-img-tag.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1d60e378bc4596eb7fac0180ba2e03069d098531 GIT binary patch literal 52173 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

v<>_dEyJdM8 z4o*VL@^C^~j-bFf5X*8n1%KvRmdD_VawJYDWFZL@e0$oGmZS+q9;Ks|c?A4LT1#4r z#ua(Aj#B2~tTK0TtTjsGkrTthPuF!);X@wpvjw#xlB`Ad+ECuIyvU+J!p$E&bay`H; zR)rqm9svB&VGDlX#FL`kP#rb0#nvO5Q;v75{$AAQpYN6MH0$7NE%hx%1XaP zDV$K)ilUTtP_)9IQCf#9bfZzFEHs8G+L9*V4+>kh2W`dnH26tVMLAZ{-+-qn+lWCG z;|t2*tU_OgRQd};GK%(P7(!uthE@75=;jJPU~!$IUY1bwOOz#8MZGMo)Qx2naRFsn z#rTKmFjQ`5OouDRd*G4qmu)TD1JhB8aR}4V@P}t*14M6^Cm diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 72a918222..ccad0a85a 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -838,8 +838,11 @@ public void testSvgLinkedFromImgTag() throws IOException { assertTrue(vt.runTest("svg-linked-from-img-tag", WITH_SVG)); } + /** + * Tests that we correctly render PDF pages in the img tag at + * the correct CSS specified sizing. Issue 344. + */ @Test - @Ignore // Still a couple of pixels off with width (noticeable when there is a border). public void testPdfLinkedFromImgTag() throws IOException { assertTrue(vt.runTest("pdf-linked-from-img-tag")); } diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java index 70d6868d5..6f99092c7 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxFastOutputDevice.java @@ -854,17 +854,16 @@ public void drawPdfAsImage(PDFormXObject _srcObject, Rectangle contentBounds, fl // Then the x and y of this object... af.translate(contentBounds.getX() / _dotsPerPoint, -(contentBounds.getY() / _dotsPerPoint)); - float conversion = 96f / 72f; - // Scale to the desired height and width... - AffineTransform scale = ReplacedElementScaleHelper.createScaleTransform(_dotsPerPoint, contentBounds, intrinsicWidth / _dotsPerPoint * conversion, intrinsicHeight / _dotsPerPoint * conversion); + AffineTransform scale = ReplacedElementScaleHelper.createScaleTransform(_dotsPerPoint, contentBounds, intrinsicWidth / _dotsPerPoint, intrinsicHeight / _dotsPerPoint); + if (scale != null) { af.concatenate(scale); } // And take into account the height of the drawn feature... // And yes these transforms were all determined by trial and error! - af.translate(0, -((intrinsicHeight / _dotsPerPoint) * conversion)); + af.translate(0, -((intrinsicHeight / _dotsPerPoint))); _cp.saveGraphics(); _cp.applyPdfMatrix(af); diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java index f9f580f9c..e1a2a7ae6 100644 --- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java +++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxPDFReplacedElement.java @@ -80,8 +80,9 @@ public static PdfBoxPDFReplacedElement create(PDDocument target, byte[] pdfBytes } PDPage page = srcDocument.getPage(pageNo); - float width = page.getMediaBox().getWidth() * shared.getDotsPerPixel(); - float height = page.getMediaBox().getHeight() * shared.getDotsPerPixel(); + float conversion = 96f / 72f; + float width = page.getMediaBox().getWidth() * shared.getDotsPerPixel() * conversion; + float height = page.getMediaBox().getHeight() * shared.getDotsPerPixel() * conversion; LayerUtility util = new LayerUtility(target); PDFormXObject formXObject = util.importPageAsForm(srcDocument, page); From 8685d2b69358e5c8aa81dd9ac4cae4d12ad4d887 Mon Sep 17 00:00:00 2001 From: danfickle Date: Wed, 17 Jul 2019 15:46:16 +1000 Subject: [PATCH 10/12] JFreeChart plugin manual test. [ci skip] --- .../replaced-sizing-jfreechart-pie.pdf | Bin 0 -> 47104 bytes .../html/replaced-sizing-jfreechart-pie.html | 42 ++++++++++++++++++ .../VisualRegressionTest.java | 18 ++++++++ 3 files changed, 60 insertions(+) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-jfreechart-pie.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/replaced-sizing-jfreechart-pie.html diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-jfreechart-pie.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-jfreechart-pie.pdf new file mode 100644 index 0000000000000000000000000000000000000000..89296c7b17a118eee6fa30d42c27b342ea4ef12e GIT binary patch literal 47104 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index ccad0a85a..0191e2a4c 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -10,7 +10,10 @@ import com.openhtmltopdf.latexsupport.LaTeXDOMMutator; import com.openhtmltopdf.mathmlsupport.MathMLDrawer; +import com.openhtmltopdf.objects.jfreechart.JFreeChartBarDiagramObjectDrawer; +import com.openhtmltopdf.objects.jfreechart.JFreeChartPieDiagramObjectDrawer; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; +import com.openhtmltopdf.render.DefaultObjectDrawerFactory; import com.openhtmltopdf.svgsupport.BatikSVGDrawer; import com.openhtmltopdf.svgsupport.BatikSVGDrawer.SvgExternalResourceMode; import com.openhtmltopdf.svgsupport.BatikSVGDrawer.SvgScriptMode; @@ -847,6 +850,21 @@ public void testPdfLinkedFromImgTag() throws IOException { assertTrue(vt.runTest("pdf-linked-from-img-tag")); } + /** + * Tests the JFreeChart object drawers respect CSS width and height + * properties. Other sizing properties are not supported for JFreeChart + * plugins. + */ + @Test + @Ignore // Unlikely to be stable across JDK versions. + public void testReplacedSizingJFreeChartPie() throws IOException { + assertTrue(vt.runTest("replaced-sizing-jfreechart-pie", builder -> { + DefaultObjectDrawerFactory factory = new DefaultObjectDrawerFactory(); + factory.registerDrawer("jfreechart/pie", new JFreeChartPieDiagramObjectDrawer()); + factory.registerDrawer("jfreechart/bar", new JFreeChartBarDiagramObjectDrawer()); + builder.useObjectDrawerFactory(factory); + })); + } // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) From 1d2c29823d7d4a4f228fedf37dc2f1af54b1dbc4 Mon Sep 17 00:00:00 2001 From: danfickle Date: Sun, 21 Jul 2019 22:37:08 +1000 Subject: [PATCH 11/12] Test for clear: both with floats. [ci skip] I thought I had broken the clear mechanism, but it was fine. Left test as a useful regression test. --- .../visualtest/expected/float-clear-both.pdf | Bin 0 -> 1097 bytes .../visualtest/html/float-clear-both.html | 28 ++++++++++++++++++ .../VisualRegressionTest.java | 9 ++++++ 3 files changed, 37 insertions(+) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/float-clear-both.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/float-clear-both.html diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/float-clear-both.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/float-clear-both.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5bb397fe57df0acdd78767694e95a30a355c22bb GIT binary patch literal 1097 zcmah|OK;Oa5WbJp|1g)Rtq}I^`i-JUvC~KtDndYTh`GJPp67Y4t7{rL$J1)47pFdTyY@%y7jJ4x#_H*2sz>9X3I z0&QspV9(OEu27Nr97ZG1MaDFzM^8$f)=TnI}bZ$})zC}iN2Q6+bHt-n= z?w%ERDx#FFAo4|s<(kZu3@ncv3Y3ab4pKeGtwjzSSdlu9I>lLah@2>25wWH34yUM- zb_AV!cEgh-+V~w!U~9=5PN+{}ie!>e{sf2alU>J zoJjBCf(N&4OE<#o;n2QQPoGLng~0&CaiQ;dj)yC1=V3|eJTEMcchZl}?nDrt-6=(3 z|KU2$xm^(UV$@~2&2`zl)wwY=^(37}^L4Q@$S;xc1Q)|FNgU6)iTv1$JmE%3Jf2Kd moJ8?>8pYvwg#S082k~M1LsJQwK~BrMsYxh&7z}RSP2n%fhW~;9 literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/float-clear-both.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/float-clear-both.html new file mode 100644 index 000000000..ba60a7459 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/float-clear-both.html @@ -0,0 +1,28 @@ + + + + + +

+
+ +
+ + +
+ + +
+
+ + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java index 0191e2a4c..121f2531d 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java @@ -865,6 +865,15 @@ public void testReplacedSizingJFreeChartPie() throws IOException { builder.useObjectDrawerFactory(factory); })); } + + /** + * Tests that a div element with clear: both set actually does clear both. + */ + @Test + public void testFloatClearBoth() throws IOException { + assertTrue(vt.runTest("float-clear-both")); + } + // TODO: // + Elements that appear just on generated overflow pages. // + content property (page counters, etc) From 9b76c1ddecdc07d64c19a859670419e3709e121d Mon Sep 17 00:00:00 2001 From: danfickle Date: Sun, 21 Jul 2019 23:09:36 +1000 Subject: [PATCH 12/12] #8 Update README and changelog with work. Hopefully this will also trigger travis. --- CHANGELOG.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74c170ca5..b962273e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## CHANGELOG ### head - 0.0.1-RC22-SNAPSHOT ++ [#372](https://github.com/danfickle/openhtmltopdf/pull/372) Much improved sizing support for `img`, `svg` and `math` elements. ++ [#344](https://github.com/danfickle/openhtmltopdf/issues/344) Use PDFs in `img` tag: `Some alt text`. ### 0.0.1-RC21 (2019-June-29) + [#361](https://github.com/danfickle/openhtmltopdf/issues/361) The SVG renderer now uses Batik in a more secure mode (no scripts, no external resource requests) by default. If you need the old behavior that allowed external resource requests and possibly scripts, please see the new BatikSVGDrawer constructor (only for trusted SVGs). Thanks @krabbenpuler. diff --git a/README.md b/README.md index d153fac07..937f6acd2 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ from ````/openhtmltopdf-examples/src/main/java/com/openhtmltopdf/testcases/Testc ## CHANGELOG ### head - 0.0.1-RC22-SNAPSHOT ++ [#372](https://github.com/danfickle/openhtmltopdf/pull/372) Much improved sizing support for `img`, `svg` and `math` elements. ++ [#344](https://github.com/danfickle/openhtmltopdf/issues/344) Use PDFs in `img` tag: `Some alt text`. ### 0.0.1-RC21 (2019-June-29) + [#361](https://github.com/danfickle/openhtmltopdf/issues/361) The SVG renderer now uses Batik in a more secure mode (no scripts, no external resource requests) by default. If you need the old behavior that allowed external resource requests and possibly scripts, please see the new BatikSVGDrawer constructor (only for trusted SVGs). Thanks @krabbenpuler.