source: josm/trunk/src/com/kitfox/svg/SVGElement.java@ 8132

Last change on this file since 8132 was 8084, checked in by bastiK, 9 years ago

add svn:eol-style=native for svgsalamander

  • Property svn:eol-style set to native
File size: 24.9 KB
Line 
1/*
2 * SVG Salamander
3 * Copyright (c) 2004, Mark McKay
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
9 *
10 * - Redistributions of source code must retain the above
11 * copyright notice, this list of conditions and the following
12 * disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials
16 * provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
32 * projects can be found at http://www.kitfox.com
33 *
34 * Created on January 26, 2004, 1:59 AM
35 */
36package com.kitfox.svg;
37
38import com.kitfox.svg.pathcmd.Arc;
39import com.kitfox.svg.pathcmd.BuildHistory;
40import com.kitfox.svg.pathcmd.Cubic;
41import com.kitfox.svg.pathcmd.CubicSmooth;
42import com.kitfox.svg.pathcmd.Horizontal;
43import com.kitfox.svg.pathcmd.LineTo;
44import com.kitfox.svg.pathcmd.MoveTo;
45import com.kitfox.svg.pathcmd.PathCommand;
46import com.kitfox.svg.pathcmd.Quadratic;
47import com.kitfox.svg.pathcmd.QuadraticSmooth;
48import com.kitfox.svg.pathcmd.Terminal;
49import com.kitfox.svg.pathcmd.Vertical;
50import com.kitfox.svg.xml.StyleAttribute;
51import com.kitfox.svg.xml.StyleSheet;
52import com.kitfox.svg.xml.XMLParseUtil;
53import java.awt.geom.AffineTransform;
54import java.awt.geom.GeneralPath;
55import java.io.Serializable;
56import java.net.URI;
57import java.util.ArrayList;
58import java.util.Collections;
59import java.util.HashMap;
60import java.util.HashSet;
61import java.util.Iterator;
62import java.util.LinkedList;
63import java.util.List;
64import java.util.Set;
65import java.util.regex.Matcher;
66import java.util.regex.Pattern;
67import org.xml.sax.Attributes;
68import org.xml.sax.SAXException;
69
70
71/**
72 * @author Mark McKay
73 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
74 */
75abstract public class SVGElement implements Serializable
76{
77
78 public static final long serialVersionUID = 0;
79 public static final String SVG_NS = "http://www.w3.org/2000/svg";
80 protected SVGElement parent = null;
81 protected final ArrayList children = new ArrayList();
82 protected String id = null;
83 /**
84 * CSS class. Used for applying style sheet information.
85 */
86 protected String cssClass = null;
87 /**
88 * Styles defined for this elemnt via the <b>style</b> attribute.
89 */
90 protected final HashMap inlineStyles = new HashMap();
91 /**
92 * Presentation attributes set for this element. Ie, any attribute other
93 * than the <b>style</b> attribute.
94 */
95 protected final HashMap presAttribs = new HashMap();
96 /**
97 * A list of presentation attributes to not include in the presentation
98 * attribute set.
99 */
100 protected static final Set ignorePresAttrib;
101
102 static
103 {
104 HashSet set = new HashSet();
105// set.add("id");
106// set.add("class");
107// set.add("style");
108// set.add("xml:base");
109
110 ignorePresAttrib = Collections.unmodifiableSet(set);
111 }
112 /**
113 * This element may override the URI we resolve against with an xml:base
114 * attribute. If so, a copy is placed here. Otherwise, we defer to our
115 * parent for the reolution base
116 */
117 protected URI xmlBase = null;
118 /**
119 * The diagram this element belongs to
120 */
121 protected SVGDiagram diagram;
122 boolean dirty = true;
123
124 /**
125 * Creates a new instance of SVGElement
126 */
127 public SVGElement()
128 {
129 this(null, null, null);
130 }
131
132 public SVGElement(String id, SVGElement parent)
133 {
134 this(id, null, parent);
135 }
136
137 public SVGElement(String id, String cssClass, SVGElement parent)
138 {
139 this.id = id;
140 this.cssClass = cssClass;
141 this.parent = parent;
142 }
143
144 abstract public String getTagName();
145
146 public SVGElement getParent()
147 {
148 return parent;
149 }
150
151 void setParent(SVGElement parent)
152 {
153 this.parent = parent;
154 }
155
156 /**
157 * @return an ordered list of nodes from the root of the tree to this node
158 */
159 public List getPath(List retVec)
160 {
161 if (retVec == null)
162 {
163 retVec = new ArrayList();
164 }
165
166 if (parent != null)
167 {
168 parent.getPath(retVec);
169 }
170 retVec.add(this);
171
172 return retVec;
173 }
174
175 /**
176 * @param retVec - A list to add all children to. If null, a new list is
177 * created and children of this group are added.
178 *
179 * @return The list containing the children of this group
180 */
181 public List getChildren(List retVec)
182 {
183 if (retVec == null)
184 {
185 retVec = new ArrayList();
186 }
187
188 retVec.addAll(children);
189
190 return retVec;
191 }
192
193 /**
194 * @param id - Id of svg element to return
195 * @return the child of the given id, or null if no such child exists.
196 */
197 public SVGElement getChild(String id)
198 {
199 for (Iterator it = children.iterator(); it.hasNext();)
200 {
201 SVGElement ele = (SVGElement) it.next();
202 String eleId = ele.getId();
203 if (eleId != null && eleId.equals(id))
204 {
205 return ele;
206 }
207 }
208
209 return null;
210 }
211
212 /**
213 * Searches children for given element. If found, returns index of child.
214 * Otherwise returns -1.
215 */
216 public int indexOfChild(SVGElement child)
217 {
218 return children.indexOf(child);
219 }
220
221 /**
222 * Swaps 2 elements in children.
223 *
224 * @i index of first
225 * @j index of second
226 *
227 * @return true if successful, false otherwise
228 */
229 public void swapChildren(int i, int j) throws SVGException
230 {
231 if ((children == null) || (i < 0) || (i >= children.size()) || (j < 0) || (j >= children.size()))
232 {
233 return;
234 }
235
236 Object temp = children.get(i);
237 children.set(i, children.get(j));
238 children.set(j, temp);
239 build();
240 }
241
242 /**
243 * Called during SAX load process to notify that this tag has begun the
244 * process of being loaded
245 *
246 * @param attrs - Attributes of this tag
247 * @param helper - An object passed to all SVG elements involved in this
248 * build process to aid in sharing information.
249 */
250 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
251 {
252 //Set identification info
253 this.parent = parent;
254 this.diagram = helper.diagram;
255
256 this.id = attrs.getValue("id");
257 if (this.id != null && !this.id.equals(""))
258 {
259 diagram.setElement(this.id, this);
260 }
261
262 String className = attrs.getValue("class");
263 this.cssClass = (className == null || className.equals("")) ? null : className;
264 //docRoot = helper.docRoot;
265 //universe = helper.universe;
266
267 //Parse style string, if any
268 String style = attrs.getValue("style");
269 if (style != null)
270 {
271 HashMap map = XMLParseUtil.parseStyle(style, inlineStyles);
272 }
273
274 String base = attrs.getValue("xml:base");
275 if (base != null && !base.equals(""))
276 {
277 try
278 {
279 xmlBase = new URI(base);
280 } catch (Exception e)
281 {
282 throw new SAXException(e);
283 }
284 }
285
286 //Place all other attributes into the presentation attribute list
287 int numAttrs = attrs.getLength();
288 for (int i = 0; i < numAttrs; i++)
289 {
290 String name = attrs.getQName(i);
291 if (ignorePresAttrib.contains(name))
292 {
293 continue;
294 }
295 String value = attrs.getValue(i);
296
297 presAttribs.put(name, new StyleAttribute(name, value));
298 }
299 }
300
301 /**
302 * @return a set of Strings that corespond to CSS attributes on this element
303 */
304 public Set getInlineAttributes()
305 {
306 return inlineStyles.keySet();
307 }
308
309 /**
310 * @return a set of Strings that corespond to XML attributes on this element
311 */
312 public Set getPresentationAttributes()
313 {
314 return presAttribs.keySet();
315 }
316
317 /**
318 * Called after the start element but before the end element to indicate
319 * each child tag that has been processed
320 */
321 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
322 {
323 children.add(child);
324 child.parent = this;
325 child.setDiagram(diagram);
326 }
327
328 protected void setDiagram(SVGDiagram diagram)
329 {
330 this.diagram = diagram;
331 diagram.setElement(id, this);
332 for (Iterator it = children.iterator(); it.hasNext();)
333 {
334 SVGElement ele = (SVGElement) it.next();
335 ele.setDiagram(diagram);
336 }
337 }
338
339 public void removeChild(SVGElement child) throws SVGElementException
340 {
341 if (!children.contains(child))
342 {
343 throw new SVGElementException(this, "Element does not contain child " + child);
344 }
345
346 children.remove(child);
347 }
348
349 /**
350 * Called during load process to add text scanned within a tag
351 */
352 public void loaderAddText(SVGLoaderHelper helper, String text)
353 {
354 }
355
356 /**
357 * Called to indicate that this tag and the tags it contains have been
358 * completely processed, and that it should finish any load processes.
359 */
360 public void loaderEndElement(SVGLoaderHelper helper) throws SVGParseException
361 {
362// try
363// {
364// build();
365// }
366// catch (SVGException se)
367// {
368// throw new SVGParseException(se);
369// }
370 }
371
372 /**
373 * Called by internal processes to rebuild the geometry of this node from
374 * it's presentation attributes, style attributes and animated tracks.
375 */
376 protected void build() throws SVGException
377 {
378 StyleAttribute sty = new StyleAttribute();
379
380 if (getPres(sty.setName("id")))
381 {
382 String newId = sty.getStringValue();
383 if (!newId.equals(id))
384 {
385 diagram.removeElement(id);
386 id = newId;
387 diagram.setElement(this.id, this);
388 }
389 }
390 if (getPres(sty.setName("class")))
391 {
392 cssClass = sty.getStringValue();
393 }
394 if (getPres(sty.setName("xml:base")))
395 {
396 xmlBase = sty.getURIValue();
397 }
398
399 //Build children
400 for (int i = 0; i < children.size(); ++i)
401 {
402 SVGElement ele = (SVGElement) children.get(i);
403 ele.build();
404 }
405 }
406
407 public URI getXMLBase()
408 {
409 return xmlBase != null ? xmlBase
410 : (parent != null ? parent.getXMLBase() : diagram.getXMLBase());
411 }
412
413 /**
414 * @return the id assigned to this node. Null if no id explicitly set.
415 */
416 public String getId()
417 {
418 return id;
419 }
420 LinkedList contexts = new LinkedList();
421
422 /**
423 * Hack to allow nodes to temporarily change their parents. The Use tag will
424 * need this so it can alter the attributes that a particular node uses.
425 */
426 protected void pushParentContext(SVGElement context)
427 {
428 contexts.addLast(context);
429 }
430
431 protected SVGElement popParentContext()
432 {
433 return (SVGElement) contexts.removeLast();
434 }
435
436 protected SVGElement getParentContext()
437 {
438 return contexts.isEmpty() ? null : (SVGElement) contexts.getLast();
439 }
440
441 public SVGRoot getRoot()
442 {
443 return parent == null ? null : parent.getRoot();
444 }
445
446 /*
447 * Returns the named style attribute. Checks for inline styles first, then
448 * internal and extranal style sheets, and finally checks for presentation
449 * attributes.
450 * @param styleName - Name of attribute to return
451 * @param recursive - If true and this object does not contain the
452 * named style attribute, checks attributes of parents abck to root until
453 * one found.
454 */
455 public boolean getStyle(StyleAttribute attrib) throws SVGException
456 {
457 return getStyle(attrib, true);
458 }
459
460 /**
461 * Copies the current style into the passed style attribute. Checks for
462 * inline styles first, then internal and extranal style sheets, and finally
463 * checks for presentation attributes. Recursively checks parents.
464 *
465 * @param attrib - Attribute to write style data to. Must have it's name set
466 * to the name of the style being queried.
467 * @param recursive - If true and this object does not contain the named
468 * style attribute, checks attributes of parents back to root until one
469 * found.
470 */
471 public boolean getStyle(StyleAttribute attrib, boolean recursive) throws SVGException
472 {
473 String styName = attrib.getName();
474
475 //Check for local inline styles
476 StyleAttribute styAttr = (StyleAttribute)inlineStyles.get(styName);
477
478 attrib.setStringValue(styAttr == null ? "" : styAttr.getStringValue());
479
480 //Return if we've found a non animated style
481 if (styAttr != null)
482 {
483 return true;
484 }
485
486
487 //Check for presentation attribute
488 StyleAttribute presAttr = (StyleAttribute)presAttribs.get(styName);
489
490 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue());
491
492 //Return if we've found a presentation attribute instead
493 if (presAttr != null)
494 {
495 return true;
496 }
497
498 //Check for style sheet
499 SVGRoot root = getRoot();
500 if (root != null)
501 {
502 StyleSheet ss = root.getStyleSheet();
503 if (ss != null)
504 {
505 return ss.getStyle(attrib, getTagName(), cssClass);
506 }
507 }
508
509 //If we're recursive, check parents
510 if (recursive)
511 {
512 SVGElement parentContext = getParentContext();
513 if (parentContext != null)
514 {
515 return parentContext.getStyle(attrib, true);
516 }
517 if (parent != null)
518 {
519 return parent.getStyle(attrib, true);
520 }
521 }
522
523 //Unsuccessful reading style attribute
524 return false;
525 }
526
527 /**
528 * @return the raw style value of this attribute. Does not take the
529 * presentation value or animation into consideration. Used by animations to
530 * determine the base to animate from.
531 */
532 public StyleAttribute getStyleAbsolute(String styName)
533 {
534 //Check for local inline styles
535 return (StyleAttribute) inlineStyles.get(styName);
536 }
537
538 /**
539 * Copies the presentation attribute into the passed one.
540 *
541 * @return - True if attribute was read successfully
542 */
543 public boolean getPres(StyleAttribute attrib) throws SVGException
544 {
545 String presName = attrib.getName();
546
547 //Make sure we have a coresponding presentation attribute
548 StyleAttribute presAttr = (StyleAttribute) presAttribs.get(presName);
549
550 //Copy presentation value directly
551 attrib.setStringValue(presAttr == null ? "" : presAttr.getStringValue());
552
553 //Return if we found presentation attribute
554 if (presAttr != null)
555 {
556 return true;
557 }
558
559 return false;
560 }
561
562 /**
563 * @return the raw presentation value of this attribute. Ignores any
564 * modifications applied by style attributes or animation. Used by
565 * animations to determine the starting point to animate from
566 */
567 public StyleAttribute getPresAbsolute(String styName)
568 {
569 //Check for local inline styles
570 return (StyleAttribute) presAttribs.get(styName);
571 }
572
573 static protected AffineTransform parseTransform(String val) throws SVGException
574 {
575 final Matcher matchExpression = Pattern.compile("\\w+\\([^)]*\\)").matcher("");
576
577 AffineTransform retXform = new AffineTransform();
578
579 matchExpression.reset(val);
580 while (matchExpression.find())
581 {
582 retXform.concatenate(parseSingleTransform(matchExpression.group()));
583 }
584
585 return retXform;
586 }
587
588 static public AffineTransform parseSingleTransform(String val) throws SVGException
589 {
590 final Matcher matchWord = Pattern.compile("([a-zA-Z]+|-?\\d+(\\.\\d+)?(e-?\\d+)?|-?\\.\\d+(e-?\\d+)?)").matcher("");
591
592 AffineTransform retXform = new AffineTransform();
593
594 matchWord.reset(val);
595 if (!matchWord.find())
596 {
597 //Return identity transformation if no data present (eg, empty string)
598 return retXform;
599 }
600
601 String function = matchWord.group().toLowerCase();
602
603 LinkedList termList = new LinkedList();
604 while (matchWord.find())
605 {
606 termList.add(matchWord.group());
607 }
608
609
610 double[] terms = new double[termList.size()];
611 Iterator it = termList.iterator();
612 int count = 0;
613 while (it.hasNext())
614 {
615 terms[count++] = XMLParseUtil.parseDouble((String) it.next());
616 }
617
618 //Calculate transformation
619 if (function.equals("matrix"))
620 {
621 retXform.setTransform(terms[0], terms[1], terms[2], terms[3], terms[4], terms[5]);
622 } else if (function.equals("translate"))
623 {
624 if (terms.length == 1)
625 {
626 retXform.setToTranslation(terms[0], 0);
627 } else
628 {
629 retXform.setToTranslation(terms[0], terms[1]);
630 }
631 } else if (function.equals("scale"))
632 {
633 if (terms.length > 1)
634 {
635 retXform.setToScale(terms[0], terms[1]);
636 } else
637 {
638 retXform.setToScale(terms[0], terms[0]);
639 }
640 } else if (function.equals("rotate"))
641 {
642 if (terms.length > 2)
643 {
644 retXform.setToRotation(Math.toRadians(terms[0]), terms[1], terms[2]);
645 } else
646 {
647 retXform.setToRotation(Math.toRadians(terms[0]));
648 }
649 } else if (function.equals("skewx"))
650 {
651 retXform.setToShear(Math.toRadians(terms[0]), 0.0);
652 } else if (function.equals("skewy"))
653 {
654 retXform.setToShear(0.0, Math.toRadians(terms[0]));
655 } else
656 {
657 throw new SVGException("Unknown transform type");
658 }
659
660 return retXform;
661 }
662
663 static protected float nextFloat(LinkedList l)
664 {
665 String s = (String) l.removeFirst();
666 return Float.parseFloat(s);
667 }
668
669 static protected PathCommand[] parsePathList(String list)
670 {
671 final Matcher matchPathCmd = Pattern.compile("([MmLlHhVvAaQqTtCcSsZz])|([-+]?((\\d*\\.\\d+)|(\\d+))([eE][-+]?\\d+)?)").matcher(list);
672
673 //Tokenize
674 LinkedList tokens = new LinkedList();
675 while (matchPathCmd.find())
676 {
677 tokens.addLast(matchPathCmd.group());
678 }
679
680
681 boolean defaultRelative = false;
682 LinkedList cmdList = new LinkedList();
683 char curCmd = 'Z';
684 while (tokens.size() != 0)
685 {
686 String curToken = (String) tokens.removeFirst();
687 char initChar = curToken.charAt(0);
688 if ((initChar >= 'A' && initChar <= 'Z') || (initChar >= 'a' && initChar <= 'z'))
689 {
690 curCmd = initChar;
691 } else
692 {
693 tokens.addFirst(curToken);
694 }
695
696 PathCommand cmd = null;
697
698 switch (curCmd)
699 {
700 case 'M':
701 cmd = new MoveTo(false, nextFloat(tokens), nextFloat(tokens));
702 curCmd = 'L';
703 break;
704 case 'm':
705 cmd = new MoveTo(true, nextFloat(tokens), nextFloat(tokens));
706 curCmd = 'l';
707 break;
708 case 'L':
709 cmd = new LineTo(false, nextFloat(tokens), nextFloat(tokens));
710 break;
711 case 'l':
712 cmd = new LineTo(true, nextFloat(tokens), nextFloat(tokens));
713 break;
714 case 'H':
715 cmd = new Horizontal(false, nextFloat(tokens));
716 break;
717 case 'h':
718 cmd = new Horizontal(true, nextFloat(tokens));
719 break;
720 case 'V':
721 cmd = new Vertical(false, nextFloat(tokens));
722 break;
723 case 'v':
724 cmd = new Vertical(true, nextFloat(tokens));
725 break;
726 case 'A':
727 cmd = new Arc(false, nextFloat(tokens), nextFloat(tokens),
728 nextFloat(tokens),
729 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f,
730 nextFloat(tokens), nextFloat(tokens));
731 break;
732 case 'a':
733 cmd = new Arc(true, nextFloat(tokens), nextFloat(tokens),
734 nextFloat(tokens),
735 nextFloat(tokens) == 1f, nextFloat(tokens) == 1f,
736 nextFloat(tokens), nextFloat(tokens));
737 break;
738 case 'Q':
739 cmd = new Quadratic(false, nextFloat(tokens), nextFloat(tokens),
740 nextFloat(tokens), nextFloat(tokens));
741 break;
742 case 'q':
743 cmd = new Quadratic(true, nextFloat(tokens), nextFloat(tokens),
744 nextFloat(tokens), nextFloat(tokens));
745 break;
746 case 'T':
747 cmd = new QuadraticSmooth(false, nextFloat(tokens), nextFloat(tokens));
748 break;
749 case 't':
750 cmd = new QuadraticSmooth(true, nextFloat(tokens), nextFloat(tokens));
751 break;
752 case 'C':
753 cmd = new Cubic(false, nextFloat(tokens), nextFloat(tokens),
754 nextFloat(tokens), nextFloat(tokens),
755 nextFloat(tokens), nextFloat(tokens));
756 break;
757 case 'c':
758 cmd = new Cubic(true, nextFloat(tokens), nextFloat(tokens),
759 nextFloat(tokens), nextFloat(tokens),
760 nextFloat(tokens), nextFloat(tokens));
761 break;
762 case 'S':
763 cmd = new CubicSmooth(false, nextFloat(tokens), nextFloat(tokens),
764 nextFloat(tokens), nextFloat(tokens));
765 break;
766 case 's':
767 cmd = new CubicSmooth(true, nextFloat(tokens), nextFloat(tokens),
768 nextFloat(tokens), nextFloat(tokens));
769 break;
770 case 'Z':
771 case 'z':
772 cmd = new Terminal();
773 break;
774 default:
775 throw new RuntimeException("Invalid path element");
776 }
777
778 cmdList.add(cmd);
779 defaultRelative = cmd.isRelative;
780 }
781
782 PathCommand[] retArr = new PathCommand[cmdList.size()];
783 cmdList.toArray(retArr);
784 return retArr;
785 }
786
787 static protected GeneralPath buildPath(String text, int windingRule)
788 {
789 PathCommand[] commands = parsePathList(text);
790
791 int numKnots = 2;
792 for (int i = 0; i < commands.length; i++)
793 {
794 numKnots += commands[i].getNumKnotsAdded();
795 }
796
797
798 GeneralPath path = new GeneralPath(windingRule, numKnots);
799
800 BuildHistory hist = new BuildHistory();
801
802 for (int i = 0; i < commands.length; i++)
803 {
804 PathCommand cmd = commands[i];
805 cmd.appendPath(path, hist);
806 }
807
808 return path;
809 }
810
811 /**
812 * Updates all attributes in this diagram associated with a time event. Ie,
813 * all attributes with track information.
814 *
815 * @return - true if this node has changed state as a result of the time
816 * update
817 */
818 abstract public boolean updateTime(double curTime) throws SVGException;
819
820 public int getNumChildren()
821 {
822 return children.size();
823 }
824
825 public SVGElement getChild(int i)
826 {
827 return (SVGElement) children.get(i);
828 }
829
830 public double lerp(double t0, double t1, double alpha)
831 {
832 return (1 - alpha) * t0 + alpha * t1;
833 }
834}
Note: See TracBrowser for help on using the repository browser.