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 private static final Pattern TRANSFORM_PATTERN = Pattern.compile("\\w+\\([^)]*\\)"); 747 static protected AffineTransform parseTransform(String val) throws SVGException 748 { 749 final Matcher matchExpression = TRANSFORM_PATTERN.matcher(""); 750 751 AffineTransform retXform = new AffineTransform(); 752 753 matchExpression.reset(val); 754 while (matchExpression.find()) 755 { 756 retXform.concatenate(parseSingleTransform(matchExpression.group())); 757 } 758 759 return retXform; 760 } 761 762 private static final Pattern WORD_PATTERN = Pattern.compile("([a-zA-Z]+|-?\\d+(\\.\\d+)?(e-?\\d+)?|-?\\.\\d+(e-?\\d+)?)"); 763 static public AffineTransform parseSingleTransform(String val) throws SVGException 764 { 765 final Matcher matchWord = WORD_PATTERN.matcher(""); 766 767 AffineTransform retXform = new AffineTransform(); 768 769 matchWord.reset(val); 770 if (!matchWord.find()) 771 { 772 //Return identity transformation if no data present (eg, empty string) 773 return retXform; 774 } 775 776 String function = matchWord.group().toLowerCase(); 777 778 LinkedList<String> termList = new LinkedList<String>(); 779 while (matchWord.find()) 780 { 781 termList.add(matchWord.group()); 782 } 783 784 785 double[] terms = new double[termList.size()]; 786 Iterator<String> it = termList.iterator(); 787 int count = 0; 788 while (it.hasNext()) 789 { 790 terms[count++] = XMLParseUtil.parseDouble(it.next()); 791 } 792 793 //Calculate transformation 794 if (function.equals("matrix")) 795 { 796 retXform.setTransform(terms[0], terms[1], terms[2], terms[3], terms[4], terms[5]); 797 } else if (function.equals("translate")) 798 { 799 if (terms.length == 1) 800 { 801 retXform.setToTranslation(terms[0], 0); 802 } else 803 { 804 retXform.setToTranslation(terms[0], terms[1]); 805 } 806 } else if (function.equals("scale")) 807 { 808 if (terms.length > 1) 809 { 810 retXform.setToScale(terms[0], terms[1]); 811 } else 812 { 813 retXform.setToScale(terms[0], terms[0]); 814 } 815 } else if (function.equals("rotate")) 816 { 817 if (terms.length > 2) 818 { 819 retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]); 820 } else 821 { 822 retXform.setToRotation(Math.toRadians(terms[0])); 823 } 824 } else if (function.equals("skewx")) 825 { 826 retXform.setToShear(Math.toRadians(terms[0]), 0.0); 827 } else if (function.equals("skewy")) 828 { 829 retXform.setToShear(0.0, Math.toRadians(terms[0])); 830 } else 831 { 832 throw new SVGException("Unknown transform type"); 833 } 834 835 return retXform; 836 } 837 838 static protected float nextFloat(LinkedList<String> l) 839 { 840 String s = (String) l.removeFirst(); 841 return Float.parseFloat(s); 842 } 843 844 private static final Pattern COMMAND_PATTERN = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)"); 845 static protected PathCommand[] parsePathList(String list) 846 { 847 final Matcher matchPathCmd = COMMAND_PATTERN.matcher(list); 848 849 //Tokenize 850 LinkedList<String> tokens = new LinkedList<String>(); 851 while (matchPathCmd.find()) 852 { 853 tokens.addLast(matchPathCmd.group()); 854 } 855 856 857 boolean defaultRelative = false; 858 LinkedList<PathCommand> cmdList = new LinkedList<PathCommand>(); 859 char curCmd = 'Z'; 860 while (tokens.size() != 0) 861 { 862 String curToken = (String) tokens.removeFirst(); 863 char initChar = curToken.charAt(0); 864 if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z')) 865 { 866 curCmd = initChar; 867 } else 868 { 869 tokens.addFirst(curToken); 870 } 871 872 PathCommand cmd = null; 873 874 switch (curCmd) 875 { 876 case 'M': 877 cmd = new MoveTo(false, nextFloat(tokens), nextFloat(tokens)); 878 curCmd = 'L'; 879 break; 880 case 'm': 881 cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens)); 882 curCmd = 'l'; 883 break; 884 case 'L': 885 cmd = new LineTo(false, nextFloat(tokens), nextFloat(tokens)); 886 break; 887 case 'l': 888 cmd = new LineTo(true, nextFloat(tokens), nextFloat(tokens)); 889 break; 890 case 'H': 891 cmd = new Horizontal(false, nextFloat(tokens)); 892 break; 893 case 'h': 894 cmd = new Horizontal(true, nextFloat(tokens)); 895 break; 896 case 'V': 897 cmd = new Vertical(false, nextFloat(tokens)); 898 break; 899 case 'v': 900 cmd = new Vertical(true, nextFloat(tokens)); 901 break; 902 case 'A': 903 cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens), 904 nextFloat(tokens), 905 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 906 nextFloat(tokens), nextFloat(tokens)); 907 break; 908 case 'a': 909 cmd = new Arc(true, nextFloat(tokens), nextFloat(tokens), 910 nextFloat(tokens), 911 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f, 912 nextFloat(tokens), nextFloat(tokens)); 913 break; 914 case 'Q': 915 cmd = new Quadratic(false, nextFloat(tokens), nextFloat(tokens), 916 nextFloat(tokens), nextFloat(tokens)); 917 break; 918 case 'q': 919 cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens), 920 nextFloat(tokens), nextFloat(tokens)); 921 break; 922 case 'T': 923 cmd = new QuadraticSmooth(false, nextFloat(tokens), nextFloat(tokens)); 924 break; 925 case 't': 926 cmd = new QuadraticSmooth(true, nextFloat(tokens), nextFloat(tokens)); 927 break; 928 case 'C': 929 cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens), 930 nextFloat(tokens), nextFloat(tokens), 931 nextFloat(tokens), nextFloat(tokens)); 932 break; 933 case 'c': 934 cmd = new Cubic(true, nextFloat(tokens), nextFloat(tokens), 935 nextFloat(tokens), nextFloat(tokens), 936 nextFloat(tokens), nextFloat(tokens)); 937 break; 938 case 'S': 939 cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens), 940 nextFloat(tokens), nextFloat(tokens)); 941 break; 942 case 's': 943 cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens), 944 nextFloat(tokens), nextFloat(tokens)); 945 break; 946 case 'Z': 947 case 'z': 948 cmd = new Terminal(); 949 break; 950 default: 951 throw new RuntimeException("Invalid path element"); 952 } 953 954 cmdList.add(cmd); 955 defaultRelative = cmd.isRelative; 956 } 957 958 PathCommand[] retArr = new PathCommand[cmdList.size()]; 959 cmdList.toArray(retArr); 960 return retArr; 961 } 962 963 static protected GeneralPath buildPath(String text, int windingRule) 964 { 965 PathCommand[] commands = parsePathList(text); 966 967 int numKnots = 2; 968 for (int i = 0; i < commands.length; i++) 969 { 970 numKnots += commands[i].getNumKnotsAdded(); 971 } 972 973 974 GeneralPath path = new GeneralPath(windingRule, numKnots); 975 976 BuildHistory hist = new BuildHistory(); 977 978 for (int i = 0; i < commands.length; i++) 979 { 980 PathCommand cmd = commands[i]; 981 cmd.appendPath(path, hist); 982 } 983 984 return path; 985 } 986 987 /** 988 * Updates all attributes in this diagram associated with a time event. Ie, 989 * all attributes with track information. 990 * 991 * @param curTime 992 * @return - true if this node has changed state as a result of the time 993 * update 994 * @throws com.kitfox.svg.SVGException 995 */ 996 abstract public boolean updateTime(double curTime) throws SVGException; 997 998 public int getNumChildren() 999 { 1000 return children.size(); 1001 } 1002 1003 public SVGElement getChild(int i) 1004 { 1005 return (SVGElement) children.get(i); 1006 } 1007 1008 public double lerp(double t0, double t1, double alpha) 1009 { 1010 return (1 - alpha) * t0 + alpha * t1; 1011 } 1012}