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 88b93a3e4..d21b69cc4 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
@@ -63,6 +63,7 @@
import io.sf.carte.echosvg.gvt.CanvasGraphicsNode;
import io.sf.carte.echosvg.gvt.CompositeGraphicsNode;
import io.sf.carte.echosvg.gvt.GraphicsNode;
+import io.sf.carte.echosvg.transcoder.impl.SizingHelper;
import io.sf.carte.echosvg.transcoder.keys.BooleanKey;
import io.sf.carte.echosvg.transcoder.keys.FloatKey;
import io.sf.carte.echosvg.transcoder.keys.LengthKey;
@@ -389,6 +390,9 @@ private SVGOMDocument importAsSVGDocument(Document document, String uri)
}
// Set the right namespace
docElm = replaceSVGRoot(docElm);
+ } else {
+ // We are in CSS context
+ SizingHelper.defaultDimensions(docElm);
}
}
}
@@ -421,6 +425,11 @@ private Element replaceSVGRoot(Element docElm) {
XMLConstants.XMLNS_ATTRIBUTE, SVGConstants.SVG_NAMESPACE_URI);
Element newRoot = replaceNamespace(docElm);
docElm.getParentNode().replaceChild(newRoot, docElm);
+
+ // We are in CSS context and need to apply some rules
+ // see https://svgwg.org/specs/integration/#svg-css-sizing
+ SizingHelper.defaultDimensions(newRoot);
+
return newRoot;
}
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
new file mode 100644
index 000000000..9b8203b9f
--- /dev/null
+++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/SizingHelper.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.transcoder.impl;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
+
+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.ExpressionValue;
+import io.sf.carte.doc.style.css.property.PercentageEvaluator;
+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;
+import io.sf.carte.echosvg.util.CSSConstants;
+
+/**
+ * Helper methods for SVG sizing.
+ */
+public class SizingHelper {
+
+ // Prevent instantiation
+ SizingHelper() {
+ super();
+ }
+
+ /**
+ * Apply the CSS context rules.
+ *
+ * To resolve 'auto' value on ‘svg’ element if the ‘viewBox’ attribute is not
+ * specified:
+ *
+ *
+ *
Use author specified width and height ‘svg’ element attributes.
+ *
If any of the sizing attributes are missing, resolve the missing ‘svg’
+ * element width to '300px' and missing height to '150px' (using CSS 2.1
+ * replaced elements size calculation).
+ *
+ *
+ * To resolve 'auto' value on ‘svg’ element if the ‘viewBox’ attribute is
+ * specified:
+ *
+ *
+ *
Use the ‘viewBox’ attribute to calculate the intrinsic aspect ratio of
+ * the ‘svg’ element.
+ *
If the width or height attributes are not specified - keep not specified
+ * width or height as 'auto'.
+ *
+ *
+ * @param svgRoot the outer svg element.
+ */
+ public static void defaultDimensions(Element svgRoot) {
+ String width = svgRoot.getAttribute("width").trim();
+ String height = svgRoot.getAttribute("height").trim();
+
+ if (width.isEmpty() || CSSConstants.CSS_AUTO_VALUE.equalsIgnoreCase(width)) {
+ defaultWidth(svgRoot, height);
+ }
+
+ if (height.isEmpty() || CSSConstants.CSS_AUTO_VALUE.equalsIgnoreCase(height)) {
+ String viewBox = svgRoot.getAttribute("viewBox").trim();
+ if (viewBox.isEmpty()) {
+ svgRoot.setAttribute("height", "150px");
+ }
+ }
+ }
+
+ private static void defaultWidth(Element svgRoot, String height) {
+ String viewBox = svgRoot.getAttribute("viewBox").trim();
+ String width;
+ if (viewBox.isEmpty()) {
+ width = "300px";
+ } else if (!height.isEmpty() && !CSSConstants.CSS_AUTO_VALUE.equalsIgnoreCase(height)) {
+ try {
+ float ratio = computeAspectRatio(viewBox);
+ float fh = floatValue(height);
+ if (!Float.isNaN(ratio) && !Float.isNaN(fh)) {
+ width = Float.toString(ratio * fh);
+ } else {
+ width = "300px";
+ }
+ } catch (TranscoderException e) {
+ // Leave as it is, maybe the value has to be computed
+ return;
+ } catch (Exception e) {
+ // Any other exception: set to 300
+ width = "300px";
+ }
+ } else {
+ return;
+ }
+
+ svgRoot.setAttribute("width", width);
+ }
+
+ static float computeAspectRatio(String viewBox) throws DOMException {
+ ValueFactory factory = new ValueFactory();
+ StyleValue value = factory.parseProperty(viewBox);
+ float[] numbers = new float[4];
+ if (!computeRectangle(value, numbers)) {
+ throw new DOMException(DOMException.SYNTAX_ERR, "Wrong viewBox attribute.");
+ }
+ return numbers[2] / numbers[3];
+ }
+
+ static boolean computeRectangle(StyleValue value, float[] numbers) throws DOMException {
+ if (value.getCssValueType() != CssType.LIST) {
+ return false;
+ }
+ ValueList list = (ValueList) value;
+ if (list.getLength() != 4) {
+ return false;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ StyleValue item = list.item(i);
+ if (item.getCssValueType() != CssType.TYPED) {
+ return false;
+ }
+ TypedValue typed;
+ switch (item.getPrimitiveType()) {
+ case NUMERIC:
+ typed = (TypedValue) item;
+ if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
+ return false;
+ }
+ break;
+ case EXPRESSION:
+ PercentageEvaluator eval = new PercentageEvaluator();
+ typed = eval.evaluateExpression((ExpressionValue) item);
+ if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ numbers[i] = typed.getFloatValue(CSSUnit.CSS_NUMBER);
+ }
+ return true;
+ }
+
+ static float floatValue(String number) throws TranscoderException, DOMException {
+ ValueFactory factory = new ValueFactory();
+ StyleValue value = factory.parseProperty(number);
+
+ if (value.getCssValueType() != CssType.TYPED) {
+ throw new TranscoderException("Leave value unchanged.");
+ }
+
+ TypedValue typed;
+ switch (value.getPrimitiveType()) {
+ case NUMERIC:
+ typed = (TypedValue) value;
+ if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
+ if (CSSUnit.isRelativeLengthUnitType(typed.getUnitType())) {
+ throw new TranscoderException("Leave value unchanged.");
+ }
+ // Either an absolute length or a wrong value
+ return typed.getFloatValue(CSSUnit.CSS_PX);
+ }
+ break;
+ case EXPRESSION:
+ PercentageEvaluator eval = new PercentageEvaluator();
+ typed = eval.evaluateExpression((ExpressionValue) value);
+ if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
+ if (CSSUnit.isRelativeLengthUnitType(typed.getUnitType())) {
+ throw new TranscoderException("Leave value unchanged.");
+ }
+ // Either an absolute length or a wrong value
+ return typed.getFloatValue(CSSUnit.CSS_PX);
+ }
+ break;
+ default:
+ throw new DOMException(DOMException.SYNTAX_ERR, "Wrong dimension.");
+ }
+
+ return typed.getFloatValue(CSSUnit.CSS_NUMBER);
+ }
+
+}
diff --git a/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/package-info.java b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/package-info.java
new file mode 100644
index 000000000..baced173b
--- /dev/null
+++ b/echosvg-transcoder/src/main/java/io/sf/carte/echosvg/transcoder/impl/package-info.java
@@ -0,0 +1,23 @@
+/*
+
+ 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.
+
+ */
+
+/**
+ * Implementation classes, do not use outside of EchoSVG.
+ */
+package io.sf.carte.echosvg.transcoder.impl;
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 9eab8ad58..7531b9c77 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
@@ -90,6 +90,7 @@
import io.sf.carte.echosvg.transcoder.TranscodingHints;
import io.sf.carte.echosvg.transcoder.image.ImageTranscoder;
import io.sf.carte.echosvg.transcoder.image.PNGTranscoder;
+import io.sf.carte.echosvg.transcoder.impl.SizingHelper;
import io.sf.carte.echosvg.util.ParsedURL;
import io.sf.carte.echosvg.util.SVGConstants;
import io.sf.carte.util.agent.AgentUtil;
@@ -721,6 +722,10 @@ private void transcodeDOMDocument(DOMDocument document, TranscoderOutput output,
}
}
+ // We are in CSS context, need to apply some rules
+ // see https://svgwg.org/specs/integration/#svg-css-sizing
+ SizingHelper.defaultDimensions(svgRoot);
+
boolean isSVG12;
String version = svgRoot.getAttribute("version");
if (version.length() == 0) {
diff --git a/samples/tests/spec/styling/css2.html b/samples/tests/spec/styling/css2.html
index 8ec378be4..f7685cfeb 100644
--- a/samples/tests/spec/styling/css2.html
+++ b/samples/tests/spec/styling/css2.html
@@ -34,7 +34,7 @@
-