Skip to content

Commit

Permalink
anim,parser: support calc() values in animated rectangles
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosame committed Jul 29, 2024
1 parent 8150f44 commit c964912
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
import org.w3c.dom.svg.SVGAnimatedRect;
import org.w3c.dom.svg.SVGRect;

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.anim.values.AnimatableRectValue;
import io.sf.carte.echosvg.anim.values.AnimatableValue;
import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
Expand Down Expand Up @@ -228,8 +236,30 @@ public void numberValue(float v) throws ParseException {
}
count++;
}

@Override
public void calcValue(int line, int column) throws ParseException {
throw new CalcParseException("Cannot handle calc().", line, column);
}
});
p.parse(s);
try {
p.parse(s);
} catch (CalcParseException cpe) {
StyleValue value;
ValueFactory factory = new ValueFactory();
try {
value = factory.parseProperty(s);
} catch (Exception e) {
LiveAttributeException ex = new LiveAttributeException(element, localName,
LiveAttributeException.ERR_ATTRIBUTE_MALFORMED, s);
ex.initCause(e);
throw ex;
}
if (!computeRectangle(value, numbers)) {
throw new LiveAttributeException(element, localName,
LiveAttributeException.ERR_ATTRIBUTE_MALFORMED, s);
}
}
x = numbers[0];
y = numbers[1];
w = numbers[2];
Expand All @@ -238,6 +268,42 @@ public void numberValue(float v) throws ParseException {
valid = true;
}

private boolean computeRectangle(StyleValue value, float[] numbers) throws LiveAttributeException {
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 = (TypedValue) item;
switch (item.getPrimitiveType()) {
case NUMERIC:
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
break;
case EXPRESSION:
PercentageEvaluator eval = new PercentageEvaluator();
typed = eval.evaluateExpression((ExpressionValue) typed);
if (typed.getUnitType() != CSSUnit.CSS_NUMBER) {
return false;
}
break;
default:
return false;
}
numbers[i] = typed.getFloatValue(CSSUnit.CSS_NUMBER);
}
return true;
}

/**
* <b>DOM</b>: Implements {@link SVGRect#getX()}.
*/
Expand Down Expand Up @@ -405,4 +471,14 @@ protected void setAnimatedValue(float x, float y, float w, float h) {

}

