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 000000000..e07de7ee3 Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/demos/images/hello.pdf differ 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 {