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