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. + *

* @version $Id$ */ public abstract class NormalizingReader extends Reader { @@ -40,6 +42,7 @@ public abstract class NormalizingReader extends Reader { * @param len Maximum number of characters to read * @return The number of characters read, or -1 if the end of the stream has * been reached + * @throws IOException If an I/O error occurs */ @Override public int read(char[] cbuf, int off, int len) throws IOException { @@ -51,12 +54,12 @@ public int read(char[] cbuf, int off, int len) throws IOException { if (c == -1) { return -1; } - int result = 0; - do { + cbuf[off] = (char) c; + int result = 1; + while (result < len && (c = read()) != -1) { cbuf[result + off] = (char) c; result++; - c = read(); - } while (c != -1 && result < len); + } return result; } diff --git a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/StreamNormalizingReader.java b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/StreamNormalizingReader.java index ebeddcf30..7208fab4a 100644 --- a/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/StreamNormalizingReader.java +++ b/echosvg-util/src/main/java/io/sf/carte/echosvg/util/io/StreamNormalizingReader.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.io.Reader; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import io.sf.carte.echosvg.util.EncodingUtilities; @@ -157,7 +158,7 @@ public void close() throws IOException { * Creates the CharDecoder mapped with the given encoding name. */ protected CharDecoder createCharDecoder(InputStream is, String enc) throws IOException { - CharDecoderFactory cdf = charDecoderFactories.get(enc.toUpperCase()); + CharDecoderFactory cdf = charDecoderFactories.get(enc.toUpperCase(Locale.ROOT)); if (cdf != null) { return cdf.createCharDecoder(is); } diff --git a/echosvg-util/src/test/java/io/sf/carte/echosvg/util/ParsedURLTest.java b/echosvg-util/src/test/java/io/sf/carte/echosvg/util/ParsedURLTest.java index a876a293d..8680c39ed 100644 --- a/echosvg-util/src/test/java/io/sf/carte/echosvg/util/ParsedURLTest.java +++ b/echosvg-util/src/test/java/io/sf/carte/echosvg/util/ParsedURLTest.java @@ -75,6 +75,14 @@ public void testJar() { "jar:file:dir/file.jar!/p/a/t/c/h/new.svg"); runTest("jar:file:dir/file.jar!/p/a/t/h/init.svg", "../c/h/new.svg#foo", "jar:file:dir/file.jar!/p/a/t/c/h/new.svg#foo"); + runTest("jar:file:dir/file.jar!/p/a/t/h/init.svg", "#id", + "jar:file:dir/file.jar!/p/a/t/h/init.svg#id"); + runTest("JAR:file:dir/file.jar!/p/a/t/h/init.svg", "#id", + "jar:file:dir/file.jar!/p/a/t/h/init.svg#id"); + runTest("jaR:file:dir/file.jar!/p/a/t/h/init.svg", "#id", + "jar:file:dir/file.jar!/p/a/t/h/init.svg#id"); + runTest("JAr:file:dir/file.jar!/p/a/t/h/init.svg", "#", + "jar:file:dir/file.jar!/p/a/t/h/init.svg"); } @Test diff --git a/gradle.properties b/gradle.properties index 59c8f269d..ad53e1ba6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,15 @@ # Dependency versions -checkstyleVersion=10.17.0 -grGitVersion=5.2.2 -css4jVersion=4.3.1 -css4jAwtVersion=4.0 +checkstyleVersion=10.21.1 +grGitVersion=5.3.0 +css4jVersion=5.1 +css4jAwtVersion=5.0 xmlDtdVersion=4.3 rhinoVersion=1.7.15 legacyColorsVersion=1.0 svgomVersion=1.0.1 xmlApisVersion=1.4.01 -commonsIOVersion=2.16.1 +commonsIOVersion=2.18.0 htmlParserVersion=1.4.16 -jclfTextVersion=5.0.1 -junitVersion=5.10.3 +jclfTextVersion=5.0.2 +junitVersion=5.11.4 jmhVersion=1.37 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0e5..e18bc253b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf133..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30dbd..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/samples/tests/spec/scripting/path_pathSegList1.svg b/samples/tests/spec/scripting/path_pathSegList1.svg index 06be551bf..c0018470c 100644 --- a/samples/tests/spec/scripting/path_pathSegList1.svg +++ b/samples/tests/spec/scripting/path_pathSegList1.svg @@ -40,14 +40,15 @@ + + + Test missing or invalid shape attributes + Missing attributes may disable the rendering of the element + + + + + + + + + + + + + <rect> + <circle> + <ellipse> + + + + + + + + + + + missing width + missing height + invalid height + + + + + + + + + missing r + invalid r + + + + + + + + + + missing rx + missing ry + invalid rx + + + + + diff --git a/samples/tests/spec/scripting/removeAttrPathPoly.svg b/samples/tests/spec/scripting/removeAttrPathPoly.svg new file mode 100644 index 000000000..ee7c52b8d --- /dev/null +++ b/samples/tests/spec/scripting/removeAttrPathPoly.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + +Script unsets or sets invalid shape attributes + + + + + + + + + + Test missing or wrong shape attributes + Missing attributes may disable the rendering of the element + + + + + + + + + + + + + <path> + <polygon> + <polyline> + + + + + + + + + + + missing d + invalid d + partly invalid d + + + + + + + + + + missing points + invalid points + partly invalid points + + + + + + + + + + missing points + invalid points + partly invalid points + + + + + diff --git a/samples/tests/spec/scripting/setAttributeECR.svg b/samples/tests/spec/scripting/setAttributeECR.svg new file mode 100644 index 000000000..d9c42b122 --- /dev/null +++ b/samples/tests/spec/scripting/setAttributeECR.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + +Script sets valid shape attributes + + + + + + + + + + Test missing or invalid shape attributes + Missing or invalid attributes are corrected by script + + + + + + + + + + + + + <rect> + <circle> + <ellipse> + + + + + + + + + + + missing width + missing height + invalid height + + + + + + + + + missing r + invalid r + + + + + + + + + + missing rx + missing ry + invalid rx + + + + + diff --git a/samples/tests/spec/scripting/setAttributePathPoly.svg b/samples/tests/spec/scripting/setAttributePathPoly.svg new file mode 100644 index 000000000..0f3fc24f7 --- /dev/null +++ b/samples/tests/spec/scripting/setAttributePathPoly.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + +Script sets valid shape attributes + + + + + + + + + + Test missing or wrong shape attributes + Missing or invalid attributes are corrected by script + + + + + + + + + + + + + <path> + <polygon> + <polyline> + + + + + + + + + + + missing d + invalid d + partly invalid d + + + + + + + + + + missing points + invalid points + partly invalid points + + + + + + + + + + missing points + invalid points + partly invalid points + + + + + diff --git a/samples/tests/spec/shapes/polygons.svg b/samples/tests/spec/shapes/polygons.svg new file mode 100644 index 000000000..4e882fbb4 --- /dev/null +++ b/samples/tests/spec/shapes/polygons.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + Polygons + Polylines + + diff --git a/samples/tests/spec/structure/useAnimEvent.svg b/samples/tests/spec/structure/useAnimEvent.svg new file mode 100644 index 000000000..6863285de --- /dev/null +++ b/samples/tests/spec/structure/useAnimEvent.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + Animate <use> + + + + + + + + Animate <use> + Check that the <use> element behaves correctly when animated + + + + + + + + + + + + + + + + + No valid href + Nonexistent href + + <animate> + Event + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/tests/spec/structure/useAnimEventXlink.svg b/samples/tests/spec/structure/useAnimEventXlink.svg new file mode 100644 index 000000000..82c3dfdab --- /dev/null +++ b/samples/tests/spec/structure/useAnimEventXlink.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + Animate <use> + + + + + + + + Animate <use> + Check that the <use> element behaves correctly when animated + + + + + + + + + + + + + + + + + No valid xlink:href + Nonexistent xlink:href + + <animate> + Event + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/tests/spec/text/arabicCharacters.svg b/samples/tests/spec/text/arabicCharacters.svg new file mode 100644 index 000000000..2cfb33c20 --- /dev/null +++ b/samples/tests/spec/text/arabicCharacters.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + Arabic Shadda : شارع طَوِي أم الظِّبا + + diff --git a/samples/wpt/path/error-handling/render-until-error.svg b/samples/wpt/path/error-handling/render-until-error.svg new file mode 100644 index 000000000..07ae6b72e --- /dev/null +++ b/samples/wpt/path/error-handling/render-until-error.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-references/io/sf/carte/echosvg/transcoder/image/px2mm72dpi.png b/test-references/io/sf/carte/echosvg/transcoder/image/px2mm72dpi.png index b38595bf0..3ad2005af 100644 Binary files a/test-references/io/sf/carte/echosvg/transcoder/image/px2mm72dpi.png and b/test-references/io/sf/carte/echosvg/transcoder/image/px2mm72dpi.png differ diff --git a/test-references/samples/canvg/issue211.png b/test-references/samples/canvg/issue211.png new file mode 100644 index 000000000..7d5e0468b Binary files /dev/null and b/test-references/samples/canvg/issue211.png differ diff --git a/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsBoth.png b/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsBoth.png new file mode 100644 index 000000000..3fba29d18 Binary files /dev/null and b/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsBoth.png differ diff --git a/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsChildSVG.png b/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsChildSVG.png new file mode 100644 index 000000000..1c5cdc6ac Binary files /dev/null and b/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsChildSVG.png differ diff --git a/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsD.png b/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsD.png new file mode 100644 index 000000000..1c5cdc6ac Binary files /dev/null and b/test-references/samples/tests/spec/fonts/accepted-variation/fontGlyphsD.png differ diff --git a/test-references/samples/tests/spec/scripting/removeAttrECR.png b/test-references/samples/tests/spec/scripting/removeAttrECR.png new file mode 100644 index 000000000..5fb73bc47 Binary files /dev/null and b/test-references/samples/tests/spec/scripting/removeAttrECR.png differ diff --git a/test-references/samples/tests/spec/scripting/removeAttrPathPoly.png b/test-references/samples/tests/spec/scripting/removeAttrPathPoly.png new file mode 100644 index 000000000..4b4dcf6e7 Binary files /dev/null and b/test-references/samples/tests/spec/scripting/removeAttrPathPoly.png differ diff --git a/test-references/samples/tests/spec/scripting/setAttributeECR.png b/test-references/samples/tests/spec/scripting/setAttributeECR.png new file mode 100644 index 000000000..86436a36f Binary files /dev/null and b/test-references/samples/tests/spec/scripting/setAttributeECR.png differ diff --git a/test-references/samples/tests/spec/scripting/setAttributePathPoly.png b/test-references/samples/tests/spec/scripting/setAttributePathPoly.png new file mode 100644 index 000000000..920bff379 Binary files /dev/null and b/test-references/samples/tests/spec/scripting/setAttributePathPoly.png differ diff --git a/test-references/samples/tests/spec/shapes/polygons.png b/test-references/samples/tests/spec/shapes/polygons.png new file mode 100644 index 000000000..982cbc7a0 Binary files /dev/null and b/test-references/samples/tests/spec/shapes/polygons.png differ diff --git a/test-references/samples/tests/spec/structure/useAnimEvent-t0.3.png b/test-references/samples/tests/spec/structure/useAnimEvent-t0.3.png new file mode 100644 index 000000000..3775c9ee0 Binary files /dev/null and b/test-references/samples/tests/spec/structure/useAnimEvent-t0.3.png differ diff --git a/test-references/samples/tests/spec/structure/useAnimEvent.png b/test-references/samples/tests/spec/structure/useAnimEvent.png new file mode 100644 index 000000000..a9aee202e Binary files /dev/null and b/test-references/samples/tests/spec/structure/useAnimEvent.png differ diff --git a/test-references/samples/tests/spec/structure/useAnimEventXlink-t0.3.png b/test-references/samples/tests/spec/structure/useAnimEventXlink-t0.3.png new file mode 100644 index 000000000..b5e7bcdce Binary files /dev/null and b/test-references/samples/tests/spec/structure/useAnimEventXlink-t0.3.png differ diff --git a/test-references/samples/tests/spec/structure/useAnimEventXlink.png b/test-references/samples/tests/spec/structure/useAnimEventXlink.png new file mode 100644 index 000000000..a7a1e96b1 Binary files /dev/null and b/test-references/samples/tests/spec/structure/useAnimEventXlink.png differ diff --git a/test-references/samples/tests/spec/text/arabicCharacters.png b/test-references/samples/tests/spec/text/arabicCharacters.png new file mode 100644 index 000000000..93145a4b3 Binary files /dev/null and b/test-references/samples/tests/spec/text/arabicCharacters.png differ diff --git a/test-references/samples/tests/spec2/foreign/mermaid-mindmap.png b/test-references/samples/tests/spec2/foreign/mermaid-mindmap.png index b55efc896..2772d9869 100644 Binary files a/test-references/samples/tests/spec2/foreign/mermaid-mindmap.png and b/test-references/samples/tests/spec2/foreign/mermaid-mindmap.png differ diff --git a/test-references/samples/tests/spec2/foreign/mermaid-timeline.png b/test-references/samples/tests/spec2/foreign/mermaid-timeline.png index 43021a954..968f07158 100644 Binary files a/test-references/samples/tests/spec2/foreign/mermaid-timeline.png and b/test-references/samples/tests/spec2/foreign/mermaid-timeline.png differ diff --git a/test-references/samples/wpt/LICENSE.md b/test-references/samples/wpt/LICENSE.md new file mode 100644 index 000000000..39c46d03a --- /dev/null +++ b/test-references/samples/wpt/LICENSE.md @@ -0,0 +1,11 @@ +# The 3-Clause BSD License + +Copyright © web-platform-tests contributors + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test-references/samples/wpt/path/error-handling/render-until-error.png b/test-references/samples/wpt/path/error-handling/render-until-error.png new file mode 100644 index 000000000..88f41af97 Binary files /dev/null and b/test-references/samples/wpt/path/error-handling/render-until-error.png differ diff --git a/test-resources/io/sf/carte/echosvg/css/JarPolicyTest.jar b/test-resources/io/sf/carte/echosvg/css/JarPolicyTest.jar index 6c7313410..4456c49dd 100644 Binary files a/test-resources/io/sf/carte/echosvg/css/JarPolicyTest.jar and b/test-resources/io/sf/carte/echosvg/css/JarPolicyTest.jar differ