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 000000000..c8d54165f
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/replaced-sizing-mathml.pdf differ
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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,