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 children = new ArrayList(); 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 inlineStyles = new HashMap(); 094 /** 095 * Presentation attributes set for this element. Ie, any attribute other 096 * than the <b>style</b> attribute. 097 */ 098 protected final HashMap presAttribs = new HashMap(); 099 /** 100 * A list of presentation attributes to not include in the presentation 101 * attribute set. 102 */ 103 protected static final Set ignorePresAttrib; 104 105 static 106 { 107 HashSet set = new HashSet(); 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 * @return an ordered list of nodes from the root of the tree to this node 165 */ 166 public List getPath(List retVec) 167 { 168 if (retVec == null) 169 { 170 retVec = new ArrayList(); 171 } 172 173 if (parent != null) 174 { 175 parent.getPath(retVec); 176 } 177 retVec.add(this); 178 179 return retVec; 180 } 181 182 /** 183 * @param retVec - A list to add all children to. If null, a new list is 184 * created and children of this group are added. 185 * 186 * @return The list containing the children of this group 187 */ 188 public List getChildren(List retVec) 189 { 190 if (retVec == null) 191 { 192 retVec = new ArrayList(); 193 } 194 195 retVec.addAll(children); 196 197 return retVec; 198 } 199 200 /** 201 * @param id - Id of svg element to return 202 * @return the child of the given id, or null if no such child exists. 203 */ 204 public SVGElement getChild(String id) 205 { 206 for (Iterator it = children.iterator(); it.hasNext();) 207 { 208 SVGElement ele = (SVGElement) it.next(); 209 String eleId = ele.getId(); 210 if (eleId != null && eleId.equals(id)) 211 { 212 return ele; 213 } 214 } 215 216 return null; 217 } 218 219 /** 220 * Searches children for given element. If found, returns index of child. 221 * Otherwise returns -1. 222 */ 223 public int indexOfChild(SVGElement child) 224 { 225 return children.indexOf(child); 226 } 227 228 /** 229 * Swaps 2 elements in children. 230 * 231 * @i index of first 232 * @j index of second 233 * 234 * @return true if successful, false otherwise 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 Object 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 */ 257 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException 258 { 259 //Set identification info 260 this.parent = parent; 261 this.diagram = helper.diagram; 262 263 this.id = attrs.getValue("id"); 264 if (this.id != null && !this.id.equals("")) 265 { 266 diagram.setElement(this.id, this); 267 } 268 269 String className = attrs.getValue("class"); 270 this.cssClass = (className == null || className.equals("")) ? null : className; 271 //docRoot = helper.docRoot; 272 //universe = helper.universe; 273 274 //Parse style string, if any 275 String style = attrs.getValue("style"); 276 if (style != null) 277 { 278 HashMap map = XMLParseUtil.parseStyle(style, inlineStyles); 279 } 280 281 String base = attrs.getValue("xml:base"); 282 if (base != null && !base.equals("")) 283 { 284 try 285 { 286 xmlBase = new URI(base); 287 } catch (Exception e) 288 { 289 throw new SAXException(e); 290 } 291 } 292 293 //Place all other attributes into the presentation attribute list 294 int numAttrs = attrs.getLength(); 295 for (int i = 0; i < numAttrs; i++) 296 { 297 String name = attrs.getQName(i); 298 if (ignorePresAttrib.contains(name)) 299 { 300 continue; 301 } 302 String value = attrs.getValue(i); 303 304 presAttribs.put(name, new StyleAttribute(name, value)); 305 } 306 } 307 308 public void removeAttribute(String name, int attribType) 309 { 310 switch (attribType) 311 { 312 case AnimationElement.AT_CSS: 313 inlineStyles.remove(name); 314 return; 315 case AnimationElement.AT_XML: 316 presAttribs.remove(name); 317 return; 318 } 319 } 320 321 public void addAttribute(String name, int attribType, String value) throws SVGElementException 322 { 323 if (hasAttribute(name, attribType)) 324 { 325 throw new SVGElementException(this, "Attribute " + name + "(" + AnimationElement.animationElementToString(attribType) + ") already exists"); 326 } 327 328 //Alter layout for id attribute 329 if ("id".equals(name)) 330 { 331 if (diagram != null) 332 { 333 diagram.removeElement(id); 334 diagram.setElement(value, this); 335 } 336 this.id = value; 337 } 338 339 switch (attribType) 340 { 341 case AnimationElement.AT_CSS: 342 inlineStyles.put(name, new StyleAttribute(name, value)); 343 return; 344 case AnimationElement.AT_XML: 345 presAttribs.put(name, new StyleAttribute(name, value)); 346 return; 347 } 348 349 throw new SVGElementException(this, "Invalid attribute type " + attribType); 350 } 351 352 public boolean hasAttribute(String name, int attribType) throws SVGElementException 353 { 354 switch (attribType) 355 { 356 case AnimationElement.AT_CSS: 357 return inlineStyles.containsKey(name); 358 case AnimationElement.AT_XML: 359 return presAttribs.containsKey(name); 360 case AnimationElement.AT_AUTO: 361 return inlineStyles.containsKey(name) || presAttribs.containsKey(name); 362 } 363 364 throw new SVGElementException(this, "Invalid attribute type " + attribType); 365 } 366 367 /** 368 * @return a set of Strings that corespond to CSS attributes on this element 369 */ 370 public Set getInlineAttributes() 371 { 372 return inlineStyles.keySet(); 373 } 374 375 /** 376 * @return a set of Strings that corespond to XML attributes on this element 377 */ 378 public Set getPresentationAttributes() 379 { 380 return presAttribs.keySet(); 381 } 382 383 /** 384 * Called after the start element but before the end element to indicate 385 * each child tag that has been processed 386 */ 387 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 388 { 389 children.add(child); 390 child.parent = this; 391 child.setDiagram(diagram); 392 393 //Add info to track if we've scanned animation element 394 if (child instanceof AnimationElement) 395 { 396 trackManager.addTrackElement((AnimationElement) child); 397 } 398 } 399 400 protected void setDiagram(SVGDiagram diagram) 401 { 402 this.diagram = diagram; 403 diagram.setElement(id, this); 404 for (Iterator it = children.iterator(); it.hasNext();) 405 { 406 SVGElement ele = (SVGElement) it.next(); 407 ele.setDiagram(diagram); 408 } 409 } 410 411 public void removeChild(SVGElement child) throws SVGElementException 412 { 413 if (!children.contains(child)) 414 { 415 throw new SVGElementException(this, "Element does not contain child " + child); 416 } 417 418 children.remove(child); 419 } 420 421 /** 422 * Called during load process to add text scanned within a tag 423 */ 424 public void loaderAddText(SVGLoaderHelper helper, String text) 425 { 426 } 427 428 /** 429 * Called to indicate that this tag and the tags it contains have been 430 * completely processed, and that it should finish any load processes. 431 */ 432 public void loaderEndElement(SVGLoaderHelper helper) throws SVGParseException 433 { 434// try 435// { 436// build(); 437// } 438// catch (SVGException se) 439// { 440// throw new SVGParseException(se); 441// } 442 } 443 444 /** 445 * Called by internal processes to rebuild the geometry of this node from 446 * it's presentation attributes, style attributes and animated tracks. 447 */ 448 protected void build() throws SVGException 449 { 450 StyleAttribute sty = new StyleAttribute(); 451 452 if (getPres(sty.setName("id"))) 453 { 454 String newId = sty.getStringValue(); 455 if (!newId.equals(id)) 456 { 457 diagram.removeElement(id); 458 id = newId; 459 diagram.setElement(this.id, this); 460 } 461 } 462 if (getPres(sty.setName("class"))) 463 { 464 cssClass = sty.getStringValue(); 465 } 466 if (getPres(sty.setName("xml:base"))) 467 { 468 xmlBase = sty.getURIValue(); 469 } 470 471 //Build children 472 for (int i = 0; i < children.size(); ++i) 473 { 474 SVGElement ele = (SVGElement) children.get(i); 475 ele.build(); 476 } 477 } 478 479 public URI getXMLBase() 480 { 481 return xmlBase != null ? xmlBase 482 : (parent != null ? parent.getXMLBase() : diagram.getXMLBase()); 483 } 484 485 /** 486 * @return the id assigned to this node. Null if no id explicitly set. 487 */ 488 public String getId() 489 { 490 return id; 491 } 492 LinkedList contexts = new LinkedList(); 493 494 /** 495 * Hack to allow nodes to temporarily change their parents. The Use tag will 496 * need this so it can alter the attributes that a particular node uses. 497 */ 498 protected void pushParentContext(SVGElement context) 499 { 500 contexts.addLast(context); 501 } 502 503 protected SVGElement popParentContext() 504 { 505 return (SVGElement) contexts.removeLast(); 506 } 507 508 protected SVGElement getParentContext() 509 { 510 return contexts.isEmpty() ? null : (SVGElement) contexts.getLast(); 511 } 512 513 public SVGRoot getRoot() 514 { 515 return parent == null ? null : parent.getRoot(); 516 } 517 518 /* 519 * Returns the named style attribute. Checks for inline styles first, then 520 * internal and extranal style sheets, and finally checks for presentation 521 * attributes. 522 * @param styleName - Name of attribute to return 523 * @param recursive - If true and this object does not contain the 524 * named style attribute, checks attributes of parents abck to root until 525 * one found. 526 */ 527 public boolean getStyle(StyleAttribute attrib) throws SVGException 528 { 529 return getStyle(attrib, true); 530 } 531 532 public void setAttribute(String name, int attribType, String value) throws SVGElementException 533 { 534 StyleAttribute styAttr; 535 536 537 switch (attribType) 538 { 539 case AnimationElement.AT_CSS: 540 { 541 styAttr = (StyleAttribute) inlineStyles.get(name); 542 break; 543 } 544 case AnimationElement.AT_XML: 545 { 546 styAttr = (StyleAttribute) presAttribs.get(name); 547 break; 548 } 549 case AnimationElement.AT_AUTO: 550 { 551 styAttr = (StyleAttribute) inlineStyles.get(name); 552 553 if (styAttr == null) 554 { 555 styAttr = (StyleAttribute) presAttribs.get(name); 556 } 557 break; 558 } 559 default: 560 throw new SVGElementException(this, "Invalid attribute type " + attribType); 561 } 562 563 if (styAttr == null) 564 { 565 throw new SVGElementException(this, "Could not find attribute " + name + "(" + AnimationElement.animationElementToString(attribType) + "). Make sure to create attribute before setting it."); 566 } 567 568 //Alter layout for relevant attributes 569 if ("id".equals(styAttr.getName())) 570 { 571 if (diagram != null) 572 { 573 diagram.removeElement(this.id); 574 diagram.setElement(value, this); 575 } 576 this.id = value; 577 } 578 579 styAttr.setStringValue(value); 580 } 581 582 /** 583 * Copies the current style into the passed style attribute. Checks for 584 * inline styles first, then internal and extranal style sheets, and finally 585 * checks for presentation attributes. Recursively checks parents. 586 * 587 * @param attrib - Attribute to write style data to. Must have it's name set 588 * to the name of the style being queried. 589 * @param recursive - If true and this object does not contain the named 590 * style attribute, checks attributes of parents back to root until one 591 * found. 592 */ 593 public boolean getStyle(StyleAttribute attrib, boolean recursive) throws SVGException 594 { 595 String styName = attrib.getName(); 596 597 //Check for local inline styles 598 StyleAttribute styAttr = (StyleAttribute)inlineStyles.get(styName); 599 600 attrib.setStringValue(styAttr == null ? "" : styAttr.getStringValue()); 601 602 //Evalutate coresponding track, if one exists 603 TrackBase track = trackManager.getTrack(styName, AnimationElement.AT_CSS); 604 if (track != null) 605 { 606 track.getValue(attrib, diagram.getUniverse().getCurTime()); 607 return true; 608 } 609 610 //Return if we've found a non animated style 611 if (styAttr != null) 612 { 613 return true; 614 } 615 616 617 //Check for presentation attribute 618 StyleAttribute presAttr = (StyleAttribute)presAttribs.get(styName); 619 620 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue()); 621 622 //Evalutate coresponding track, if one exists 623 track = trackManager.getTrack(styName, AnimationElement.AT_XML); 624 if (track != null) 625 { 626 track.getValue(attrib, diagram.getUniverse().getCurTime()); 627 return true; 628 } 629 630 //Return if we've found a presentation attribute instead 631 if (presAttr != null) 632 { 633 return true; 634 } 635 636 //Check for style sheet 637 SVGRoot root = getRoot(); 638 if (root != null) 639 { 640 StyleSheet ss = root.getStyleSheet(); 641 if (ss != null) 642 { 643 return ss.getStyle(attrib, getTagName(), cssClass); 644 } 645 } 646 647 //If we're recursive, check parents 648 if (recursive) 649 { 650 SVGElement parentContext = getParentContext(); 651 if (parentContext != null) 652 { 653 return parentContext.getStyle(attrib, true); 654 } 655 if (parent != null) 656 { 657 return parent.getStyle(attrib, true); 658 } 659 } 660 661 //Unsuccessful reading style attribute 662 return false; 663 } 664 665 /** 666 * @return the raw style value of this attribute. Does not take the 667 * presentation value or animation into consideration. Used by animations to 668 * determine the base to animate from. 669 */ 670 public StyleAttribute getStyleAbsolute(String styName) 671 { 672 //Check for local inline styles 673 return (StyleAttribute) inlineStyles.get(styName); 674 } 675 676 /** 677 * Copies the presentation attribute into the passed one. 678 * 679 * @return - True if attribute was read successfully 680 */ 681 public boolean getPres(StyleAttribute attrib) throws SVGException 682 { 683 String presName = attrib.getName(); 684 685 //Make sure we have a coresponding presentation attribute 686 StyleAttribute presAttr = (StyleAttribute) presAttribs.get(presName); 687 688 //Copy presentation value directly 689 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue()); 690 691 //Evalutate coresponding track, if one exists 692 TrackBase track = trackManager.getTrack(presName, AnimationElement.AT_XML); 693 if (track != null) 694 { 695 track.getValue(attrib, diagram.getUniverse().getCurTime()); 696 return true; 697 } 698 699 //Return if we found presentation attribute 700 if (presAttr != null) 701 { 702 return true; 703 } 704 705 return false; 706 } 707 708 /** 709 * @return the raw presentation value of this attribute. Ignores any 710 * modifications applied by style attributes or animation. Used by 711 * animations to determine the starting point to animate from 712 */ 713 public StyleAttribute getPresAbsolute(String styName) 714 { 715 //Check for local inline styles 716 return (StyleAttribute) presAttribs.get(styName); 717 } 718 719 static protected AffineTransform parseTransform(String val) throws SVGException 720 { 721 final Matcher matchExpression = Pattern.compile("\\w+\\([^)]*\\)").matcher(""); 722 723 AffineTransform retXform = new AffineTransform(); 724 725 matchExpression.reset(val); 726 while (matchExpression.find()) 727 { 728 retXform.concatenate(parseSingleTransform(matchExpression.group())); 729 } 730 731 return retXform; 732 } 733 734 static public AffineTransform parseSingleTransform(String val) throws SVGException 735 { 736 final Matcher matchWord = Pattern.compile("[-.\\w]+").matcher(""); 737 738 AffineTransform retXform = new AffineTransform(); 739 740 matchWord.reset(val); 741 if (!matchWord.find()) 742 { 743 //Return identity transformation if no data present (eg, empty string) 744 return retXform; 745 } 746 747 String function = matchWord.group().toLowerCase(); 748 749 LinkedList termList = new LinkedList(); 750 while (matchWord.find()) 751 { 752 termList.add(matchWord.group()); 753 } 754 755 756 double[] terms = new double[termList.size()]; 757 Iterator it = termList.iterator(); 758 int count = 0; 759 while (it.hasNext()) 760 { 761 terms[count++] = XMLParseUtil.parseDouble((String) it.next()); 762 } 763 764 //Calculate transformation 765 if (function.equals("matrix")) 766 { 767 retXform.setTransform(terms[0], terms[1], terms[2], terms[3], terms[4], terms[5]); 768 } else if (function.equals("translate")) 769 { 770 if (terms.length == 1) 771 { 772 retXform.setToTranslation(terms[0], 0); 773 } else 774 { 775 retXform.setToTranslation(terms[0], terms[1]); 776 } 777 } else if (function.equals("scale")) 778 { 779 if (terms.length > 1) 780 { 781 retXform.setToScale(terms[0], terms[1]); 782 } else 783 { 784 retXform.setToScale(terms[0], terms[0]); 785 } 786 } else if (function.equals("rotate")) 787 { 788 if (terms.length > 2) 789 { 790 retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]); 791 } else 792 { 793 retXform.setToRotation(Math.toRadians(terms[0])); 794 } 795 } else if (function.equals("skewx")) 796 { 797 retXform.setToShear(Math.toRadians(terms[0]), 0.0); 798 } else if (function.equals("skewy")) 799 { 800 retXform.setToShear(0.0, Math.toRadians(terms[0])); 801 } else 802 { 803 throw new SVGException("Unknown transform type"); 804 } 805 806 return retXform; 807 } 808 809 static protected float nextFloat(LinkedList l) 810 { 811 String s = (String) l.removeFirst(); 812 return Float.parseFloat(s); 813 } 814 815 static protected PathCommand[] parsePathList(String list) 816 { 817 final Matcher matchPathCmd = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)").matcher(list); 818 819 //Tokenize 820 LinkedList tokens = new LinkedList(); 821 while (matchPathCmd.find()) 822 { 823 tokens.addLast(matchPathCmd.group()); 824 } 825 826 827 boolean defaultRelative = false; 828 LinkedList cmdList = new LinkedList(); 829 char curCmd = 'Z'; 830 while (tokens.size() != 0) 831 { 832 String curToken = (String) tokens.removeFirst(); 833 char initChar = curToken.charAt(0); 834 if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z')) 835 { 836 curCmd = initChar; 837 } else 838 { 839 tokens.addFirst(curToken); 840 } 841 842 PathCommand cmd = null; 843 844 switch (curCmd) 845 { 846 case 'M': 847 cmd = new MoveTo(false, nextFloat(tokens), nextFloat(tokens)); 848 curCmd = 'L'; 849 break; 850 case 'm': 851 cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens)); 852 curCmd = 'l'; 853 break; 854 case 'L': 855 cmd = new LineTo(false, nextFloat(tokens), nextFloat(tokens)); 856 break; 857 case 'l': 858 cmd = new LineTo(true, nextFloat(tokens), nextFloat(tokens)); 859 break; 860 case 'H': 861 cmd = new Horizontal(false, nextFloat(tokens)); 862 break; 863 case 'h': 864 cmd = new Horizontal(true, nextFloat(tokens)); 865 break; 866 case 'V': 867 cmd = new Vertical(false, nextFloat(tokens)); 868 break; 869 case 'v': 870 cmd = new Vertical(true, nextFloat(tokens)); 871 break; 872 case 'A': 873 cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens), 874 nextFloat(tokens), 875 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 876 nextFloat(tokens), nextFloat(tokens)); 877 break; 878 case 'a': 879 cmd = new Arc(true, nextFloat(tokens), nextFloat(tokens), 880 nextFloat(tokens), 881 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 882 nextFloat(tokens), nextFloat(tokens)); 883 break; 884 case 'Q': 885 cmd = new Quadratic(false, nextFloat(tokens), nextFloat(tokens), 886 nextFloat(tokens), nextFloat(tokens)); 887 break; 888 case 'q': 889 cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens), 890 nextFloat(tokens), nextFloat(tokens)); 891 break; 892 case 'T': 893 cmd = new QuadraticSmooth(false, nextFloat(tokens), nextFloat(tokens)); 894 break; 895 case 't': 896 cmd = new QuadraticSmooth(true, nextFloat(tokens), nextFloat(tokens)); 897 break; 898 case 'C': 899 cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens), 900 nextFloat(tokens), nextFloat(tokens), 901 nextFloat(tokens), nextFloat(tokens)); 902 break; 903 case 'c': 904 cmd = new Cubic(true, nextFloat(tokens), nextFloat(tokens), 905 nextFloat(tokens), nextFloat(tokens), 906 nextFloat(tokens), nextFloat(tokens)); 907 break; 908 case 'S': 909 cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens), 910 nextFloat(tokens), nextFloat(tokens)); 911 break; 912 case 's': 913 cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens), 914 nextFloat(tokens), nextFloat(tokens)); 915 break; 916 case 'Z': 917 case 'z': 918 cmd = new Terminal(); 919 break; 920 default: 921 throw new RuntimeException("Invalid path element"); 922 } 923 924 cmdList.add(cmd); 925 defaultRelative = cmd.isRelative; 926 } 927 928 PathCommand[] retArr = new PathCommand[cmdList.size()]; 929 cmdList.toArray(retArr); 930 return retArr; 931 } 932 933 static protected GeneralPath buildPath(String text, int windingRule) 934 { 935 PathCommand[] commands = parsePathList(text); 936 937 int numKnots = 2; 938 for (int i = 0; i < commands.length; i++) 939 { 940 numKnots += commands[i].getNumKnotsAdded(); 941 } 942 943 944 GeneralPath path = new GeneralPath(windingRule, numKnots); 945 946 BuildHistory hist = new BuildHistory(); 947 948 for (int i = 0; i < commands.length; i++) 949 { 950 PathCommand cmd = commands[i]; 951 cmd.appendPath(path, hist); 952 } 953 954 return path; 955 } 956 957 /** 958 * Updates all attributes in this diagram associated with a time event. Ie, 959 * all attributes with track information. 960 * 961 * @return - true if this node has changed state as a result of the time 962 * update 963 */ 964 abstract public boolean updateTime(double curTime) throws SVGException; 965 966 public int getNumChildren() 967 { 968 return children.size(); 969 } 970 971 public SVGElement getChild(int i) 972 { 973 return (SVGElement) children.get(i); 974 } 975 976 public double lerp(double t0, double t1, double alpha) 977 { 978 return (1 - alpha) * t0 + alpha * t1; 979 } 980}