diff --git a/NOTICE b/NOTICE
index fe6e2dcb1..0c6f1d534 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,6 +1,6 @@
EchoSVG
-Copyright 2020-2024 Contributors to the EchoSVG project
+Copyright 2020-2025 Contributors to the EchoSVG project
Copyright 1999-2020 The Apache Software Foundation
This product includes software developed at The Apache Software Foundation
@@ -23,3 +23,7 @@ This product includes images from the Pasodoble Icon Theme
This product's test suite includes images from the Canvg Project
(https://canvg.js.org/, https://github.com/canvg/canvg), which uses a MIT
license. See samples/canvg/LICENSE.
+
+This product's test suite includes images from the Web Platform Tests
+(https://github.com/web-platform-tests/wpt), which use a 3-Clause BSD License.
+See samples/wpt/LICENSE.
diff --git a/README.md b/README.md
index 1bc29b1cd..3bb195a00 100644
--- a/README.md
+++ b/README.md
@@ -230,20 +230,18 @@ repository in a `repositories` section of your build file:
```groovy
repositories {
maven {
- url "https://css4j.github.io/maven/"
+ url = "https://css4j.github.io/maven/"
mavenContent {
releasesOnly()
}
content {
- includeGroup 'io.sf.carte'
- includeGroup 'io.sf.jclf'
- includeGroup 'io.sf.graphics'
+ includeGroupByRegex 'io\\.sf\\..*'
}
}
}
```
-please use that repository **only** for the artifact groups that it supplies
-(basically those listed in the above `includeGroup` statements).
+please use that repository only for the artifact groups that it supplies
+(basically those listed in the above `includeGroupByRegex` statement).
Then, in your `build.gradle` file you can list the dependencies, for example:
```groovy
@@ -258,7 +256,7 @@ dependencies {
}
```
where `echosvgVersion` would be defined in a `gradle.properties` file (current
-version is `1.2.1`).
+version is `1.2.4`).
diff --git a/RELEASE_HOWTO.md b/RELEASE_HOWTO.md
index a0aa32d8c..44c6c16f4 100644
--- a/RELEASE_HOWTO.md
+++ b/RELEASE_HOWTO.md
@@ -47,19 +47,19 @@ cd /path/to/echosvg
5) For convenience, now copy all the produced _jar_ files into a new `jar`
directory and create a Zip archive of them. For example if you are releasing
-`1.2.1`:
+`1.2.2`:
```shell
./gradlew copyJars
-mv jar echosvg-1.2.1-bin
-7z a -mx7 echosvg-1.2.1-binaries.zip echosvg-1.2.1-bin
+mv jar echosvg-1.2.2-bin
+7z a -mx7 echosvg-1.2.2-binaries.zip echosvg-1.2.2-bin
```
6) Use `changes.sh ` to create a `CHANGES.txt` file for the new
version, with the changes from the latest tag:
```shell
-./changes.sh 1.2.1
+./changes.sh 1.2.2
```
Edit the resulting `CHANGES.txt` as convenient, to use it as the basis for the
@@ -99,9 +99,9 @@ archiver):
cd /path/to/echosvg
./gradlew modularJavadoc
cd echosvg-all/build/docs
-mv modular echosvg-1.2.1-modular-javadocs
-7z a echosvg-1.2.1-modular-javadocs.7z echosvg-1.2.1-modular-javadocs
-7z a -mx9 echosvg-1.2.1-modular-javadocs.zip echosvg-1.2.1-modular-javadocs
+mv modular echosvg-1.2.2-modular-javadocs
+7z a echosvg-1.2.2-modular-javadocs.7z echosvg-1.2.2-modular-javadocs
+7z a -mx9 echosvg-1.2.2-modular-javadocs.zip echosvg-1.2.2-modular-javadocs
```
The compressed archives will be part of the published release. Notice that the
@@ -114,8 +114,8 @@ Provided that you have the required credentials, you could update it via_ `rsync
```shell
cd /path/to/echosvg
-git tag -s v1.2.1 -m "Release 1.2.1"
-git push origin v1.2.1
+git tag -s v1.2.2 -m "Release 1.2.2"
+git push origin v1.2.2
```
or `git tag -a` instead of `-s` if you do not plan to sign the tag. But it is
@@ -130,9 +130,25 @@ Summarize the most important changes in the release description, then create a
`## Detail of changes` section and paste the contents of the `CHANGES.txt` file
under it.
-Add to the Github release the _jar_ files from the `jar` directory in your copy
-of the EchoSVG release code, and also the modular javadoc archives
-(`echosvg-1.2.1-modular-javadocs.7z` and `echosvg-1.2.1-modular-javadocs.zip`).
+Add to the Github release the `echosvg-1.2.2-bin.zip` archive that you created,
+the modular javadoc archives (`echosvg-1.2.2-modular-javadocs.7z` and
+`echosvg-1.2.2-modular-javadocs.zip`), and the result of executing:
+
+```shell
+./gradlew uberjar
+```
+to be found at the `echosvg-all/build/libs/echosvg-all-1.2.2-alldeps.jar`. Then execute:
+
+```shell
+./gradlew echosvg-svggen-jar-with-deps
+```
+and add the archive at `echosvg-svggen/build/libs/echosvg-svggen-1.2.2-with-deps.jar`.
+Finally run:
+
+```shell
+./gradlew echosvg-transcoder-jar-with-deps
+```
+to add the archive `echosvg-transcoder/build/libs/echosvg-transcoder-1.2.2-with-deps.jar`.
13) Verify that the new [Github packages](https://github.com/orgs/css4j/packages?repo_name=echosvg)
were created successfully by the [Gradle Package](https://github.com/css4j/echosvg/actions/workflows/gradle-publish.yml)
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 00fde6c7e..bff200de0 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -9,6 +9,6 @@ repositories {
}
dependencies {
- implementation('org.owasp:dependency-check-gradle:10.0.3')
- implementation('com.github.jk1:gradle-license-report:2.8')
+ implementation('org.owasp:dependency-check-gradle:12.0.1')
+ implementation('com.github.jk1:gradle-license-report:2.9')
}
diff --git a/buildSrc/src/main/groovy/echosvg.java-conventions.gradle b/buildSrc/src/main/groovy/echosvg.java-conventions.gradle
index 7fd718944..8aefff310 100644
--- a/buildSrc/src/main/groovy/echosvg.java-conventions.gradle
+++ b/buildSrc/src/main/groovy/echosvg.java-conventions.gradle
@@ -16,15 +16,13 @@ repositories {
releasesOnly()
}
content {
- includeGroup 'io.sf.carte'
- includeGroup 'io.sf.graphics'
- includeGroup 'io.sf.jclf'
+ includeGroupByRegex 'io\\.sf\\..*'
}
}
}
group = 'io.sf.carte'
-version = '1.2.1'
+version = '1.2.4'
java {
withSourcesJar()
@@ -98,14 +96,10 @@ tasks.register("${project.name}-jar-with-deps", Jar) {
archiveClassifier = 'with-deps'
- dependsOn configurations.compileClasspath
dependsOn configurations.runtimeClasspath
doFirst {
from sourceSets.main.output
- from {
- configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
- }
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}
diff --git a/changes.sh b/changes.sh
index b3baee995..fbaf415ab 100755
--- a/changes.sh
+++ b/changes.sh
@@ -5,10 +5,10 @@
# You'll probably want to edit manually the result of executing the script.
#
if [[ $# -eq 0 ]] ; then
- echo "No version supplied (e.g. '0.1.2')"
+ echo "No version supplied (e.g. '1.2.4')"
exit 1
fi
-OLDTAG=`git tag -l --merged master --sort=-taggerdate|head -1`
+OLDTAG=`git tag -l --merged 1-stable --sort=-taggerdate|head -1`
echo "Writing changes from tag $OLDTAG"
TITLE="EchoSVG CHANGES"
VERHDR="Version ${1}"
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGAnimatedLength.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGAnimatedLength.java
index 04a24fe1d..8c9b55e22 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGAnimatedLength.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGAnimatedLength.java
@@ -301,9 +301,9 @@ protected void revalidate() {
Attr attr = element.getAttributeNodeNS(namespaceURI, localName);
String s;
if (attr == null) {
+ missing = true;
s = getDefaultValue();
if (s == null) {
- missing = true;
return;
}
} else {
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGLengthList.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGLengthList.java
index 666acc1de..f738cd014 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGLengthList.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/AbstractSVGLengthList.java
@@ -161,6 +161,13 @@ protected void checkItemType(Object newItem) throws SVGException {
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ AbstractSVGLengthList other = (AbstractSVGLengthList) list;
+ other.direction = direction;
+ }
+
/**
* An {@link SVGLength} in the list.
*/
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGDOMImplementation.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGDOMImplementation.java
index e473c6e75..f6748e764 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGDOMImplementation.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGDOMImplementation.java
@@ -48,6 +48,7 @@
import io.sf.carte.echosvg.dom.AbstractDocument;
import io.sf.carte.echosvg.dom.AbstractStylableDocument;
import io.sf.carte.echosvg.dom.ExtensibleDOMImplementation;
+import io.sf.carte.echosvg.dom.GenericElement;
import io.sf.carte.echosvg.dom.events.DOMTimeEvent;
import io.sf.carte.echosvg.dom.events.DocumentEventSupport;
import io.sf.carte.echosvg.dom.svg.SVGOMEvent;
@@ -215,13 +216,27 @@ public CSSStyleSheet getUserAgentStyleSheet() {
*/
@Override
public Element createElementNS(AbstractDocument document, String namespaceURI, String qualifiedName) {
+ if (namespaceURI == null)
+ return new GenericElement(qualifiedName.intern(), document);
+
+ String name = DOMUtilities.getLocalName(qualifiedName);
+
if (SVGConstants.SVG_NAMESPACE_URI.equals(namespaceURI)) {
- String name = DOMUtilities.getLocalName(qualifiedName);
ElementFactory ef = factories.get(name);
if (ef != null)
return ef.create(DOMUtilities.getPrefix(qualifiedName), document);
- throw document.createDOMException(DOMException.NOT_FOUND_ERR, "invalid.element",
- new Object[] { namespaceURI, qualifiedName });
+ // Typically a div inside SVG
+ if ("div".equals(name)) {
+ namespaceURI = "http://www.w3.org/1999/xhtml";
+ }
+ }
+ if (customFactories != null) {
+ ElementFactory cef;
+ cef = (ElementFactory) customFactories.get(namespaceURI, name);
+ if (cef != null) {
+ String prefix = DOMUtilities.getPrefix(qualifiedName);
+ return cef.create(prefix, document);
+ }
}
return super.createElementNS(document, namespaceURI, qualifiedName);
@@ -1516,6 +1531,27 @@ public Element create(String prefix, Document doc) {
return new SVGOMPathElement(prefix, (AbstractDocument) doc);
}
+ @Override
+ public void importAttributes(Element imported, Node toImport, boolean trimId) {
+ ElementFactory.super.importAttributes(imported, toImport, trimId);
+ if (toImport instanceof SVGOMPathElement) {
+ SVGOMPathElement path = (SVGOMPathElement) imported;
+ SVGOMPathElement otherPath = (SVGOMPathElement) toImport;
+ SVGOMAnimatedPathData d = path.d;
+ // Make sure pathSegs exists
+ otherPath.d.getPathSegList();
+ boolean wasValid = otherPath.d.pathSegs.isValid();
+ if (otherPath.d.check() <= 0) {
+ // Copy only if check was successful, so the errors will be reported
+ d.getPathSegList();
+ otherPath.d.pathSegs.copyTo(d.pathSegs);
+ } else if (!wasValid) {
+ // invalidate, so the errors can be reported
+ otherPath.d.pathSegs.invalidate();
+ }
+ }
+ }
+
}
/**
@@ -1552,6 +1588,26 @@ public Element create(String prefix, Document doc) {
return new SVGOMPolygonElement(prefix, (AbstractDocument) doc);
}
+ @Override
+ public void importAttributes(Element imported, Node toImport, boolean trimId) {
+ ElementFactory.super.importAttributes(imported, toImport, trimId);
+ if (toImport instanceof SVGPointShapeElement) {
+ SVGPointShapeElement poly = (SVGPointShapeElement) imported;
+ SVGPointShapeElement otherPoly = (SVGPointShapeElement) toImport;
+ SVGOMAnimatedPoints points = poly.points;
+ otherPoly.points.getPoints();
+ boolean wasValid = otherPoly.points.baseVal.isValid();
+ if (otherPoly.points.check() <= 0) {
+ // Copy only if check was successful, so the errors will be reported
+ points.getPoints();
+ otherPoly.points.baseVal.copyTo(points.baseVal);
+ } else if (!wasValid) {
+ // invalidate, so the errors can be reported
+ otherPoly.points.baseVal.invalidate();
+ }
+ }
+ }
+
}
/**
@@ -1570,6 +1626,26 @@ public Element create(String prefix, Document doc) {
return new SVGOMPolylineElement(prefix, (AbstractDocument) doc);
}
+ @Override
+ public void importAttributes(Element imported, Node toImport, boolean trimId) {
+ ElementFactory.super.importAttributes(imported, toImport, trimId);
+ if (toImport instanceof SVGPointShapeElement) {
+ SVGPointShapeElement poly = (SVGPointShapeElement) imported;
+ SVGPointShapeElement otherPoly = (SVGPointShapeElement) toImport;
+ SVGOMAnimatedPoints points = poly.points;
+ otherPoly.points.getPoints();
+ boolean wasValid = otherPoly.points.baseVal.isValid();
+ if (otherPoly.points.check() <= 0) {
+ // Copy only if check was successful, so the errors will be reported
+ points.getPoints();
+ otherPoly.points.baseVal.copyTo(points.baseVal);
+ } else if (!wasValid) {
+ // invalidate, so the errors can be reported
+ otherPoly.points.baseVal.invalidate();
+ }
+ }
+ }
+
}
/**
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedLengthList.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedLengthList.java
index c3cf67e26..98d944aac 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedLengthList.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedLengthList.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
@@ -31,6 +32,7 @@
import io.sf.carte.echosvg.anim.values.AnimatableLengthListValue;
import io.sf.carte.echosvg.anim.values.AnimatableValue;
+import io.sf.carte.echosvg.dom.svg.AbstractSVGList;
import io.sf.carte.echosvg.dom.svg.ListBuilder;
import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
import io.sf.carte.echosvg.dom.svg.SVGItem;
@@ -322,31 +324,41 @@ protected void revalidate() {
malformed = false;
String s = getValueAsString();
- boolean isEmpty = s != null && s.length() == 0;
+ boolean isEmpty = s != null && (s = s.trim()).isEmpty();
if (s == null || isEmpty && !emptyAllowed) {
missing = true;
+ clear(itemList);
+ itemList = new ArrayList<>(1);
return;
}
if (isEmpty) {
itemList = new ArrayList<>(1);
} else {
+ ListBuilder builder = new ListBuilder(this);
try {
- ListBuilder builder = new ListBuilder(this);
-
doParse(s, builder);
-
- if (builder.getList() != null) {
- clear(itemList);
- }
- itemList = builder.getList();
} catch (ParseException e) {
- itemList = new ArrayList<>(1);
- valid = true;
malformed = true;
+ } finally {
+ clear(itemList);
+ List parsedList = builder.getList();
+ if (parsedList != null) {
+ itemList = parsedList;
+ } else {
+ itemList = new ArrayList<>(1);
+ }
}
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ BaseSVGLengthList other = (BaseSVGLengthList) list;
+ other.malformed = malformed;
+ other.missing = missing;
+ }
+
}
/**
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedNumberList.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedNumberList.java
index 0588c058a..a07dd474b 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedNumberList.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedNumberList.java
@@ -29,8 +29,10 @@
import org.w3c.dom.svg.SVGNumber;
import org.w3c.dom.svg.SVGNumberList;
+import io.sf.carte.echosvg.anim.dom.SVGOMAnimatedLengthList.BaseSVGLengthList;
import io.sf.carte.echosvg.anim.values.AnimatableNumberListValue;
import io.sf.carte.echosvg.anim.values.AnimatableValue;
+import io.sf.carte.echosvg.dom.svg.AbstractSVGList;
import io.sf.carte.echosvg.dom.svg.AbstractSVGNumberList;
import io.sf.carte.echosvg.dom.svg.ListBuilder;
import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
@@ -331,6 +333,14 @@ protected void revalidate() {
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ BaseSVGLengthList other = (BaseSVGLengthList) list;
+ other.malformed = malformed;
+ other.missing = missing;
+ }
+
}
/**
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPathData.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPathData.java
index ab54191af..66fefa0ca 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPathData.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPathData.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
@@ -30,6 +31,7 @@
import io.sf.carte.echosvg.anim.values.AnimatablePathDataValue;
import io.sf.carte.echosvg.anim.values.AnimatableValue;
+import io.sf.carte.echosvg.dom.svg.AbstractSVGList;
import io.sf.carte.echosvg.dom.svg.AbstractSVGNormPathSegList;
import io.sf.carte.echosvg.dom.svg.AbstractSVGPathSegList;
import io.sf.carte.echosvg.dom.svg.ListBuilder;
@@ -39,6 +41,7 @@
import io.sf.carte.echosvg.dom.svg.SVGPathSegItem;
import io.sf.carte.echosvg.parser.ParseException;
import io.sf.carte.echosvg.parser.PathArrayProducer;
+import io.sf.carte.echosvg.util.CSSConstants;
/**
* This class is the implementation of the {@link SVGAnimatedPathData}
@@ -160,23 +163,31 @@ public SVGPathSegList getPathSegList() {
}
/**
- * Throws an exception if the path data is malformed.
+ * Check whether the attribute is missing, empty or malformed.
+ *
+ * @return -1 if the value is present and is valid,
+ * LiveAttributeException.ERR_ATTRIBUTE_MISSING if the attribute is
+ * missing or {@code none}, ERR_ATTRIBUTE_MALFORMED if it is invalid,
+ * -2 if it is invalid but that was already returned.
*/
- public void check() {
+ public short check() {
if (!hasAnimVal) {
if (pathSegs == null) {
pathSegs = new BaseSVGPathSegList();
}
+
+ boolean wasValid = pathSegs.isValid();
+
pathSegs.revalidate();
- if (pathSegs.missing) {
- throw new LiveAttributeException(element, localName, LiveAttributeException.ERR_ATTRIBUTE_MISSING,
- null);
+
+ if (pathSegs.none) {
+ return LiveAttributeException.ERR_ATTRIBUTE_MISSING;
}
if (pathSegs.malformed) {
- throw new LiveAttributeException(element, localName, LiveAttributeException.ERR_ATTRIBUTE_MALFORMED,
- pathSegs.getValueAsString());
+ return wasValid ? -2 : LiveAttributeException.ERR_ATTRIBUTE_MALFORMED;
}
}
+ return -1;
}
/**
@@ -271,15 +282,19 @@ public void attrRemoved(Attr node, String oldv) {
public class BaseSVGPathSegList extends AbstractSVGPathSegList {
/**
- * Whether the attribute is missing.
+ * Whether the attribute is missing or 'none'.
*/
- protected boolean missing;
+ protected boolean none;
/**
* Whether the attribute is malformed.
*/
protected boolean malformed;
+ boolean isValid() {
+ return valid;
+ }
+
/**
* Create a DOMException.
*/
@@ -327,7 +342,7 @@ protected void setAttributeValue(String value) {
@Override
protected void resetAttribute() {
super.resetAttribute();
- missing = false;
+ none = false;
malformed = false;
}
@@ -338,7 +353,7 @@ protected void resetAttribute() {
@Override
protected void resetAttribute(SVGItem item) {
super.resetAttribute(item);
- missing = false;
+ none = false;
malformed = false;
}
@@ -352,29 +367,41 @@ protected void revalidate() {
}
valid = true;
- missing = false;
+ none = false;
malformed = false;
- String s = getValueAsString();
- if (s == null) {
- missing = true;
+ String s = getValueAsString().trim(); // Default is 'none', not null
+ if (CSSConstants.CSS_NONE_VALUE.equalsIgnoreCase(s)) {
+ none = true;
+ clear(itemList);
+ itemList = new ArrayList<>(1);
return;
}
- try {
- ListBuilder builder = new ListBuilder(this);
+ ListBuilder builder = new ListBuilder(this);
+ try {
doParse(s, builder);
-
- if (builder.getList() != null) {
- clear(itemList);
- }
- itemList = builder.getList();
} catch (ParseException e) {
- itemList = new ArrayList<>(1);
malformed = true;
+ } finally {
+ clear(itemList);
+ List parsedList = builder.getList();
+ if (parsedList != null) {
+ itemList = parsedList;
+ } else {
+ itemList = new ArrayList<>(1);
+ }
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ BaseSVGPathSegList b = (BaseSVGPathSegList) list;
+ b.malformed = malformed;
+ b.none = none;
+ }
+
}
/**
@@ -384,9 +411,9 @@ protected void revalidate() {
public class NormalizedBaseSVGPathSegList extends AbstractSVGNormPathSegList {
/**
- * Whether the attribute is missing.
+ * Whether the attribute is missing or 'none'.
*/
- protected boolean missing;
+ protected boolean none;
/**
* Whether the attribute is malformed.
@@ -444,29 +471,41 @@ protected void revalidate() {
}
valid = true;
- missing = false;
+ none = false;
malformed = false;
- String s = getValueAsString();
- if (s == null) {
- missing = true;
+ String s = getValueAsString().trim(); // Default is 'none', not null
+ if (CSSConstants.CSS_NONE_VALUE.equalsIgnoreCase(s)) {
+ none = true;
+ clear(itemList);
+ itemList = new ArrayList<>(1);
return;
}
- try {
- ListBuilder builder = new ListBuilder(this);
+ ListBuilder builder = new ListBuilder(this);
+ try {
doParse(s, builder);
-
- if (builder.getList() != null) {
- clear(itemList);
- }
- itemList = builder.getList();
} catch (ParseException e) {
- itemList = new ArrayList<>(1);
malformed = true;
+ } finally {
+ clear(itemList);
+ List parsedList = builder.getList();
+ if (parsedList != null) {
+ itemList = parsedList;
+ } else {
+ itemList = new ArrayList<>(1);
+ }
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ NormalizedBaseSVGPathSegList b = (NormalizedBaseSVGPathSegList) list;
+ b.malformed = malformed;
+ b.none = none;
+ }
+
}
/**
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPoints.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPoints.java
index 032a2a99a..aeb557f27 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPoints.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedPoints.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
@@ -30,6 +31,7 @@
import io.sf.carte.echosvg.anim.values.AnimatablePointListValue;
import io.sf.carte.echosvg.anim.values.AnimatableValue;
+import io.sf.carte.echosvg.dom.svg.AbstractSVGList;
import io.sf.carte.echosvg.dom.svg.AbstractSVGPointList;
import io.sf.carte.echosvg.dom.svg.ListBuilder;
import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
@@ -40,8 +42,10 @@
/**
* This class is the implementation of the SVGAnimatedPoints interface.
*
- * @author Nicolas Socheleau
- * @author For later modifications, see Git history.
+ *
+ * Original author: Nicolas Socheleau.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public class SVGOMAnimatedPoints extends AbstractSVGAnimatedValue implements SVGAnimatedPoints {
@@ -102,23 +106,28 @@ public SVGPointList getAnimatedPoints() {
}
/**
- * Throws an exception if the points list value is malformed.
+ * Checks whether the points list value is malformed.
+ *
+ * @return LiveAttributeException.ERR_ATTRIBUTE_MISSING if the points list value is missing,
+ * ERR_ATTRIBUTE_MALFORMED if is invalid, or {@code -1}
+ * otherwise.
*/
- public void check() {
+ public short check() {
if (!hasAnimVal) {
if (baseVal == null) {
baseVal = new BaseSVGPointList();
}
+
baseVal.revalidate();
- if (baseVal.missing) {
- throw new LiveAttributeException(element, localName, LiveAttributeException.ERR_ATTRIBUTE_MISSING,
- null);
+
+ if (baseVal.none) {
+ return LiveAttributeException.ERR_ATTRIBUTE_MISSING;
}
if (baseVal.malformed) {
- throw new LiveAttributeException(element, localName, LiveAttributeException.ERR_ATTRIBUTE_MALFORMED,
- baseVal.getValueAsString());
+ return LiveAttributeException.ERR_ATTRIBUTE_MALFORMED;
}
}
+ return -1;
}
/**
@@ -203,15 +212,19 @@ public void attrRemoved(Attr node, String oldv) {
protected class BaseSVGPointList extends AbstractSVGPointList {
/**
- * Whether the attribute is missing.
+ * Whether the attribute is missing or no value.
*/
- protected boolean missing;
+ protected boolean none;
/**
* Whether the attribute is malformed.
*/
protected boolean malformed;
+ boolean isValid() {
+ return valid;
+ }
+
/**
* Create a DOMException.
*/
@@ -260,7 +273,7 @@ protected void setAttributeValue(String value) {
@Override
protected void resetAttribute() {
super.resetAttribute();
- missing = false;
+ none = false;
malformed = false;
}
@@ -271,7 +284,7 @@ protected void resetAttribute() {
@Override
protected void resetAttribute(SVGItem item) {
super.resetAttribute(item);
- missing = false;
+ none = false;
malformed = false;
}
@@ -285,29 +298,41 @@ protected void revalidate() {
}
valid = true;
- missing = false;
+ none = false;
malformed = false;
- String s = getValueAsString();
- if (s == null) {
- missing = true;
+ String s = getValueAsString().trim(); // Default is ''
+ if (s.isEmpty()) {
+ none = true;
+ clear(itemList);
+ itemList = new ArrayList<>(1);
return;
}
- try {
- ListBuilder builder = new ListBuilder(this);
+ ListBuilder builder = new ListBuilder(this);
+ try {
doParse(s, builder);
-
- if (builder.getList() != null) {
- clear(itemList);
- }
- itemList = builder.getList();
} catch (ParseException e) {
- itemList = new ArrayList<>(1);
malformed = true;
+ } finally {
+ clear(itemList);
+ List parsedList = builder.getList();
+ if (parsedList != null) {
+ itemList = parsedList;
+ } else {
+ itemList = new ArrayList<>(1);
+ }
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ BaseSVGPointList other = (BaseSVGPointList) list;
+ other.malformed = malformed;
+ other.none = none;
+ }
+
}
/**
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedRect.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedRect.java
index 1705662f6..c9679e4ec 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedRect.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedRect.java
@@ -23,12 +23,12 @@
import org.w3c.dom.svg.SVGAnimatedRect;
import org.w3c.dom.svg.SVGRect;
+import io.sf.carte.doc.style.css.CSSExpressionValue;
+import io.sf.carte.doc.style.css.CSSTypedValue;
import io.sf.carte.doc.style.css.CSSUnit;
import io.sf.carte.doc.style.css.CSSValue.CssType;
import io.sf.carte.doc.style.css.property.Evaluator;
-import io.sf.carte.doc.style.css.property.ExpressionValue;
import io.sf.carte.doc.style.css.property.StyleValue;
-import io.sf.carte.doc.style.css.property.TypedValue;
import io.sf.carte.doc.style.css.property.ValueFactory;
import io.sf.carte.doc.style.css.property.ValueList;
import io.sf.carte.echosvg.anim.values.AnimatableRectValue;
@@ -286,7 +286,7 @@ private boolean computeRectangle(StyleValue value, float[] numbers) throws DOMEx
if (item.getCssValueType() != CssType.TYPED) {
return false;
}
- TypedValue typed = (TypedValue) item;
+ CSSTypedValue typed = (CSSTypedValue) item;
switch (item.getPrimitiveType()) {
case NUMERIC:
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
@@ -294,8 +294,8 @@ private boolean computeRectangle(StyleValue value, float[] numbers) throws DOMEx
}
break;
case EXPRESSION:
- Evaluator eval = new Evaluator();
- typed = eval.evaluateExpression((ExpressionValue) typed);
+ Evaluator eval = new Evaluator(CSSUnit.CSS_NUMBER);
+ typed = eval.evaluateExpression((CSSExpressionValue) typed);
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedTransformList.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedTransformList.java
index b74ecd4cf..5a97d8abf 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedTransformList.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMAnimatedTransformList.java
@@ -29,8 +29,10 @@
import org.w3c.dom.svg.SVGTransform;
import org.w3c.dom.svg.SVGTransformList;
+import io.sf.carte.echosvg.anim.dom.SVGOMAnimatedLengthList.BaseSVGLengthList;
import io.sf.carte.echosvg.anim.values.AnimatableTransformListValue;
import io.sf.carte.echosvg.anim.values.AnimatableValue;
+import io.sf.carte.echosvg.dom.svg.AbstractSVGList;
import io.sf.carte.echosvg.dom.svg.AbstractSVGTransformList;
import io.sf.carte.echosvg.dom.svg.ListBuilder;
import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
@@ -306,6 +308,14 @@ protected void revalidate() {
}
}
+ @Override
+ public void copyTo(AbstractSVGList list) {
+ super.copyTo(list);
+ BaseSVGLengthList other = (BaseSVGLengthList) list;
+ other.malformed = malformed;
+ other.missing = missing;
+ }
+
}
/**
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMDocument.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMDocument.java
index e2209668e..04fd8ad74 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMDocument.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMDocument.java
@@ -54,6 +54,7 @@
import io.sf.carte.echosvg.css.engine.CSSNavigableDocumentListener;
import io.sf.carte.echosvg.css.engine.CSSStylableElement;
import io.sf.carte.echosvg.dom.AbstractStylableDocument;
+import io.sf.carte.echosvg.dom.ExtensibleDOMImplementation.ElementFactory;
import io.sf.carte.echosvg.dom.GenericAttr;
import io.sf.carte.echosvg.dom.GenericAttrNS;
import io.sf.carte.echosvg.dom.GenericCDATASection;
@@ -67,6 +68,7 @@
import io.sf.carte.echosvg.dom.events.EventSupport;
import io.sf.carte.echosvg.dom.svg.IdContainer;
import io.sf.carte.echosvg.dom.svg.SVGContext;
+import io.sf.carte.echosvg.dom.util.DOMUtilities;
import io.sf.carte.echosvg.dom.util.XMLSupport;
import io.sf.carte.echosvg.i18n.Localizable;
import io.sf.carte.echosvg.i18n.LocalizableSupport;
@@ -367,6 +369,28 @@ public Element createElementNS(String namespaceURI, String qualifiedName) throws
return impl.createElementNS(this, namespaceURI, qualifiedName);
}
+ @Override
+ protected Element importElement(Node importMe, boolean trimId) {
+ SVGDOMImplementation impl = (SVGDOMImplementation) implementation;
+ String namespaceURI = importMe.getNamespaceURI();
+ if (SVGConstants.SVG_NAMESPACE_URI.equals(namespaceURI)) {
+ String qualifiedName = importMe.getNodeName();
+ String name = DOMUtilities.getLocalName(qualifiedName);
+ ElementFactory ef = impl.factories.get(name);
+ if (ef != null) {
+ Element e = ef.create(name, this);
+ ef.importAttributes(e, importMe, trimId);
+ return e;
+ }
+ // Typically a div inside SVG
+ if ("div".equals(name)) {
+ namespaceURI = "http://www.w3.org/1999/xhtml";
+ }
+ }
+
+ return super.importElement(importMe, trimId);
+ }
+
/**
* Returns whether the document supports SVG 1.2.
*/
diff --git a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMPathElement.java b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMPathElement.java
index 55e33d9d6..75696ea13 100644
--- a/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMPathElement.java
+++ b/echosvg-anim/src/main/java/io/sf/carte/echosvg/anim/dom/SVGOMPathElement.java
@@ -105,7 +105,7 @@ protected void initializeAllLiveAttributes() {
* Initializes the live attribute values of this element.
*/
private void initializeLiveAttributes() {
- d = createLiveAnimatedPathData(null, SVG_D_ATTRIBUTE, "");
+ d = createLiveAnimatedPathData(null, SVG_D_ATTRIBUTE, "none");
}
/**
diff --git a/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/image/rendered/TurbulencePatternRed.java b/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/image/rendered/TurbulencePatternRed.java
index eb45150ce..604a5b05d 100644
--- a/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/image/rendered/TurbulencePatternRed.java
+++ b/echosvg-awt-util/src/main/java/io/sf/carte/echosvg/ext/awt/image/rendered/TurbulencePatternRed.java
@@ -261,10 +261,12 @@ private void initLattice(int seed) {
for (k = 0; k < 4; k++) {
for (i = 0; i < BSize; i++) {
- u = (((seed = random(seed)) % (BSize + BSize)) - BSize);
- v = (((seed = random(seed)) % (BSize + BSize)) - BSize);
+ do {
+ u = (((seed = random(seed)) % (BSize + BSize)) - BSize);
+ v = (((seed = random(seed)) % (BSize + BSize)) - BSize);
+ } while (u == 0.0 && v == 0.0);
- s = 1 / Math.sqrt(u * u + v * v);
+ s = 1d / Math.sqrt(u * u + v * v);
gradient[i * 8 + k * 2] = u * s;
gradient[i * 8 + k * 2 + 1] = v * s;
}
diff --git a/echosvg-bridge/build.gradle b/echosvg-bridge/build.gradle
index 9a3113cf3..95c06eee5 100644
--- a/echosvg-bridge/build.gradle
+++ b/echosvg-bridge/build.gradle
@@ -8,6 +8,12 @@ dependencies {
api project(':echosvg-script')
api "io.sf.graphics:legacy-colors:${legacyColorsVersion}"
compileOnly "xml-apis:xml-apis:$xmlApisVersion" // Required by Java 8 compat
+ testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}"
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
+test {
+ useJUnitPlatform()
}
description = 'io.sf.carte:echosvg-bridge'
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractGraphicsNodeBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractGraphicsNodeBridge.java
index d8b3878d3..813cf3b45 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractGraphicsNodeBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractGraphicsNodeBridge.java
@@ -164,11 +164,7 @@ float safeAnimatedLength(AbstractSVGAnimatedLength animValue) throws BridgeExcep
try {
value = animValue.getCheckedValue();
} catch (LiveAttributeException ex) {
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
value = animValue.getDefault();
}
return value;
@@ -186,11 +182,7 @@ float safeAnimatedLength(AbstractSVGAnimatedLength animValue, float defValue) th
try {
value = animValue.getCheckedValue();
} catch (LiveAttributeException ex) {
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
value = defValue;
}
return value;
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractSVGBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractSVGBridge.java
index 07525224c..e8fbb20db 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractSVGBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/AbstractSVGBridge.java
@@ -18,13 +18,16 @@
*/
package io.sf.carte.echosvg.bridge;
+import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
import io.sf.carte.echosvg.util.SVGConstants;
/**
* The base bridge class for SVG elements.
*
- * @author Thierry Kormann
- * @author For later modifications, see Git history.
+ *
+ * Original author: Thierry Kormann.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public abstract class AbstractSVGBridge implements Bridge, SVGConstants {
@@ -53,4 +56,21 @@ public Bridge getInstance() {
return this;
}
+ static void reportLiveAttributeException(BridgeContext ctx, LiveAttributeException ex)
+ throws RuntimeException {
+ if (ex.getCode() != LiveAttributeException.ERR_ATTRIBUTE_MISSING) {
+ BridgeException be = new BridgeException(ctx, ex);
+ displayErrorOrThrow(ctx, be);
+ }
+
+ }
+
+ static void displayErrorOrThrow(BridgeContext ctx, RuntimeException ex) throws RuntimeException {
+ if (ctx.userAgent != null) {
+ ctx.userAgent.displayError(ex);
+ } else {
+ throw ex;
+ }
+ }
+
}
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGCircleElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGCircleElementBridge.java
index 582e089ba..ce14a7023 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGCircleElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGCircleElementBridge.java
@@ -72,24 +72,24 @@ public Bridge getInstance() {
*/
@Override
protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode) {
- try {
- SVGOMCircleElement ce = (SVGOMCircleElement) e;
+ SVGOMCircleElement ce = (SVGOMCircleElement) e;
- // 'cx' attribute - default is 0
- AbstractSVGAnimatedLength _cx = (AbstractSVGAnimatedLength) ce.getCx();
- float cx = safeAnimatedLength(_cx, 0f);
+ // 'cx' attribute - default is 0
+ AbstractSVGAnimatedLength _cx = (AbstractSVGAnimatedLength) ce.getCx();
+ float cx = safeAnimatedLength(_cx, 0f);
- // 'cy' attribute - default is 0
- AbstractSVGAnimatedLength _cy = (AbstractSVGAnimatedLength) ce.getCy();
- float cy = safeAnimatedLength(_cy, 0f);
+ // 'cy' attribute - default is 0
+ AbstractSVGAnimatedLength _cy = (AbstractSVGAnimatedLength) ce.getCy();
+ float cy = safeAnimatedLength(_cy, 0f);
- // 'r' attribute - default is 0 (SVG2)
- AbstractSVGAnimatedLength _r = (AbstractSVGAnimatedLength) ce.getR();
- float r = safeAnimatedLength(_r, 0f);
+ // 'r' attribute - default is 0 (SVG2)
+ AbstractSVGAnimatedLength _r = (AbstractSVGAnimatedLength) ce.getR();
+ float r = safeAnimatedLength(_r, 0f);
- float x = cx - r;
- float y = cy - r;
- float w = r * 2f;
+ float x = cx - r;
+ float y = cy - r;
+ float w = r * 2f;
+ try {
shapeNode.setShape(new Ellipse2D.Float(x, y, w, w));
} catch (LiveAttributeException ex) {
throw new BridgeException(ctx, ex);
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGColorProfileElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGColorProfileElementBridge.java
index 23a22bfd8..a9dbba029 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGColorProfileElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGColorProfileElementBridge.java
@@ -21,8 +21,6 @@
import java.awt.color.ICC_Profile;
import java.io.IOException;
-import io.sf.graphics.java2d.color.ICCColorSpaceWithIntent;
-import io.sf.graphics.java2d.color.RenderingIntent;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -31,6 +29,8 @@
import io.sf.carte.echosvg.dom.util.XLinkSupport;
import io.sf.carte.echosvg.ext.awt.color.NamedProfileCache;
import io.sf.carte.echosvg.util.ParsedURL;
+import io.sf.graphics.java2d.color.ICCColorSpaceWithIntent;
+import io.sf.graphics.java2d.color.RenderingIntent;
/**
* This class bridges an SVG color-profile element with an
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGEllipseElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGEllipseElementBridge.java
index b954ae635..583234d43 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGEllipseElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGEllipseElementBridge.java
@@ -92,11 +92,7 @@ protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode)
} catch (LiveAttributeException ex) {
rx = 0f;
rxAuto = true;
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
}
// 'ry' attribute - default is auto (SVG2)
@@ -106,11 +102,7 @@ protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode)
ry = _ry.getCheckedValue();
} catch (LiveAttributeException ex) {
ry = rx;
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
}
// Check whether rx was auto
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGImageElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGImageElementBridge.java
index 5f20399fa..d122b1155 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGImageElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGImageElementBridge.java
@@ -31,8 +31,6 @@
import java.util.ArrayList;
import java.util.List;
-import io.sf.graphics.java2d.color.ICCColorSpaceWithIntent;
-import io.sf.graphics.java2d.color.RenderingIntent;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.DocumentEvent;
@@ -68,6 +66,8 @@
import io.sf.carte.echosvg.util.HaltingThread;
import io.sf.carte.echosvg.util.MimeTypeConstants;
import io.sf.carte.echosvg.util.ParsedURL;
+import io.sf.graphics.java2d.color.ICCColorSpaceWithIntent;
+import io.sf.graphics.java2d.color.RenderingIntent;
/**
* Bridge class for the <image> element.
@@ -133,10 +133,7 @@ public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
if (!uriStr.isEmpty() && uriStr.indexOf('#') == -1) {
BridgeException be = new BridgeException(ctx, e, ERR_URI_IMAGE_INVALID,
new Object[] { uriStr });
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ displayErrorOrThrow(ctx, be);
}
return null;
}
@@ -510,8 +507,11 @@ public void handleAnimatedAttributeChanged(AnimatedLiveAttributeValue alav) thro
} else if (ln.equals(SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE)) {
updateImageBounds();
return;
+ } else if (ln.equals(XLINK_HREF_ATTRIBUTE)) {
+ rebuildImageNode();
+ return;
}
- } else if (ns.equals(XLINK_NAMESPACE_URI) && ln.equals(XLINK_HREF_ATTRIBUTE)) {
+ } else if (ln.equals(XLINK_HREF_ATTRIBUTE)) {
rebuildImageNode();
return;
}
@@ -951,10 +951,11 @@ protected static Rectangle2D getImageBounds(BridgeContext ctx, Element element)
}
/**
- * Give a safe value for an animated length, regardless of exceptions.
+ * Give a safe value for an animated length, regardless of exceptions. Assumes a
+ * default value of {@code 0}.
*
* @param animValue the animated length.
- * @param defValue the default value.
+ * @param ctx the bridge context.
* @return the value.
*/
private static float safeLength(AbstractSVGAnimatedLength animValue, BridgeContext ctx)
@@ -963,11 +964,7 @@ private static float safeLength(AbstractSVGAnimatedLength animValue, BridgeConte
try {
value = animValue.getCheckedValue();
} catch (LiveAttributeException ex) {
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
value = 0f;
}
return value;
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPathElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPathElementBridge.java
index 2d7b9d82e..4e14ef4f9 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPathElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPathElementBridge.java
@@ -39,8 +39,10 @@
/**
* Bridge class for the <path> element.
*
- * @author Thierry Kormann
- * @author For later modifications, see Git history.
+ *
+ * Original author: Thierry Kormann.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public class SVGPathElementBridge extends SVGDecoratedShapeElementBridge implements SVGPathContext {
@@ -81,17 +83,52 @@ public Bridge getInstance() {
*/
@Override
protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode) {
-
SVGOMPathElement pe = (SVGOMPathElement) e;
AWTPathProducer app = new AWTPathProducer();
+ // 'd' attribute - ignore shape if not present
+ SVGOMAnimatedPathData _d = pe.getAnimatedPathData();
+
+ short check = _d.check();
+ if (check >= 0) { // Either 'none' or new errors detected
+ if (check == LiveAttributeException.ERR_ATTRIBUTE_MISSING) {
+ // none
+ /*
+ * SVG 2.0 § 9.3.2. "The value none indicates that there is no path data for the
+ * element. For ‘path’ elements, this means that the element does not render or
+ * contribute to the bounding box of ancestor container elements."
+ */
+
+ // We could just return, but in case that we are updating the
+ // ShapeNode, we want to set the shape.
+ shapeNode.setShape(EMPTY_SHAPE);
+ shapeNode.setVisible(false);
+ return;
+ }
+ // Must be LiveAttributeException.ERR_ATTRIBUTE_MALFORMED:
+ LiveAttributeException lex = new LiveAttributeException(e, e.getLocalName(), check,
+ _d.getPathSegList().toString());
+ BridgeException be = new BridgeException(ctx, lex);
+ displayErrorOrThrow(ctx, be);
+ }
+
+ if (_d.getPathSegList().getNumberOfItems() == 0) {
+ // § 9.3.2. "If the path data string contains no valid commands,
+ // then the behavior is the same as the none value."
+ //
+ // We could just return, but in case that we are updating the
+ // ShapeNode, we want to set the shape.
+ shapeNode.setShape(EMPTY_SHAPE);
+ shapeNode.setVisible(false);
+ return;
+ }
+
+ SVGPathSegList p = _d.getAnimatedPathSegList();
try {
- // 'd' attribute - required
- SVGOMAnimatedPathData _d = pe.getAnimatedPathData();
- _d.check();
- SVGPathSegList p = _d.getAnimatedPathSegList();
app.setWindingRule(CSSUtilities.convertFillRule(e));
+ shapeNode.setVisible(CSSUtilities.convertVisibility(e));
SVGAnimatedPathDataSupport.handlePathSegList(p, app);
} catch (LiveAttributeException ex) {
+ // Probably not thrown here
throw new BridgeException(ctx, ex);
} finally {
shapeNode.setShape(app.getShape());
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPatternElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPatternElementBridge.java
index 71a66ab73..23150ec53 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPatternElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPatternElementBridge.java
@@ -219,7 +219,6 @@ public Paint createPaint(BridgeContext ctx, Element patternElement, Element pain
* @param ctx the bridge context to use
*/
protected static RootGraphicsNode extractPatternContent(Element patternElement, BridgeContext ctx) {
-
List refs = new LinkedList<>();
for (;;) {
RootGraphicsNode content = extractLocalPatternContent(patternElement, ctx);
@@ -233,13 +232,20 @@ protected static RootGraphicsNode extractPatternContent(Element patternElement,
// check if there is circular dependencies
SVGOMDocument doc = (SVGOMDocument) patternElement.getOwnerDocument();
ParsedURL purl = new ParsedURL(doc.getURL(), uri);
- if (!purl.complete())
- throw new BridgeException(ctx, patternElement, ERR_URI_MALFORMED, new Object[] { uri });
+ if (!purl.complete()) {
+ BridgeException be = new BridgeException(ctx, patternElement, ERR_URI_MALFORMED,
+ new Object[] { uri });
+ displayErrorOrThrow(ctx, be);
+ return null;
+ }
if (contains(refs, purl)) {
- throw new BridgeException(ctx, patternElement, ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
- new Object[] { uri });
+ BridgeException be = new BridgeException(ctx, patternElement,
+ ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES, new Object[] { uri });
+ displayErrorOrThrow(ctx, be);
+ return null;
}
+
patternElement = ctx.getReferencedElement(patternElement, uri);
if (patternElement == null) {
return null; // Missing reference
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolygonElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolygonElementBridge.java
index 20dfa1d38..f37d736d4 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolygonElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolygonElementBridge.java
@@ -18,9 +18,6 @@
*/
package io.sf.carte.echosvg.bridge;
-import java.awt.Shape;
-import java.awt.geom.GeneralPath;
-
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGPoint;
import org.w3c.dom.svg.SVGPointList;
@@ -36,17 +33,14 @@
/**
* Bridge class for the <polygon> element.
*
- * @author Thierry Kormann
- * @author For later modifications, see Git history.
+ *
+ * Original author: Thierry Kormann.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public class SVGPolygonElementBridge extends SVGDecoratedShapeElementBridge {
- /**
- * default shape for the update of 'points' when the value is the empty string.
- */
- protected static final Shape DEFAULT_SHAPE = new GeneralPath();
-
/**
* Constructs a new bridge for the <polygon> element.
*/
@@ -78,15 +72,34 @@ public Bridge getInstance() {
*/
@Override
protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode) {
-
SVGOMPolygonElement pe = (SVGOMPolygonElement) e;
+ SVGOMAnimatedPoints _points = pe.getSVGOMAnimatedPoints();
+
+ short check = _points.check();
+ if (check >= 0) { // Either (none) or errors detected
+ if (check == LiveAttributeException.ERR_ATTRIBUTE_MISSING) {
+ // "The initial value, (none), indicates that the polygon element
+ // is valid, but does not render."
+ //
+ // We could just return, but in case that we are updating the
+ // ShapeNode, we want to set the shape.
+ shapeNode.setShape(EMPTY_SHAPE);
+ shapeNode.setVisible(false);
+ return;
+ }
+ // Must be LiveAttributeException.ERR_ATTRIBUTE_MALFORMED:
+ LiveAttributeException lex = new LiveAttributeException(e, e.getLocalName(), check,
+ _points.getPoints().toString());
+ BridgeException be = new BridgeException(ctx, lex);
+ displayErrorOrThrow(ctx, be);
+ }
+
try {
- SVGOMAnimatedPoints _points = pe.getSVGOMAnimatedPoints();
- _points.check();
SVGPointList pl = _points.getAnimatedPoints();
int size = pl.getNumberOfItems();
if (size == 0) {
- shapeNode.setShape(DEFAULT_SHAPE);
+ shapeNode.setShape(EMPTY_SHAPE);
+ shapeNode.setVisible(false);
} else {
AWTPolygonProducer app = new AWTPolygonProducer();
app.setWindingRule(CSSUtilities.convertFillRule(e));
@@ -96,6 +109,7 @@ protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode) {
app.point(p.getX(), p.getY());
}
app.endPoints();
+ shapeNode.setVisible(CSSUtilities.convertVisibility(e));
shapeNode.setShape(app.getShape());
}
} catch (LiveAttributeException ex) {
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolylineElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolylineElementBridge.java
index 42d4ceffb..b454bdcc9 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolylineElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGPolylineElementBridge.java
@@ -18,9 +18,6 @@
*/
package io.sf.carte.echosvg.bridge;
-import java.awt.Shape;
-import java.awt.geom.GeneralPath;
-
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGPoint;
import org.w3c.dom.svg.SVGPointList;
@@ -36,17 +33,14 @@
/**
* Bridge class for the <polyline> element.
*
- * @author Thierry Kormann
- * @author For later modifications, see Git history.
+ *
+ * Original author: Thierry Kormann.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public class SVGPolylineElementBridge extends SVGDecoratedShapeElementBridge {
- /**
- * default shape for the update of 'points' when the value is the empty string.
- */
- protected static final Shape DEFAULT_SHAPE = new GeneralPath();
-
/**
* Constructs a new bridge for the <polyline> element.
*/
@@ -78,15 +72,34 @@ public Bridge getInstance() {
*/
@Override
protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode) {
-
SVGOMPolylineElement pe = (SVGOMPolylineElement) e;
+ SVGOMAnimatedPoints _points = pe.getSVGOMAnimatedPoints();
+
+ short check = _points.check();
+ if (check >= 0) { // Either (none) or errors detected
+ if (check == LiveAttributeException.ERR_ATTRIBUTE_MISSING) {
+ // "The initial value, (none), indicates that the polyline element
+ // is valid, but does not render."
+ //
+ // We could just return, but in case that we are updating the
+ // ShapeNode, we want to set the shape.
+ shapeNode.setShape(EMPTY_SHAPE);
+ shapeNode.setVisible(false);
+ return;
+ }
+ // Must be LiveAttributeException.ERR_ATTRIBUTE_MALFORMED:
+ LiveAttributeException lex = new LiveAttributeException(e, e.getLocalName(), check,
+ _points.getPoints().toString());
+ BridgeException be = new BridgeException(ctx, lex);
+ displayErrorOrThrow(ctx, be);
+ }
+
try {
- SVGOMAnimatedPoints _points = pe.getSVGOMAnimatedPoints();
- _points.check();
SVGPointList pl = _points.getAnimatedPoints();
int size = pl.getNumberOfItems();
if (size == 0) {
- shapeNode.setShape(DEFAULT_SHAPE);
+ shapeNode.setShape(EMPTY_SHAPE);
+ shapeNode.setVisible(false);
} else {
AWTPolylineProducer app = new AWTPolylineProducer();
app.setWindingRule(CSSUtilities.convertFillRule(e));
@@ -96,6 +109,7 @@ protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode) {
app.point(p.getX(), p.getY());
}
app.endPoints();
+ shapeNode.setVisible(CSSUtilities.convertVisibility(e));
shapeNode.setShape(app.getShape());
}
} catch (LiveAttributeException ex) {
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGRectElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGRectElementBridge.java
index e191bdb14..c8b7b2ff9 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGRectElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGRectElementBridge.java
@@ -101,11 +101,7 @@ protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode)
} catch (LiveAttributeException ex) {
rx = 0f;
rxAuto = true;
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
}
if (rx > w / 2f) {
rx = w / 2f;
@@ -118,11 +114,7 @@ protected void buildShape(BridgeContext ctx, Element e, ShapeNode shapeNode)
ry = _ry.getCheckedValue();
} catch (LiveAttributeException ex) {
ry = rx;
- BridgeException be = new BridgeException(ctx, ex);
- if (ctx.userAgent == null) {
- throw be;
- }
- ctx.userAgent.displayError(be);
+ reportLiveAttributeException(ctx, ex);
}
if (ry > h / 2f) {
ry = h / 2f;
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGSVGElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGSVGElementBridge.java
index c2988a22b..a8bc3bc60 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGSVGElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGSVGElementBridge.java
@@ -110,35 +110,35 @@ public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) throws Brid
associateSVGContext(ctx, e, cgn);
- try {
- // In some cases we converted document fragments which didn't
- // have a parent SVG element, this check makes sure only the
- // real root of the SVG Document tries to do negotiation with
- // the UA.
- SVGDocument doc = (SVGDocument) e.getOwnerDocument();
- SVGOMSVGElement se = (SVGOMSVGElement) e;
- boolean isOutermost = (doc.getRootElement() == e);
- float x = 0;
- float y = 0;
- // x and y have no meaning on the outermost 'svg' element
- if (!isOutermost) {
- // 'x' attribute - default is 0
- AbstractSVGAnimatedLength _x = (AbstractSVGAnimatedLength) se.getX();
- x = safeAnimatedLength(_x, 0f);
-
- // 'y' attribute - default is 0
- AbstractSVGAnimatedLength _y = (AbstractSVGAnimatedLength) se.getY();
- y = safeAnimatedLength(_y, 0f);
- }
+ // In some cases we converted document fragments which didn't
+ // have a parent SVG element, this check makes sure only the
+ // real root of the SVG Document tries to do negotiation with
+ // the UA.
+ SVGDocument doc = (SVGDocument) e.getOwnerDocument();
+ SVGOMSVGElement se = (SVGOMSVGElement) e;
+ boolean isOutermost = (doc.getRootElement() == e);
+ float x = 0;
+ float y = 0;
+ // x and y have no meaning on the outermost 'svg' element
+ if (!isOutermost) {
+ // 'x' attribute - default is 0
+ AbstractSVGAnimatedLength _x = (AbstractSVGAnimatedLength) se.getX();
+ x = safeAnimatedLength(_x, 0f);
+
+ // 'y' attribute - default is 0
+ AbstractSVGAnimatedLength _y = (AbstractSVGAnimatedLength) se.getY();
+ y = safeAnimatedLength(_y, 0f);
+ }
- // 'width' attribute - default is 100%
- AbstractSVGAnimatedLength _width = (AbstractSVGAnimatedLength) se.getWidth();
- float w = safeAnimatedLength(_width);
+ // 'width' attribute - default is 100%
+ AbstractSVGAnimatedLength _width = (AbstractSVGAnimatedLength) se.getWidth();
+ float w = safeAnimatedLength(_width);
- // 'height' attribute - default is 100%
- AbstractSVGAnimatedLength _height = (AbstractSVGAnimatedLength) se.getHeight();
- float h = safeAnimatedLength(_height);
+ // 'height' attribute - default is 100%
+ AbstractSVGAnimatedLength _height = (AbstractSVGAnimatedLength) se.getHeight();
+ float h = safeAnimatedLength(_height);
+ try {
// 'visibility'
cgn.setVisible(CSSUtilities.convertVisibility(e));
@@ -244,11 +244,7 @@ public void setSize(double w, double h) {
actualWidth = vbr.getWidth();
actualHeight = vbr.getHeight();
} catch (RuntimeException ex) {
- if (ctx.userAgent != null) {
- ctx.userAgent.displayError(ex);
- } else {
- throw ex;
- }
+ displayErrorOrThrow(ctx, ex);
}
}
ctx.openViewport(e, new SVGSVGElementViewport(actualWidth, actualHeight));
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGShapeElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGShapeElementBridge.java
index fe457b949..6f52b44d1 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGShapeElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGShapeElementBridge.java
@@ -19,6 +19,9 @@
package io.sf.carte.echosvg.bridge;
import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
import org.w3c.dom.Element;
@@ -39,6 +42,11 @@
*/
public abstract class SVGShapeElementBridge extends AbstractGraphicsNodeBridge {
+ /**
+ * An empty shape to use when the element cannot be displayed.
+ */
+ protected static final Shape EMPTY_SHAPE = new GeneralPath(Path2D.WIND_NON_ZERO, 1);
+
/**
* Constructs a new bridge for SVG shapes.
*/
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGUseElementBridge.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGUseElementBridge.java
index 0f527ed6a..cd2e1b055 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGUseElementBridge.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/SVGUseElementBridge.java
@@ -122,16 +122,61 @@ public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
public CompositeGraphicsNode buildCompositeGraphicsNode(BridgeContext ctx, Element e, CompositeGraphicsNode gn) {
// get the referenced element
SVGOMUseElement ue = (SVGOMUseElement) e;
- String uri = ue.getHref().getAnimVal();
- if (uri.length() == 0) {
- throw new BridgeException(ctx, e, ERR_ATTRIBUTE_MISSING, new Object[] { "xlink:href" });
+ String uri = ue.getHref().getAnimVal().trim();
+ if (uri.isEmpty()) {
+ BridgeException be = new BridgeException(ctx, e, ERR_ATTRIBUTE_MISSING,
+ new Object[] { "href" });
+ displayErrorOrThrow(ctx, be);
+ if (gn != null) {
+ clearGraphicsNode(gn);
+ }
+ return null;
}
Element refElement = ctx.getReferencedElement(e, uri);
+
if (refElement == null) {
+ if (gn != null) {
+ clearGraphicsNode(gn);
+ }
return null; // Missing reference
}
+ // Check for ancestor circularity
+ Node anc = e;
+ do {
+ if (refElement == anc) {
+ BridgeException be = new BridgeException(ctx, e, ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
+ new Object[] { "href" });
+ displayErrorOrThrow(ctx, be);
+ if (gn != null) {
+ clearGraphicsNode(gn);
+ }
+ return null; // Circularity
+ }
+ anc = anc.getParentNode();
+ } while (anc != null && anc.getNodeType() == Node.ELEMENT_NODE);
+
+ // Check that the referenced element is SVG
+ if (!SVG_NAMESPACE_URI.equals(refElement.getNamespaceURI())) {
+ /* @formatter:off
+ *
+ * § 5.5:
+ * "If the referenced element that results from resolving the URL is
+ * not an SVG element, then the reference is invalid and the ‘use’
+ * element is in error."
+ *
+ * @formatter:on
+ */
+ BridgeException be = new BridgeException(ctx, e, ERR_URI_BAD_TARGET,
+ new Object[] { "href" });
+ displayErrorOrThrow(ctx, be);
+ if (gn != null) {
+ clearGraphicsNode(gn);
+ }
+ return null;
+ }
+
SVGOMDocument document, refDocument;
document = (SVGOMDocument) e.getOwnerDocument();
refDocument = (SVGOMDocument) refElement.getOwnerDocument();
@@ -196,9 +241,7 @@ public CompositeGraphicsNode buildCompositeGraphicsNode(BridgeContext ctx, Eleme
gn = new CompositeGraphicsNode();
associateSVGContext(ctx, e, node);
} else {
- int s = gn.size();
- for (int i = 0; i < s; i++)
- gn.remove(0);
+ clearGraphicsNode(gn);
}
Node oldRoot = ue.getCSSFirstChild();
@@ -278,6 +321,12 @@ public CompositeGraphicsNode buildCompositeGraphicsNode(BridgeContext ctx, Eleme
return gn;
}
+ private static void clearGraphicsNode(CompositeGraphicsNode gn) {
+ int s = gn.size();
+ for (int i = 0; i < s; i++)
+ gn.remove(0);
+ }
+
@Override
public void dispose() {
if (l != null) {
@@ -424,10 +473,13 @@ public void handleAnimatedAttributeChanged(AnimatedLiveAttributeValue alav) {
if (ln.equals(SVG_X_ATTRIBUTE) || ln.equals(SVG_Y_ATTRIBUTE) || ln.equals(SVG_TRANSFORM_ATTRIBUTE)) {
node.setTransform(computeTransform((SVGTransformable) e, ctx));
handleGeometryChanged();
- } else if (ln.equals(SVG_WIDTH_ATTRIBUTE) || ln.equals(SVG_HEIGHT_ATTRIBUTE))
+ } else if (ln.equals(SVG_WIDTH_ATTRIBUTE) || ln.equals(SVG_HEIGHT_ATTRIBUTE)) {
+ buildCompositeGraphicsNode(ctx, e, (CompositeGraphicsNode) node);
+ } else if (ln.equals(XLINK_HREF_ATTRIBUTE)) {
buildCompositeGraphicsNode(ctx, e, (CompositeGraphicsNode) node);
+ }
} else {
- if (ns.equals(XLINK_NAMESPACE_URI) && ln.equals(XLINK_HREF_ATTRIBUTE))
+ if (ln.equals(XLINK_HREF_ATTRIBUTE))
buildCompositeGraphicsNode(ctx, e, (CompositeGraphicsNode) node);
}
} catch (LiveAttributeException ex) {
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/UserAgent.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/UserAgent.java
index e3c64a8ef..2fcd675b0 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/UserAgent.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/UserAgent.java
@@ -54,11 +54,15 @@ public interface UserAgent {
/**
* Displays an error resulting from the specified Exception.
+ *
+ * @param ex the exception.
*/
void displayError(Exception ex);
/**
* Displays a message in the User Agent interface.
+ *
+ * @param message the message.
*/
void displayMessage(String message);
diff --git a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/ViewBox.java b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/ViewBox.java
index a97c99e0a..ba156649e 100644
--- a/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/ViewBox.java
+++ b/echosvg-bridge/src/main/java/io/sf/carte/echosvg/bridge/ViewBox.java
@@ -28,12 +28,12 @@
import org.w3c.dom.svg.SVGAnimatedRect;
import org.w3c.dom.svg.SVGPreserveAspectRatio;
+import io.sf.carte.doc.style.css.CSSExpressionValue;
+import io.sf.carte.doc.style.css.CSSTypedValue;
import io.sf.carte.doc.style.css.CSSUnit;
import io.sf.carte.doc.style.css.CSSValue.CssType;
import io.sf.carte.doc.style.css.property.Evaluator;
-import io.sf.carte.doc.style.css.property.ExpressionValue;
import io.sf.carte.doc.style.css.property.StyleValue;
-import io.sf.carte.doc.style.css.property.TypedValue;
import io.sf.carte.doc.style.css.property.ValueFactory;
import io.sf.carte.doc.style.css.property.ValueList;
import io.sf.carte.echosvg.anim.dom.SVGOMAnimatedRect;
@@ -226,11 +226,7 @@ public static AffineTransform getPreserveAspectRatioTransform(Element e, String
try {
vb = parseViewBoxAttribute(e, viewBox, ctx);
} catch (BridgeException be) {
- if (ctx.userAgent != null) {
- ctx.userAgent.displayError(be);
- } else {
- throw be;
- }
+ AbstractSVGBridge.displayErrorOrThrow(ctx, be);
return new AffineTransform();
}
@@ -243,11 +239,7 @@ public static AffineTransform getPreserveAspectRatioTransform(Element e, String
} catch (ParseException pEx) {
BridgeException be = new BridgeException(ctx, e, pEx, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] { SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE, aspectRatio, pEx });
- if (ctx.userAgent != null) {
- ctx.userAgent.displayError(be);
- } else {
- throw be;
- }
+ AbstractSVGBridge.displayErrorOrThrow(ctx, be);
return new AffineTransform();
}
@@ -344,11 +336,7 @@ public static AffineTransform getPreserveAspectRatioTransform(Element e, SVGAnim
try {
art = getPreserveAspectRatioTransform(e, vb, w, h, aPAR, ctx);
} catch (BridgeException ex) {
- if (ctx.userAgent != null) {
- ctx.userAgent.displayError(ex);
- } else {
- throw ex;
- }
+ AbstractSVGBridge.displayErrorOrThrow(ctx, ex);
art = new AffineTransform();
}
return art;
@@ -412,17 +400,17 @@ static boolean computeRectangle(StyleValue value, float[] numbers) throws DOMExc
if (item.getCssValueType() != CssType.TYPED) {
return false;
}
- TypedValue typed;
+ CSSTypedValue typed;
switch (item.getPrimitiveType()) {
case NUMERIC:
- typed = (TypedValue) item;
+ typed = (CSSTypedValue) item;
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
break;
case EXPRESSION:
- Evaluator eval = new Evaluator();
- typed = eval.evaluateExpression((ExpressionValue) item);
+ Evaluator eval = new Evaluator(CSSUnit.CSS_NUMBER);
+ typed = eval.evaluateExpression((CSSExpressionValue) item);
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/CircleTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/CircleTest.java
new file mode 100644
index 000000000..0d65fac1d
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/CircleTest.java
@@ -0,0 +1,155 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.CSSConstants;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * Test circle bounding boxes.
+ */
+public class CircleTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testMissingR() {
+ Element circle = createDocumentWithCircle();
+ Document document = circle.getOwnerDocument();
+ assertBounds(document, 40, 50, 160, 150);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testEmptyR() {
+ Element circle = createDocumentWithCircle();
+ Document document = circle.getOwnerDocument();
+ Attr d = document.createAttribute(SVGConstants.SVG_R_ATTRIBUTE);
+ circle.setAttributeNode(d);
+ assertBounds(document, 40, 50, 160, 150);
+ assertEquals(1, errorCount);
+ }
+
+ @Test
+ public void testInvalidR() {
+ Element circle = createDocumentWithCircle();
+ Document document = circle.getOwnerDocument();
+ circle.setAttribute(SVGConstants.SVG_R_ATTRIBUTE, ",");
+ assertBounds(document, 40, 50, 160, 150);
+ assertEquals(1, errorCount);
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ @Test
+ public void testOperations() {
+ context.setDynamic(true);
+ Element circle = createDocumentWithCircle();
+ Document document = circle.getOwnerDocument();
+ circle.setAttribute(SVGConstants.SVG_R_ATTRIBUTE, "20");
+ assertBounds(document, 20, 30, 180, 170);
+
+ circle.setAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY, "hidden");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ circle.removeAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY);
+ assertBounds(document, 20, 30, 180, 170);
+
+ assertEquals(0, errorCount);
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocumentWithCircle() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+ Element rect = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ svg.appendChild(rect);
+
+ Element circle = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_CIRCLE_TAG);
+ circle.setAttribute(SVGConstants.SVG_CX_ATTRIBUTE, "40");
+ circle.setAttribute(SVGConstants.SVG_CY_ATTRIBUTE, "50");
+ svg.appendChild(circle);
+
+ return circle;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/EllipseTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/EllipseTest.java
new file mode 100644
index 000000000..db9b1a708
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/EllipseTest.java
@@ -0,0 +1,156 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.CSSConstants;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * Test ellipse bounding boxes.
+ */
+public class EllipseTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testMissingR() {
+ Element ellipse = createDocumentWithEllipse();
+ Document document = ellipse.getOwnerDocument();
+ assertBounds(document, 40, 50, 160, 150);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testEmptyRX() {
+ Element ellipse = createDocumentWithEllipse();
+ Document document = ellipse.getOwnerDocument();
+ Attr d = document.createAttribute(SVGConstants.SVG_RX_ATTRIBUTE);
+ ellipse.setAttributeNode(d);
+ assertBounds(document, 40, 50, 160, 150);
+ assertEquals(1, errorCount);
+ }
+
+ @Test
+ public void testInvalidRX() {
+ Element ellipse = createDocumentWithEllipse();
+ Document document = ellipse.getOwnerDocument();
+ ellipse.setAttribute(SVGConstants.SVG_RX_ATTRIBUTE, ",");
+ assertBounds(document, 40, 50, 160, 150);
+ assertEquals(1, errorCount);
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ @Test
+ public void testOperations() {
+ context.setDynamic(true);
+ Element ellipse = createDocumentWithEllipse();
+ Document document = ellipse.getOwnerDocument();
+ ellipse.setAttribute(SVGConstants.SVG_RX_ATTRIBUTE, "20");
+ ellipse.setAttribute(SVGConstants.SVG_RY_ATTRIBUTE, "35");
+ assertBounds(document, 20, 15, 180, 185);
+
+ ellipse.setAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY, "hidden");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ ellipse.removeAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY);
+ assertBounds(document, 20, 15, 180, 185);
+
+ assertEquals(0, errorCount);
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocumentWithEllipse() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+ Element rect = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ svg.appendChild(rect);
+
+ Element ellipse = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_ELLIPSE_TAG);
+ ellipse.setAttribute(SVGConstants.SVG_CX_ATTRIBUTE, "40");
+ ellipse.setAttribute(SVGConstants.SVG_CY_ATTRIBUTE, "50");
+ svg.appendChild(ellipse);
+
+ return ellipse;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PathTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PathTest.java
new file mode 100644
index 000000000..2bc3d03a6
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PathTest.java
@@ -0,0 +1,187 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGPathSegList;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.anim.dom.SVGOMPathElement;
+import io.sf.carte.echosvg.constants.XMLConstants;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.CSSConstants;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * See issue #113
+ */
+public class PathTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testPathMissingD() {
+ Element path = createDocumentWithPath();
+ Document document = path.getOwnerDocument();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testPathEmptyD() {
+ Element path = createDocumentWithPath();
+ Document document = path.getOwnerDocument();
+ Attr d = document.createAttribute(SVGConstants.SVG_D_ATTRIBUTE);
+ path.setAttributeNode(d);
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testPathDNone() {
+ Element path = createDocumentWithPath();
+ Document document = path.getOwnerDocument();
+ Attr d = document.createAttribute(SVGConstants.SVG_D_ATTRIBUTE);
+ d.setValue("none");
+ path.setAttributeNode(d);
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testPathInvalidD() {
+ Element path = createDocumentWithPath();
+ Document document = path.getOwnerDocument();
+ path.setAttribute(SVGConstants.SVG_D_ATTRIBUTE, "M 5 5, 10 10, 20 - 1");
+ assertBounds(document, 4.65, 4.65, 195.35, 195.35);
+ assertEquals(1, errorCount);
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ @Test
+ public void testPathOperations() {
+ context.setDynamic(true);
+ SVGOMPathElement path = (SVGOMPathElement) createDocumentWithPath();
+ Document document = path.getOwnerDocument();
+ path.setAttribute(SVGConstants.SVG_D_ATTRIBUTE, "M 5 5, 10 10");
+ SVGPathSegList segList = path.getPathSegList();
+ assertEquals(2, segList.getNumberOfItems());
+
+ segList.appendItem(path.createSVGPathSegMovetoRel(10, 5));
+ assertEquals(3, segList.getNumberOfItems());
+ assertBounds(document, 4.65, 4.65, 195.35, 195.35);
+
+ segList.insertItemBefore(path.createSVGPathSegMovetoAbs(20, 25), 2);
+ assertEquals(4, segList.getNumberOfItems());
+ assertBounds(document, 4.65, 4.65, 195.35, 195.35);
+
+ path.setAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY, "hidden");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ path.removeAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY);
+ assertBounds(document, 4.65, 4.65, 195.35, 195.35);
+
+
+ segList.replaceItem(path.createSVGPathSegMovetoAbs(35, 35), 0);
+ segList.replaceItem(path.createSVGPathSegMovetoAbs(45, 45), 1);
+ segList.removeItem(2);
+ assertEquals(3, segList.getNumberOfItems());
+ assertEquals("M 35.0 35.0 M 45.0 45.0 m 10.0 5.0", segList.toString());
+
+ segList.clear();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocumentWithPath() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+ Element rect = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ svg.appendChild(rect);
+
+ Element path = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_PATH_TAG);
+ path.setAttribute(XMLConstants.XML_ID_ATTRIBUTE, "path1");
+ path.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#111");
+ svg.appendChild(path);
+
+ return path;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PolygonTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PolygonTest.java
new file mode 100644
index 000000000..5583a2a95
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PolygonTest.java
@@ -0,0 +1,168 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGPointList;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.anim.dom.SVGOMPolygonElement;
+import io.sf.carte.echosvg.dom.svg.SVGPointItem;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.CSSConstants;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * Check polygon bounding boxes.
+ */
+public class PolygonTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testMissingPoints() {
+ Element poly = createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testEmptyPoints() {
+ Element poly = createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ poly.setAttribute(SVGConstants.SVG_POINTS_ATTRIBUTE, "");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testInvalidPoints() {
+ Element poly = createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ poly.setAttribute(SVGConstants.SVG_POINTS_ATTRIBUTE, "5, 5 10, 10 20 - 1");
+ assertBounds(document, 5, 5, 195, 195);
+ assertEquals(1, errorCount);
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ @Test
+ public void testPolyOperations() {
+ context.setDynamic(true);
+ SVGOMPolygonElement poly = (SVGOMPolygonElement) createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ poly.setAttribute(SVGConstants.SVG_POINTS_ATTRIBUTE, "5,5 10,20");
+
+ SVGPointList ptsList = poly.getPoints();
+ assertEquals(2, ptsList.getNumberOfItems());
+
+ SVGPointItem point = new SVGPointItem(14, 4);
+ ptsList.appendItem(point);
+ assertEquals(3, ptsList.getNumberOfItems());
+ assertBounds(document, 5, 4, 195, 196);
+
+ poly.setAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY, "hidden");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ poly.removeAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY);
+ assertBounds(document, 5, 4, 195, 196);
+
+ ptsList.removeItem(1);
+ assertEquals(2, ptsList.getNumberOfItems());
+ assertEquals("5.0,5.0 14.0,4.0", ptsList.toString());
+ assertBounds(document, 5, 4, 195, 196);
+
+ ptsList.clear();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocumentWithPoly() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+ Element rect = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ svg.appendChild(rect);
+
+ Element poly = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_POLYGON_TAG);
+ svg.appendChild(poly);
+
+ return poly;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PolylineTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PolylineTest.java
new file mode 100644
index 000000000..15a79f2ac
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/PolylineTest.java
@@ -0,0 +1,167 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGPointList;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.anim.dom.SVGOMPolylineElement;
+import io.sf.carte.echosvg.dom.svg.SVGPointItem;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.CSSConstants;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * Check polyline bounding boxes.
+ */
+public class PolylineTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testMissingPoints() {
+ Element poly = createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testEmptyPoints() {
+ Element poly = createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ poly.setAttribute(SVGConstants.SVG_POINTS_ATTRIBUTE, "");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testInvalidPoints() {
+ Element poly = createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ poly.setAttribute(SVGConstants.SVG_POINTS_ATTRIBUTE, "5, 5 10, 10 20 - 1");
+ assertBounds(document, 5, 5, 195, 195);
+ assertEquals(1, errorCount);
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ @Test
+ public void testPolyOperations() {
+ context.setDynamic(true);
+ SVGOMPolylineElement poly = (SVGOMPolylineElement) createDocumentWithPoly();
+ Document document = poly.getOwnerDocument();
+ poly.setAttribute(SVGConstants.SVG_POINTS_ATTRIBUTE, "5,5 10,20");
+
+ SVGPointList ptsList = poly.getPoints();
+ assertEquals(2, ptsList.getNumberOfItems());
+
+ SVGPointItem point = new SVGPointItem(14, 4);
+ ptsList.appendItem(point);
+ assertEquals(3, ptsList.getNumberOfItems());
+ assertBounds(document, 5, 4, 195, 196);
+
+ poly.setAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY, "hidden");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ poly.removeAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY);
+ assertBounds(document, 5, 4, 195, 196);
+
+ ptsList.removeItem(1);
+ assertEquals(2, ptsList.getNumberOfItems());
+ assertEquals("5.0,5.0 14.0,4.0", ptsList.toString());
+
+ ptsList.clear();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(0, errorCount);
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocumentWithPoly() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+ Element rect = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ svg.appendChild(rect);
+
+ Element poly = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_POLYLINE_TAG);
+ svg.appendChild(poly);
+
+ return poly;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/RectTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/RectTest.java
new file mode 100644
index 000000000..88a7525f3
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/RectTest.java
@@ -0,0 +1,156 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.CSSConstants;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * Test rect0 bounding boxes.
+ */
+public class RectTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testMissingWidth() {
+ Element rect = createDocumentWithRect();
+ Document document = rect.getOwnerDocument();
+ assertBounds(document, 30, 40, 170, 160);
+ assertEquals(0, errorCount);
+ }
+
+ @Test
+ public void testEmptyWidth() {
+ Element rect = createDocumentWithRect();
+ Document document = rect.getOwnerDocument();
+ Attr d = document.createAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE);
+ rect.setAttributeNode(d);
+ assertBounds(document, 30, 40, 170, 160);
+ assertEquals(1, errorCount);
+ }
+
+ @Test
+ public void testInvalidWidth() {
+ Element rect = createDocumentWithRect();
+ Document document = rect.getOwnerDocument();
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, ",");
+ assertBounds(document, 30, 40, 170, 160);
+ assertEquals(1, errorCount);
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ @Test
+ public void testOperations() {
+ context.setDynamic(true);
+ Element rect = createDocumentWithRect();
+ Document document = rect.getOwnerDocument();
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "60");
+ assertBounds(document, 30, 40, 170, 160);
+
+ rect.setAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY, "hidden");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ rect.removeAttribute(CSSConstants.CSS_VISIBILITY_PROPERTY);
+ assertBounds(document, 30, 40, 170, 160);
+
+ assertEquals(0, errorCount);
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocumentWithRect() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+ Element rect0 = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect0.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect0.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect0.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect0.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect0.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ svg.appendChild(rect0);
+
+ Element e = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ e.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "30");
+ e.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "40");
+ e.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "50");
+ svg.appendChild(e);
+
+ return e;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/UseTest.java b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/UseTest.java
new file mode 100644
index 000000000..af29ad306
--- /dev/null
+++ b/echosvg-bridge/src/test/java/io/sf/carte/echosvg/bridge/UseTest.java
@@ -0,0 +1,203 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.awt.Dimension;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
+import io.sf.carte.echosvg.constants.XMLConstants;
+import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.util.SVGConstants;
+
+/**
+ * Test the <use> element.
+ *
+ * Show that invalid <use> elements are handled according to
+ * the specification, and do not contribute to the bounding box.
+ *
+ */
+public class UseTest {
+
+ BridgeContext context;
+
+ int errorCount;
+
+ @BeforeEach
+ public void setupBeforeEach() {
+ context = createBridgeContext();
+ errorCount = 0;
+ }
+
+ @Test
+ public void testMissingHref() {
+ Element use = createDocument();
+ Document document = use.getOwnerDocument();
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ /*
+ * Not an error according to the specification, but useful anyway.
+ */
+ assertEquals(1, errorCount);
+ }
+
+ @Test
+ public void testEmptyHref() {
+ Element use = createDocument();
+ Document document = use.getOwnerDocument();
+ Attr d = document.createAttribute(XMLConstants.XLINK_HREF_ATTRIBUTE);
+ d.setValue("");
+ use.setAttributeNode(d);
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ /*
+ * Not an error according to the specification, but useful anyway.
+ */
+ assertEquals(1, errorCount);
+ }
+
+ @Timeout(1)
+ @Test
+ public void testHrefItself() {
+ Element use = createDocument();
+ Document document = use.getOwnerDocument();
+ use.setAttribute(XMLConstants.XLINK_HREF_ATTRIBUTE, "#use1");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(1, errorCount);
+ }
+
+ @Timeout(1)
+ @Test
+ public void testHrefParent() {
+ Element use = createDocument();
+ Document document = use.getOwnerDocument();
+ use.setAttribute(XMLConstants.XLINK_HREF_ATTRIBUTE, "#root");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(1, errorCount);
+ }
+
+ @Test
+ public void testHrefNonSVG() {
+ Element use = createDocument();
+ Document document = use.getOwnerDocument();
+
+ Element root = document.getDocumentElement();
+ Element fo = document.createElementNS(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_FOREIGN_OBJECT_TAG);
+ Element div = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ div.setAttribute(XMLConstants.XML_ID_ATTRIBUTE, "div1");
+ div.setTextContent("Hi");
+ fo.appendChild(div);
+ root.appendChild(fo);
+
+ use.setAttribute(XMLConstants.XLINK_HREF_ATTRIBUTE, "#div1");
+ assertBounds(document, 99.5, 99.5, 100.5, 100.5);
+ assertEquals(1, errorCount);
+ }
+
+ @Test
+ public void testInvalidHref() {
+ Element use = createDocument();
+ Document document = use.getOwnerDocument();
+ use.setAttribute(XMLConstants.XLINK_HREF_ATTRIBUTE, "/:::");
+ /*
+ * Generally users want this kind of exception to be thrown, instead of
+ * just being reported by the user agent.
+ */
+ BridgeException be = assertThrows(BridgeException.class, () -> createGraphicsNode(document));
+ assertEquals("uri.unsecure", be.getCode());
+ }
+
+ private void assertBounds(Document document, double x, double y, double width, double height) {
+ GraphicsNode node = createGraphicsNode(document);
+ Rectangle2D bnds = node.getBounds();
+ assertEquals(x, bnds.getX(), 0.01, "Wrong x");
+ assertEquals(y, bnds.getY(), 0.01, "Wrong y");
+ assertEquals(width, bnds.getWidth(), 0.01, "Wrong width");
+ assertEquals(height, bnds.getHeight(), 0.01, "Wrong height");
+ }
+
+ private GraphicsNode createGraphicsNode(Document document) {
+ return new GVTBuilder().build(context, document);
+ }
+
+ private static Element createDocument() {
+ DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
+ DocumentType dtd = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.1//EN",
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
+ SVGDocument doc = (SVGDocument) impl.createDocument(SVGConstants.SVG_NAMESPACE_URI,
+ SVGConstants.SVG_SVG_TAG, dtd);
+
+ SVGSVGElement svg = doc.getRootElement();
+ svg.setAttribute(XMLConstants.XML_ID_ATTRIBUTE, "root");
+ svg.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "200");
+ svg.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "200");
+
+ Element g = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_G_TAG);
+ g.setAttribute(XMLConstants.XML_ID_ATTRIBUTE, "g1");
+ svg.appendChild(g);
+
+ Element rect = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_RECT_TAG);
+ rect.setAttribute(XMLConstants.XML_ID_ATTRIBUTE, "rect1");
+ rect.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "100");
+ rect.setAttribute(SVGConstants.SVG_STROKE_ATTRIBUTE, "#107");
+ g.appendChild(rect);
+
+ Element use = doc.createElementNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_USE_TAG);
+ use.setAttribute(XMLConstants.XML_ID_ATTRIBUTE, "use1");
+ use.setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "120");
+ use.setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, "120");
+ svg.appendChild(use);
+
+ return use;
+ }
+
+ private BridgeContext createBridgeContext() {
+ return new BridgeContext(new UserAgentAdapter() {
+
+ @Override
+ public Dimension2D getViewportSize() {
+ return new Dimension(200, 200);
+ }
+
+ @Override
+ public void displayError(String message) {
+ errorCount++;
+ }
+
+ });
+ }
+
+}
diff --git a/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java b/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java
index 3d4d755d7..3d5c32430 100644
--- a/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java
+++ b/echosvg-css/src/main/java/io/sf/carte/echosvg/css/engine/value/AbstractColorManager.java
@@ -24,12 +24,15 @@
import org.w3c.dom.DOMException;
import org.w3c.dom.css.CSSPrimitiveValue;
+import io.sf.carte.doc.style.css.CSSColor;
import io.sf.carte.doc.style.css.CSSTypedValue;
import io.sf.carte.doc.style.css.CSSValue.CssType;
+import io.sf.carte.doc.style.css.RGBAColor;
import io.sf.carte.doc.style.css.nsac.CSSParseException;
import io.sf.carte.doc.style.css.nsac.LexicalUnit;
import io.sf.carte.doc.style.css.nsac.LexicalUnit.LexicalType;
import io.sf.carte.doc.style.css.parser.CSSParser;
+import io.sf.carte.doc.style.css.property.NumberValue;
import io.sf.carte.doc.style.css.property.StyleValue;
import io.sf.carte.doc.style.css.property.ValueFactory;
import io.sf.carte.echosvg.css.engine.CSSEngine;
@@ -145,7 +148,9 @@ public Value createValue(LexicalUnit lunit, CSSEngine engine) throws DOMExceptio
if (css4jValue.getCssValueType() != CssType.TYPED) {
throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType());
}
- rgbSerialization = ((CSSTypedValue) css4jValue).toRGBColor().toString();
+ RGBAColor rgb = ((CSSTypedValue) css4jValue).toRGBColor();
+ setComponentsMaximumFractionDigits(rgb, 6);
+ rgbSerialization = rgb.toString();
} catch (DOMException e) {
throw createInvalidLexicalUnitDOMException(lunit.getLexicalUnitType());
}
@@ -190,6 +195,12 @@ public Value createValue(LexicalUnit lunit, CSSEngine engine) throws DOMExceptio
}
}
+ private static void setComponentsMaximumFractionDigits(CSSColor color, int maxFractionDigits) {
+ ((NumberValue) color.item(1)).setMaximumFractionDigits(maxFractionDigits);
+ ((NumberValue) color.item(2)).setMaximumFractionDigits(maxFractionDigits);
+ ((NumberValue) color.item(3)).setMaximumFractionDigits(maxFractionDigits);
+ }
+
/**
* Implements
* {@link ValueManager#computeValue(CSSStylableElement,String,CSSEngine,int,StyleMap,Value)}.
diff --git a/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/AbstractDocument.java b/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/AbstractDocument.java
index 8c6075931..c41659e96 100644
--- a/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/AbstractDocument.java
+++ b/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/AbstractDocument.java
@@ -358,21 +358,7 @@ public Node importNode(Node importedNode, boolean deep, boolean trimId) {
Node result;
switch (importedNode.getNodeType()) {
case ELEMENT_NODE:
- Element e = createElementNS(importedNode.getNamespaceURI(), importedNode.getNodeName());
- result = e;
- if (importedNode.hasAttributes()) {
- NamedNodeMap attr = importedNode.getAttributes();
- int len = attr.getLength();
- for (int i = 0; i < len; i++) {
- Attr a = (Attr) attr.item(i);
- if (!a.getSpecified())
- continue;
- AbstractAttr aa = (AbstractAttr) importNode(a, true);
- if (trimId && aa.isId())
- aa.setIsId(false); // don't consider this an Id.
- e.setAttributeNodeNS(aa);
- }
- }
+ result = importElement(importedNode, trimId);
break;
case ATTRIBUTE_NODE:
@@ -435,6 +421,24 @@ public Node importNode(Node importedNode, boolean deep, boolean trimId) {
return result;
}
+ protected Element importElement(Node importMe, boolean trimId) {
+ Element e = createElementNS(importMe.getNamespaceURI(), importMe.getNodeName());
+ if (importMe.hasAttributes()) {
+ NamedNodeMap attr = importMe.getAttributes();
+ int len = attr.getLength();
+ for (int i = 0; i < len; i++) {
+ Attr a = (Attr) attr.item(i);
+ if (!a.getSpecified())
+ continue;
+ AbstractAttr aa = (AbstractAttr) importNode(a, true);
+ if (trimId && aa.isId())
+ aa.setIsId(false); // don't consider this an Id.
+ e.setAttributeNodeNS(aa);
+ }
+ }
+ return e;
+ }
+
/**
* DOM: Implements {@link org.w3c.dom.Node#cloneNode(boolean)}.
*/
diff --git a/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/ExtensibleDOMImplementation.java b/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/ExtensibleDOMImplementation.java
index 4d0e6b011..b3f66f07c 100644
--- a/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/ExtensibleDOMImplementation.java
+++ b/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/ExtensibleDOMImplementation.java
@@ -24,10 +24,13 @@
import java.util.ListIterator;
import java.util.ServiceLoader;
+import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
import org.w3c.dom.css.DOMImplementationCSS;
import org.w3c.dom.css.ViewCSS;
@@ -209,6 +212,24 @@ public interface ElementFactory {
*/
Element create(String prefix, Document doc);
+ default void importAttributes(Element imported, Node toImport, boolean trimId) {
+ if (toImport.hasAttributes()) {
+ NamedNodeMap attr = toImport.getAttributes();
+ int len = attr.getLength();
+ for (int i = 0; i < len; i++) {
+ Attr a = (Attr) attr.item(i);
+ if (!a.getSpecified())
+ continue;
+ AbstractAttr aa = (AbstractAttr) imported.getOwnerDocument()
+ .createAttributeNS(a.getNamespaceURI(), a.getNodeName());
+ aa.setNodeValue(a.getNodeValue());
+ if (trimId && aa.isId())
+ aa.setIsId(false); // don't consider this an Id.
+ imported.setAttributeNodeNS(aa);
+ }
+ }
+ }
+
}
// Service /////////////////////////////////////////////////////////
diff --git a/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/GenericDOMImplementation.java b/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/GenericDOMImplementation.java
index c356e3a80..e171a506a 100644
--- a/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/GenericDOMImplementation.java
+++ b/echosvg-dom/src/main/java/io/sf/carte/echosvg/dom/GenericDOMImplementation.java
@@ -63,7 +63,9 @@ public static DOMImplementation getDOMImplementation() {
public Document createDocument(String namespaceURI, String qualifiedName, DocumentType doctype)
throws DOMException {
Document result = new GenericDocument(doctype, this);
- result.appendChild(result.createElementNS(namespaceURI, qualifiedName));
+ if (qualifiedName != null) {
+ result.appendChild(result.createElementNS(namespaceURI, qualifiedName));
+ }
return result;
}
diff --git a/echosvg-i18n/src/main/java/io/sf/carte/echosvg/i18n/LocalizableSupport.java b/echosvg-i18n/src/main/java/io/sf/carte/echosvg/i18n/LocalizableSupport.java
index d8d359efc..9d38c43ea 100644
--- a/echosvg-i18n/src/main/java/io/sf/carte/echosvg/i18n/LocalizableSupport.java
+++ b/echosvg-i18n/src/main/java/io/sf/carte/echosvg/i18n/LocalizableSupport.java
@@ -326,7 +326,11 @@ public String getString(String key) throws MissingResourceException {
}
}
String classStr = (cls != null) ? cls.toString() : bundleName;
- throw new MissingResourceException("Unable to find resource: " + key, classStr, key);
+ if (key.isEmpty()) {
+ throw new MissingResourceException("Requested an empty resource key.", classStr, key);
+ }
+ System.err.println("Unable to find resource: " + key + " in " + classStr);
+ return key;
}
/**
diff --git a/echosvg-parser/src/main/java/io/sf/carte/echosvg/parser/NumberParser.java b/echosvg-parser/src/main/java/io/sf/carte/echosvg/parser/NumberParser.java
index 381ea0d92..ce0df10f9 100644
--- a/echosvg-parser/src/main/java/io/sf/carte/echosvg/parser/NumberParser.java
+++ b/echosvg-parser/src/main/java/io/sf/carte/echosvg/parser/NumberParser.java
@@ -358,6 +358,7 @@ private float handleCalc() throws IOException {
char[] calcUCRef = { 'A', 'L', 'C', '(' };
char[] calcBuf = new char[4];
+ // For performance, ignore the number of bytes read
reader.read(calcBuf);
if (equalsAny(calcLCRef, calcUCRef, calcBuf)) {
@@ -370,10 +371,8 @@ private float handleCalc() throws IOException {
}
private static boolean equalsAny(char[] lcRef, char[] ucRef, char[] buf) {
- // Assume that lcRef and ucRef have the same length
- if (lcRef.length != buf.length) {
- return false;
- }
+ // The caller must make sure that buf, lcRef and ucRef have the same length
+ assert lcRef.length == buf.length;
for (int i = 0; i < lcRef.length; i++) {
char c = buf[i];
diff --git a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGItem.java b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGItem.java
index 7d829cd75..82633be98 100644
--- a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGItem.java
+++ b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGItem.java
@@ -25,7 +25,7 @@
* @author For later modifications, see Git history.
* @version $Id$
*/
-public abstract class AbstractSVGItem implements SVGItem {
+public abstract class AbstractSVGItem implements SVGItem, Cloneable {
/**
* List the item belongs to.
@@ -90,4 +90,23 @@ public String getValueAsString() {
return itemStringValue;
}
+ /**
+ * Clone this value except for the parent list.
+ *
+ * @return a parentless clone of this value.
+ */
+ @Override
+ public AbstractSVGItem clone() {
+ try {
+ return (AbstractSVGItem) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getValueAsString();
+ }
+
}
diff --git a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGList.java b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGList.java
index f72df511e..04d6c8f4f 100644
--- a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGList.java
+++ b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/AbstractSVGList.java
@@ -377,20 +377,28 @@ protected void revalidate() {
return;
}
- try {
- ListBuilder builder = new ListBuilder(this);
+ valid = true;
- doParse(getValueAsString(), builder);
+ String s = getValueAsString();
+ if (s == null || (s = s.trim()).isEmpty()) {
+ clear(itemList);
+ itemList = new ArrayList<>(1);
+ return;
+ }
+ ListBuilder builder = new ListBuilder(this);
+ try {
+ doParse(s, builder);
+ } catch (ParseException e) {
+ } finally {
List parsedList = builder.getList();
+ clear(itemList);
if (parsedList != null) {
- clear(itemList);
+ itemList = parsedList;
+ } else {
+ itemList = new ArrayList<>(1);
}
- itemList = parsedList;
- } catch (ParseException e) {
- itemList = null;
}
- valid = true;
}
/**
@@ -474,4 +482,21 @@ protected void clear(List list) {
list.clear();
}
+ public void copyTo(AbstractSVGList list) {
+ list.valid = valid;
+ if (itemList != null) {
+ list.itemList = new ArrayList<>(itemList.size());
+ for (SVGItem item : itemList) {
+ item = ((AbstractSVGItem) item).clone();
+ item.setParent(list);
+ list.itemList.add(item);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getValueAsString();
+ }
+
}
diff --git a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/ListBuilder.java b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/ListBuilder.java
index 8266b6c17..4ab82d446 100644
--- a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/ListBuilder.java
+++ b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/ListBuilder.java
@@ -74,4 +74,13 @@ public void item(SVGItem item) {
public void endList() {
}
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ for (SVGItem item : list) {
+ buf.append(item.getValueAsString()).append('\n');
+ }
+ return buf.toString();
+ }
+
}
diff --git a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPathSegItem.java b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPathSegItem.java
index d3bf3d848..a3727ffe4 100644
--- a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPathSegItem.java
+++ b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPathSegItem.java
@@ -163,4 +163,23 @@ public void setY2(float y2) {
this.y2 = y2;
}
+ @Override
+ public SVGPathSegItem clone() {
+ SVGPathSegItem clon = (SVGPathSegItem) super.clone();
+ clon.type = type;
+ clon.letter = letter;
+ clon.x = x;
+ clon.y = y;
+ clon.x1 = x1;
+ clon.y1 = y1;
+ clon.x2 = x2;
+ clon.y2 = y2;
+ clon.r1 = r1;
+ clon.r2 = r2;
+ clon.angle = angle;
+ clon.largeArcFlag = largeArcFlag;
+ clon.sweepFlag = sweepFlag;
+ return clon;
+ }
+
}
diff --git a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPointItem.java b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPointItem.java
index 47dfd3e72..43f695672 100644
--- a/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPointItem.java
+++ b/echosvg-svg-dom/src/main/java/io/sf/carte/echosvg/dom/svg/SVGPointItem.java
@@ -94,4 +94,12 @@ public SVGPoint matrixTransform(SVGMatrix matrix) {
return SVGOMPoint.matrixTransform(this, matrix);
}
+ @Override
+ public SVGPointItem clone() {
+ SVGPointItem clon = (SVGPointItem) super.clone();
+ clon.x = x;
+ clon.y = y;
+ return clon;
+ }
+
}
diff --git a/echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/SVGColor.java b/echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/SVGColor.java
index f78f6a955..871f6c20f 100644
--- a/echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/SVGColor.java
+++ b/echosvg-svggen/src/main/java/io/sf/carte/echosvg/svggen/SVGColor.java
@@ -171,7 +171,8 @@ private static String serializeColor(Color color) {
}
cssColorBuffer.append(')');
} else {
- cssColorBuffer = new StringBuilder(RGB_PREFIX);
+ cssColorBuffer = new StringBuilder(16);
+ cssColorBuffer.append(RGB_PREFIX);
cssColorBuffer.append(color.getRed());
cssColorBuffer.append(COMMA);
cssColorBuffer.append(color.getGreen());
diff --git a/echosvg-test/src/main/java/io/sf/carte/echosvg/test/image/ImageComparator.java b/echosvg-test/src/main/java/io/sf/carte/echosvg/test/image/ImageComparator.java
index dd30f7d6a..fd5de91ed 100644
--- a/echosvg-test/src/main/java/io/sf/carte/echosvg/test/image/ImageComparator.java
+++ b/echosvg-test/src/main/java/io/sf/carte/echosvg/test/image/ImageComparator.java
@@ -20,6 +20,9 @@
import java.awt.Color;
import java.awt.Graphics2D;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
@@ -77,12 +80,12 @@ public class ImageComparator {
public static final short DIFFERENT_DATA_TYPES = 6;
/**
- * The images have have too many below-threshold different pixels.
+ * The images have too many below-threshold different pixels.
*/
public static final short DIFFERENT_PIXELS_BELOW_THRESHOLD = 10;
/**
- * The images have have too many over-threshold different pixels.
+ * The images have too many over-threshold different pixels.
*/
public static final short DIFFERENT_PIXELS_OVER_THRESHOLD = 11;
@@ -186,10 +189,14 @@ public static short compareImages(BufferedImage ref, BufferedImage can, int pixe
}
}
- final int colorSpace = ref.getColorModel().getColorSpace().getType();
- // Compare color spaces
- if (colorSpace != can.getColorModel().getColorSpace().getType()) {
- return DIFFERENT_COLOR_SPACES;
+ if (ref.getType() == BufferedImage.TYPE_CUSTOM) {
+ ColorSpace refColorSpace = ref.getColorModel().getColorSpace();
+ ColorSpace canColorSpace = can.getColorModel().getColorSpace();
+ // Compare color spaces
+ if (refColorSpace.getType() != canColorSpace.getType()
+ || !isSameColorSpace(refColorSpace, canColorSpace)) {
+ return DIFFERENT_COLOR_SPACES;
+ }
}
final int numBands = ref.getSampleModel().getNumBands();
@@ -270,6 +277,26 @@ public static short compareImages(BufferedImage ref, BufferedImage can, int pixe
return result;
}
+ private static boolean isSameColorSpace(ColorSpace refColorSpace, ColorSpace canColorSpace) {
+ if (refColorSpace == canColorSpace) {
+ return true;
+ }
+ if (!(refColorSpace instanceof ICC_ColorSpace) || !(canColorSpace instanceof ICC_ColorSpace)) {
+ // False, otherwise they would be the same object
+ return false;
+ }
+ ICC_Profile refProfile = ((ICC_ColorSpace) refColorSpace).getProfile();
+ ICC_Profile canProfile = ((ICC_ColorSpace) canColorSpace).getProfile();
+ if (refProfile.getProfileClass() != canProfile.getProfileClass()
+ || refProfile.getMajorVersion() != canProfile.getMajorVersion()
+ || refProfile.getMinorVersion() != canProfile.getMinorVersion()
+ || Math.abs(refProfile.getData().length - canProfile.getData().length) > 16) {
+ // Allow up to 16 different bytes of length, to prevent potential padding issues
+ return false;
+ }
+ return true;
+ }
+
private static int computeMaxDiffPixels(float numpxFrac, float allowedPercent, int width,
int height) {
int maxDiffPx;
@@ -625,10 +652,10 @@ public static String getResultDescription(short code) {
desc = "The images use different buffer data types";
break;
case DIFFERENT_PIXELS_BELOW_THRESHOLD:
- desc = "The images have have too many below-threshold different pixels";
+ desc = "The images have too many below-threshold different pixels";
break;
case DIFFERENT_PIXELS_OVER_THRESHOLD:
- desc = "The images have have too many over-threshold different pixels";
+ desc = "The images have too many over-threshold different pixels";
break;
case NO_VARIANTS:
desc = "A variant comparison was executed but no variants were found";
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/parser/test/PathParserFailureTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/parser/test/PathParserFailureTest.java
index f0870f569..ca4213b6f 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/parser/test/PathParserFailureTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/parser/test/PathParserFailureTest.java
@@ -24,6 +24,7 @@
import org.junit.jupiter.api.Test;
+import io.sf.carte.echosvg.parser.ParseException;
import io.sf.carte.echosvg.parser.PathParser;
/**
@@ -44,7 +45,7 @@ private void testPathParserFailure(String sourcePath) {
try {
pp.parse(new StringReader(sourcePath));
fail("Must throw exception for: " + sourcePath);
- } catch (Exception e) {
+ } catch (ParseException e) {
}
}
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractBypassRenderingCheck.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractBypassRenderingCheck.java
index 5e094e615..e1daa589d 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractBypassRenderingCheck.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractBypassRenderingCheck.java
@@ -54,6 +54,8 @@
*/
public class AbstractBypassRenderingCheck {
+ static final String BROWSER_MEDIA = "screen";
+
static final String PRINT_MEDIUM = "print";
void test(String file) throws TranscoderException, IOException {
@@ -61,7 +63,13 @@ void test(String file) throws TranscoderException, IOException {
}
void test(String file, int expectedErrorCount) throws TranscoderException, IOException {
- test(file, SVGRenderingAccuracyTest.DEFAULT_MEDIUM, false, null, null, true, expectedErrorCount);
+ test(file, expectedErrorCount, true);
+ }
+
+ void test(String file, int expectedErrorCount, boolean validating)
+ throws TranscoderException, IOException {
+ test(file, SVGRenderingAccuracyTest.DEFAULT_MEDIUM, false, null, null, validating,
+ expectedErrorCount);
}
/**
@@ -210,13 +218,14 @@ void testUserSheet(String file, boolean validating, int expectedErrorCount)
* reference image.
*
*
- * @param file the SVG file to test.
- * @param medium the target medium ({@code screen}, {@code print},
- * etc).
- * @param darkMode if true, dark mode is enabled in CSS.
- * @param selector the selector to find the SVG element.
- * @param validating if true, the SVG is validated.
- * @param expectedErrorCount the expected number of errors.
+ * @param file the SVG file to test.
+ * @param medium the target medium ({@code screen}, {@code print},
+ * etc).
+ * @param darkMode if true, dark mode is enabled in CSS.
+ * @param backgroundColor the background color.
+ * @param selector the selector to find the SVG element.
+ * @param validating if true, the SVG is validated.
+ * @param expectedErrorCount the expected number of errors.
* @throws TranscoderException
* @throws IOException
*/
@@ -258,7 +267,8 @@ void configureAndRun(BypassRenderingTest runner, String file, boolean darkMode,
* @throws IOException
*/
void testAllInputSources(String file, String medium, boolean darkMode, Color backgroundColor,
- String selector, boolean validating, int expectedErrorCount) throws TranscoderException, IOException {
+ String selector, boolean validating, int expectedErrorCount)
+ throws TranscoderException, IOException {
BypassRenderingTest runner = new BypassRenderingTest(medium, expectedErrorCount);
configureAndRun(runner, file, darkMode, backgroundColor, selector, validating);
@@ -384,6 +394,8 @@ protected void encode(URL srcURL, FileOutputStream fos)
errorHandler.assertErrorCount(expectedErrorCount);
+ checkErrorHandler(errorHandler);
+
fos.getChannel().force(false);
}
@@ -424,7 +436,7 @@ ImageTranscoder getTestImageTranscoder() {
@Override
ImageTranscoder createTestImageTranscoder() {
- return new NoStackTraceTranscoder();
+ return new ErrIgnoreTranscoder();
}
@Override
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractSamplesRendering.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractSamplesRendering.java
index 6aa902703..392b6605b 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractSamplesRendering.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/AbstractSamplesRendering.java
@@ -114,12 +114,13 @@ void test(String file, String lang) throws TranscoderException, IOException {
* reference image.
*
*
- * @param file the SVG file to test.
- * @param validating if true, the SVG is validated.
+ * @param file the SVG file to test.
+ * @param validating if true, the SVG is validated.
* @throws TranscoderException
* @throws IOException if an I/O error occurs.
*/
- void test(String file, boolean validating) throws TranscoderException, IOException {
+ void test(String file, boolean validating)
+ throws TranscoderException, IOException {
RenderingTest runner = new RenderingTest();
runner.setValidating(validating);
runner.setFile(file);
@@ -143,7 +144,7 @@ void test(String file, boolean validating) throws TranscoderException, IOExcepti
*/
void testNVErrIgnore(String file, String media, int expectedErrorCount)
throws TranscoderException, IOException {
- testErrIgnore(file, media, expectedErrorCount, false);
+ testErrIgnore(file, media, false, expectedErrorCount);
}
/**
@@ -154,15 +155,15 @@ void testNVErrIgnore(String file, String media, int expectedErrorCount)
* reference image.
*
*
- * @param file the SVG file to test.
- * @param media the media to test, or {@code null} if default
- * media.
- * @param expectedErrorCount the expected error count.
- * @param validate whether to validate or not.
+ * @param file the SVG file to test.
+ * @param media the media to test, or {@code null} if default
+ * media.
+ * @param validate whether to validate or not.
+ * @param expectedErrorCount the expected error count.
* @throws TranscoderException
* @throws IOException if an I/O error occurs.
*/
- void testErrIgnore(String file, String media, int expectedErrorCount, boolean validate)
+ void testErrIgnore(String file, String media, boolean validate, int expectedErrorCount)
throws TranscoderException, IOException {
RenderingTest runner = new ErrIgnoreTest(expectedErrorCount);
runner.setValidating(validate);
@@ -358,14 +359,24 @@ void testAnim(String file, float[] times) throws TranscoderException, IOExceptio
}
void testAnim(String file, float[] times, boolean validate) throws TranscoderException, IOException {
- for (float time : times) {
- RenderingTest runner = new SVGAnimationRenderingAccuracyTest(time);
+ for (int i = 0; i < times.length; i++) {
+ RenderingTest runner = new SVGAnimationRenderingAccuracyTest(times[i], 0);
runner.setValidating(validate);
runner.setFile(file);
runner.runTest(getBelowThresholdAllowed(), getOverThresholdAllowed());
}
}
+ void testAnim(String file, float[] times, int[] expectedErrorCount)
+ throws TranscoderException, IOException {
+ for (int i = 0; i < times.length; i++) {
+ RenderingTest runner = new SVGAnimationRenderingAccuracyTest(times[i], expectedErrorCount[i]);
+ runner.setValidating(Boolean.FALSE);
+ runner.setFile(file);
+ runner.runTest(getBelowThresholdAllowed(), getOverThresholdAllowed());
+ }
+ }
+
/**
* Dynamic test of the rendering of a SVG file.
*
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ErrIgnoreTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ErrIgnoreTest.java
index c1d8989ef..90122c9af 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ErrIgnoreTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/ErrIgnoreTest.java
@@ -72,19 +72,20 @@ ImageTranscoder createTestImageTranscoder() {
protected void checkErrorHandler(ErrorHandler errorHandler) {
DummyErrorHandler handler = (DummyErrorHandler) errorHandler;
handler.assertErrorCount(expectedErrorCount);
+ super.checkErrorHandler(errorHandler);
}
- class ErrIgnoreTranscoder extends NoStackTraceTranscoder {
+ class ErrIgnoreTranscoder extends InternalPNGTranscoder {
@Override
protected UserAgent createUserAgent() {
- return new TestTranscoderUserAgent();
+ return new BrokenLinkUserAgent();
}
- class TestTranscoderUserAgent
- extends NoStackTraceTranscoder.NoStackTraceTranscoderUserAgent {
+ class BrokenLinkUserAgent
+ extends InternalPNGTranscoder.TestTranscoderUserAgent {
- public TestTranscoderUserAgent() {
+ public BrokenLinkUserAgent() {
super();
}
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/MermaidRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/MermaidRenderingTest.java
index 47e8ec950..02e2df121 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/MermaidRenderingTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/MermaidRenderingTest.java
@@ -25,6 +25,7 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import io.sf.carte.echosvg.test.ScriptUtil;
import io.sf.carte.echosvg.test.TestFonts;
import io.sf.carte.echosvg.transcoder.TranscoderException;
@@ -56,7 +57,8 @@ void testMermaid(String file) throws TranscoderException, IOException {
* @throws TranscoderException
* @throws IOException
*/
- void testMermaid(String file, int expectedErrorCount) throws TranscoderException, IOException {
+ void testMermaid(String file, int expectedErrorCount)
+ throws TranscoderException, IOException {
test(file, SVGRenderingAccuracyTest.DEFAULT_MEDIUM, false, Color.white, null, false,
expectedErrorCount);
}
@@ -64,6 +66,7 @@ void testMermaid(String file, int expectedErrorCount) throws TranscoderException
@BeforeAll
public static void setUpBeforeClass() throws Exception {
TestFonts.loadTestFonts();
+ ScriptUtil.defaultRhinoShutter();
}
@Test
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGAnimationRenderingAccuracyTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGAnimationRenderingAccuracyTest.java
index aefcf19e4..f9c080e93 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGAnimationRenderingAccuracyTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGAnimationRenderingAccuracyTest.java
@@ -18,8 +18,10 @@
*/
package io.sf.carte.echosvg.test.svg;
+import io.sf.carte.echosvg.transcoder.ErrorHandler;
import io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder;
import io.sf.carte.echosvg.transcoder.image.ImageTranscoder;
+import io.sf.carte.echosvg.transcoder.test.DummyErrorHandler;
/**
* Checks for regressions in rendering of a document with animations.
@@ -29,11 +31,14 @@
*/
public class SVGAnimationRenderingAccuracyTest extends RenderingTest {
+ private final int expectedErrorCount;
+
private float time;
- public SVGAnimationRenderingAccuracyTest(float time) {
+ public SVGAnimationRenderingAccuracyTest(float time, int expectedErrorCount) {
super();
this.time = time;
+ this.expectedErrorCount = expectedErrorCount;
}
@Override
@@ -55,7 +60,20 @@ protected CharSequence getImageSuffix() {
ImageTranscoder getTestImageTranscoder() {
ImageTranscoder t = super.getTestImageTranscoder();
t.addTranscodingHint(SVGAbstractTranscoder.KEY_SNAPSHOT_TIME, time);
+ t.setErrorHandler(new DummyErrorHandler());
return t;
}
+ @Override
+ ImageTranscoder createTestImageTranscoder() {
+ return new ErrIgnoreTranscoder();
+ }
+
+ @Override
+ protected void checkErrorHandler(ErrorHandler errorHandler) {
+ DummyErrorHandler handler = (DummyErrorHandler) errorHandler;
+ handler.assertErrorCount(expectedErrorCount);
+ super.checkErrorHandler(errorHandler);
+ }
+
}
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGRenderingAccuracyTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGRenderingAccuracyTest.java
index 2009240e9..e3ecd3032 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGRenderingAccuracyTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SVGRenderingAccuracyTest.java
@@ -47,8 +47,10 @@
* reference image. The test passes if the rasterized SVG and the reference
* image match exactly (i.e., all pixel values are the same).
*
- * @author Vincent Hardy
- * @author For later modifications, see Git history.
+ *
+ * Original author: Vincent Hardy.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public class SVGRenderingAccuracyTest extends AbstractRenderingAccuracyTest {
@@ -353,17 +355,18 @@ protected UserAgent createUserAgent() {
/**
* A Transcoder user agent that does not print a stack trace.
*/
- class FailOnErrorTranscoderUserAgent
+ class TestTranscoderUserAgent
extends SVGAbstractTranscoder.SVGAbstractTranscoderUserAgent {
+ }
+
+ /**
+ * A Transcoder user agent that does not print a stack trace.
+ */
+ class FailOnErrorTranscoderUserAgent extends TestTranscoderUserAgent {
@Override
public void displayError(String message) {
- TranscoderException ex = new TranscoderException(message);
- try {
- InternalPNGTranscoder.this.handler.error(ex);
- } catch (TranscoderException e) {
- throw new RuntimeException(e);
- }
+ super.displayError(message);
throw new RuntimeException(message);
}
@@ -375,11 +378,7 @@ public void displayError(String message) {
*/
@Override
public void displayError(Exception e) {
- try {
- InternalPNGTranscoder.this.handler.error(new TranscoderException(e));
- } catch (TranscoderException ex) {
- throw new RuntimeException(ex);
- }
+ super.displayError(e);
throw new RuntimeException(e);
}
@@ -387,37 +386,11 @@ public void displayError(Exception e) {
}
- /**
- * A PNG transcoder that does not print a stack trace.
- */
- class NoStackTraceTranscoder extends InternalPNGTranscoder {
+ class ErrIgnoreTranscoder extends InternalPNGTranscoder {
@Override
protected UserAgent createUserAgent() {
- return new NoStackTraceTranscoderUserAgent();
- }
-
- /**
- * A Transcoder user agent that does not print a stack trace.
- */
- class NoStackTraceTranscoderUserAgent
- extends SVGAbstractTranscoder.SVGAbstractTranscoderUserAgent {
-
- /**
- * Displays the specified error using the ErrorHandler.
- *
- * And does not print a stack trace.
- *
- */
- @Override
- public void displayError(Exception e) {
- try {
- NoStackTraceTranscoder.this.handler.error(new TranscoderException(e));
- } catch (TranscoderException ex) {
- throw new RuntimeException(ex.getMessage());
- }
- }
-
+ return new InternalPNGTranscoder.TestTranscoderUserAgent();
}
}
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesCanvgRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesCanvgRenderingTest.java
index 3124b37c6..786ea603a 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesCanvgRenderingTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesCanvgRenderingTest.java
@@ -453,7 +453,7 @@ public void testIssue79() throws TranscoderException, IOException {
@Disabled
@Test
public void testIssue82() throws TranscoderException, IOException {
- testErrIgnore("samples/canvg/issue82.svg", BROWSER_MEDIA, 1, true);
+ testErrIgnore("samples/canvg/issue82.svg", BROWSER_MEDIA, true, 1);
}
@Test
@@ -483,12 +483,12 @@ public void testIssue94() throws TranscoderException, IOException {
@Test
public void testIssue97() throws TranscoderException, IOException {
- testNV("samples/canvg/issue97.svg");
+ test("samples/canvg/issue97.svg", false);
}
@Test
public void testIssue98() throws TranscoderException, IOException {
- testNV("samples/canvg/issue98.svg");
+ test("samples/canvg/issue98.svg", false);
}
@Test
@@ -555,7 +555,7 @@ public void testIssue122() throws TranscoderException, IOException {
@Test
public void testIssue125a() throws TranscoderException, IOException {
- testNV("samples/canvg/issue125a.svg");
+ test("samples/canvg/issue125a.svg", false);
}
@Test
@@ -753,13 +753,9 @@ public void testIssue206() throws TranscoderException, IOException {
testNVErrIgnore("samples/canvg/issue206.svg", BROWSER_MEDIA, 66);
}
- /*
- * TODO: investigate.
- */
- @Disabled
@Test
public void testIssue211() throws TranscoderException, IOException {
- testNV("samples/canvg/issue211.svg");
+ testNVErrIgnore("samples/canvg/issue211.svg", BROWSER_MEDIA, 1);
}
@Test
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java
index e1e7f3279..571da1471 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesSpecRenderingTest.java
@@ -498,7 +498,7 @@ public void testPaintsRadialGradientOrientation() throws TranscoderException, IO
*/
@Test
public void testRenderingInvalidViewbox() throws TranscoderException, IOException {
- testErrIgnore("samples/tests/spec/rendering/invalidViewbox.svg", BROWSER_MEDIA, 3, true);
+ testErrIgnore("samples/tests/spec/rendering/invalidViewbox.svg", BROWSER_MEDIA, true, 3);
}
@Test
@@ -528,6 +528,11 @@ public void testRenderingZeroWidthViewbox() throws TranscoderException, IOExcept
/*
* Shapes
*/
+ @Test
+ public void testShapesPolygons() throws TranscoderException, IOException {
+ testNVErrIgnore("samples/tests/spec/shapes/polygons.svg", BROWSER_MEDIA, 2);
+ }
+
@Test
public void testShapesWrongAttr() throws TranscoderException, IOException {
testNVErrIgnore("samples/tests/spec/shapes/wrongAttr.svg", null, 10);
@@ -545,7 +550,7 @@ public void testShapesZero() throws TranscoderException, IOException {
@Test
public void testShapesEmptyShape() throws TranscoderException, IOException {
- test("samples/tests/spec/shapes/emptyShape.svg");
+ test("samples/tests/spec/shapes/emptyShape.svg", true);
}
/*
@@ -621,6 +626,20 @@ public void testStructureToolTips() throws TranscoderException, IOException {
test("samples/tests/spec/structure/toolTips.svg");
}
+ @Test
+ public void testStructureUseAnimEvent() throws TranscoderException, IOException {
+ float[] times = { 0f, 0.3f };
+ int[] errors = { 4, 2 };
+ testAnim("samples/tests/spec/structure/useAnimEvent.svg", times, errors);
+ }
+
+ @Test
+ public void testStructureUseAnimEventXlink() throws TranscoderException, IOException {
+ float[] times = { 0f, 0.3f };
+ int[] errors = { 6, 4 };
+ testAnim("samples/tests/spec/structure/useAnimEventXlink.svg", times, errors);
+ }
+
@Test
public void testStructureUseMultiple() throws TranscoderException, IOException {
test("samples/tests/spec/structure/useMultiple.svg");
@@ -807,6 +826,11 @@ public void testTextAnchor3() throws TranscoderException, IOException {
test("samples/tests/spec/text/textAnchor3.svg");
}
+ @Test
+ public void testTextArabicCharacters() throws TranscoderException, IOException {
+ testNV("samples/tests/spec/text/arabicCharacters.svg");
+ }
+
@Test
public void testTextBiDi() throws TranscoderException, IOException {
test("samples/tests/spec/text/textBiDi.svg");
@@ -1109,11 +1133,31 @@ public void testScriptRemove() throws TranscoderException, IOException {
test("samples/tests/spec/scripting/remove.svg");
}
+ @Test
+ public void testScriptRemoveAttrECR() throws TranscoderException, IOException {
+ testErrIgnore("samples/tests/spec/scripting/removeAttrECR.svg", BROWSER_MEDIA, true, 3);
+ }
+
+ @Test
+ public void testScriptRemoveAttrPathPoly() throws TranscoderException, IOException {
+ testErrIgnore("samples/tests/spec/scripting/removeAttrPathPoly.svg", BROWSER_MEDIA, true, 6);
+ }
+
@Test
public void testScriptRemoveOnclick() throws TranscoderException, IOException {
test("samples/tests/spec/scripting/removeOnclick.svg");
}
+ @Test
+ public void testScriptSetAttributeECR() throws TranscoderException, IOException {
+ testNVErrIgnore("samples/tests/spec/scripting/setAttributeECR.svg", BROWSER_MEDIA, 3);
+ }
+
+ @Test
+ public void testScriptSetAttributePathPoly() throws TranscoderException, IOException {
+ testNVErrIgnore("samples/tests/spec/scripting/setAttributePathPoly.svg", BROWSER_MEDIA, 6);
+ }
+
@Test
public void testScriptText() throws TranscoderException, IOException {
test("samples/tests/spec/scripting/text.svg");
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesWPTRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesWPTRenderingTest.java
new file mode 100644
index 000000000..ca12fc247
--- /dev/null
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/SamplesWPTRenderingTest.java
@@ -0,0 +1,52 @@
+/*
+
+ See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package io.sf.carte.echosvg.test.svg;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import io.sf.carte.echosvg.test.TestFonts;
+import io.sf.carte.echosvg.transcoder.TranscoderException;
+
+/**
+ * This test renders a number of SVG files, generally under the {@code samples/wpt}
+ * directory, and compares the result with a reference image.
+ *
+ * Read the {@code IMAGE_COMPARISONS.md} file for details about the handling of
+ * candidate and reference images.
+ *
+ */
+public class SamplesWPTRenderingTest extends AbstractSamplesRendering {
+
+ @BeforeAll
+ public static void setUpBeforeClass() throws Exception {
+ TestFonts.loadTestFonts();
+ }
+
+ /*
+ * Render path until error
+ */
+ @Test
+ public void testRenderUntilError() throws TranscoderException, IOException {
+ testErrIgnore("samples/wpt/path/error-handling/render-until-error.svg", BROWSER_MEDIA, false, 3);
+ }
+
+}
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java
index f87c40af8..54bc0eba8 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/StyleBypassRenderingTest.java
@@ -23,6 +23,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import io.sf.carte.echosvg.test.ScriptUtil;
import io.sf.carte.echosvg.test.TestFonts;
import io.sf.carte.echosvg.transcoder.TranscoderException;
@@ -46,6 +47,7 @@ public class StyleBypassRenderingTest extends AbstractBypassRenderingCheck {
@BeforeAll
public static void setUpBeforeClass() throws Exception {
TestFonts.loadTestFonts();
+ ScriptUtil.defaultRhinoShutter();
}
@Test
@@ -388,7 +390,7 @@ public void testFilterSvgEnableBackground() throws TranscoderException, IOExcept
*/
/*
- * This test depends on the Batik Font, which is licensing-problematic.
+ * This test depends on the Batik Font, which is license-problematic.
* The license only allows usage of ASF trademarks for attribution purposes.
*/
@Test
@@ -682,7 +684,7 @@ public void testShapesZero() throws TranscoderException, IOException {
@Test
public void testShapesEmptyShape() throws TranscoderException, IOException {
- test("samples/tests/spec/shapes/emptyShape.svg");
+ test("samples/tests/spec/shapes/emptyShape.svg", 0);
}
/*
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLErrIgnoreTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLErrIgnoreTest.java
index f91270308..106c5d546 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLErrIgnoreTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/test/svg/XHTMLErrIgnoreTest.java
@@ -49,15 +49,16 @@ ImageTranscoder getTestImageTranscoder() {
return t;
}
- @Override
- ImageTranscoder createTestImageTranscoder() {
- return new NoStackTraceTranscoder();
- }
-
@Override
protected void checkErrorHandler(ErrorHandler errorHandler) {
DummyErrorHandler handler = (DummyErrorHandler) errorHandler;
handler.assertErrorCount(expectedErrorCount);
+ super.checkErrorHandler(errorHandler);
+ }
+
+ @Override
+ ImageTranscoder createTestImageTranscoder() {
+ return new ErrIgnoreTranscoder();
}
}
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/image/test/AbstractImageTranscoderTest.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/image/test/AbstractImageTranscoderTest.java
index f227dd31f..4b6e212cf 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/image/test/AbstractImageTranscoderTest.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/image/test/AbstractImageTranscoderTest.java
@@ -275,6 +275,10 @@ private boolean compareImage(BufferedImage img) throws TranscoderException, IOEx
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
TranscoderOutput output = new TranscoderOutput(out);
PNGTranscoder t = new PNGTranscoder();
+ Map hints = createTranscodingHints();
+ if (hints != null) {
+ t.setTranscodingHints(hints);
+ }
t.writeImage(img, output);
byte[] imgData = out.toByteArray();
diff --git a/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/test/DummyErrorHandler.java b/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/test/DummyErrorHandler.java
index 76b95d00b..29d431cd4 100644
--- a/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/test/DummyErrorHandler.java
+++ b/echosvg-test/src/test/java/io/sf/carte/echosvg/transcoder/test/DummyErrorHandler.java
@@ -38,8 +38,6 @@ public class DummyErrorHandler implements ErrorHandler {
private List errors = null;
- private int warningCount = 0;
-
@Override
public void error(TranscoderException ex) throws TranscoderException {
prepareErrorList();
@@ -61,7 +59,6 @@ public void fatalError(TranscoderException ex) throws TranscoderException {
@Override
public void warning(TranscoderException ex) throws TranscoderException {
- warningCount++;
}
/**
@@ -82,15 +79,6 @@ public int getErrorCount() {
return errors == null ? 0 : errors.size();
}
- /**
- * Get the warning count.
- *
- * @return the warning count.
- */
- public int getWarningCount() {
- return warningCount;
- }
-
public void assertErrorCount(int expectedErrorCount) {
int errCount = getErrorCount();
if (errCount != expectedErrorCount) {
diff --git a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java
index e3a3166ab..996bace25 100644
--- a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java
+++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/SVGAbstractTranscoder.java
@@ -83,8 +83,10 @@
* the SVG image
*
*
- * @author Thierry Kormann
- * @author For later modifications, see Git history.
+ *
+ * Original author: Thierry Kormann.
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public abstract class SVGAbstractTranscoder extends XMLAbstractTranscoder {
@@ -1287,10 +1289,9 @@ public void displayError(String message) {
@Override
public void displayError(Exception e) {
try {
- e.printStackTrace();
SVGAbstractTranscoder.this.handler.error(new TranscoderException(e));
} catch (TranscoderException ex) {
- throw new RuntimeException(ex.getMessage());
+ throw new RuntimeException(ex);
}
}
diff --git a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/SizingHelper.java b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/SizingHelper.java
index edceac0f9..9634dae1c 100644
--- a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/SizingHelper.java
+++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/SizingHelper.java
@@ -21,12 +21,12 @@
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
+import io.sf.carte.doc.style.css.CSSExpressionValue;
+import io.sf.carte.doc.style.css.CSSTypedValue;
import io.sf.carte.doc.style.css.CSSUnit;
import io.sf.carte.doc.style.css.CSSValue.CssType;
import io.sf.carte.doc.style.css.property.Evaluator;
-import io.sf.carte.doc.style.css.property.ExpressionValue;
import io.sf.carte.doc.style.css.property.StyleValue;
-import io.sf.carte.doc.style.css.property.TypedValue;
import io.sf.carte.doc.style.css.property.ValueFactory;
import io.sf.carte.doc.style.css.property.ValueList;
import io.sf.carte.echosvg.transcoder.TranscoderException;
@@ -139,17 +139,17 @@ static boolean computeRectangle(StyleValue value, float[] numbers) throws DOMExc
if (item.getCssValueType() != CssType.TYPED) {
return false;
}
- TypedValue typed;
+ CSSTypedValue typed;
switch (item.getPrimitiveType()) {
case NUMERIC:
- typed = (TypedValue) item;
+ typed = (CSSTypedValue) item;
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
break;
case EXPRESSION:
- Evaluator eval = new Evaluator();
- typed = eval.evaluateExpression((ExpressionValue) item);
+ Evaluator eval = new Evaluator(CSSUnit.CSS_NUMBER);
+ typed = eval.evaluateExpression((CSSExpressionValue) item);
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
@@ -170,10 +170,10 @@ static float floatValue(String number) throws TranscoderException, DOMException
throw new TranscoderException("Leave value unchanged.");
}
- TypedValue typed;
+ CSSTypedValue typed;
switch (value.getPrimitiveType()) {
case NUMERIC:
- typed = (TypedValue) value;
+ typed = (CSSTypedValue) value;
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
if (CSSUnit.isRelativeLengthUnitType(typed.getUnitType())) {
throw new TranscoderException("Leave value unchanged.");
@@ -183,8 +183,8 @@ static float floatValue(String number) throws TranscoderException, DOMException
}
break;
case EXPRESSION:
- Evaluator eval = new Evaluator();
- typed = eval.evaluateExpression((ExpressionValue) value);
+ Evaluator eval = new Evaluator(CSSUnit.CSS_NUMBER);
+ typed = eval.evaluateExpression((CSSExpressionValue) value);
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
if (CSSUnit.isRelativeLengthUnitType(typed.getUnitType())) {
throw new TranscoderException("Leave value unchanged.");
diff --git a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/util/CSSTranscodingHelper.java b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/util/CSSTranscodingHelper.java
index 7531b9c77..e293f592c 100644
--- a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/util/CSSTranscodingHelper.java
+++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/util/CSSTranscodingHelper.java
@@ -69,6 +69,7 @@
import io.sf.carte.doc.style.css.nsac.CombinatorSelector;
import io.sf.carte.doc.style.css.nsac.Condition;
import io.sf.carte.doc.style.css.nsac.ConditionalSelector;
+import io.sf.carte.doc.style.css.nsac.LexicalUnit;
import io.sf.carte.doc.style.css.nsac.Selector;
import io.sf.carte.doc.style.css.nsac.SelectorList;
import io.sf.carte.doc.style.css.om.AbstractCSSCanvas;
@@ -1101,7 +1102,9 @@ public CSSTypedValue getInitialColor() {
}
@Override
- public boolean supports(String property, CSSValue value) {
+ public boolean supports(String property, LexicalUnit lunit) {
+ ValueFactory valueFactory = new ValueFactory();
+ CSSValue value = valueFactory.createCSSValue(lunit);
if ("color".equalsIgnoreCase(property)
|| "background-color".equalsIgnoreCase(property)) {
return supportsColor(value);
@@ -1183,8 +1186,16 @@ private boolean supports(Condition condition) {
private class MyCanvas extends AbstractCSSCanvas {
+ private CSSDocument document;
+
protected MyCanvas(CSSDocument doc) {
- super(doc);
+ super();
+ document = doc;
+ }
+
+ @Override
+ public CSSDocument getDocument() {
+ return document;
}
@Override
diff --git a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURL.java b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURL.java
index 81b3b38e6..e1a3250ba 100644
--- a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURL.java
+++ b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURL.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
@@ -63,7 +64,9 @@
* @author For later modifications, see Git history.
* @version $Id$
*/
-public class ParsedURL {
+public class ParsedURL implements Serializable {
+
+ private static final long serialVersionUID = 1L;
/**
* The data class we defer most things to.
diff --git a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLData.java b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLData.java
index fbca0fd1d..ac3db7dbc 100644
--- a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLData.java
+++ b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLData.java
@@ -21,6 +21,7 @@
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
@@ -39,7 +40,9 @@
* @author For later modifications, see Git history.
* @version $Id$
*/
-public class ParsedURLData {
+public class ParsedURLData implements Serializable {
+
+ private static final long serialVersionUID = 1L;
protected static final String HTTP_USER_AGENT_HEADER = "User-Agent";
@@ -124,8 +127,8 @@ public static InputStream checkGZIP(InputStream is) throws IOException {
public String contentType = null;
public String contentEncoding = null;
- public InputStream stream = null;
- public boolean hasBeenOpened = false;
+ public transient InputStream stream = null;
+ public transient boolean hasBeenOpened = false;
/**
* The extracted type/subtype from the Content-Type header.
@@ -548,7 +551,6 @@ public InputStream openStream(String userAgent, Iterator mimeTypes) thro
* (may be null)
*/
public InputStream openStreamRaw(String userAgent, Iterator mimeTypes) throws IOException {
-
InputStream ret = openStreamInternal(userAgent, mimeTypes, null);
stream = null;
return ret;
diff --git a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLJarProtocolHandler.java b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLJarProtocolHandler.java
index 1d6221105..505cb1782 100644
--- a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLJarProtocolHandler.java
+++ b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/ParsedURLJarProtocolHandler.java
@@ -25,8 +25,10 @@
* Protocol Handler for the 'jar' protocol. This appears to have the format:
* jar:<URL for jar file>!<path in jar file>
*
- * @author Thomas DeWeese
- * @author For later modifications, see Git history.
+ *
+ * Original author: Thomas DeWeese
+ * For later modifications, see Git history.
+ *
* @version $Id$
*/
public class ParsedURLJarProtocolHandler extends ParsedURLDefaultProtocolHandler {
@@ -42,10 +44,8 @@ public ParsedURLJarProtocolHandler() {
// is an absolute URL.
@Override
public ParsedURLData parseURL(ParsedURL baseURL, String urlStr) {
- String start = urlStr.substring(0, JAR.length() + 1).toLowerCase();
-
- // urlStr is absolute...
- if (start.equals(JAR + ":"))
+ // Check whether urlStr is absolute...
+ if (startsWithJarColon(urlStr))
return parseURL(urlStr);
// It's relative so base it off baseURL.
@@ -58,4 +58,11 @@ public ParsedURLData parseURL(ParsedURL baseURL, String urlStr) {
}
}
+ private boolean startsWithJarColon(String url) {
+ char c;
+ return url.length() > 3 && ((c = url.charAt(0)) == 'j' || c == 'J')
+ && ((c = url.charAt(1)) == 'a' || c == 'A')
+ && ((c = url.charAt(2)) == 'r' || c == 'R') && url.charAt(3) == ':';
+ }
+
}
diff --git a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/NormalizingReader.java b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/NormalizingReader.java
index 805d76f7c..aa4ef78ee 100644
--- a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/NormalizingReader.java
+++ b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/NormalizingReader.java
@@ -26,8 +26,10 @@
* are replaced by \n. The methods of this reader are not synchronized. The
* input is buffered.
*
- * @author Stephane Hillion
- * @author For later modifications, see Git history.
+ *
+ * Original author: Stephane Hillion.
+ * For later modifications, see Git history.
+ *