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 public boolean getStyle(StyleAttribute attrib, boolean recursive) throws SVGException 583 { 584 return getStyle(attrib, recursive, true); 585 } 586 587 /** 588 * Copies the current style into the passed style attribute. Checks for 589 * inline styles first, then internal and extranal style sheets, and finally 590 * checks for presentation attributes. Recursively checks parents. 591 * 592 * @param attrib - Attribute to write style data to. Must have it's name set 593 * to the name of the style being queried. 594 * @param recursive - If true and this object does not contain the named 595 * style attribute, checks attributes of parents back to root until one 596 * found. 597 */ 598 public boolean getStyle(StyleAttribute attrib, boolean recursive, boolean evalAnimation) 599 throws SVGException 600 { 601 String styName = attrib.getName(); 602 603 //Check for local inline styles 604 StyleAttribute styAttr = (StyleAttribute)inlineStyles.get(styName); 605 606 attrib.setStringValue(styAttr == null ? "" : styAttr.getStringValue()); 607 608 //Evalutate coresponding track, if one exists 609 if (evalAnimation) 610 { 611 TrackBase track = trackManager.getTrack(styName, AnimationElement.AT_CSS); 612 if (track != null) 613 { 614 track.getValue(attrib, diagram.getUniverse().getCurTime()); 615 return true; 616 } 617 } 618 619 //Return if we've found a non animated style 620 if (styAttr != null) 621 { 622 return true; 623 } 624 625 626 //Check for presentation attribute 627 StyleAttribute presAttr = (StyleAttribute)presAttribs.get(styName); 628 629 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue()); 630 631 //Evalutate coresponding track, if one exists 632 if (evalAnimation) 633 { 634 TrackBase track = trackManager.getTrack(styName, AnimationElement.AT_XML); 635 if (track != null) 636 { 637 track.getValue(attrib, diagram.getUniverse().getCurTime()); 638 return true; 639 } 640 } 641 642 //Return if we've found a presentation attribute instead 643 if (presAttr != null) 644 { 645 return true; 646 } 647 648 //Check for style sheet 649 SVGRoot root = getRoot(); 650 if (root != null) 651 { 652 StyleSheet ss = root.getStyleSheet(); 653 if (ss != null) 654 { 655 return ss.getStyle(attrib, getTagName(), cssClass); 656 } 657 } 658 659 //If we're recursive, check parents 660 if (recursive) 661 { 662 SVGElement parentContext = getParentContext(); 663 if (parentContext != null) 664 { 665 return parentContext.getStyle(attrib, true); 666 } 667 if (parent != null) 668 { 669 return parent.getStyle(attrib, true); 670 } 671 } 672 673 //Unsuccessful reading style attribute 674 return false; 675 } 676 677 /** 678 * @return the raw style value of this attribute. Does not take the 679 * presentation value or animation into consideration. Used by animations to 680 * determine the base to animate from. 681 */ 682 public StyleAttribute getStyleAbsolute(String styName) 683 { 684 //Check for local inline styles 685 return (StyleAttribute) inlineStyles.get(styName); 686 } 687 688 /** 689 * Copies the presentation attribute into the passed one. 690 * 691 * @return - True if attribute was read successfully 692 */ 693 public boolean getPres(StyleAttribute attrib) throws SVGException 694 { 695 String presName = attrib.getName(); 696 697 //Make sure we have a coresponding presentation attribute 698 StyleAttribute presAttr = (StyleAttribute) presAttribs.get(presName); 699 700 //Copy presentation value directly 701 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue()); 702 703 //Evalutate coresponding track, if one exists 704 TrackBase track = trackManager.getTrack(presName, AnimationElement.AT_XML); 705 if (track != null) 706 { 707 track.getValue(attrib, diagram.getUniverse().getCurTime()); 708 return true; 709 } 710 711 //Return if we found presentation attribute 712 if (presAttr != null) 713 { 714 return true; 715 } 716 717 return false; 718 } 719 720 /** 721 * @return the raw presentation value of this attribute. Ignores any 722 * modifications applied by style attributes or animation. Used by 723 * animations to determine the starting point to animate from 724 */ 725 public StyleAttribute getPresAbsolute(String styName) 726 { 727 //Check for local inline styles 728 return (StyleAttribute) presAttribs.get(styName); 729 } 730 731 static protected AffineTransform parseTransform(String val) throws SVGException 732 { 733 final Matcher matchExpression = Pattern.compile("\\w+\\([^)]*\\)").matcher(""); 734 735 AffineTransform retXform = new AffineTransform(); 736 737 matchExpression.reset(val); 738 while (matchExpression.find()) 739 { 740 retXform.concatenate(parseSingleTransform(matchExpression.group())); 741 } 742 743 return retXform; 744 } 745 746 static public AffineTransform parseSingleTransform(String val) throws SVGException 747 { 748 final Matcher matchWord = Pattern.compile("[-.\\w]+").matcher(""); 749 750 AffineTransform retXform = new AffineTransform(); 751 752 matchWord.reset(val); 753 if (!matchWord.find()) 754 { 755 //Return identity transformation if no data present (eg, empty string) 756 return retXform; 757 } 758 759 String function = matchWord.group().toLowerCase(); 760 761 LinkedList termList = new LinkedList(); 762 while (matchWord.find()) 763 { 764 termList.add(matchWord.group()); 765 } 766 767 768 double[] terms = new double[termList.size()]; 769 Iterator it = termList.iterator(); 770 int count = 0; 771 while (it.hasNext()) 772 { 773 terms[count++] = XMLParseUtil.parseDouble((String) it.next()); 774 } 775 776 //Calculate transformation 777 if (function.equals("matrix")) 778 { 779 retXform.setTransform(terms[0], terms[1], terms[2], terms[3], terms[4], terms[5]); 780 } else if (function.equals("translate")) 781 { 782 if (terms.length == 1) 783 { 784 retXform.setToTranslation(terms[0], 0); 785 } else 786 { 787 retXform.setToTranslation(terms[0], terms[1]); 788 } 789 } else if (function.equals("scale")) 790 { 791 if (terms.length > 1) 792 { 793 retXform.setToScale(terms[0], terms[1]); 794 } else 795 { 796 retXform.setToScale(terms[0], terms[0]); 797 } 798 } else if (function.equals("rotate")) 799 { 800 if (terms.length > 2) 801 { 802 retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]); 803 } else 804 { 805 retXform.setToRotation(Math.toRadians(terms[0])); 806 } 807 } else if (function.equals("skewx")) 808 { 809 retXform.setToShear(Math.toRadians(terms[0]), 0.0); 810 } else if (function.equals("skewy")) 811 { 812 retXform.setToShear(0.0, Math.toRadians(terms[0])); 813 } else 814 { 815 throw new SVGException("Unknown transform type"); 816 } 817 818 return retXform; 819 } 820 821 static protected float nextFloat(LinkedList l) 822 { 823 String s = (String) l.removeFirst(); 824 return Float.parseFloat(s); 825 } 826 827 static protected PathCommand[] parsePathList(String list) 828 { 829 final Matcher matchPathCmd = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)").matcher(list); 830 831 //Tokenize 832 LinkedList tokens = new LinkedList(); 833 while (matchPathCmd.find()) 834 { 835 tokens.addLast(matchPathCmd.group()); 836 } 837 838 839 boolean defaultRelative = false; 840 LinkedList cmdList = new LinkedList(); 841 char curCmd = 'Z'; 842 while (tokens.size() != 0) 843 { 844 String curToken = (String) tokens.removeFirst(); 845 char initChar = curToken.charAt(0); 846 if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z')) 847 { 848 curCmd = initChar; 849 } else 850 { 851 tokens.addFirst(curToken); 852 } 853 854 PathCommand cmd = null; 855 856 switch (curCmd) 857 { 858 case 'M': 859 cmd = new MoveTo(false, nextFloat(tokens), nextFloat(tokens)); 860 curCmd = 'L'; 861 break; 862 case 'm': 863 cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens)); 864 curCmd = 'l'; 865 break; 866 case 'L': 867 cmd = new LineTo(false, nextFloat(tokens), nextFloat(tokens)); 868 break; 869 case 'l': 870 cmd = new LineTo(true, nextFloat(tokens), nextFloat(tokens)); 871 break; 872 case 'H': 873 cmd = new Horizontal(false, nextFloat(tokens)); 874 break; 875 case 'h': 876 cmd = new Horizontal(true, nextFloat(tokens)); 877 break; 878 case 'V': 879 cmd = new Vertical(false, nextFloat(tokens)); 880 break; 881 case 'v': 882 cmd = new Vertical(true, nextFloat(tokens)); 883 break; 884 case 'A': 885 cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens), 886 nextFloat(tokens), 887 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 888 nextFloat(tokens), nextFloat(tokens)); 889 break; 890 case 'a': 891 cmd = new Arc(true, nextFloat(tokens), nextFloat(tokens), 892 nextFloat(tokens), 893 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 894 nextFloat(tokens), nextFloat(tokens)); 895 break; 896 case 'Q': 897 cmd = new Quadratic(false, nextFloat(tokens), nextFloat(tokens), 898 nextFloat(tokens), nextFloat(tokens)); 899 break; 900 case 'q': 901 cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens), 902 nextFloat(tokens), nextFloat(tokens)); 903 break; 904 case 'T': 905 cmd = new QuadraticSmooth(false, nextFloat(tokens), nextFloat(tokens)); 906 break; 907 case 't': 908 cmd = new QuadraticSmooth(true, nextFloat(tokens), nextFloat(tokens)); 909 break; 910 case 'C': 911 cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens), 912 nextFloat(tokens), nextFloat(tokens), 913 nextFloat(tokens), nextFloat(tokens)); 914 break; 915 case 'c': 916 cmd = new Cubic(true, nextFloat(tokens), nextFloat(tokens), 917 nextFloat(tokens), nextFloat(tokens), 918 nextFloat(tokens), nextFloat(tokens)); 919 break; 920 case 'S': 921 cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens), 922 nextFloat(tokens), nextFloat(tokens)); 923 break; 924 case 's': 925 cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens), 926 nextFloat(tokens), nextFloat(tokens)); 927 break; 928 case 'Z': 929 case 'z': 930 cmd = new Terminal(); 931 break; 932 default: 933 throw new RuntimeException("Invalid path element"); 934 } 935 936 cmdList.add(cmd); 937 defaultRelative = cmd.isRelative; 938 } 939 940 PathCommand[] retArr = new PathCommand[cmdList.size()]; 941 cmdList.toArray(retArr); 942 return retArr; 943 } 944 945 static protected GeneralPath buildPath(String text, int windingRule) 946 { 947 PathCommand[] commands = parsePathList(text); 948 949 int numKnots = 2; 950 for (int i = 0; i < commands.length; i++) 951 { 952 numKnots += commands[i].getNumKnotsAdded(); 953 } 954 955 956 GeneralPath path = new GeneralPath(windingRule, numKnots); 957 958 BuildHistory hist = new BuildHistory(); 959 960 for (int i = 0; i < commands.length; i++) 961 { 962 PathCommand cmd = commands[i]; 963 cmd.appendPath(path, hist); 964 } 965 966 return path; 967 } 968 969 /** 970 * Updates all attributes in this diagram associated with a time event. Ie, 971 * all attributes with track information. 972 * 973 * @return - true if this node has changed state as a result of the time 974 * update 975 */ 976 abstract public boolean updateTime(double curTime) throws SVGException; 977 978 public int getNumChildren() 979 { 980 return children.size(); 981 } 982 983 public SVGElement getChild(int i) 984 { 985 return (SVGElement) children.get(i); 986 } 987 988 public double lerp(double t0, double t1, double alpha) 989 { 990 return (1 - alpha) * t0 + alpha * t1; 991 } 992}