From: <rb...@us...> - 2017-06-04 13:40:42
|
Revision: 14546 http://sourceforge.net/p/htmlunit/code/14546 Author: rbri Date: 2017-06-04 13:40:39 +0000 (Sun, 04 Jun 2017) Log Message: ----------- SVGMatrix operations (flipX, flipY, inverse, multiply, rotate, rotateFromVector, scale, scaleNonUniform, skewX, skewY, translate) implemented Modified Paths: -------------- trunk/htmlunit/src/changes/changes.xml trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/svg/SVGMatrix.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/svg/SvgMatrixTest.java Modified: trunk/htmlunit/src/changes/changes.xml =================================================================== --- trunk/htmlunit/src/changes/changes.xml 2017-06-04 09:58:08 UTC (rev 14545) +++ trunk/htmlunit/src/changes/changes.xml 2017-06-04 13:40:39 UTC (rev 14546) @@ -8,6 +8,10 @@ <body> <release version="2.28" date="???" description="Bugfixes"> + <action type="add" dev="rbri" > + JavaScript: SVGMatrix operations (flipX, flipY, inverse, multiply, rotate, rotateFromVector, + scale, scaleNonUniform, skewX, skewY, translate) implemented. + </action> </release> <release version="2.27" date="June 4, 2017" description="FF52, Bugfixes"> <action type="add" dev="asashour" issue="44336828" system="stackoverflow"> Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/svg/SVGMatrix.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/svg/SVGMatrix.java 2017-06-04 09:58:08 UTC (rev 14545) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/svg/SVGMatrix.java 2017-06-04 13:40:39 UTC (rev 14546) @@ -18,6 +18,9 @@ import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE; import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; + import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass; import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor; @@ -26,25 +29,27 @@ import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter; import com.gargoylesoftware.htmlunit.javascript.host.Window; +import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime; + /** * A JavaScript object for {@code SVGMatrix}. * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/SVGMatrix">MDN doc</a> * @author Marc Guillemot + * @author Ronald Brill */ @JsxClass public class SVGMatrix extends SimpleScriptable { - private double fieldA_ = 1; - private double fieldB_ = 0; - private double fieldC_ = 0; - private double fieldD_ = 1; - private double fieldE_ = 0; - private double fieldF_ = 0; + private static final AffineTransform FLIP_X_TRANSFORM = new AffineTransform(-1, 0, 0, 1, 0, 0); + private static final AffineTransform FLIP_Y_TRANSFORM = new AffineTransform(1, 0, 0, -1, 0, 0); + private AffineTransform affineTransform_; + /** * Creates an instance. */ @JsxConstructor({CHROME, FF, EDGE}) public SVGMatrix() { + affineTransform_ = new AffineTransform(); } /** @@ -52,6 +57,7 @@ * @param scope the parent scope */ public SVGMatrix(final Window scope) { + this(); setParentScope(scope); setPrototype(getPrototype(getClass())); } @@ -62,7 +68,7 @@ */ @JsxGetter public double getA() { - return fieldA_; + return affineTransform_.getScaleX(); } /** @@ -71,7 +77,7 @@ */ @JsxGetter public double getB() { - return fieldB_; + return affineTransform_.getShearY(); } /** @@ -80,7 +86,7 @@ */ @JsxGetter public double getC() { - return fieldC_; + return affineTransform_.getShearX(); } /** @@ -89,7 +95,7 @@ */ @JsxGetter public double getD() { - return fieldD_; + return affineTransform_.getScaleY(); } /** @@ -98,7 +104,7 @@ */ @JsxGetter public double getE() { - return fieldE_; + return affineTransform_.getTranslateX(); } /** @@ -107,7 +113,7 @@ */ @JsxGetter public double getF() { - return fieldF_; + return affineTransform_.getTranslateY(); } /** @@ -116,7 +122,13 @@ */ @JsxSetter public void setA(final double newValue) { - fieldA_ = newValue; + affineTransform_.setTransform( + newValue, + affineTransform_.getShearY(), + affineTransform_.getShearX(), + affineTransform_.getScaleY(), + affineTransform_.getTranslateX(), + affineTransform_.getTranslateY()); } /** @@ -125,7 +137,13 @@ */ @JsxSetter public void setB(final double newValue) { - fieldB_ = newValue; + affineTransform_.setTransform( + affineTransform_.getScaleX(), + newValue, + affineTransform_.getShearX(), + affineTransform_.getScaleY(), + affineTransform_.getTranslateX(), + affineTransform_.getTranslateY()); } /** @@ -134,7 +152,13 @@ */ @JsxSetter public void setC(final double newValue) { - fieldC_ = newValue; + affineTransform_.setTransform( + affineTransform_.getScaleX(), + affineTransform_.getShearY(), + newValue, + affineTransform_.getScaleY(), + affineTransform_.getTranslateX(), + affineTransform_.getTranslateY()); } /** @@ -143,7 +167,13 @@ */ @JsxSetter public void setD(final double newValue) { - fieldD_ = newValue; + affineTransform_.setTransform( + affineTransform_.getScaleX(), + affineTransform_.getShearY(), + affineTransform_.getShearX(), + newValue, + affineTransform_.getTranslateX(), + affineTransform_.getTranslateY()); } /** @@ -152,7 +182,13 @@ */ @JsxSetter public void setE(final double newValue) { - fieldE_ = newValue; + affineTransform_.setTransform( + affineTransform_.getScaleX(), + affineTransform_.getShearY(), + affineTransform_.getShearX(), + affineTransform_.getScaleY(), + newValue, + affineTransform_.getTranslateY()); } /** @@ -161,7 +197,13 @@ */ @JsxSetter public void setF(final double newValue) { - fieldF_ = newValue; + affineTransform_.setTransform( + affineTransform_.getScaleX(), + affineTransform_.getShearY(), + affineTransform_.getShearX(), + affineTransform_.getScaleY(), + affineTransform_.getTranslateX(), + newValue); } /** @@ -172,12 +214,9 @@ public SVGMatrix flipX() { final SVGMatrix result = new SVGMatrix(getWindow()); - result.setA(fieldA_ * -1); - result.setB(fieldB_ * -1); - result.setC(fieldC_); - result.setD(fieldD_); - result.setE(fieldE_); - result.setF(fieldF_); + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.concatenate(FLIP_X_TRANSFORM); + result.affineTransform_ = tr; return result; } @@ -190,12 +229,9 @@ public SVGMatrix flipY() { final SVGMatrix result = new SVGMatrix(getWindow()); - result.setA(fieldA_); - result.setB(fieldB_); - result.setC(fieldC_ * -1); - result.setD(fieldD_ * -1); - result.setE(fieldE_); - result.setF(fieldF_); + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.concatenate(FLIP_Y_TRANSFORM); + result.affineTransform_ = tr; return result; } @@ -206,7 +242,17 @@ */ @JsxFunction public SVGMatrix inverse() { - return new SVGMatrix(getWindow()); + try { + final SVGMatrix result = new SVGMatrix(getWindow()); + + result.affineTransform_ = affineTransform_.createInverse(); + + return result; + } + catch (final NoninvertibleTransformException e) { + throw ScriptRuntime.constructError("Error", + "Failed to execute 'inverse' on 'SVGMatrix': The matrix is not invertible."); + } } /** @@ -216,7 +262,13 @@ */ @JsxFunction public SVGMatrix multiply(final SVGMatrix by) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.concatenate(by.affineTransform_); + result.affineTransform_ = tr; + + return result; } /** @@ -226,7 +278,13 @@ */ @JsxFunction public SVGMatrix rotate(final double angle) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.rotate(Math.toRadians(angle)); + result.affineTransform_ = tr; + + return result; } /** @@ -237,7 +295,18 @@ */ @JsxFunction public SVGMatrix rotateFromVector(final double x, final double y) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + if (x == 0 || y == 0) { + throw ScriptRuntime.constructError("Error", + "Failed to execute 'rotateFromVector' on 'SVGMatrix': Arguments cannot be zero."); + } + + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.rotate(Math.atan2(y, x)); + result.affineTransform_ = tr; + + return result; } /** @@ -249,12 +318,9 @@ public SVGMatrix scale(final double factor) { final SVGMatrix result = new SVGMatrix(getWindow()); - result.setA(fieldA_ * factor); - result.setB(fieldB_ * factor); - result.setC(fieldC_ * factor); - result.setD(fieldD_ * factor); - result.setE(fieldE_); - result.setF(fieldF_); + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.scale(factor, factor); + result.affineTransform_ = tr; return result; } @@ -267,7 +333,13 @@ */ @JsxFunction public SVGMatrix scaleNonUniform(final double factorX, final double factorY) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.scale(factorX, factorY); + result.affineTransform_ = tr; + + return result; } /** @@ -277,7 +349,13 @@ */ @JsxFunction public SVGMatrix skewX(final double angle) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.concatenate(AffineTransform.getShearInstance(Math.tan(Math.toRadians(angle)), 0)); + result.affineTransform_ = tr; + + return result; } /** @@ -287,7 +365,13 @@ */ @JsxFunction public SVGMatrix skewY(final double angle) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.concatenate(AffineTransform.getShearInstance(0, Math.tan(Math.toRadians(angle)))); + result.affineTransform_ = tr; + + return result; } /** @@ -298,6 +382,12 @@ */ @JsxFunction public SVGMatrix translate(final double x, final double y) { - return new SVGMatrix(getWindow()); // TODO: this is wrong, compute it! + final SVGMatrix result = new SVGMatrix(getWindow()); + + final AffineTransform tr = (AffineTransform) affineTransform_.clone(); + tr.translate(x, y); + result.affineTransform_ = tr; + + return result; } } Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/svg/SvgMatrixTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/svg/SvgMatrixTest.java 2017-06-04 09:58:08 UTC (rev 14545) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/svg/SvgMatrixTest.java 2017-06-04 13:40:39 UTC (rev 14546) @@ -14,8 +14,15 @@ */ package com.gargoylesoftware.htmlunit.svg; +import static com.gargoylesoftware.htmlunit.BrowserRunner.TestedBrowser.IE; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.openqa.selenium.WebDriver; import com.gargoylesoftware.htmlunit.BrowserRunner; import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts; @@ -28,6 +35,7 @@ * * @author Marc Guillemot * @author Frank Danek + * @author Ronald Brill */ @RunWith(BrowserRunner.class) public class SvgMatrixTest extends WebDriverTestCase { @@ -142,7 +150,6 @@ */ @Test @Alerts("-2, 1, 1.5, -0.5, 1, -2") - @NotYetImplemented public void inverse() throws Exception { transformTest("inverse()"); } @@ -151,11 +158,184 @@ * @throws Exception if the test fails */ @Test + @Alerts("exception") + public void inverseNotPossible() throws Exception { + final String html = HtmlPageTest.STANDARDS_MODE_PREFIX_ + + "<html><body>\n" + + " <svg xmlns='http://www.w3.org/2000/svg' id='myId' version='1.1'>\n" + + " </svg>\n" + + "<script>\n" + + "function alertFields(m) {\n" + + " var fields = ['a', 'b', 'c', 'd', 'e', 'f'];\n" + + " for (var i = 0; i < fields.length; i++) {\n" + + " fields[i] = m[fields[i]];\n" + + " }\n" + + " alert(fields.join(', '));\n" + + "}\n" + + "var svg = document.getElementById('myId');\n" + + "try {\n" + + " var m = svg.createSVGMatrix();\n" + + " m.a = 1;\n" + + " m.b = 1;\n" + + " m.c = 1;\n" + + " m.d = 1;\n" + + " m.e = 5;\n" + + " m.f = 6;\n" + + " m = m.inverse();\n" + + " alertFields(m);\n" + + "} catch(e) { alert('exception'); }\n" + + "</script>\n" + + "</body></html>"; + + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("25, 38, 17, 26, 14, 20") + public void multiply() throws Exception { + final String html = HtmlPageTest.STANDARDS_MODE_PREFIX_ + + "<html><body>\n" + + " <svg xmlns='http://www.w3.org/2000/svg' id='myId' version='1.1'>\n" + + " </svg>\n" + + "<script>\n" + + "function alertFields(m) {\n" + + " var fields = ['a', 'b', 'c', 'd', 'e', 'f'];\n" + + " for (var i = 0; i < fields.length; i++) {\n" + + " fields[i] = m[fields[i]];\n" + + " }\n" + + " alert(fields.join(', '));\n" + + "}\n" + + "var svg = document.getElementById('myId');\n" + + "try {\n" + + " var m = svg.createSVGMatrix();\n" + + " m.a = 1;\n" + + " m.b = 2;\n" + + " m.c = 3;\n" + + " m.d = 4;\n" + + " m.e = 5;\n" + + " m.f = 6;\n" + + + " var n = svg.createSVGMatrix();\n" + + " n.a = 7;\n" + + " n.b = 6;\n" + + " n.c = 5;\n" + + " n.d = 4;\n" + + " n.e = 3;\n" + + " n.f = 2;\n" + + " m = m.multiply(n);\n" + + " alertFields(m);\n" + + "} catch(e) { alert('exception'); }\n" + + "</script>\n" + + "</body></html>"; + + loadPageWithAlerts2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "1.2322946786880493, 2.307671070098877, 2.912292957305908, 3.8307511806488037, 5, 6", + CHROME = "1.2322946209166628, 2.307671050377636, 2.912292905471539, 3.8307511434768218, 5, 6", + IE = "1.2322945594787597, 2.307671070098877, 2.912292718887329, 3.8307509422302246, 5, 6") + public void rotate() throws Exception { + transformTest("rotate(4.5)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "3.147735595703125, 4.346245765686035, -0.3029201924800873, -1.0536353588104248, 5, 6", + CHROME = "3.1477355949224934, 4.346245800520598, -0.302920161854466, -1.053635345580751, 5, 6", + IE = "3.147735595703125, 4.346245765686035, -0.30292022228240967, -1.0536353588104248, 5, 6") + public void rotateFromVector() throws Exception { + transformTest("rotateFromVector(17, 74)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "exception", + IE = "3, 4, -1, -2, 5, 6") + @NotYetImplemented(IE) + public void rotateFromVectorZeroX() throws Exception { + transformTest("rotateFromVector(0, 74)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "exception", + IE = "1, 2, 3, 4, 5, 6") + @NotYetImplemented(IE) + public void rotateFromVectorZeroY() throws Exception { + transformTest("rotateFromVector(17, 0)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("exception") + public void rotateFromVectorZeroXY() throws Exception { + transformTest("rotateFromVector(0, 0)"); + } + + /** + * @throws Exception if the test fails + */ + @Test @Alerts("3, 6, 9, 12, 5, 6") public void scale() throws Exception { transformTest("scale(3)"); } + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("7, 14, 21, 28, 5, 6") + public void scaleNonUniform() throws Exception { + transformTest("scale(7, 22)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "1, 2, 3.0699267387390137, 4.139853477478027, 5, 6", + CHROME = "1, 2, 3.0699268119435104, 4.139853623887021, 5, 6", + IE = "1, 2, 3.0699267387390136, 4.139853477478027, 5, 6") + public void skewX() throws Exception { + transformTest("skewX(4)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts(DEFAULT = "1.6926045417785645, 2.9234728813171387, 3, 4, 5, 6", + CHROME = "1.6926045733766895, 2.9234727645022525, 3, 4, 5, 6", + IE = "1.6926045417785644, 2.9234728813171386, 3, 4, 5, 6") + public void skewY() throws Exception { + transformTest("skewY(13)"); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("1, 2, 3, 4, 69, 100") + public void translate() throws Exception { + transformTest("translate(13 , 17)"); + } + private void transformTest(final String transforamtion) throws Exception { final String html = HtmlPageTest.STANDARDS_MODE_PREFIX_ + "<html><body>\n" @@ -184,6 +364,32 @@ + "</script>\n" + "</body></html>"; - loadPageWithAlerts2(html); + final String[] expectedAlerts = getExpectedAlerts(); + + final WebDriver driver = loadPage2(html, URL_FIRST); + final List<String> actualAlerts = getCollectedAlerts(DEFAULT_WAIT_TIME, driver, expectedAlerts.length); + + assertEquals(expectedAlerts.length, actualAlerts.size()); + if (useRealBrowser()) { + for (int i = expectedAlerts.length - 1; i >= 0; i--) { + assertEquals(expectedAlerts[i], actualAlerts.get(i)); + } + } + else { + for (int i = expectedAlerts.length - 1; i >= 0; i--) { + final String[] expected = StringUtils.split(expectedAlerts[i], ','); + final String[] actual = StringUtils.split(actualAlerts.get(i), ','); + + assertEquals(expected.length, actual.length); + for (int j = expected.length - 1; j >= 0; j--) { + try { + Assert.assertEquals(Double.parseDouble(expected[j]), Double.parseDouble(actual[j]), 0.000001); + } + catch (final NumberFormatException e) { + assertEquals(expected[j], actual[j]); + } + } + } + } } } |