/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ package org.lobobrowser.html.svgimpl; import java.util.ArrayList; import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.lobobrowser.html.HtmlAttributeProperties; import org.lobobrowser.w3c.svg.SVGAnimatedNumber; import org.lobobrowser.w3c.svg.SVGAnimatedPathData; import org.lobobrowser.w3c.svg.SVGAnimatedTransformList; import org.lobobrowser.w3c.svg.SVGPathElement; import org.lobobrowser.w3c.svg.SVGPathSegArcAbs; import org.lobobrowser.w3c.svg.SVGPathSegArcRel; import org.lobobrowser.w3c.svg.SVGPathSegClosePath; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoCubicAbs; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoCubicRel; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoCubicSmoothAbs; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoCubicSmoothRel; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoQuadraticAbs; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoQuadraticRel; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoQuadraticSmoothAbs; import org.lobobrowser.w3c.svg.SVGPathSegCurvetoQuadraticSmoothRel; import org.lobobrowser.w3c.svg.SVGPathSegLinetoAbs; import org.lobobrowser.w3c.svg.SVGPathSegLinetoHorizontalAbs; import org.lobobrowser.w3c.svg.SVGPathSegLinetoHorizontalRel; import org.lobobrowser.w3c.svg.SVGPathSegLinetoRel; import org.lobobrowser.w3c.svg.SVGPathSegLinetoVerticalAbs; import org.lobobrowser.w3c.svg.SVGPathSegLinetoVerticalRel; import org.lobobrowser.w3c.svg.SVGPathSegList; import org.lobobrowser.w3c.svg.SVGPathSegMovetoAbs; import org.lobobrowser.w3c.svg.SVGPathSegMovetoRel; import org.lobobrowser.w3c.svg.SVGPoint; /** * <p> * Paths represent the outline of a shape which can be filled, stroked, used as * a clipping path, or any combination of the three. (See Filling, Stroking and * Paint Servers and Clipping, Masking and Compositing.) * </p> * <p> * A path is described using the concept of a current point. In an analogy with * drawing on paper, the current point can be thought of as the location of the * pen. The position of the pen can be changed, and the outline of a shape (open * or closed) can be traced by dragging the pen in either straight lines or * curves. * </p> * <p> * Paths represent the geometry of the outline of an object, defined in terms of * moveto (set a new current point), lineto (draw a straight line), curveto * (draw a curve using a cubic B�zier), arc (elliptical or circular arc) and * closepath (close the current shape by drawing a line to the last moveto) * elements. Compound paths (i.e., a path with multiple subpaths) are possible * to allow effects such as "donut holes" in objects. * </p> * */ public class SVGPathElementImpl extends SVGSVGElementImpl implements SVGPathElement { private SVGAnimatedPathData pathData = new SVGAnimatedPathDataImpl(); public SVGPathElementImpl(String name) { super(name); } @Override public SVGAnimatedTransformList getTransform() { return new SVGAnimatedTransformListImpl(this.getAttribute(HtmlAttributeProperties.TRANSFORM)); } @Override public SVGPathSegList getPathSegList() { ArrayList<String> parts = parts(); for (int a = 0; a<parts.size(); a++) { a = interpretDValue(parts, a); } return pathData.getPathSegList(); } @Override public SVGPathSegList getNormalizedPathSegList() { ArrayList<String> parts = parts(); for (int a = 0; a<parts.size(); a++) { a = interpretDValue(parts, a); } return pathData.getPathSegList(); } @Override public SVGPathSegList getAnimatedPathSegList() { ArrayList<String> parts = parts(); for (int a = 0; a<parts.size(); a++) { a = interpretDValue(parts, a); } return pathData.getPathSegList(); } @Override public SVGPathSegList getAnimatedNormalizedPathSegList() { ArrayList<String> parts = parts(); for (int a = 0; a<parts.size(); a++) { a = interpretDValue(parts, a); } return pathData.getPathSegList(); } @Override public SVGAnimatedNumber getPathLength() { // TODO Auto-generated method stub return null; } @Override public float getTotalLength() { return pathData.getPathSegList().getNumberOfItems(); } @Override public SVGPoint getPointAtLength(float distance) { // TODO Auto-generated method stub return null; } @Override public int getPathSegAtLength(float distance) { // TODO Auto-generated method stub return 0; } @Override public SVGPathSegClosePath createSVGPathSegClosePath() { SVGPathSegClosePath pathSeg = new SVGPathSegClosePathImpl(); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegMovetoAbs createSVGPathSegMovetoAbs(float x, float y) { SVGPathSegMovetoAbs pathSeg = new SVGPathSegMovetoAbsImpl(x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegMovetoRel createSVGPathSegMovetoRel(float x, float y) { SVGPathSegMovetoRel pathSeg = new SVGPathSegMovetoRelImpl(x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegLinetoAbs createSVGPathSegLinetoAbs(float x, float y) { SVGPathSegLinetoAbs pathSeg = new SVGPathSegLinetoAbsImpl(x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegLinetoRel createSVGPathSegLinetoRel(float x, float y) { SVGPathSegLinetoRel pathSeg = new SVGPathSegLinetoRelImpl(x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoCubicAbs createSVGPathSegCurvetoCubicAbs(float x1, float y1, float x2, float y2, float x, float y) { SVGPathSegCurvetoCubicAbs pathSeg = new SVGPathSegCurvetoCubicAbsImpl(x1, y1, x2, y2, x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoCubicRel createSVGPathSegCurvetoCubicRel(float x, float y, float x1, float y1, float x2, float y2) { SVGPathSegCurvetoCubicRel pathSeg = new SVGPathSegCurvetoCubicRelImpl(x, y, x1, y1, x2, y2); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoQuadraticAbs createSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1, float y1) { SVGPathSegCurvetoQuadraticAbs pathSeg = new SVGPathSegCurvetoQuadraticAbsImpl(x, y, x1, y1); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoQuadraticRel createSVGPathSegCurvetoQuadraticRel(float x, float y, float x1, float y1) { SVGPathSegCurvetoQuadraticRel pathSeg = new SVGPathSegCurvetoQuadraticRelImpl(x, y, x1, y1); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegArcAbs createSVGPathSegArcAbs(float x, float y, float r1, float r2, float angle, boolean largeArcFlag, boolean sweepFlag) { SVGPathSegArcAbs pathSeg = new SVGPathSegArcAbsImpl(x, y, r1, r2, angle, largeArcFlag, sweepFlag); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegArcRel createSVGPathSegArcRel(float x, float y, float r1, float r2, float angle, boolean largeArcFlag, boolean sweepFlag) { SVGPathSegArcRel pathSeg = new SVGPathSegArcRelImpl(x, y, r1, r2, angle, largeArcFlag, sweepFlag); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegLinetoHorizontalAbs createSVGPathSegLinetoHorizontalAbs(float x) { SVGPathSegLinetoHorizontalAbs pathSeg = new SVGPathSegLinetoHorizontalAbsImpl(x); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegLinetoHorizontalRel createSVGPathSegLinetoHorizontalRel(float x) { SVGPathSegLinetoHorizontalRel pathSeg = new SVGPathSegLinetoHorizontalRelImpl(x); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegLinetoVerticalAbs createSVGPathSegLinetoVerticalAbs(float y) { SVGPathSegLinetoVerticalAbs pathSeg = new SVGPathSegLinetoVerticalAbsImpl(y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegLinetoVerticalRel createSVGPathSegLinetoVerticalRel(float y) { SVGPathSegLinetoVerticalRel pathSeg = new SVGPathSegLinetoVerticalRelImpl(y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoCubicSmoothAbs createSVGPathSegCurvetoCubicSmoothAbs(float x2, float y2, float x, float y) { SVGPathSegCurvetoCubicSmoothAbs pathSeg = new SVGPathSegCurvetoCubicSmoothAbsImpl(x2, y2, x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoCubicSmoothRel createSVGPathSegCurvetoCubicSmoothRel(float x2, float y2, float x, float y) { SVGPathSegCurvetoCubicSmoothRel pathSeg = new SVGPathSegCurvetoCubicSmoothRelImpl(x2, y2, x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoQuadraticSmoothAbs createSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) { SVGPathSegCurvetoQuadraticSmoothAbs pathSeg = new SVGPathSegCurvetoQuadraticSmoothAbsImpl(x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } @Override public SVGPathSegCurvetoQuadraticSmoothRel createSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) { SVGPathSegCurvetoQuadraticSmoothRel pathSeg = new SVGPathSegCurvetoQuadraticSmoothRelImpl(x, y); pathData.getPathSegList().appendItem(pathSeg); return pathSeg; } private int interpretDValue(ArrayList<String> parts, int i) { String part = parts.get(i); float x, y; float x1, y1; float x2, y2; float angle; boolean f1; boolean f2; switch (part) { case "A": x1 = convertToFloat(parts.get(i + 1)); y1 = convertToFloat(parts.get(i + 2)); angle = convertToFloat(parts.get(i + 3)); f1 = convertToBoolean(parts.get(i + 4)); f2 = convertToBoolean(parts.get(i + 5)); x = convertToFloat(parts.get(i + 6)); y = convertToFloat(parts.get(i + 7)); createSVGPathSegArcAbs(x, y, x1, y1, angle, f1, f2); i += 7; break; case "a": x1 = convertToFloat(parts.get(i + 1)); y1 = convertToFloat(parts.get(i + 2)); angle = convertToFloat(parts.get(i + 3)); f1 = convertToBoolean(parts.get(i + 4)); f2 = convertToBoolean(parts.get(i + 5)); x = convertToFloat(parts.get(i + 6)); y = convertToFloat(parts.get(i + 7)); createSVGPathSegArcRel(x, y, x1, y1, angle, f1, f2); i += 7; break; case "C": x1 = convertToFloat(parts.get(i + 1)); y1 = convertToFloat(parts.get(i + 2)); x2 = convertToFloat(parts.get(i + 3)); y2 = convertToFloat(parts.get(i + 4)); x = convertToFloat(parts.get(i + 5)); y = convertToFloat(parts.get(i + 6)); createSVGPathSegCurvetoCubicAbs(x1, y1, x2, y2, x, y); i += 6; break; case "c": x1 = convertToFloat(parts.get(i + 1)); y1 = convertToFloat(parts.get(i + 2)); x2 = convertToFloat(parts.get(i + 3)); y2 = convertToFloat(parts.get(i + 4)); x = convertToFloat(parts.get(i + 5)); y = convertToFloat(parts.get(i + 6)); createSVGPathSegCurvetoCubicRel(x1, y1, x2, y2, x, y); i += 6; break; case "H": x = convertToFloat(parts.get(i + 1)); createSVGPathSegLinetoHorizontalAbs(x); i += 1; break; case "h": x = convertToFloat(parts.get(i + 1)); createSVGPathSegLinetoHorizontalRel(x); i += 1; break; case "L": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); createSVGPathSegLinetoAbs(x, y); i += 2; break; case "l": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); createSVGPathSegLinetoRel(x, y); i += 2; break; case "M": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); createSVGPathSegMovetoAbs(x, y); i += 2; break; case "m": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); createSVGPathSegMovetoRel(x, y); i += 2; break; case "Q": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); x1 = convertToFloat(parts.get(i + 3)); y1 = convertToFloat(parts.get(i + 4)); createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1); i += 4; break; case "q": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); x1 = convertToFloat(parts.get(i + 3)); y1 = convertToFloat(parts.get(i + 4)); createSVGPathSegCurvetoQuadraticRel(x, y, x1, y1); i += 4; break; case "S": x2 = convertToFloat(parts.get(i + 1)); y2 = convertToFloat(parts.get(i + 2)); x = convertToFloat(parts.get(i + 3)); y = convertToFloat(parts.get(i + 4)); createSVGPathSegCurvetoCubicSmoothAbs(x2, y2, x, y); i += 4; break; case "s": x2 = convertToFloat(parts.get(i + 1)); y2 = convertToFloat(parts.get(i + 2)); x = convertToFloat(parts.get(i + 3)); y = convertToFloat(parts.get(i + 4)); createSVGPathSegCurvetoCubicSmoothRel(x2, y2, x, y); i += 4; break; case "T": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); createSVGPathSegCurvetoQuadraticSmoothAbs(x, y); i += 4; break; case "t": x = convertToFloat(parts.get(i + 1)); y = convertToFloat(parts.get(i + 2)); createSVGPathSegCurvetoQuadraticSmoothRel(x, y); i += 4; break; case "V": y = convertToFloat(parts.get(i + 1)); createSVGPathSegLinetoVerticalAbs(y); i += 1; break; case "v": y = convertToFloat(parts.get(i + 1)); createSVGPathSegLinetoVerticalRel(y); i += 1; break; case "z": createSVGPathSegClosePath(); break; default: break; } return i; } private float convertToFloat(String value) { try { float f = Float.parseFloat(value); return f; } catch (NumberFormatException e) { return 0.0f; } } private boolean convertToBoolean(String value) { try { float flt = Float.parseFloat(value); if (flt == 1) { return true; } else { return false; } } catch (Exception e) { return false; } } private ArrayList<String> parts() { final Matcher matchPathCmd = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)").matcher(getD()); LinkedList<String> tokens = new LinkedList<String>(); while (matchPathCmd.find()) { tokens.addLast(matchPathCmd.group()); } ArrayList<String> list = new ArrayList<String>(); char charCmd = 'Z'; while (tokens.size() != 0) { String curToken = tokens.removeFirst(); char initChar = curToken.charAt(0); if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z')) { charCmd = initChar; } else { tokens.addFirst(curToken); } String curCmd = String.valueOf(charCmd); switch (curCmd) { case "M": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); curCmd = "L"; break; case "m": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); curCmd = "l"; break; case "L": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "l": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "H": list.add(curCmd); list.add(nextString(tokens)); break; case "h": list.add(curCmd); list.add(nextString(tokens)); break; case "V": list.add(curCmd); list.add(nextString(tokens)); break; case "v": list.add(curCmd); list.add(nextString(tokens)); break; case "A": case "a": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "Q": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "q": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "T": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "t": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "C": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "c": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "S": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "s": list.add(curCmd); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); list.add(nextString(tokens)); break; case "Z": case "z": list.add(curCmd); break; default: throw new RuntimeException("Invalid path element"); } } return list; } static protected String nextString(LinkedList<String> l) { return l.removeFirst(); } }