001/* 002 * SVG Salamander 003 * Copyright (c) 2004, Mark McKay 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * - Redistributions of source code must retain the above 011 * copyright notice, this list of conditions and the following 012 * disclaimer. 013 * - Redistributions in binary form must reproduce the above 014 * copyright notice, this list of conditions and the following 015 * disclaimer in the documentation and/or other materials 016 * provided with the distribution. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 029 * OF THE POSSIBILITY OF SUCH DAMAGE. 030 * 031 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other 032 * projects can be found at http://www.kitfox.com 033 * 034 * Created on January 26, 2004, 1:59 AM 035 */ 036package com.kitfox.svg; 037 038import com.kitfox.svg.animation.AnimationElement; 039import com.kitfox.svg.animation.TrackBase; 040import com.kitfox.svg.animation.TrackManager; 041import com.kitfox.svg.pathcmd.Arc; 042import com.kitfox.svg.pathcmd.BuildHistory; 043import com.kitfox.svg.pathcmd.Cubic; 044import com.kitfox.svg.pathcmd.CubicSmooth; 045import com.kitfox.svg.pathcmd.Horizontal; 046import com.kitfox.svg.pathcmd.LineTo; 047import com.kitfox.svg.pathcmd.MoveTo; 048import com.kitfox.svg.pathcmd.PathCommand; 049import com.kitfox.svg.pathcmd.Quadratic; 050import com.kitfox.svg.pathcmd.QuadraticSmooth; 051import com.kitfox.svg.pathcmd.Terminal; 052import com.kitfox.svg.pathcmd.Vertical; 053import com.kitfox.svg.xml.StyleAttribute; 054import com.kitfox.svg.xml.StyleSheet; 055import com.kitfox.svg.xml.XMLParseUtil; 056import java.awt.geom.AffineTransform; 057import java.awt.geom.GeneralPath; 058import java.io.Serializable; 059import java.net.URI; 060import java.util.ArrayList; 061import java.util.Collections; 062import java.util.HashMap; 063import java.util.HashSet; 064import java.util.Iterator; 065import java.util.LinkedList; 066import java.util.List; 067import java.util.Set; 068import java.util.regex.Matcher; 069import java.util.regex.Pattern; 070import org.xml.sax.Attributes; 071import org.xml.sax.SAXException; 072 073 074/** 075 * @author Mark McKay 076 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 077 */ 078abstract public class SVGElement implements Serializable 079{ 080 081 public static final long serialVersionUID = 0; 082 public static final String SVG_NS = "http://www.w3.org/2000/svg"; 083 protected SVGElement parent = null; 084 protected final ArrayList<SVGElement> children = new ArrayList<SVGElement>(); 085 protected String id = null; 086 /** 087 * CSS class. Used for applying style sheet information. 088 */ 089 protected String cssClass = null; 090 /** 091 * Styles defined for this elemnt via the <b>style</b> attribute. 092 */ 093 protected final HashMap<String, StyleAttribute> inlineStyles = new HashMap<String, StyleAttribute>(); 094 /** 095 * Presentation attributes set for this element. Ie, any attribute other 096 * than the <b>style</b> attribute. 097 */ 098 protected final HashMap<String, StyleAttribute> presAttribs = new HashMap<String, StyleAttribute>(); 099 /** 100 * A list of presentation attributes to not include in the presentation 101 * attribute set. 102 */ 103 protected static final Set<String> ignorePresAttrib; 104 105 static 106 { 107 HashSet<String> set = new HashSet<String>(); 108// set.add("id"); 109// set.add("class"); 110// set.add("style"); 111// set.add("xml:base"); 112 113 ignorePresAttrib = Collections.unmodifiableSet(set); 114 } 115 /** 116 * This element may override the URI we resolve against with an xml:base 117 * attribute. If so, a copy is placed here. Otherwise, we defer to our 118 * parent for the reolution base 119 */ 120 protected URI xmlBase = null; 121 /** 122 * The diagram this element belongs to 123 */ 124 protected SVGDiagram diagram; 125 /** 126 * Link to the universe we reside in 127 */ 128 protected final TrackManager trackManager = new TrackManager(); 129 boolean dirty = true; 130 131 /** 132 * Creates a new instance of SVGElement 133 */ 134 public SVGElement() 135 { 136 this(null, null, null); 137 } 138 139 public SVGElement(String id, SVGElement parent) 140 { 141 this(id, null, parent); 142 } 143 144 public SVGElement(String id, String cssClass, SVGElement parent) 145 { 146 this.id = id; 147 this.cssClass = cssClass; 148 this.parent = parent; 149 } 150 151 abstract public String getTagName(); 152 153 public SVGElement getParent() 154 { 155 return parent; 156 } 157 158 void setParent(SVGElement parent) 159 { 160 this.parent = parent; 161 } 162 163 /** 164 * @param retVec 165 * @return an ordered list of nodes from the root of the tree to this node 166 */ 167 public List<SVGElement> getPath(List<SVGElement> retVec) 168 { 169 if (retVec == null) 170 { 171 retVec = new ArrayList<SVGElement>(); 172 } 173 174 if (parent != null) 175 { 176 parent.getPath(retVec); 177 } 178 retVec.add(this); 179 180 return retVec; 181 } 182 183 /** 184 * @param retVec - A list to add all children to. If null, a new list is 185 * created and children of this group are added. 186 * 187 * @return The list containing the children of this group 188 */ 189 public List<SVGElement> getChildren(List<SVGElement> retVec) 190 { 191 if (retVec == null) 192 { 193 retVec = new ArrayList<SVGElement>(); 194 } 195 196 retVec.addAll(children); 197 198 return retVec; 199 } 200 201 /** 202 * @param id - Id of svg element to return 203 * @return the child of the given id, or null if no such child exists. 204 */ 205 public SVGElement getChild(String id) 206 { 207 for (SVGElement ele : children) { 208 String eleId = ele.getId(); 209 if (eleId != null && eleId.equals(id)) 210 { 211 return ele; 212 } 213 } 214 215 return null; 216 } 217 218 /** 219 * Searches children for given element. If found, returns index of child. 220 * Otherwise returns -1. 221 * @param child 222 * @return index of child 223 */ 224 public int indexOfChild(SVGElement child) 225 { 226 return children.indexOf(child); 227 } 228 229 /** 230 * Swaps 2 elements in children. 231 * 232 * @param i index of first child 233 * @param j index of second child 234 * @throws com.kitfox.svg.SVGException 235 */ 236 public void swapChildren(int i, int j) throws SVGException 237 { 238 if ((children == null) || (i < 0) || (i >= children.size()) || (j < 0) || (j >= children.size())) 239 { 240 return; 241 } 242 243 SVGElement temp = children.get(i); 244 children.set(i, children.get(j)); 245 children.set(j, temp); 246 build(); 247 } 248 249 /** 250 * Called during SAX load process to notify that this tag has begun the 251 * process of being loaded 252 * 253 * @param attrs - Attributes of this tag 254 * @param helper - An object passed to all SVG elements involved in this 255 * build process to aid in sharing information. 256 * @param parent 257 * @throws org.xml.sax.SAXException 258 */ 259 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException 260 { 261 //Set identification info 262 this.parent = parent; 263 this.diagram = helper.diagram; 264 265 this.id = attrs.getValue("id"); 266 if (this.id != null && !this.id.equals("")) 267 { 268 diagram.setElement(this.id, this); 269 } 270 271 String className = attrs.getValue("class"); 272 this.cssClass = (className == null || className.equals("")) ? null : className; 273 //docRoot = helper.docRoot; 274 //universe = helper.universe; 275 276 //Parse style string, if any 277 String style = attrs.getValue("style"); 278 if (style != null) 279 { 280 HashMap<?, ?> map = XMLParseUtil.parseStyle(style, inlineStyles); 281 } 282 283 String base = attrs.getValue("xml:base"); 284 if (base != null && !base.equals("")) 285 { 286 try 287 { 288 xmlBase = new URI(base); 289 } catch (Exception e) 290 { 291 throw new SAXException(e); 292 } 293 } 294 295 //Place all other attributes into the presentation attribute list 296 int numAttrs = attrs.getLength(); 297 for (int i = 0; i < numAttrs; i++) 298 { 299 String name = attrs.getQName(i); 300 if (ignorePresAttrib.contains(name)) 301 { 302 continue; 303 } 304 String value = attrs.getValue(i); 305 306 presAttribs.put(name, new StyleAttribute(name, value)); 307 } 308 } 309 310 public void removeAttribute(String name, int attribType) 311 { 312 switch (attribType) 313 { 314 case AnimationElement.AT_CSS: 315 inlineStyles.remove(name); 316 return; 317 case AnimationElement.AT_XML: 318 presAttribs.remove(name); 319 return; 320 } 321 } 322 323 public void addAttribute(String name, int attribType, String value) throws SVGElementException 324 { 325 if (hasAttribute(name, attribType)) 326 { 327 throw new SVGElementException(this, "Attribute " + name + "(" + AnimationElement.animationElementToString(attribType) + ") already exists"); 328 } 329 330 //Alter layout for id attribute 331 if ("id".equals(name)) 332 { 333 if (diagram != null) 334 { 335 diagram.removeElement(id); 336 diagram.setElement(value, this); 337 } 338 this.id = value; 339 } 340 341 switch (attribType) 342 { 343 case AnimationElement.AT_CSS: 344 inlineStyles.put(name, new StyleAttribute(name, value)); 345 return; 346 case AnimationElement.AT_XML: 347 presAttribs.put(name, new StyleAttribute(name, value)); 348 return; 349 } 350 351 throw new SVGElementException(this, "Invalid attribute type " + attribType); 352 } 353 354 public boolean hasAttribute(String name, int attribType) throws SVGElementException 355 { 356 switch (attribType) 357 { 358 case AnimationElement.AT_CSS: 359 return inlineStyles.containsKey(name); 360 case AnimationElement.AT_XML: 361 return presAttribs.containsKey(name); 362 case AnimationElement.AT_AUTO: 363 return inlineStyles.containsKey(name) || presAttribs.containsKey(name); 364 } 365 366 throw new SVGElementException(this, "Invalid attribute type " + attribType); 367 } 368 369 /** 370 * @return a set of Strings that corespond to CSS attributes on this element 371 */ 372 public Set<String> getInlineAttributes() 373 { 374 return inlineStyles.keySet(); 375 } 376 377 /** 378 * @return a set of Strings that corespond to XML attributes on this element 379 */ 380 public Set<String> getPresentationAttributes() 381 { 382 return presAttribs.keySet(); 383 } 384 385 /** 386 * Called after the start element but before the end element to indicate 387 * each child tag that has been processed 388 * @param helper 389 * @param child 390 * @throws com.kitfox.svg.SVGElementException 391 */ 392 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 393 { 394 children.add(child); 395 child.parent = this; 396 child.setDiagram(diagram); 397 398 //Add info to track if we've scanned animation element 399 if (child instanceof AnimationElement) 400 { 401 trackManager.addTrackElement((AnimationElement) child); 402 } 403 } 404 405 protected void setDiagram(SVGDiagram diagram) 406 { 407 this.diagram = diagram; 408 diagram.setElement(id, this); 409 for (SVGElement ele : children) { 410 ele.setDiagram(diagram); 411 } 412 } 413 414 public void removeChild(SVGElement child) throws SVGElementException 415 { 416 if (!children.contains(child)) 417 { 418 throw new SVGElementException(this, "Element does not contain child " + child); 419 } 420 421 children.remove(child); 422 } 423 424 /** 425 * Called during load process to add text scanned within a tag 426 * @param helper 427 * @param text 428 */ 429 public void loaderAddText(SVGLoaderHelper helper, String text) 430 { 431 } 432 433 /** 434 * Called to indicate that this tag and the tags it contains have been 435 * completely processed, and that it should finish any load processes. 436 * @param helper 437 * @throws com.kitfox.svg.SVGParseException 438 */ 439 public void loaderEndElement(SVGLoaderHelper helper) throws SVGParseException 440 { 441// try 442// { 443// build(); 444// } 445// catch (SVGException se) 446// { 447// throw new SVGParseException(se); 448// } 449 } 450 451 /** 452 * Called by internal processes to rebuild the geometry of this node from 453 * it's presentation attributes, style attributes and animated tracks. 454 * @throws com.kitfox.svg.SVGException 455 */ 456 protected void build() throws SVGException 457 { 458 StyleAttribute sty = new StyleAttribute(); 459 460 if (getPres(sty.setName("id"))) 461 { 462 String newId = sty.getStringValue(); 463 if (!newId.equals(id)) 464 { 465 diagram.removeElement(id); 466 id = newId; 467 diagram.setElement(this.id, this); 468 } 469 } 470 if (getPres(sty.setName("class"))) 471 { 472 cssClass = sty.getStringValue(); 473 } 474 if (getPres(sty.setName("xml:base"))) 475 { 476 xmlBase = sty.getURIValue(); 477 } 478 479 //Build children 480 for (int i = 0; i < children.size(); ++i) 481 { 482 SVGElement ele = (SVGElement) children.get(i); 483 ele.build(); 484 } 485 } 486 487 public URI getXMLBase() 488 { 489 return xmlBase != null ? xmlBase 490 : (parent != null ? parent.getXMLBase() : diagram.getXMLBase()); 491 } 492 493 /** 494 * @return the id assigned to this node. Null if no id explicitly set. 495 */ 496 public String getId() 497 { 498 return id; 499 } 500 LinkedList<SVGElement> contexts = new LinkedList<SVGElement>(); 501 502 /** 503 * Hack to allow nodes to temporarily change their parents. The Use tag will 504 * need this so it can alter the attributes that a particular node uses. 505 * @param context 506 */ 507 protected void pushParentContext(SVGElement context) 508 { 509 contexts.addLast(context); 510 } 511 512 protected SVGElement popParentContext() 513 { 514 return (SVGElement) contexts.removeLast(); 515 } 516 517 protected SVGElement getParentContext() 518 { 519 return contexts.isEmpty() ? null : (SVGElement) contexts.getLast(); 520 } 521 522 public SVGRoot getRoot() 523 { 524 return parent == null ? null : parent.getRoot(); 525 } 526 527 /* 528 * Returns the named style attribute. Checks for inline styles first, then 529 * internal and extranal style sheets, and finally checks for presentation 530 * attributes. 531 * @param styleName - Name of attribute to return 532 * @param recursive - If true and this object does not contain the 533 * named style attribute, checks attributes of parents abck to root until 534 * one found. 535 */ 536 public boolean getStyle(StyleAttribute attrib) throws SVGException 537 { 538 return getStyle(attrib, true); 539 } 540 541 public void setAttribute(String name, int attribType, String value) throws SVGElementException 542 { 543 StyleAttribute styAttr; 544 545 546 switch (attribType) 547 { 548 case AnimationElement.AT_CSS: 549 { 550 styAttr = (StyleAttribute) inlineStyles.get(name); 551 break; 552 } 553 case AnimationElement.AT_XML: 554 { 555 styAttr = (StyleAttribute) presAttribs.get(name); 556 break; 557 } 558 case AnimationElement.AT_AUTO: 559 { 560 styAttr = (StyleAttribute) inlineStyles.get(name); 561 562 if (styAttr == null) 563 { 564 styAttr = (StyleAttribute) presAttribs.get(name); 565 } 566 break; 567 } 568 default: 569 throw new SVGElementException(this, "Invalid attribute type " + attribType); 570 } 571 572 if (styAttr == null) 573 { 574 throw new SVGElementException(this, "Could not find attribute " + name + "(" + AnimationElement.animationElementToString(attribType) + "). Make sure to create attribute before setting it."); 575 } 576 577 //Alter layout for relevant attributes 578 if ("id".equals(styAttr.getName())) 579 { 580 if (diagram != null) 581 { 582 diagram.removeElement(this.id); 583 diagram.setElement(value, this); 584 } 585 this.id = value; 586 } 587 588 styAttr.setStringValue(value); 589 } 590 591 public boolean getStyle(StyleAttribute attrib, boolean recursive) throws SVGException 592 { 593 return getStyle(attrib, recursive, true); 594 } 595 596 /** 597 * Copies the current style into the passed style attribute. Checks for 598 * inline styles first, then internal and extranal style sheets, and finally 599 * checks for presentation attributes. Recursively checks parents. 600 * 601 * @param attrib - Attribute to write style data to. Must have it's name set 602 * to the name of the style being queried. 603 * @param recursive - If true and this object does not contain the named 604 * style attribute, checks attributes of parents back to root until one 605 * found. 606 * @param evalAnimation 607 * @return 608 */ 609 public boolean getStyle(StyleAttribute attrib, boolean recursive, boolean evalAnimation) 610 throws SVGException 611 { 612 String styName = attrib.getName(); 613 614 //Check for local inline styles 615 StyleAttribute styAttr = (StyleAttribute)inlineStyles.get(styName); 616 617 attrib.setStringValue(styAttr == null ? "" : styAttr.getStringValue()); 618 619 //Evalutate coresponding track, if one exists 620 if (evalAnimation) 621 { 622 TrackBase track = trackManager.getTrack(styName, AnimationElement.AT_CSS); 623 if (track != null) 624 { 625 track.getValue(attrib, diagram.getUniverse().getCurTime()); 626 return true; 627 } 628 } 629 630 //Return if we've found a non animated style 631 if (styAttr != null) 632 { 633 return true; 634 } 635 636 637 //Check for presentation attribute 638 StyleAttribute presAttr = (StyleAttribute)presAttribs.get(styName); 639 640 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue()); 641 642 //Evalutate coresponding track, if one exists 643 if (evalAnimation) 644 { 645 TrackBase track = trackManager.getTrack(styName, AnimationElement.AT_XML); 646 if (track != null) 647 { 648 track.getValue(attrib, diagram.getUniverse().getCurTime()); 649 return true; 650 } 651 } 652 653 //Return if we've found a presentation attribute instead 654 if (presAttr != null) 655 { 656 return true; 657 } 658 659 //Check for style sheet 660 SVGRoot root = getRoot(); 661 if (root != null) 662 { 663 StyleSheet ss = root.getStyleSheet(); 664 if (ss != null) 665 { 666 return ss.getStyle(attrib, getTagName(), cssClass); 667 } 668 } 669 670 //If we're recursive, check parents 671 if (recursive) 672 { 673 SVGElement parentContext = getParentContext(); 674 if (parentContext != null) 675 { 676 return parentContext.getStyle(attrib, true); 677 } 678 if (parent != null) 679 { 680 return parent.getStyle(attrib, true); 681 } 682 } 683 684 //Unsuccessful reading style attribute 685 return false; 686 } 687 688 /** 689 * @param styName 690 * @return the raw style value of this attribute. Does not take the 691 * presentation value or animation into consideration. Used by animations to 692 * determine the base to animate from. 693 */ 694 public StyleAttribute getStyleAbsolute(String styName) 695 { 696 //Check for local inline styles 697 return (StyleAttribute) inlineStyles.get(styName); 698 } 699 700 /** 701 * Copies the presentation attribute into the passed one. 702 * 703 * @param attrib 704 * @return - True if attribute was read successfully 705 * @throws com.kitfox.svg.SVGException 706 */ 707 public boolean getPres(StyleAttribute attrib) throws SVGException 708 { 709 String presName = attrib.getName(); 710 711 //Make sure we have a coresponding presentation attribute 712 StyleAttribute presAttr = (StyleAttribute) presAttribs.get(presName); 713 714 //Copy presentation value directly 715 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue()); 716 717 //Evalutate coresponding track, if one exists 718 TrackBase track = trackManager.getTrack(presName, AnimationElement.AT_XML); 719 if (track != null) 720 { 721 track.getValue(attrib, diagram.getUniverse().getCurTime()); 722 return true; 723 } 724 725 //Return if we found presentation attribute 726 if (presAttr != null) 727 { 728 return true; 729 } 730 731 return false; 732 } 733 734 /** 735 * @param styName 736 * @return the raw presentation value of this attribute. Ignores any 737 * modifications applied by style attributes or animation. Used by 738 * animations to determine the starting point to animate from 739 */ 740 public StyleAttribute getPresAbsolute(String styName) 741 { 742 //Check for local inline styles 743 return (StyleAttribute) presAttribs.get(styName); 744 } 745 746 static protected AffineTransform parseTransform(String val) throws SVGException 747 { 748 final Matcher matchExpression = Pattern.compile("\\w+\\([^)]*\\)").matcher(""); 749 750 AffineTransform retXform = new AffineTransform(); 751 752 matchExpression.reset(val); 753 while (matchExpression.find()) 754 { 755 retXform.concatenate(parseSingleTransform(matchExpression.group())); 756 } 757 758 return retXform; 759 } 760 761 static public AffineTransform parseSingleTransform(String val) throws SVGException 762 { 763 final Matcher matchWord = Pattern.compile("([a-zA-Z]+|-?\\d+(\\.\\d+)?(e-?\\d+)?|-?\\.\\d+(e-?\\d+)?)").matcher(""); 764 765 AffineTransform retXform = new AffineTransform(); 766 767 matchWord.reset(val); 768 if (!matchWord.find()) 769 { 770 //Return identity transformation if no data present (eg, empty string) 771 return retXform; 772 } 773 774 String function = matchWord.group().toLowerCase(); 775 776 LinkedList<String> termList = new LinkedList<String>(); 777 while (matchWord.find()) 778 { 779 termList.add(matchWord.group()); 780 } 781 782 783 double[] terms = new double[termList.size()]; 784 Iterator<String> it = termList.iterator(); 785 int count = 0; 786 while (it.hasNext()) 787 { 788 terms[count++] = XMLParseUtil.parseDouble(it.next()); 789 } 790 791 //Calculate transformation 792 if (function.equals("matrix")) 793 { 794 retXform.setTransform(terms[0], terms[1], terms[2], terms[3], terms[4], terms[5]); 795 } else if (function.equals("translate")) 796 { 797 if (terms.length == 1) 798 { 799 retXform.setToTranslation(terms[0], 0); 800 } else 801 { 802 retXform.setToTranslation(terms[0], terms[1]); 803 } 804 } else if (function.equals("scale")) 805 { 806 if (terms.length > 1) 807 { 808 retXform.setToScale(terms[0], terms[1]); 809 } else 810 { 811 retXform.setToScale(terms[0], terms[0]); 812 } 813 } else if (function.equals("rotate")) 814 { 815 if (terms.length > 2) 816 { 817 retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]); 818 } else 819 { 820 retXform.setToRotation(Math.toRadians(terms[0])); 821 } 822 } else if (function.equals("skewx")) 823 { 824 retXform.setToShear(Math.toRadians(terms[0]), 0.0); 825 } else if (function.equals("skewy")) 826 { 827 retXform.setToShear(0.0, Math.toRadians(terms[0])); 828 } else 829 { 830 throw new SVGException("Unknown transform type"); 831 } 832 833 return retXform; 834 } 835 836 static protected float nextFloat(LinkedList<String> l) 837 { 838 String s = (String) l.removeFirst(); 839 return Float.parseFloat(s); 840 } 841 842 static protected PathCommand[] parsePathList(String list) 843 { 844 final Matcher matchPathCmd = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)").matcher(list); 845 846 //Tokenize 847 LinkedList<String> tokens = new LinkedList<String>(); 848 while (matchPathCmd.find()) 849 { 850 tokens.addLast(matchPathCmd.group()); 851 } 852 853 854 boolean defaultRelative = false; 855 LinkedList<PathCommand> cmdList = new LinkedList<PathCommand>(); 856 char curCmd = 'Z'; 857 while (tokens.size() != 0) 858 { 859 String curToken = (String) tokens.removeFirst(); 860 char initChar = curToken.charAt(0); 861 if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z')) 862 { 863 curCmd = initChar; 864 } else 865 { 866 tokens.addFirst(curToken); 867 } 868 869 PathCommand cmd = null; 870 871 switch (curCmd) 872 { 873 case 'M': 874 cmd = new MoveTo(false, nextFloat(tokens), nextFloat(tokens)); 875 curCmd = 'L'; 876 break; 877 case 'm': 878 cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens)); 879 curCmd = 'l'; 880 break; 881 case 'L': 882 cmd = new LineTo(false, nextFloat(tokens), nextFloat(tokens)); 883 break; 884 case 'l': 885 cmd = new LineTo(true, nextFloat(tokens), nextFloat(tokens)); 886 break; 887 case 'H': 888 cmd = new Horizontal(false, nextFloat(tokens)); 889 break; 890 case 'h': 891 cmd = new Horizontal(true, nextFloat(tokens)); 892 break; 893 case 'V': 894 cmd = new Vertical(false, nextFloat(tokens)); 895 break; 896 case 'v': 897 cmd = new Vertical(true, nextFloat(tokens)); 898 break; 899 case 'A': 900 cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens), 901 nextFloat(tokens), 902 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 903 nextFloat(tokens), nextFloat(tokens)); 904 break; 905 case 'a': 906 cmd = new Arc(true, nextFloat(tokens), nextFloat(tokens), 907 nextFloat(tokens), 908 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 909 nextFloat(tokens), nextFloat(tokens)); 910 break; 911 case 'Q': 912 cmd = new Quadratic(false, nextFloat(tokens), nextFloat(tokens), 913 nextFloat(tokens), nextFloat(tokens)); 914 break; 915 case 'q': 916 cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens), 917 nextFloat(tokens), nextFloat(tokens)); 918 break; 919 case 'T': 920 cmd = new QuadraticSmooth(false, nextFloat(tokens), nextFloat(tokens)); 921 break; 922 case 't': 923 cmd = new QuadraticSmooth(true, nextFloat(tokens), nextFloat(tokens)); 924 break; 925 case 'C': 926 cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens), 927 nextFloat(tokens), nextFloat(tokens), 928 nextFloat(tokens), nextFloat(tokens)); 929 break; 930 case 'c': 931 cmd = new Cubic(true, nextFloat(tokens), nextFloat(tokens), 932 nextFloat(tokens), nextFloat(tokens), 933 nextFloat(tokens), nextFloat(tokens)); 934 break; 935 case 'S': 936 cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens), 937 nextFloat(tokens), nextFloat(tokens)); 938 break; 939 case 's': 940 cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens), 941 nextFloat(tokens), nextFloat(tokens)); 942 break; 943 case 'Z': 944 case 'z': 945 cmd = new Terminal(); 946 break; 947 default: 948 throw new RuntimeException("Invalid path element"); 949 } 950 951 cmdList.add(cmd); 952 defaultRelative = cmd.isRelative; 953 } 954 955 PathCommand[] retArr = new PathCommand[cmdList.size()]; 956 cmdList.toArray(retArr); 957 return retArr; 958 } 959 960 static protected GeneralPath buildPath(String text, int windingRule) 961 { 962 PathCommand[] commands = parsePathList(text); 963 964 int numKnots = 2; 965 for (int i = 0; i < commands.length; i++) 966 { 967 numKnots += commands[i].getNumKnotsAdded(); 968 } 969 970 971 GeneralPath path = new GeneralPath(windingRule, numKnots); 972 973 BuildHistory hist = new BuildHistory(); 974 975 for (int i = 0; i < commands.length; i++) 976 { 977 PathCommand cmd = commands[i]; 978 cmd.appendPath(path, hist); 979 } 980 981 return path; 982 } 983 984 /** 985 * Updates all attributes in this diagram associated with a time event. Ie, 986 * all attributes with track information. 987 * 988 * @param curTime 989 * @return - true if this node has changed state as a result of the time 990 * update 991 * @throws com.kitfox.svg.SVGException 992 */ 993 abstract public boolean updateTime(double curTime) throws SVGException; 994 995 public int getNumChildren() 996 { 997 return children.size(); 998 } 999 1000 public SVGElement getChild(int i) 1001 { 1002 return (SVGElement) children.get(i); 1003 } 1004 1005 public double lerp(double t0, double t1, double alpha) 1006 { 1007 return (1 - alpha) * t0 + alpha * t1; 1008 } 1009}