static class CalcParseException extends ParseException {

private static final long serialVersionUID = 1L;

public CalcParseException(String message, int line, int column) {
super(message, line, column);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
/**
* An handler interface for parsing NumberLists.
*
* @author [email protected]
* @author For later modifications, see Git history.
* <p>
* Original author: [email protected].
* For later modifications, see Git history.
* </p>
* @version $Id$
*/
public interface NumberListHandler {
Expand Down Expand Up @@ -64,4 +66,15 @@ public interface NumberListHandler {
*/
void numberValue(float v) throws ParseException;

/**
* Report that a {@code calc()} function was found.
*
* @param line the line number where it was found.
* @param column the column number where it was found.
* @throws ParseException if the {@code calc()} value cannot be handled.
*/
default void calcValue(int line, int column) throws ParseException {
throw new ParseException("Cannot handle calc().", line, column);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,9 @@ protected void doParse() throws ParseException, IOException {
numberListHandler.endNumberList();
}

@Override
protected void handleCalc(int line, int column) {
numberListHandler.calcValue(line, column);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ protected float parseFloat() throws ParseException, IOException {
boolean expPos = true;

switch (current) {
case 'c':
case 'C':
// calc() ?
return handleCalc();
case '-':
mantPos = false;
// fallthrough
Expand Down Expand Up @@ -330,4 +334,69 @@ public static float buildFloat(int mant, int exp) {
}
}

/**
* Handle a possible {@code calc()} value.
* <p>
* Any handling of numbers must deal with calc().
* </p>
* <p>
* From https://www.w3.org/TR/css3-values/#funcdef-calc
* </p>
* <p>
* [calc()] "can be used wherever [...] &lt;number&gt; or &lt;integer&gt; values
* are allowed."
* </p>
*
* @return 0.
* @throws IOException if an I/O error occurs.
*/
private float handleCalc() throws IOException {
int line = reader.getLine();
int column = reader.getColumn();

char[] calcLCRef = { 'a', 'l', 'c', '(' };
char[] calcUCRef = { 'A', 'L', 'C', '(' };
char[] calcBuf = new char[4];

reader.read(calcBuf);

if (equalsAny(calcLCRef, calcUCRef, calcBuf)) {
handleCalc(line, column);
} else {
reportError("character.unexpected", new Object[] { current }, line, column);
}

return 0.0f;
}

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;
}

for (int i = 0; i < lcRef.length; i++) {
char c = buf[i];
if (c != lcRef[i] && c != ucRef[i]) {
return false;
}
}

return true;
}

/**
* Report that we found a calc() value.
*
* @param line the line number.
* @param column the column number.
*/
protected void handleCalc(int line, int column) {
reportError("character.unexpected", new Object[] { 'c' }, line, column);
}

private void reportError(String key, Object[] args, int line, int column) {
errorHandler.error(new ParseException(createErrorMessage(key, args), line, column));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
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.anim.dom.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGRect;

import io.sf.carte.echosvg.anim.dom.AbstractElement;
import io.sf.carte.echosvg.anim.dom.SVGDOMImplementation;
import io.sf.carte.echosvg.anim.dom.SVGOMAnimatedRect;
import io.sf.carte.echosvg.dom.svg.LiveAttributeException;
import io.sf.carte.echosvg.parser.ParseException;
import io.sf.carte.echosvg.util.SVGConstants;

/**
* To test the SVGOMAnimatedRect.
*
* @author github.com/carlosame
* @author For later modifications, see Git history.
* @version $Id$
*/
public class SVGOMAnimatedRectTest {

// Floating point equality tolerance
private static final float TOL = 1e-5f;

private AbstractElement element;

@BeforeEach
public void setUpBeforeEach() throws DOMException {
DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();
Document document = domImpl.createDocument(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_SVG_TAG,
null);
element = (AbstractElement) document.getDocumentElement();
}

@Test
public void testFloatValues() {
SVGOMAnimatedRect animRect = createAnimatedRect("-10 -20 500 450");
SVGRect rect = animRect.getBaseVal();
assertNotNull(rect);

assertEquals(-10f, rect.getX(), TOL);
assertEquals(-20f, rect.getY(), TOL);
assertEquals(500f, rect.getWidth(), TOL);
assertEquals(450f, rect.getHeight(), TOL);
}

@Test
public void testFloatValuesComma() {
SVGOMAnimatedRect animRect = createAnimatedRect("-1.1e1, -20.2, 5e2, 450.07");
SVGRect rect = animRect.getBaseVal();
assertNotNull(rect);

assertEquals(-11f, rect.getX(), TOL);
assertEquals(-20.2f, rect.getY(), TOL);
assertEquals(500f, rect.getWidth(), TOL);
assertEquals(450.07f, rect.getHeight(), TOL);
}

@Test
public void testUnitValues() throws ParseException {
SVGOMAnimatedRect animRect = createAnimatedRect("-10 -20 500 450mm");
SVGRect rect = animRect.getBaseVal();

assertThrows(ParseException.class, () -> rect.getX());
}

@Test
public void test3Values() throws LiveAttributeException {
SVGOMAnimatedRect animRect = createAnimatedRect("-10 -20 500");
SVGRect rect = animRect.getBaseVal();

assertThrows(LiveAttributeException.class, () -> rect.getX());
}

@Test
public void test2Values() throws LiveAttributeException {
SVGOMAnimatedRect animRect = createAnimatedRect("-10 -20 ");
SVGRect rect = animRect.getBaseVal();

assertThrows(LiveAttributeException.class, () -> rect.getX());
}

@Test
public void test1Value() throws LiveAttributeException {
SVGOMAnimatedRect animRect = createAnimatedRect(" 500 ");
SVGRect rect = animRect.getBaseVal();

assertThrows(LiveAttributeException.class, () -> rect.getX());
}

/**
* Test rectangles with calc() values.
* <p>
* From the CSS Values Level 3 specification:
* </p>
* <p>
* [<code>calc()</code>] "can be used wherever &lt;length&gt;,
* &lt;frequency&gt;, &lt;angle&gt;, &lt;time&gt;, &lt;percentage&gt;,
* &lt;number&gt;, or &lt;integer&gt; values are allowed."
* </p>
*/
@Test
public void testCalcValue() {
SVGOMAnimatedRect animRect = createAnimatedRect("-10 -2e1 calc(5*1e2) 450");
SVGRect rect = animRect.getBaseVal();
assertNotNull(rect);

assertEquals(-10f, rect.getX(), TOL);
assertEquals(-20f, rect.getY(), TOL);
assertEquals(500f, rect.getWidth(), TOL);
assertEquals(450f, rect.getHeight(), TOL);
}

@Test
public void testCalcMixedCase() {
SVGOMAnimatedRect animRect = createAnimatedRect(
"Calc(10 - 20) CaLc(10 - 30) CALC(5*100) cALC(400 + 50)");
SVGRect rect = animRect.getBaseVal();
assertNotNull(rect);

assertEquals(-10f, rect.getX(), TOL);
assertEquals(-20f, rect.getY(), TOL);
assertEquals(500f, rect.getWidth(), TOL);
assertEquals(450f, rect.getHeight(), TOL);
}

private SVGOMAnimatedRect createAnimatedRect(String attrValue) {
element.setAttributeNS(SVGConstants.SVG_NAMESPACE_URI, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE,
attrValue);
SVGOMAnimatedRect animRect = new SVGOMAnimatedRect(element, SVGConstants.SVG_NAMESPACE_URI,
SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, null);
return animRect;
}

}

0 comments on commit c964912

Please sign in to comment.