source: josm/trunk/src/com/kitfox/svg/SVGRoot.java@ 15912

Last change on this file since 15912 was 14333, checked in by Don-vip, 6 years ago

fix #16838 - render nested svg elements properly

see https://github.com/blackears/svgSalamander/issues/31

  • Property svn:eol-style set to native
File size: 15.1 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 February 18, 2004, 5:33 PM
35 */
36
37package com.kitfox.svg;
38
39import com.kitfox.svg.xml.NumberWithUnits;
40import com.kitfox.svg.xml.StyleAttribute;
41import com.kitfox.svg.xml.StyleSheet;
42import java.awt.Graphics2D;
43import java.awt.Rectangle;
44import java.awt.Shape;
45import java.awt.geom.AffineTransform;
46import java.awt.geom.NoninvertibleTransformException;
47import java.awt.geom.Point2D;
48import java.awt.geom.Rectangle2D;
49import java.util.List;
50
51/**
52 * The root element of an SVG tree.
53 *
54 * @author Mark McKay
55 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
56 */
57public class SVGRoot extends Group
58{
59 public static final String TAG_NAME = "svg";
60
61 NumberWithUnits x;
62 NumberWithUnits y;
63 NumberWithUnits width;
64 NumberWithUnits height;
65
66 Rectangle2D.Float viewBox = null;
67
68 public static final int PA_X_NONE = 0;
69 public static final int PA_X_MIN = 1;
70 public static final int PA_X_MID = 2;
71 public static final int PA_X_MAX = 3;
72
73 public static final int PA_Y_NONE = 0;
74 public static final int PA_Y_MIN = 1;
75 public static final int PA_Y_MID = 2;
76 public static final int PA_Y_MAX = 3;
77
78 public static final int PS_MEET = 0;
79 public static final int PS_SLICE = 1;
80
81 int parSpecifier = PS_MEET;
82 int parAlignX = PA_X_MID;
83 int parAlignY = PA_Y_MID;
84
85 final AffineTransform viewXform = new AffineTransform();
86 final Rectangle2D.Float clipRect = new Rectangle2D.Float();
87
88 private StyleSheet styleSheet;
89
90 /** Creates a new instance of SVGRoot */
91 public SVGRoot()
92 {
93 }
94
95 @Override
96 public String getTagName()
97 {
98 return TAG_NAME;
99 }
100
101 @Override
102 public void build() throws SVGException
103 {
104 super.build();
105
106 StyleAttribute sty = new StyleAttribute();
107
108 if (getPres(sty.setName("x")))
109 {
110 x = sty.getNumberWithUnits();
111 }
112
113 if (getPres(sty.setName("y")))
114 {
115 y = sty.getNumberWithUnits();
116 }
117
118 if (getPres(sty.setName("width")))
119 {
120 width = sty.getNumberWithUnits();
121 }
122
123 if (getPres(sty.setName("height")))
124 {
125 height = sty.getNumberWithUnits();
126 }
127
128 if (getPres(sty.setName("viewBox")))
129 {
130 float[] coords = sty.getFloatList();
131 viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
132 }
133
134 if (getPres(sty.setName("preserveAspectRatio")))
135 {
136 String preserve = sty.getStringValue();
137
138 if (contains(preserve, "none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
139 else if (contains(preserve, "xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
140 else if (contains(preserve, "xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
141 else if (contains(preserve, "xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
142 else if (contains(preserve, "xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
143 else if (contains(preserve, "xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
144 else if (contains(preserve, "xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
145 else if (contains(preserve, "xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
146 else if (contains(preserve, "xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
147 else if (contains(preserve, "xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
148
149 if (contains(preserve, "meet"))
150 {
151 parSpecifier = PS_MEET;
152 }
153 else if (contains(preserve, "slice"))
154 {
155 parSpecifier = PS_SLICE;
156 }
157 }
158
159 prepareViewport();
160 }
161
162 private boolean contains(String text, String find)
163 {
164 return (text.indexOf(find) != -1);
165 }
166
167 @Override
168 public SVGRoot getRoot()
169 {
170 return this;
171 }
172
173 protected void prepareViewport()
174 {
175 Rectangle deviceViewport = diagram.getDeviceViewport();
176
177 Rectangle2D defaultBounds;
178 try
179 {
180 defaultBounds = getBoundingBox();
181 }
182 catch (SVGException ex)
183 {
184 defaultBounds= new Rectangle2D.Float();
185 }
186
187 //Determine destination rectangle
188 float xx, yy, ww, hh;
189 if (width != null)
190 {
191 xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue());
192 if (width.getUnits() == NumberWithUnits.UT_PERCENT)
193 {
194 ww = width.getValue() * deviceViewport.width;
195 }
196 else
197 {
198 ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue());
199 }
200 }
201 else if (viewBox != null)
202 {
203 xx = viewBox.x;
204 ww = viewBox.width;
205 width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
206 x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
207 }
208 else
209 {
210 //Estimate size from scene bounding box
211 xx = (float)defaultBounds.getX();
212 ww = (float)defaultBounds.getWidth();
213 width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
214 x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
215 }
216
217 if (height != null)
218 {
219 yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue());
220 if (height.getUnits() == NumberWithUnits.UT_PERCENT)
221 {
222 hh = height.getValue() * deviceViewport.height;
223 }
224 else
225 {
226 hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue());
227 }
228 }
229 else if (viewBox != null)
230 {
231 yy = viewBox.y;
232 hh = viewBox.height;
233 height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
234 y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
235 }
236 else
237 {
238 //Estimate size from scene bounding box
239 yy = (float)defaultBounds.getY();
240 hh = (float)defaultBounds.getHeight();
241 height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
242 y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
243 }
244
245 clipRect.setRect(xx, yy, ww, hh);
246
247// if (viewBox == null)
248// {
249// viewXform.setToIdentity();
250// }
251// else
252// {
253// //If viewport window is set, we are drawing to entire viewport
254// clipRect.setRect(deviceViewport);
255//
256// viewXform.setToIdentity();
257// viewXform.setToTranslation(deviceViewport.x, deviceViewport.y);
258// viewXform.scale(deviceViewport.width, deviceViewport.height);
259// viewXform.scale(1 / viewBox.width, 1 / viewBox.height);
260// viewXform.translate(-viewBox.x, -viewBox.y);
261// }
262 }
263
264 public void renderToViewport(Graphics2D g) throws SVGException
265 {
266 render(g);
267 }
268
269 @Override
270 public void render(Graphics2D g) throws SVGException
271 {
272 prepareViewport();
273
274 Rectangle targetViewport = g.getClipBounds();
275//
276// if (targetViewport == null)
277// {
278// Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
279// targetViewport = new Rectangle(0, 0, size.width, size.height);
280// }
281// clipRect.setRect(targetViewport);
282
283
284 Rectangle deviceViewport = diagram.getDeviceViewport();
285 if (width != null && height != null)
286 {
287 float xx, yy, ww, hh;
288
289 xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue());
290 if (width.getUnits() == NumberWithUnits.UT_PERCENT)
291 {
292 ww = width.getValue() * deviceViewport.width;
293 }
294 else
295 {
296 ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue());
297 }
298
299 yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue());
300 if (height.getUnits() == NumberWithUnits.UT_PERCENT)
301 {
302 hh = height.getValue() * deviceViewport.height;
303 }
304 else
305 {
306 hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue());
307 }
308
309 targetViewport = new Rectangle((int)xx, (int)yy, (int)ww, (int)hh);
310 }
311 else
312 {
313// Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
314// targetViewport = new Rectangle(0, 0, size.width, size.height);
315 targetViewport = new Rectangle(deviceViewport);
316 }
317 clipRect.setRect(targetViewport);
318
319 if (viewBox == null)
320 {
321 viewXform.setToIdentity();
322 }
323 else
324 {
325 viewXform.setToIdentity();
326 viewXform.setToTranslation(targetViewport.x, targetViewport.y);
327 viewXform.scale(targetViewport.width, targetViewport.height);
328 viewXform.scale(1 / viewBox.width, 1 / viewBox.height);
329 viewXform.translate(-viewBox.x, -viewBox.y);
330 }
331
332 AffineTransform cachedXform = g.getTransform();
333 g.transform(viewXform);
334
335 super.render(g);
336
337 g.setTransform(cachedXform);
338 }
339
340 @Override
341 public void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException
342 {
343 if (viewXform != null)
344 {
345 ltw = new AffineTransform(ltw);
346 ltw.concatenate(viewXform);
347 }
348
349 super.pick(pickArea, ltw, boundingBox, retVec);
350 }
351
352 @Override
353 public void pick(Point2D point, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException
354 {
355 Point2D xPoint = new Point2D.Double(point.getX(), point.getY());
356 if (viewXform != null)
357 {
358 try
359 {
360 viewXform.inverseTransform(point, xPoint);
361 } catch (NoninvertibleTransformException ex)
362 {
363 throw new SVGException(ex);
364 }
365 }
366
367 super.pick(xPoint, boundingBox, retVec);
368 }
369
370 @Override
371 public Shape getShape()
372 {
373 Shape shape = super.getShape();
374 return viewXform.createTransformedShape(shape);
375 }
376
377 @Override
378 public Rectangle2D getBoundingBox() throws SVGException
379 {
380 Rectangle2D bbox = super.getBoundingBox();
381 return viewXform.createTransformedShape(bbox).getBounds2D();
382 }
383
384 public float getDeviceWidth()
385 {
386 return clipRect.width;
387 }
388
389 public float getDeviceHeight()
390 {
391 return clipRect.height;
392 }
393
394 public Rectangle2D getDeviceRect(Rectangle2D rect)
395 {
396 rect.setRect(clipRect);
397 return rect;
398 }
399
400 /**
401 * Updates all attributes in this diagram associated with a time event.
402 * Ie, all attributes with track information.
403 * @return - true if this node has changed state as a result of the time
404 * update
405 */
406 @Override
407 public boolean updateTime(double curTime) throws SVGException
408 {
409 boolean changeState = super.updateTime(curTime);
410
411 StyleAttribute sty = new StyleAttribute();
412 boolean shapeChange = false;
413
414 if (getPres(sty.setName("x")))
415 {
416 NumberWithUnits newVal = sty.getNumberWithUnits();
417 if (!newVal.equals(x))
418 {
419 x = newVal;
420 shapeChange = true;
421 }
422 }
423
424 if (getPres(sty.setName("y")))
425 {
426 NumberWithUnits newVal = sty.getNumberWithUnits();
427 if (!newVal.equals(y))
428 {
429 y = newVal;
430 shapeChange = true;
431 }
432 }
433
434 if (getPres(sty.setName("width")))
435 {
436 NumberWithUnits newVal = sty.getNumberWithUnits();
437 if (!newVal.equals(width))
438 {
439 width = newVal;
440 shapeChange = true;
441 }
442 }
443
444 if (getPres(sty.setName("height")))
445 {
446 NumberWithUnits newVal = sty.getNumberWithUnits();
447 if (!newVal.equals(height))
448 {
449 height = newVal;
450 shapeChange = true;
451 }
452 }
453
454 if (getPres(sty.setName("viewBox")))
455 {
456 float[] coords = sty.getFloatList();
457 Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
458 if (!newViewBox.equals(viewBox))
459 {
460 viewBox = newViewBox;
461 shapeChange = true;
462 }
463 }
464
465 if (shapeChange)
466 {
467 build();
468 }
469
470 return changeState || shapeChange;
471 }
472
473 /**
474 * @return the styleSheet
475 */
476 public StyleSheet getStyleSheet()
477 {
478 if (styleSheet == null)
479 {
480 for (int i = 0; i < getNumChildren(); ++i)
481 {
482 SVGElement ele = getChild(i);
483 if (ele instanceof Style)
484 {
485 return ((Style)ele).getStyleSheet();
486 }
487 }
488 }
489
490 return styleSheet;
491 }
492
493 /**
494 * @param styleSheet the styleSheet to set
495 */
496 public void setStyleSheet(StyleSheet styleSheet)
497 {
498 this.styleSheet = styleSheet;
499 }
500
501}
Note: See TracBrowser for help on using the repository browser.