picrate 0.0.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +47 -0
  3. data/.mvn/extensions.xml +9 -0
  4. data/.mvn/wrapper/maven-wrapper.properties +1 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +4 -0
  7. data/LICENSE.md +165 -0
  8. data/README.md +51 -0
  9. data/Rakefile +59 -0
  10. data/bin/picrate +8 -0
  11. data/docs/.gitignore +6 -0
  12. data/docs/_config.yml +30 -0
  13. data/docs/_includes/footer.html +38 -0
  14. data/docs/_includes/head.html +16 -0
  15. data/docs/_includes/header.html +27 -0
  16. data/docs/_includes/icon-github.html +1 -0
  17. data/docs/_includes/icon-github.svg +1 -0
  18. data/docs/_includes/icon-twitter.html +1 -0
  19. data/docs/_includes/icon-twitter.svg +1 -0
  20. data/docs/_includes/navigation.html +24 -0
  21. data/docs/_layouts/default.html +20 -0
  22. data/docs/_layouts/page.html +14 -0
  23. data/docs/_layouts/post.html +15 -0
  24. data/docs/_posts/2018-05-06-getting_started.md +8 -0
  25. data/docs/_posts/2018-05-06-install_jruby.md +35 -0
  26. data/docs/_sass/_base.scss +206 -0
  27. data/docs/_sass/_layout.scss +242 -0
  28. data/docs/_sass/_syntax-highlighting.scss +71 -0
  29. data/docs/about.md +10 -0
  30. data/docs/css/main.scss +38 -0
  31. data/docs/favicon.ico +0 -0
  32. data/docs/feed.xml +30 -0
  33. data/docs/index.html +38 -0
  34. data/lib/picrate.rb +10 -0
  35. data/lib/picrate/app.rb +187 -0
  36. data/lib/picrate/creators/sketch_class.rb +57 -0
  37. data/lib/picrate/creators/sketch_factory.rb +12 -0
  38. data/lib/picrate/creators/sketch_writer.rb +21 -0
  39. data/lib/picrate/helper_methods.rb +214 -0
  40. data/lib/picrate/helpers/numeric.rb +9 -0
  41. data/lib/picrate/library.rb +69 -0
  42. data/lib/picrate/library_loader.rb +53 -0
  43. data/lib/picrate/native_folder.rb +35 -0
  44. data/lib/picrate/native_loader.rb +27 -0
  45. data/lib/picrate/runner.rb +81 -0
  46. data/lib/picrate/version.rb +4 -0
  47. data/library/boids/boids.rb +209 -0
  48. data/library/chooser/chooser.rb +19 -0
  49. data/library/control_panel/control_panel.rb +182 -0
  50. data/library/library_proxy/README.md +99 -0
  51. data/library/library_proxy/library_proxy.rb +14 -0
  52. data/library/slider/slider.rb +42 -0
  53. data/library/vector_utils/vector_utils.rb +69 -0
  54. data/library/video_event/video_event.rb +3 -0
  55. data/license.txt +508 -0
  56. data/picrate.gemspec +35 -0
  57. data/pom.rb +122 -0
  58. data/pom.xml +214 -0
  59. data/src/main/java/japplemenubar/JAppleMenuBar.java +88 -0
  60. data/src/main/java/japplemenubar/libjAppleMenuBar.jnilib +0 -0
  61. data/src/main/java/monkstone/ColorUtil.java +115 -0
  62. data/src/main/java/monkstone/MathToolModule.java +236 -0
  63. data/src/main/java/monkstone/PicrateLibrary.java +47 -0
  64. data/src/main/java/monkstone/core/LibraryProxy.java +127 -0
  65. data/src/main/java/monkstone/fastmath/Deglut.java +122 -0
  66. data/src/main/java/monkstone/fastmath/package-info.java +6 -0
  67. data/src/main/java/monkstone/filechooser/Chooser.java +48 -0
  68. data/src/main/java/monkstone/noise/SimplexNoise.java +465 -0
  69. data/src/main/java/monkstone/slider/CustomHorizontalSlider.java +168 -0
  70. data/src/main/java/monkstone/slider/CustomVerticalSlider.java +182 -0
  71. data/src/main/java/monkstone/slider/SimpleHorizontalSlider.java +149 -0
  72. data/src/main/java/monkstone/slider/SimpleSlider.java +196 -0
  73. data/src/main/java/monkstone/slider/SimpleVerticalSlider.java +163 -0
  74. data/src/main/java/monkstone/slider/Slider.java +67 -0
  75. data/src/main/java/monkstone/slider/SliderBar.java +277 -0
  76. data/src/main/java/monkstone/slider/SliderGroup.java +78 -0
  77. data/src/main/java/monkstone/slider/WheelHandler.java +35 -0
  78. data/src/main/java/monkstone/vecmath/AppRender.java +87 -0
  79. data/src/main/java/monkstone/vecmath/JRender.java +56 -0
  80. data/src/main/java/monkstone/vecmath/ShapeRender.java +87 -0
  81. data/src/main/java/monkstone/vecmath/package-info.java +20 -0
  82. data/src/main/java/monkstone/vecmath/vec2/Vec2.java +757 -0
  83. data/src/main/java/monkstone/vecmath/vec2/package-info.java +6 -0
  84. data/src/main/java/monkstone/vecmath/vec3/Vec3.java +727 -0
  85. data/src/main/java/monkstone/vecmath/vec3/package-info.java +6 -0
  86. data/src/main/java/monkstone/videoevent/VideoInterface.java +42 -0
  87. data/src/main/java/monkstone/videoevent/package-info.java +20 -0
  88. data/src/main/java/processing/awt/PGraphicsJava2D.java +3098 -0
  89. data/src/main/java/processing/awt/PShapeJava2D.java +401 -0
  90. data/src/main/java/processing/awt/PSurfaceAWT.java +1660 -0
  91. data/src/main/java/processing/core/PApplet.java +17647 -0
  92. data/src/main/java/processing/core/PConstants.java +1033 -0
  93. data/src/main/java/processing/core/PFont.java +1250 -0
  94. data/src/main/java/processing/core/PGraphics.java +9614 -0
  95. data/src/main/java/processing/core/PImage.java +3608 -0
  96. data/src/main/java/processing/core/PMatrix.java +347 -0
  97. data/src/main/java/processing/core/PMatrix2D.java +694 -0
  98. data/src/main/java/processing/core/PMatrix3D.java +1153 -0
  99. data/src/main/java/processing/core/PShape.java +4332 -0
  100. data/src/main/java/processing/core/PShapeOBJ.java +544 -0
  101. data/src/main/java/processing/core/PShapeSVG.java +1987 -0
  102. data/src/main/java/processing/core/PStyle.java +208 -0
  103. data/src/main/java/processing/core/PSurface.java +242 -0
  104. data/src/main/java/processing/core/PSurfaceNone.java +479 -0
  105. data/src/main/java/processing/core/PVector.java +1140 -0
  106. data/src/main/java/processing/data/FloatDict.java +829 -0
  107. data/src/main/java/processing/data/FloatList.java +912 -0
  108. data/src/main/java/processing/data/IntDict.java +796 -0
  109. data/src/main/java/processing/data/IntList.java +913 -0
  110. data/src/main/java/processing/data/JSONArray.java +1260 -0
  111. data/src/main/java/processing/data/JSONObject.java +2282 -0
  112. data/src/main/java/processing/data/JSONTokener.java +435 -0
  113. data/src/main/java/processing/data/Sort.java +46 -0
  114. data/src/main/java/processing/data/StringDict.java +601 -0
  115. data/src/main/java/processing/data/StringList.java +775 -0
  116. data/src/main/java/processing/data/Table.java +4923 -0
  117. data/src/main/java/processing/data/TableRow.java +198 -0
  118. data/src/main/java/processing/data/XML.java +1149 -0
  119. data/src/main/java/processing/event/Event.java +108 -0
  120. data/src/main/java/processing/event/KeyEvent.java +70 -0
  121. data/src/main/java/processing/event/MouseEvent.java +149 -0
  122. data/src/main/java/processing/event/TouchEvent.java +57 -0
  123. data/src/main/java/processing/javafx/PGraphicsFX2D.java +354 -0
  124. data/src/main/java/processing/opengl/FontTexture.java +379 -0
  125. data/src/main/java/processing/opengl/FrameBuffer.java +503 -0
  126. data/src/main/java/processing/opengl/LinePath.java +623 -0
  127. data/src/main/java/processing/opengl/LineStroker.java +685 -0
  128. data/src/main/java/processing/opengl/PGL.java +3366 -0
  129. data/src/main/java/processing/opengl/PGraphics2D.java +615 -0
  130. data/src/main/java/processing/opengl/PGraphics3D.java +281 -0
  131. data/src/main/java/processing/opengl/PGraphicsOpenGL.java +13634 -0
  132. data/src/main/java/processing/opengl/PJOGL.java +1966 -0
  133. data/src/main/java/processing/opengl/PShader.java +1478 -0
  134. data/src/main/java/processing/opengl/PShapeOpenGL.java +5234 -0
  135. data/src/main/java/processing/opengl/PSurfaceJOGL.java +1315 -0
  136. data/src/main/java/processing/opengl/Texture.java +1670 -0
  137. data/src/main/java/processing/opengl/VertexBuffer.java +88 -0
  138. data/src/main/java/processing/opengl/cursors/arrow.png +0 -0
  139. data/src/main/java/processing/opengl/cursors/cross.png +0 -0
  140. data/src/main/java/processing/opengl/cursors/hand.png +0 -0
  141. data/src/main/java/processing/opengl/cursors/license.txt +27 -0
  142. data/src/main/java/processing/opengl/cursors/move.png +0 -0
  143. data/src/main/java/processing/opengl/cursors/text.png +0 -0
  144. data/src/main/java/processing/opengl/cursors/wait.png +0 -0
  145. data/src/main/java/processing/opengl/shaders/ColorFrag.glsl +32 -0
  146. data/src/main/java/processing/opengl/shaders/ColorVert.glsl +34 -0
  147. data/src/main/java/processing/opengl/shaders/LightFrag.glsl +33 -0
  148. data/src/main/java/processing/opengl/shaders/LightVert-vc4.glsl +154 -0
  149. data/src/main/java/processing/opengl/shaders/LightVert.glsl +151 -0
  150. data/src/main/java/processing/opengl/shaders/LineFrag.glsl +32 -0
  151. data/src/main/java/processing/opengl/shaders/LineVert.glsl +100 -0
  152. data/src/main/java/processing/opengl/shaders/MaskFrag.glsl +40 -0
  153. data/src/main/java/processing/opengl/shaders/PointFrag.glsl +32 -0
  154. data/src/main/java/processing/opengl/shaders/PointVert.glsl +56 -0
  155. data/src/main/java/processing/opengl/shaders/TexFrag.glsl +37 -0
  156. data/src/main/java/processing/opengl/shaders/TexLightFrag.glsl +37 -0
  157. data/src/main/java/processing/opengl/shaders/TexLightVert-vc4.glsl +160 -0
  158. data/src/main/java/processing/opengl/shaders/TexLightVert.glsl +157 -0
  159. data/src/main/java/processing/opengl/shaders/TexVert.glsl +38 -0
  160. data/src/main/resources/icon/icon-1024.png +0 -0
  161. data/src/main/resources/icon/icon-128.png +0 -0
  162. data/src/main/resources/icon/icon-16.png +0 -0
  163. data/src/main/resources/icon/icon-256.png +0 -0
  164. data/src/main/resources/icon/icon-32.png +0 -0
  165. data/src/main/resources/icon/icon-48.png +0 -0
  166. data/src/main/resources/icon/icon-512.png +0 -0
  167. data/src/main/resources/icon/icon-64.png +0 -0
  168. data/src/main/resources/license.txt +508 -0
  169. data/test/create_test.rb +68 -0
  170. data/test/deglut_spec_test.rb +24 -0
  171. data/test/helper_methods_test.rb +58 -0
  172. data/test/math_tool_test.rb +75 -0
  173. data/test/respond_to_test.rb +215 -0
  174. data/test/sketches/key_event.rb +37 -0
  175. data/test/sketches/library/my_library/my_library.rb +32 -0
  176. data/test/test_helper.rb +3 -0
  177. data/test/vecmath_spec_test.rb +522 -0
  178. data/vendors/Rakefile +127 -0
  179. metadata +289 -0
@@ -0,0 +1,1987 @@
1
+ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
+
3
+ /*
4
+ Part of the Processing project - http://processing.org
5
+
6
+ Copyright (c) 2012-15 The Processing Foundation
7
+ Copyright (c) 2006-12 Ben Fry and Casey Reas
8
+ Copyright (c) 2004-06 Michael Chang
9
+
10
+ This library is free software; you can redistribute it and/or
11
+ modify it under the terms of the GNU Lesser General Public
12
+ License version 2.1 as published by the Free Software Foundation.
13
+
14
+ This library is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
+ Lesser General Public License for more details.
18
+
19
+ You should have received a copy of the GNU Lesser General
20
+ Public License along with this library; if not, write to the
21
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
22
+ Boston, MA 02111-1307 USA
23
+ */
24
+
25
+ package processing.core;
26
+
27
+ import processing.data.*;
28
+
29
+ // TODO replace these with PMatrix2D
30
+ import java.awt.geom.AffineTransform;
31
+ import java.awt.geom.Point2D;
32
+
33
+ import java.util.Map;
34
+ import java.util.HashMap;
35
+
36
+
37
+ /**
38
+ * This class is not part of the Processing API and should not be used
39
+ * directly. Instead, use loadShape() and methods like it, which will make
40
+ * use of this class. Using this class directly will cause your code to break
41
+ * when combined with future versions of Processing.
42
+ * <p>
43
+ * SVG stands for Scalable Vector Graphics, a portable graphics format.
44
+ * It is a vector format so it allows for "infinite" resolution and relatively
45
+ * small file sizes. Most modern media software can view SVG files, including
46
+ * Adobe products, Firefox, etc. Illustrator and Inkscape can edit SVG files.
47
+ * View the SVG specification <A HREF="http://www.w3.org/TR/SVG">here</A>.
48
+ * <p>
49
+ * We have no intention of turning this into a full-featured SVG library.
50
+ * The goal of this project is a basic shape importer that originally was small
51
+ * enough to be included with applets, meaning that its download size should be
52
+ * in the neighborhood of 25-30 Kb. Though we're far less limited nowadays on
53
+ * size constraints, we remain extremely limited in terms of time, and do not
54
+ * have volunteers who are available to maintain a larger SVG library.
55
+ * <p>
56
+ * For more sophisticated import/export, consider the
57
+ * <A HREF="http://xmlgraphics.apache.org/batik/">Batik</A>
58
+ * library from the Apache Software Foundation.
59
+ * <p>
60
+ * Batik is used in the SVG Export library in Processing 3, however using it
61
+ * for full SVG import is still a considerable amount of work. Wiring it to
62
+ * Java2D wouldn't be too bad, but using it with OpenGL, JavaFX, and features
63
+ * like begin/endRecord() and begin/endRaw() would be considerable effort.
64
+ * <p>
65
+ * Future improvements to this library may focus on this properly supporting
66
+ * a specific subset of SVG, for instance the simpler SVG profiles known as
67
+ * <A HREF="http://www.w3.org/TR/SVGMobile/">SVG Tiny or Basic</A>,
68
+ * although we still would not support the interactivity options.
69
+ *
70
+ * <p> <hr noshade> <p>
71
+ *
72
+ * A minimal example program using SVG:
73
+ * (assuming a working moo.svg is in your data folder)
74
+ *
75
+ * <PRE>
76
+ * PShape moo;
77
+ *
78
+ * void setup() {
79
+ * size(400, 400);
80
+ * moo = loadShape("moo.svg");
81
+ * }
82
+ * void draw() {
83
+ * background(255);
84
+ * shape(moo, mouseX, mouseY);
85
+ * }
86
+ * </PRE>
87
+ */
88
+ public class PShapeSVG extends PShape {
89
+ XML element;
90
+
91
+ /// Values between 0 and 1.
92
+
93
+ /**
94
+ *
95
+ */
96
+ protected float opacity;
97
+ float strokeOpacity;
98
+ float fillOpacity;
99
+
100
+ /** Width of containing SVG (used for percentages). */
101
+ protected float svgWidth;
102
+
103
+ /** Height of containing SVG (used for percentages). */
104
+ protected float svgHeight;
105
+
106
+ /** √((w² + h²)/2) of containing SVG (used for percentages). */
107
+ protected float svgSizeXY;
108
+
109
+ /**
110
+ *
111
+ */
112
+ protected Gradient strokeGradient;
113
+ String strokeName; // id of another object, gradients only?
114
+
115
+ /**
116
+ *
117
+ */
118
+ protected Gradient fillGradient;
119
+ String fillName; // id of another object
120
+
121
+
122
+ /**
123
+ * Initializes a new SVG object from the given XML object.
124
+ * @param svg
125
+ */
126
+ public PShapeSVG(XML svg) {
127
+ this(null, svg, true);
128
+
129
+ if (!svg.getName().equals("svg")) {
130
+ if (svg.getName().toLowerCase().equals("html")) {
131
+ // Common case is that files aren't downloaded properly
132
+ throw new RuntimeException("This appears to be a web page, not an SVG file.");
133
+ } else {
134
+ throw new RuntimeException("The root node is not <svg>, it's <" + svg.getName() + ">");
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ *
141
+ * @param parent
142
+ * @param properties
143
+ * @param parseKids
144
+ */
145
+ protected PShapeSVG(PShapeSVG parent, XML properties, boolean parseKids) {
146
+ setParent(parent);
147
+
148
+ // Need to get width/height in early.
149
+ if (properties.getName().equals("svg")) {
150
+ String unitWidth = properties.getString("width");
151
+ String unitHeight = properties.getString("height");
152
+
153
+ // Can't handle width/height as percentages easily. I'm just going
154
+ // to put in 100 as a dummy value, beacuse this means that it will
155
+ // come out as a reasonable value.
156
+ if (unitWidth != null) width = parseUnitSize(unitWidth, 100);
157
+ if (unitHeight != null) height = parseUnitSize(unitHeight, 100);
158
+
159
+ String viewBoxStr = properties.getString("viewBox");
160
+ if (viewBoxStr != null) {
161
+ float[] viewBox = PApplet.parseFloat(PApplet.splitTokens(viewBoxStr));
162
+ if (unitWidth == null || unitHeight == null) {
163
+ // Not proper parsing of the viewBox, but will cover us for cases where
164
+ // the width and height of the object is not specified.
165
+ width = viewBox[2];
166
+ height = viewBox[3];
167
+ } else {
168
+ // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
169
+ // TODO: preserveAspectRatio.
170
+ if (matrix == null) matrix = new PMatrix2D();
171
+ matrix.scale(width/viewBox[2], height/viewBox[3]);
172
+ matrix.translate(-viewBox[0], -viewBox[1]);
173
+ }
174
+ }
175
+
176
+ // Negative size is illegal.
177
+ if (width < 0 || height < 0)
178
+ throw new RuntimeException("<svg>: width (" + width +
179
+ ") and height (" + height + ") must not be negative.");
180
+
181
+ // It's technically valid to have width or height == 0. Not specified at
182
+ // all is what to test for.
183
+ if ((unitWidth == null || unitHeight == null) && viewBoxStr == null) {
184
+ //throw new RuntimeException("width/height not specified");
185
+ PGraphics.showWarning("The width and/or height is not " +
186
+ "readable in the <svg> tag of this file.");
187
+ // For the spec, the default is 100% and 100%. For purposes
188
+ // here, insert a dummy value because this is prolly just a
189
+ // font or something for which the w/h doesn't matter.
190
+ width = 1;
191
+ height = 1;
192
+ }
193
+
194
+ svgWidth = width;
195
+ svgHeight = height;
196
+ svgSizeXY = PApplet.sqrt((svgWidth*svgWidth + svgHeight*svgHeight)/2.0f);
197
+ }
198
+
199
+ element = properties;
200
+ name = properties.getString("id");
201
+ // @#$(* adobe illustrator mangles names of objects when re-saving
202
+ if (name != null) {
203
+ while (true) {
204
+ String[] m = PApplet.match(name, "_x([A-Za-z0-9]{2})_");
205
+ if (m == null) break;
206
+ char repair = (char) PApplet.unhex(m[1]);
207
+ name = name.replace(m[0], "" + repair);
208
+ }
209
+ }
210
+
211
+ String displayStr = properties.getString("display", "inline");
212
+ visible = !displayStr.equals("none");
213
+
214
+ String transformStr = properties.getString("transform");
215
+ if (transformStr != null) {
216
+ if (matrix == null) {
217
+ matrix = parseTransform(transformStr);
218
+ } else {
219
+ matrix.preApply(parseTransform(transformStr));
220
+ }
221
+ }
222
+
223
+ if (parseKids) {
224
+ parseColors(properties);
225
+ parseChildren(properties);
226
+ }
227
+ }
228
+
229
+
230
+ // Broken out so that subclasses can copy any additional variables
231
+ // (i.e. fillGradientPaint and strokeGradientPaint)
232
+
233
+ /**
234
+ *
235
+ * @param parent
236
+ */
237
+ protected void setParent(PShapeSVG parent) {
238
+ // Need to set this so that findChild() works.
239
+ // Otherwise 'parent' is null until addChild() is called later.
240
+ this.parent = parent;
241
+
242
+ if (parent == null) {
243
+ // set values to their defaults according to the SVG spec
244
+ stroke = false;
245
+ strokeColor = 0xff000000;
246
+ strokeWeight = 1;
247
+ strokeCap = PConstants.SQUARE; // equivalent to BUTT in svg spec
248
+ strokeJoin = PConstants.MITER;
249
+ strokeGradient = null;
250
+ // strokeGradientPaint = null;
251
+ strokeName = null;
252
+
253
+ fill = true;
254
+ fillColor = 0xff000000;
255
+ fillGradient = null;
256
+ // fillGradientPaint = null;
257
+ fillName = null;
258
+
259
+ //hasTransform = false;
260
+ //transformation = null; //new float[] { 1, 0, 0, 1, 0, 0 };
261
+
262
+ // svgWidth, svgHeight, and svgXYSize done below.
263
+
264
+ strokeOpacity = 1;
265
+ fillOpacity = 1;
266
+ opacity = 1;
267
+
268
+ } else {
269
+ stroke = parent.stroke;
270
+ strokeColor = parent.strokeColor;
271
+ strokeWeight = parent.strokeWeight;
272
+ strokeCap = parent.strokeCap;
273
+ strokeJoin = parent.strokeJoin;
274
+ strokeGradient = parent.strokeGradient;
275
+ // strokeGradientPaint = parent.strokeGradientPaint;
276
+ strokeName = parent.strokeName;
277
+
278
+ fill = parent.fill;
279
+ fillColor = parent.fillColor;
280
+ fillGradient = parent.fillGradient;
281
+ // fillGradientPaint = parent.fillGradientPaint;
282
+ fillName = parent.fillName;
283
+
284
+ svgWidth = parent.svgWidth;
285
+ svgHeight = parent.svgHeight;
286
+ svgSizeXY = parent.svgSizeXY;
287
+
288
+ opacity = parent.opacity;
289
+ }
290
+
291
+ // The rect and ellipse modes are set to CORNER since it is the expected
292
+ // mode for svg shapes.
293
+ rectMode = CORNER;
294
+ ellipseMode = CORNER;
295
+ }
296
+
297
+
298
+ /** Factory method for subclasses.
299
+ * @param parent
300
+ * @param properties
301
+ * @param parseKids
302
+ * @return */
303
+ protected PShapeSVG createShape(PShapeSVG parent, XML properties, boolean parseKids) {
304
+ return new PShapeSVG(parent, properties, parseKids);
305
+ }
306
+
307
+ /**
308
+ *
309
+ * @param graphics
310
+ */
311
+ protected void parseChildren(XML graphics) {
312
+ XML[] elements = graphics.getChildren();
313
+ children = new PShape[elements.length];
314
+ childCount = 0;
315
+
316
+ for (XML elem : elements) {
317
+ PShape kid = parseChild(elem);
318
+ if (kid != null) addChild(kid);
319
+ }
320
+ children = (PShape[]) PApplet.subset(children, 0, childCount);
321
+ }
322
+
323
+
324
+ /**
325
+ * Parse a child XML element.
326
+ * Override this method to add parsing for more SVG elements.
327
+ * @param elem
328
+ * @return
329
+ */
330
+ protected PShape parseChild(XML elem) {
331
+ // System.err.println("parsing child in pshape " + elem.getName());
332
+ String name = elem.getName();
333
+ PShapeSVG shape = null;
334
+
335
+
336
+ if (name == null) {
337
+ // just some whitespace that can be ignored (hopefully)
338
+
339
+ } else if (name.equals("g")) {
340
+ shape = createShape(this, elem, true);
341
+
342
+ } else if (name.equals("defs")) {
343
+ // generally this will contain gradient info, so may
344
+ // as well just throw it into a group element for parsing
345
+ shape = createShape(this, elem, true);
346
+
347
+ } else if (name.equals("line")) {
348
+ shape = createShape(this, elem, true);
349
+ shape.parseLine();
350
+
351
+ } else if (name.equals("circle")) {
352
+ shape = createShape(this, elem, true);
353
+ shape.parseEllipse(true);
354
+
355
+ } else if (name.equals("ellipse")) {
356
+ shape = createShape(this, elem, true);
357
+ shape.parseEllipse(false);
358
+
359
+ } else if (name.equals("rect")) {
360
+ shape = createShape(this, elem, true);
361
+ shape.parseRect();
362
+
363
+ } else if (name.equals("polygon")) {
364
+ shape = createShape(this, elem, true);
365
+ shape.parsePoly(true);
366
+
367
+ } else if (name.equals("polyline")) {
368
+ shape = createShape(this, elem, true);
369
+ shape.parsePoly(false);
370
+
371
+ } else if (name.equals("path")) {
372
+ shape = createShape(this, elem, true);
373
+ shape.parsePath();
374
+
375
+ } else if (name.equals("radialGradient")) {
376
+ return new RadialGradient(this, elem);
377
+
378
+ } else if (name.equals("linearGradient")) {
379
+ return new LinearGradient(this, elem);
380
+
381
+ } else if (name.equals("font")) {
382
+ return new Font(this, elem);
383
+
384
+ // } else if (name.equals("font-face")) {
385
+ // return new FontFace(this, elem);
386
+
387
+ // } else if (name.equals("glyph") || name.equals("missing-glyph")) {
388
+ // return new FontGlyph(this, elem);
389
+
390
+ } else if (name.equals("text")) { // || name.equals("font")) {
391
+ PGraphics.showWarning("Text and fonts in SVG files are " +
392
+ "not currently supported, convert text to outlines instead.");
393
+
394
+ } else if (name.equals("filter")) {
395
+ PGraphics.showWarning("Filters are not supported.");
396
+
397
+ } else if (name.equals("mask")) {
398
+ PGraphics.showWarning("Masks are not supported.");
399
+
400
+ } else if (name.equals("pattern")) {
401
+ PGraphics.showWarning("Patterns are not supported.");
402
+
403
+ } else if (name.equals("stop")) {
404
+ // stop tag is handled by gradient parser, so don't warn about it
405
+
406
+ } else if (name.equals("sodipodi:namedview")) {
407
+ // these are always in Inkscape files, the warnings get tedious
408
+
409
+ } else if (name.equals("metadata")
410
+ || name.equals("title") || name.equals("desc")) {
411
+ // fontforge just stuffs <metadata> in as a comment.
412
+ // All harmless stuff, irrelevant to rendering.
413
+ return null;
414
+
415
+ } else if (!name.startsWith("#")) {
416
+ PGraphics.showWarning("Ignoring <" + name + "> tag.");
417
+ // new Exception().printStackTrace();
418
+ }
419
+ return shape;
420
+ }
421
+
422
+ /**
423
+ *
424
+ */
425
+ protected void parseLine() {
426
+ kind = LINE;
427
+ family = PRIMITIVE;
428
+ params = new float[] {
429
+ getFloatWithUnit(element, "x1", svgWidth),
430
+ getFloatWithUnit(element, "y1", svgHeight),
431
+ getFloatWithUnit(element, "x2", svgWidth),
432
+ getFloatWithUnit(element, "y2", svgHeight)
433
+ };
434
+ }
435
+
436
+
437
+ /**
438
+ * Handles parsing ellipse and circle tags.
439
+ * @param circle true if this is a circle and not an ellipse
440
+ */
441
+ protected void parseEllipse(boolean circle) {
442
+ kind = ELLIPSE;
443
+ family = PRIMITIVE;
444
+ params = new float[4];
445
+
446
+ params[0] = getFloatWithUnit(element, "cx", svgWidth);
447
+ params[1] = getFloatWithUnit(element, "cy", svgHeight);
448
+
449
+ float rx, ry;
450
+ if (circle) {
451
+ rx = ry = getFloatWithUnit(element, "r", svgSizeXY);
452
+ } else {
453
+ rx = getFloatWithUnit(element, "rx", svgWidth);
454
+ ry = getFloatWithUnit(element, "ry", svgHeight);
455
+ }
456
+ params[0] -= rx;
457
+ params[1] -= ry;
458
+
459
+ params[2] = rx*2;
460
+ params[3] = ry*2;
461
+ }
462
+
463
+ /**
464
+ *
465
+ */
466
+ protected void parseRect() {
467
+ kind = RECT;
468
+ family = PRIMITIVE;
469
+ params = new float[] {
470
+ getFloatWithUnit(element, "x", svgWidth),
471
+ getFloatWithUnit(element, "y", svgHeight),
472
+ getFloatWithUnit(element, "width", svgWidth),
473
+ getFloatWithUnit(element, "height", svgHeight)
474
+ };
475
+ }
476
+
477
+
478
+ /**
479
+ * Parse a polyline or polygon from an SVG file.
480
+ * Syntax defined at http://www.w3.org/TR/SVG/shapes.html#PointsBNF
481
+ * @param close true if shape is closed (polygon), false if not (polyline)
482
+ */
483
+ protected void parsePoly(boolean close) {
484
+ family = PATH;
485
+ this.close = close;
486
+
487
+ String pointsAttr = element.getString("points");
488
+ if (pointsAttr != null) {
489
+ String[] pointsBuffer = PApplet.splitTokens(pointsAttr);
490
+ vertexCount = pointsBuffer.length;
491
+ vertices = new float[vertexCount][2];
492
+ for (int i = 0; i < vertexCount; i++) {
493
+ String pb[] = PApplet.splitTokens(pointsBuffer[i], ", \t\r\n");
494
+ vertices[i][X] = Float.parseFloat(pb[0]);
495
+ vertices[i][Y] = Float.parseFloat(pb[1]);
496
+ }
497
+ }
498
+ }
499
+
500
+ /**
501
+ *
502
+ */
503
+ protected void parsePath() {
504
+ family = PATH;
505
+ kind = 0;
506
+
507
+ String pathData = element.getString("d");
508
+ if (pathData == null || PApplet.trim(pathData).length() == 0) {
509
+ return;
510
+ }
511
+ char[] pathDataChars = pathData.toCharArray();
512
+
513
+ StringBuilder pathBuffer = new StringBuilder();
514
+ boolean lastSeparate = false;
515
+
516
+ for (int i = 0; i < pathDataChars.length; i++) {
517
+ char c = pathDataChars[i];
518
+ boolean separate = false;
519
+
520
+ if (c == 'M' || c == 'm' ||
521
+ c == 'L' || c == 'l' ||
522
+ c == 'H' || c == 'h' ||
523
+ c == 'V' || c == 'v' ||
524
+ c == 'C' || c == 'c' || // beziers
525
+ c == 'S' || c == 's' ||
526
+ c == 'Q' || c == 'q' || // quadratic beziers
527
+ c == 'T' || c == 't' ||
528
+ c == 'A' || c == 'a' || // elliptical arc
529
+ c == 'Z' || c == 'z' || // closepath
530
+ c == ',') {
531
+ separate = true;
532
+ if (i != 0) {
533
+ pathBuffer.append("|");
534
+ }
535
+ }
536
+ if (c == 'Z' || c == 'z') {
537
+ separate = false;
538
+ }
539
+ if (c == '-' && !lastSeparate) {
540
+ // allow for 'e' notation in numbers, e.g. 2.10e-9
541
+ // http://dev.processing.org/bugs/show_bug.cgi?id=1408
542
+ if (i == 0 || pathDataChars[i-1] != 'e') {
543
+ pathBuffer.append("|");
544
+ }
545
+ }
546
+ if (c != ',') {
547
+ pathBuffer.append(c); //"" + pathDataBuffer.charAt(i));
548
+ }
549
+ if (separate && c != ',' && c != '-') {
550
+ pathBuffer.append("|");
551
+ }
552
+ lastSeparate = separate;
553
+ }
554
+
555
+ // use whitespace constant to get rid of extra spaces and CR or LF
556
+ String[] pathTokens =
557
+ PApplet.splitTokens(pathBuffer.toString(), "|" + WHITESPACE);
558
+ vertices = new float[pathTokens.length][2];
559
+ vertexCodes = new int[pathTokens.length];
560
+
561
+ float cx = 0;
562
+ float cy = 0;
563
+ int i = 0;
564
+
565
+ char implicitCommand = '\0';
566
+ // char prevCommand = '\0';
567
+ boolean prevCurve = false;
568
+ float ctrlX, ctrlY;
569
+ // store values for closepath so that relative coords work properly
570
+ float movetoX = 0;
571
+ float movetoY = 0;
572
+
573
+ while (i < pathTokens.length) {
574
+ char c = pathTokens[i].charAt(0);
575
+ if (((c >= '0' && c <= '9') || (c == '-')) && implicitCommand != '\0') {
576
+ c = implicitCommand;
577
+ i--;
578
+ } else {
579
+ implicitCommand = c;
580
+ }
581
+ switch (c) {
582
+
583
+ case 'M': // M - move to (absolute)
584
+ cx = PApplet.parseFloat(pathTokens[i + 1]);
585
+ cy = PApplet.parseFloat(pathTokens[i + 2]);
586
+ movetoX = cx;
587
+ movetoY = cy;
588
+ parsePathMoveto(cx, cy);
589
+ implicitCommand = 'L';
590
+ i += 3;
591
+ break;
592
+
593
+ case 'm': // m - move to (relative)
594
+ cx = cx + PApplet.parseFloat(pathTokens[i + 1]);
595
+ cy = cy + PApplet.parseFloat(pathTokens[i + 2]);
596
+ movetoX = cx;
597
+ movetoY = cy;
598
+ parsePathMoveto(cx, cy);
599
+ implicitCommand = 'l';
600
+ i += 3;
601
+ break;
602
+
603
+ case 'L':
604
+ cx = PApplet.parseFloat(pathTokens[i + 1]);
605
+ cy = PApplet.parseFloat(pathTokens[i + 2]);
606
+ parsePathLineto(cx, cy);
607
+ i += 3;
608
+ break;
609
+
610
+ case 'l':
611
+ cx = cx + PApplet.parseFloat(pathTokens[i + 1]);
612
+ cy = cy + PApplet.parseFloat(pathTokens[i + 2]);
613
+ parsePathLineto(cx, cy);
614
+ i += 3;
615
+ break;
616
+
617
+ // horizontal lineto absolute
618
+ case 'H':
619
+ cx = PApplet.parseFloat(pathTokens[i + 1]);
620
+ parsePathLineto(cx, cy);
621
+ i += 2;
622
+ break;
623
+
624
+ // horizontal lineto relative
625
+ case 'h':
626
+ cx = cx + PApplet.parseFloat(pathTokens[i + 1]);
627
+ parsePathLineto(cx, cy);
628
+ i += 2;
629
+ break;
630
+
631
+ case 'V':
632
+ cy = PApplet.parseFloat(pathTokens[i + 1]);
633
+ parsePathLineto(cx, cy);
634
+ i += 2;
635
+ break;
636
+
637
+ case 'v':
638
+ cy = cy + PApplet.parseFloat(pathTokens[i + 1]);
639
+ parsePathLineto(cx, cy);
640
+ i += 2;
641
+ break;
642
+
643
+ // C - curve to (absolute)
644
+ case 'C': {
645
+ float ctrlX1 = PApplet.parseFloat(pathTokens[i + 1]);
646
+ float ctrlY1 = PApplet.parseFloat(pathTokens[i + 2]);
647
+ float ctrlX2 = PApplet.parseFloat(pathTokens[i + 3]);
648
+ float ctrlY2 = PApplet.parseFloat(pathTokens[i + 4]);
649
+ float endX = PApplet.parseFloat(pathTokens[i + 5]);
650
+ float endY = PApplet.parseFloat(pathTokens[i + 6]);
651
+ parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
652
+ cx = endX;
653
+ cy = endY;
654
+ i += 7;
655
+ prevCurve = true;
656
+ }
657
+ break;
658
+
659
+ // c - curve to (relative)
660
+ case 'c': {
661
+ float ctrlX1 = cx + PApplet.parseFloat(pathTokens[i + 1]);
662
+ float ctrlY1 = cy + PApplet.parseFloat(pathTokens[i + 2]);
663
+ float ctrlX2 = cx + PApplet.parseFloat(pathTokens[i + 3]);
664
+ float ctrlY2 = cy + PApplet.parseFloat(pathTokens[i + 4]);
665
+ float endX = cx + PApplet.parseFloat(pathTokens[i + 5]);
666
+ float endY = cy + PApplet.parseFloat(pathTokens[i + 6]);
667
+ parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY);
668
+ cx = endX;
669
+ cy = endY;
670
+ i += 7;
671
+ prevCurve = true;
672
+ }
673
+ break;
674
+
675
+ // S - curve to shorthand (absolute)
676
+ // Draws a cubic Bézier curve from the current point to (x,y). The first
677
+ // control point is assumed to be the reflection of the second control
678
+ // point on the previous command relative to the current point.
679
+ // (x2,y2) is the second control point (i.e., the control point
680
+ // at the end of the curve). S (uppercase) indicates that absolute
681
+ // coordinates will follow; s (lowercase) indicates that relative
682
+ // coordinates will follow. Multiple sets of coordinates may be specified
683
+ // to draw a polybézier. At the end of the command, the new current point
684
+ // becomes the final (x,y) coordinate pair used in the polybézier.
685
+ case 'S': {
686
+ // (If there is no previous command or if the previous command was not
687
+ // an C, c, S or s, assume the first control point is coincident with
688
+ // the current point.)
689
+ if (!prevCurve) {
690
+ ctrlX = cx;
691
+ ctrlY = cy;
692
+ } else {
693
+ float ppx = vertices[vertexCount-2][X];
694
+ float ppy = vertices[vertexCount-2][Y];
695
+ float px = vertices[vertexCount-1][X];
696
+ float py = vertices[vertexCount-1][Y];
697
+ ctrlX = px + (px - ppx);
698
+ ctrlY = py + (py - ppy);
699
+ }
700
+ float ctrlX2 = PApplet.parseFloat(pathTokens[i + 1]);
701
+ float ctrlY2 = PApplet.parseFloat(pathTokens[i + 2]);
702
+ float endX = PApplet.parseFloat(pathTokens[i + 3]);
703
+ float endY = PApplet.parseFloat(pathTokens[i + 4]);
704
+ parsePathCurveto(ctrlX, ctrlY, ctrlX2, ctrlY2, endX, endY);
705
+ cx = endX;
706
+ cy = endY;
707
+ i += 5;
708
+ prevCurve = true;
709
+ }
710
+ break;
711
+
712
+ // s - curve to shorthand (relative)
713
+ case 's': {
714
+ if (!prevCurve) {
715
+ ctrlX = cx;
716
+ ctrlY = cy;
717
+ } else {
718
+ float ppx = vertices[vertexCount-2][X];
719
+ float ppy = vertices[vertexCount-2][Y];
720
+ float px = vertices[vertexCount-1][X];
721
+ float py = vertices[vertexCount-1][Y];
722
+ ctrlX = px + (px - ppx);
723
+ ctrlY = py + (py - ppy);
724
+ }
725
+ float ctrlX2 = cx + PApplet.parseFloat(pathTokens[i + 1]);
726
+ float ctrlY2 = cy + PApplet.parseFloat(pathTokens[i + 2]);
727
+ float endX = cx + PApplet.parseFloat(pathTokens[i + 3]);
728
+ float endY = cy + PApplet.parseFloat(pathTokens[i + 4]);
729
+ parsePathCurveto(ctrlX, ctrlY, ctrlX2, ctrlY2, endX, endY);
730
+ cx = endX;
731
+ cy = endY;
732
+ i += 5;
733
+ prevCurve = true;
734
+ }
735
+ break;
736
+
737
+ // Q - quadratic curve to (absolute)
738
+ // Draws a quadratic Bézier curve from the current point to (x,y) using
739
+ // (x1,y1) as the control point. Q (uppercase) indicates that absolute
740
+ // coordinates will follow; q (lowercase) indicates that relative
741
+ // coordinates will follow. Multiple sets of coordinates may be specified
742
+ // to draw a polybézier. At the end of the command, the new current point
743
+ // becomes the final (x,y) coordinate pair used in the polybézier.
744
+ case 'Q': {
745
+ ctrlX = PApplet.parseFloat(pathTokens[i + 1]);
746
+ ctrlY = PApplet.parseFloat(pathTokens[i + 2]);
747
+ float endX = PApplet.parseFloat(pathTokens[i + 3]);
748
+ float endY = PApplet.parseFloat(pathTokens[i + 4]);
749
+ //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
750
+ parsePathQuadto(ctrlX, ctrlY, endX, endY);
751
+ cx = endX;
752
+ cy = endY;
753
+ i += 5;
754
+ prevCurve = true;
755
+ }
756
+ break;
757
+
758
+ // q - quadratic curve to (relative)
759
+ case 'q': {
760
+ ctrlX = cx + PApplet.parseFloat(pathTokens[i + 1]);
761
+ ctrlY = cy + PApplet.parseFloat(pathTokens[i + 2]);
762
+ float endX = cx + PApplet.parseFloat(pathTokens[i + 3]);
763
+ float endY = cy + PApplet.parseFloat(pathTokens[i + 4]);
764
+ //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
765
+ parsePathQuadto(ctrlX, ctrlY, endX, endY);
766
+ cx = endX;
767
+ cy = endY;
768
+ i += 5;
769
+ prevCurve = true;
770
+ }
771
+ break;
772
+
773
+ // T - quadratic curveto shorthand (absolute)
774
+ // The control point is assumed to be the reflection of the control
775
+ // point on the previous command relative to the current point.
776
+ case 'T': {
777
+ // If there is no previous command or if the previous command was
778
+ // not a Q, q, T or t, assume the control point is coincident
779
+ // with the current point.
780
+ if (!prevCurve) {
781
+ ctrlX = cx;
782
+ ctrlY = cy;
783
+ } else {
784
+ float ppx = vertices[vertexCount-2][X];
785
+ float ppy = vertices[vertexCount-2][Y];
786
+ float px = vertices[vertexCount-1][X];
787
+ float py = vertices[vertexCount-1][Y];
788
+ ctrlX = px + (px - ppx);
789
+ ctrlY = py + (py - ppy);
790
+ }
791
+ float endX = PApplet.parseFloat(pathTokens[i + 1]);
792
+ float endY = PApplet.parseFloat(pathTokens[i + 2]);
793
+ //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
794
+ parsePathQuadto(ctrlX, ctrlY, endX, endY);
795
+ cx = endX;
796
+ cy = endY;
797
+ i += 3;
798
+ prevCurve = true;
799
+ }
800
+ break;
801
+
802
+ // t - quadratic curveto shorthand (relative)
803
+ case 't': {
804
+ if (!prevCurve) {
805
+ ctrlX = cx;
806
+ ctrlY = cy;
807
+ } else {
808
+ float ppx = vertices[vertexCount-2][X];
809
+ float ppy = vertices[vertexCount-2][Y];
810
+ float px = vertices[vertexCount-1][X];
811
+ float py = vertices[vertexCount-1][Y];
812
+ ctrlX = px + (px - ppx);
813
+ ctrlY = py + (py - ppy);
814
+ }
815
+ float endX = cx + PApplet.parseFloat(pathTokens[i + 1]);
816
+ float endY = cy + PApplet.parseFloat(pathTokens[i + 2]);
817
+ //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY);
818
+ parsePathQuadto(ctrlX, ctrlY, endX, endY);
819
+ cx = endX;
820
+ cy = endY;
821
+ i += 3;
822
+ prevCurve = true;
823
+ }
824
+ break;
825
+
826
+ // A - elliptical arc to (absolute)
827
+ case 'A': {
828
+ float rx = PApplet.parseFloat(pathTokens[i + 1]);
829
+ float ry = PApplet.parseFloat(pathTokens[i + 2]);
830
+ float angle = PApplet.parseFloat(pathTokens[i + 3]);
831
+ boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0;
832
+ boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
833
+ float endX = PApplet.parseFloat(pathTokens[i + 6]);
834
+ float endY = PApplet.parseFloat(pathTokens[i + 7]);
835
+ parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
836
+ cx = endX;
837
+ cy = endY;
838
+ i += 8;
839
+ prevCurve = true;
840
+ }
841
+ break;
842
+
843
+ // a - elliptical arc to (relative)
844
+ case 'a': {
845
+ float rx = PApplet.parseFloat(pathTokens[i + 1]);
846
+ float ry = PApplet.parseFloat(pathTokens[i + 2]);
847
+ float angle = PApplet.parseFloat(pathTokens[i + 3]);
848
+ boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0;
849
+ boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0;
850
+ float endX = cx + PApplet.parseFloat(pathTokens[i + 6]);
851
+ float endY = cy + PApplet.parseFloat(pathTokens[i + 7]);
852
+ parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY);
853
+ cx = endX;
854
+ cy = endY;
855
+ i += 8;
856
+ prevCurve = true;
857
+ }
858
+ break;
859
+
860
+ case 'Z':
861
+ case 'z':
862
+ // since closing the path, the 'current' point needs
863
+ // to return back to the last moveto location.
864
+ // http://code.google.com/p/processing/issues/detail?id=1058
865
+ cx = movetoX;
866
+ cy = movetoY;
867
+ close = true;
868
+ i++;
869
+ break;
870
+
871
+ default:
872
+ String parsed =
873
+ PApplet.join(PApplet.subset(pathTokens, 0, i), ",");
874
+ String unparsed =
875
+ PApplet.join(PApplet.subset(pathTokens, i), ",");
876
+ System.err.println("parsed: " + parsed);
877
+ System.err.println("unparsed: " + unparsed);
878
+ throw new RuntimeException("shape command not handled: " + pathTokens[i]);
879
+ }
880
+ // prevCommand = c;
881
+ }
882
+ }
883
+
884
+
885
+ // private void parsePathCheck(int num) {
886
+ // if (vertexCount + num-1 >= vertices.length) {
887
+ // //vertices = (float[][]) PApplet.expand(vertices);
888
+ // float[][] temp = new float[vertexCount << 1][2];
889
+ // System.arraycopy(vertices, 0, temp, 0, vertexCount);
890
+ // vertices = temp;
891
+ // }
892
+ // }
893
+
894
+ private void parsePathVertex(float x, float y) {
895
+ if (vertexCount == vertices.length) {
896
+ //vertices = (float[][]) PApplet.expand(vertices);
897
+ float[][] temp = new float[vertexCount << 1][2];
898
+ System.arraycopy(vertices, 0, temp, 0, vertexCount);
899
+ vertices = temp;
900
+ }
901
+ vertices[vertexCount][X] = x;
902
+ vertices[vertexCount][Y] = y;
903
+ vertexCount++;
904
+ }
905
+
906
+
907
+ private void parsePathCode(int what) {
908
+ if (vertexCodeCount == vertexCodes.length) {
909
+ vertexCodes = PApplet.expand(vertexCodes);
910
+ }
911
+ vertexCodes[vertexCodeCount++] = what;
912
+ }
913
+
914
+
915
+ private void parsePathMoveto(float px, float py) {
916
+ if (vertexCount > 0) {
917
+ parsePathCode(BREAK);
918
+ }
919
+ parsePathCode(VERTEX);
920
+ parsePathVertex(px, py);
921
+ }
922
+
923
+
924
+ private void parsePathLineto(float px, float py) {
925
+ parsePathCode(VERTEX);
926
+ parsePathVertex(px, py);
927
+ }
928
+
929
+
930
+ private void parsePathCurveto(float x1, float y1,
931
+ float x2, float y2,
932
+ float x3, float y3) {
933
+ parsePathCode(BEZIER_VERTEX);
934
+ parsePathVertex(x1, y1);
935
+ parsePathVertex(x2, y2);
936
+ parsePathVertex(x3, y3);
937
+ }
938
+
939
+ // private void parsePathQuadto(float x1, float y1,
940
+ // float cx, float cy,
941
+ // float x2, float y2) {
942
+ // //System.out.println("quadto: " + x1 + "," + y1 + " " + cx + "," + cy + " " + x2 + "," + y2);
943
+ //// parsePathCode(BEZIER_VERTEX);
944
+ // parsePathCode(QUAD_BEZIER_VERTEX);
945
+ // // x1/y1 already covered by last moveto, lineto, or curveto
946
+ //
947
+ // parsePathVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f));
948
+ // parsePathVertex(x2 + ((cx-x2)*2/3.0f), y2 + ((cy-y2)*2/3.0f));
949
+ // parsePathVertex(x2, y2);
950
+ // }
951
+
952
+ private void parsePathQuadto(float cx, float cy,
953
+ float x2, float y2) {
954
+ //System.out.println("quadto: " + x1 + "," + y1 + " " + cx + "," + cy + " " + x2 + "," + y2);
955
+ // parsePathCode(BEZIER_VERTEX);
956
+ parsePathCode(QUADRATIC_VERTEX);
957
+ // x1/y1 already covered by last moveto, lineto, or curveto
958
+ parsePathVertex(cx, cy);
959
+ parsePathVertex(x2, y2);
960
+ }
961
+
962
+
963
+ // Approximates elliptical arc by several bezier segments.
964
+ // Meets SVG standard requirements from:
965
+ // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
966
+ // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
967
+ // Based on arc to bezier curve equations from:
968
+ // http://www.spaceroots.org/documents/ellipse/node22.html
969
+ private void parsePathArcto(float x1, float y1,
970
+ float rx, float ry,
971
+ float angle,
972
+ boolean fa, boolean fs,
973
+ float x2, float y2) {
974
+ if (x1 == x2 && y1 == y2) return;
975
+ if (rx == 0 || ry == 0) { parsePathLineto(x2, y2); return; }
976
+
977
+ rx = PApplet.abs(rx); ry = PApplet.abs(ry);
978
+
979
+ float phi = PApplet.radians(((angle % 360) + 360) % 360);
980
+ float cosPhi = PApplet.cos(phi), sinPhi = PApplet.sin(phi);
981
+
982
+ float x1r = ( cosPhi * (x1 - x2) + sinPhi * (y1 - y2)) / 2;
983
+ float y1r = (-sinPhi * (x1 - x2) + cosPhi * (y1 - y2)) / 2;
984
+
985
+ float cxr, cyr;
986
+ {
987
+ float A = (x1r*x1r) / (rx*rx) + (y1r*y1r) / (ry*ry);
988
+ if (A > 1) {
989
+ // No solution, scale ellipse up according to SVG standard
990
+ float sqrtA = PApplet.sqrt(A);
991
+ rx *= sqrtA; cxr = 0;
992
+ ry *= sqrtA; cyr = 0;
993
+ } else {
994
+ float k = ((fa == fs) ? -1f : 1f) *
995
+ PApplet.sqrt((rx*rx * ry*ry) / ((rx*rx * y1r*y1r) + (ry*ry * x1r*x1r)) - 1f);
996
+ cxr = k * rx * y1r / ry;
997
+ cyr = -k * ry * x1r / rx;
998
+ }
999
+ }
1000
+
1001
+ float cx = cosPhi * cxr - sinPhi * cyr + (x1 + x2) / 2;
1002
+ float cy = sinPhi * cxr + cosPhi * cyr + (y1 + y2) / 2;
1003
+
1004
+ float phi1, phiDelta;
1005
+ {
1006
+ float sx = ( x1r - cxr) / rx, sy = ( y1r - cyr) / ry;
1007
+ float tx = (-x1r - cxr) / rx, ty = (-y1r - cyr) / ry;
1008
+ phi1 = PApplet.atan2(sy, sx);
1009
+ phiDelta = (((PApplet.atan2(ty, tx) - phi1) % TWO_PI) + TWO_PI) % TWO_PI;
1010
+ if (!fs) phiDelta -= TWO_PI;
1011
+ }
1012
+
1013
+ // One segment can not cover more that PI, less than PI/2 is
1014
+ // recommended to avoid visible inaccuracies caused by rounding errors
1015
+ int segmentCount = PApplet.ceil(PApplet.abs(phiDelta) / TWO_PI * 4);
1016
+
1017
+ float inc = phiDelta / segmentCount;
1018
+ float a = PApplet.sin(inc) *
1019
+ (PApplet.sqrt(4 + 3 * PApplet.sq(PApplet.tan(inc / 2))) - 1) / 3;
1020
+
1021
+ float sinPhi1 = PApplet.sin(phi1), cosPhi1 = PApplet.cos(phi1);
1022
+
1023
+ float p1x = x1;
1024
+ float p1y = y1;
1025
+ float relq1x = a * (-rx * cosPhi * sinPhi1 - ry * sinPhi * cosPhi1);
1026
+ float relq1y = a * (-rx * sinPhi * sinPhi1 + ry * cosPhi * cosPhi1);
1027
+
1028
+ for (int i = 0; i < segmentCount; i++) {
1029
+ float eta = phi1 + (i + 1) * inc;
1030
+ float sinEta = PApplet.sin(eta), cosEta = PApplet.cos(eta);
1031
+
1032
+ float p2x = cx + rx * cosPhi * cosEta - ry * sinPhi * sinEta;
1033
+ float p2y = cy + rx * sinPhi * cosEta + ry * cosPhi * sinEta;
1034
+ float relq2x = a * (-rx * cosPhi * sinEta - ry * sinPhi * cosEta);
1035
+ float relq2y = a * (-rx * sinPhi * sinEta + ry * cosPhi * cosEta);
1036
+
1037
+ if (i == segmentCount - 1) { p2x = x2; p2y = y2; }
1038
+
1039
+ parsePathCode(BEZIER_VERTEX);
1040
+ parsePathVertex(p1x + relq1x, p1y + relq1y);
1041
+ parsePathVertex(p2x - relq2x, p2y - relq2y);
1042
+ parsePathVertex(p2x, p2y);
1043
+
1044
+ p1x = p2x; relq1x = relq2x;
1045
+ p1y = p2y; relq1y = relq2y;
1046
+ }
1047
+ }
1048
+
1049
+
1050
+ /**
1051
+ * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D
1052
+ * is rotated relative to the SVG definition, so parameters are rearranged
1053
+ * here. More about the transformation matrices in
1054
+ * <a href="http://www.w3.org/TR/SVG/coords.html#TransformAttribute">this section</a>
1055
+ * of the SVG documentation.
1056
+ * @param matrixStr text of the matrix param.
1057
+ * @return a good old-fashioned PMatrix2D
1058
+ */
1059
+ static protected PMatrix2D parseTransform(String matrixStr) {
1060
+ matrixStr = matrixStr.trim();
1061
+ PMatrix2D outgoing = null;
1062
+ int start = 0;
1063
+ int stop = -1;
1064
+ while ((stop = matrixStr.indexOf(')', start)) != -1) {
1065
+ PMatrix2D m = parseSingleTransform(matrixStr.substring(start, stop+1));
1066
+ if (outgoing == null) {
1067
+ outgoing = m;
1068
+ } else {
1069
+ outgoing.apply(m);
1070
+ }
1071
+ start = stop + 1;
1072
+ }
1073
+ return outgoing;
1074
+ }
1075
+
1076
+ /**
1077
+ *
1078
+ * @param matrixStr
1079
+ * @return
1080
+ */
1081
+ static protected PMatrix2D parseSingleTransform(String matrixStr) {
1082
+ //String[] pieces = PApplet.match(matrixStr, "^\\s*(\\w+)\\((.*)\\)\\s*$");
1083
+ String[] pieces = PApplet.match(matrixStr, "[,\\s]*(\\w+)\\((.*)\\)");
1084
+ if (pieces == null) {
1085
+ System.err.println("Could not parse transform " + matrixStr);
1086
+ return null;
1087
+ }
1088
+ float[] m = PApplet.parseFloat(PApplet.splitTokens(pieces[2], ", "));
1089
+ if (pieces[1].equals("matrix")) {
1090
+ return new PMatrix2D(m[0], m[2], m[4], m[1], m[3], m[5]);
1091
+
1092
+ } else if (pieces[1].equals("translate")) {
1093
+ float tx = m[0];
1094
+ float ty = (m.length == 2) ? m[1] : m[0];
1095
+ return new PMatrix2D(1, 0, tx, 0, 1, ty);
1096
+
1097
+ } else if (pieces[1].equals("scale")) {
1098
+ float sx = m[0];
1099
+ float sy = (m.length == 2) ? m[1] : m[0];
1100
+ return new PMatrix2D(sx, 0, 0, 0, sy, 0);
1101
+
1102
+ } else if (pieces[1].equals("rotate")) {
1103
+ float angle = m[0];
1104
+
1105
+ if (m.length == 1) {
1106
+ float c = PApplet.cos(angle);
1107
+ float s = PApplet.sin(angle);
1108
+ // SVG version is cos(a) sin(a) -sin(a) cos(a) 0 0
1109
+ return new PMatrix2D(c, -s, 0, s, c, 0);
1110
+
1111
+ } else if (m.length == 3) {
1112
+ PMatrix2D mat = new PMatrix2D(0, 1, m[1], 1, 0, m[2]);
1113
+ mat.rotate(m[0]);
1114
+ mat.translate(-m[1], -m[2]);
1115
+ return mat;
1116
+ }
1117
+
1118
+ } else if (pieces[1].equals("skewX")) {
1119
+ return new PMatrix2D(1, 0, 1, PApplet.tan(m[0]), 0, 0);
1120
+
1121
+ } else if (pieces[1].equals("skewY")) {
1122
+ return new PMatrix2D(1, 0, 1, 0, PApplet.tan(m[0]), 0);
1123
+ }
1124
+ return null;
1125
+ }
1126
+
1127
+ /**
1128
+ *
1129
+ * @param properties
1130
+ */
1131
+ protected void parseColors(XML properties) {
1132
+ if (properties.hasAttribute("opacity")) {
1133
+ String opacityText = properties.getString("opacity");
1134
+ setOpacity(opacityText);
1135
+ }
1136
+
1137
+ if (properties.hasAttribute("stroke")) {
1138
+ String strokeText = properties.getString("stroke");
1139
+ setColor(strokeText, false);
1140
+ }
1141
+
1142
+ if (properties.hasAttribute("stroke-opacity")) {
1143
+ String strokeOpacityText = properties.getString("stroke-opacity");
1144
+ setStrokeOpacity(strokeOpacityText);
1145
+ }
1146
+
1147
+ if (properties.hasAttribute("stroke-width")) {
1148
+ // if NaN (i.e. if it's 'inherit') then default back to the inherit setting
1149
+ String lineweight = properties.getString("stroke-width");
1150
+ setStrokeWeight(lineweight);
1151
+ }
1152
+
1153
+ if (properties.hasAttribute("stroke-linejoin")) {
1154
+ String linejoin = properties.getString("stroke-linejoin");
1155
+ setStrokeJoin(linejoin);
1156
+ }
1157
+
1158
+ if (properties.hasAttribute("stroke-linecap")) {
1159
+ String linecap = properties.getString("stroke-linecap");
1160
+ setStrokeCap(linecap);
1161
+ }
1162
+
1163
+ // fill defaults to black (though stroke defaults to "none")
1164
+ // http://www.w3.org/TR/SVG/painting.html#FillProperties
1165
+ if (properties.hasAttribute("fill")) {
1166
+ String fillText = properties.getString("fill");
1167
+ setColor(fillText, true);
1168
+ }
1169
+
1170
+ if (properties.hasAttribute("fill-opacity")) {
1171
+ String fillOpacityText = properties.getString("fill-opacity");
1172
+ setFillOpacity(fillOpacityText);
1173
+ }
1174
+
1175
+ if (properties.hasAttribute("style")) {
1176
+ String styleText = properties.getString("style");
1177
+ String[] styleTokens = PApplet.splitTokens(styleText, ";");
1178
+
1179
+ //PApplet.println(styleTokens);
1180
+ for (int i = 0; i < styleTokens.length; i++) {
1181
+ String[] tokens = PApplet.splitTokens(styleTokens[i], ":");
1182
+ //PApplet.println(tokens);
1183
+
1184
+ tokens[0] = PApplet.trim(tokens[0]);
1185
+
1186
+ if (tokens[0].equals("fill")) {
1187
+ setColor(tokens[1], true);
1188
+
1189
+ } else if(tokens[0].equals("fill-opacity")) {
1190
+ setFillOpacity(tokens[1]);
1191
+
1192
+ } else if(tokens[0].equals("stroke")) {
1193
+ setColor(tokens[1], false);
1194
+
1195
+ } else if(tokens[0].equals("stroke-width")) {
1196
+ setStrokeWeight(tokens[1]);
1197
+
1198
+ } else if(tokens[0].equals("stroke-linecap")) {
1199
+ setStrokeCap(tokens[1]);
1200
+
1201
+ } else if(tokens[0].equals("stroke-linejoin")) {
1202
+ setStrokeJoin(tokens[1]);
1203
+
1204
+ } else if(tokens[0].equals("stroke-opacity")) {
1205
+ setStrokeOpacity(tokens[1]);
1206
+
1207
+ } else if(tokens[0].equals("opacity")) {
1208
+ setOpacity(tokens[1]);
1209
+
1210
+ } else {
1211
+ // Other attributes are not yet implemented
1212
+ }
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+
1218
+ void setOpacity(String opacityText) {
1219
+ opacity = PApplet.parseFloat(opacityText);
1220
+ strokeColor = ((int) (opacity * 255)) << 24 | strokeColor & 0xFFFFFF;
1221
+ fillColor = ((int) (opacity * 255)) << 24 | fillColor & 0xFFFFFF;
1222
+ }
1223
+
1224
+
1225
+ void setStrokeWeight(String lineweight) {
1226
+ strokeWeight = parseUnitSize(lineweight, svgSizeXY);
1227
+ }
1228
+
1229
+
1230
+ void setStrokeOpacity(String opacityText) {
1231
+ strokeOpacity = PApplet.parseFloat(opacityText);
1232
+ strokeColor = ((int) (strokeOpacity * 255)) << 24 | strokeColor & 0xFFFFFF;
1233
+ }
1234
+
1235
+
1236
+ void setStrokeJoin(String linejoin) {
1237
+ if (linejoin.equals("inherit")) {
1238
+ // do nothing, will inherit automatically
1239
+
1240
+ } else if (linejoin.equals("miter")) {
1241
+ strokeJoin = PConstants.MITER;
1242
+
1243
+ } else if (linejoin.equals("round")) {
1244
+ strokeJoin = PConstants.ROUND;
1245
+
1246
+ } else if (linejoin.equals("bevel")) {
1247
+ strokeJoin = PConstants.BEVEL;
1248
+ }
1249
+ }
1250
+
1251
+
1252
+ void setStrokeCap(String linecap) {
1253
+ if (linecap.equals("inherit")) {
1254
+ // do nothing, will inherit automatically
1255
+
1256
+ } else if (linecap.equals("butt")) {
1257
+ strokeCap = PConstants.SQUARE;
1258
+
1259
+ } else if (linecap.equals("round")) {
1260
+ strokeCap = PConstants.ROUND;
1261
+
1262
+ } else if (linecap.equals("square")) {
1263
+ strokeCap = PConstants.PROJECT;
1264
+ }
1265
+ }
1266
+
1267
+
1268
+ void setFillOpacity(String opacityText) {
1269
+ fillOpacity = PApplet.parseFloat(opacityText);
1270
+ fillColor = ((int) (fillOpacity * 255)) << 24 | fillColor & 0xFFFFFF;
1271
+ }
1272
+
1273
+
1274
+ void setColor(String colorText, boolean isFill) {
1275
+ colorText = colorText.trim();
1276
+ int opacityMask = fillColor & 0xFF000000;
1277
+ boolean visible = true;
1278
+ int color = 0;
1279
+ String name = "";
1280
+ // String lColorText = colorText.toLowerCase();
1281
+ Gradient gradient = null;
1282
+ // Object paint = null;
1283
+ if (colorText.equals("none")) {
1284
+ visible = false;
1285
+ } else if (colorText.startsWith("url(#")) {
1286
+ name = colorText.substring(5, colorText.length() - 1);
1287
+ Object object = findChild(name);
1288
+ if (object instanceof Gradient) {
1289
+ gradient = (Gradient) object;
1290
+ // in 3.0a11, do this on first draw inside PShapeJava2D
1291
+ // paint = calcGradientPaint(gradient); //, opacity);
1292
+ } else {
1293
+ // visible = false;
1294
+ System.err.println("url " + name + " refers to unexpected data: " + object);
1295
+ }
1296
+ } else {
1297
+ // Prints errors itself.
1298
+ color = opacityMask | parseSimpleColor(colorText);
1299
+ }
1300
+ if (isFill) {
1301
+ fill = visible;
1302
+ fillColor = color;
1303
+ fillName = name;
1304
+ fillGradient = gradient;
1305
+ // fillGradientPaint = paint;
1306
+ } else {
1307
+ stroke = visible;
1308
+ strokeColor = color;
1309
+ strokeName = name;
1310
+ strokeGradient = gradient;
1311
+ // strokeGradientPaint = paint;
1312
+ }
1313
+ }
1314
+
1315
+
1316
+ /**
1317
+ * Parses the "color" datatype only, and prints an error if it is not of this form.
1318
+ * http://www.w3.org/TR/SVG/types.html#DataTypeColor
1319
+ * @param colorText
1320
+ * @return 0xRRGGBB (no alpha). Zero on error.
1321
+ */
1322
+ static protected int parseSimpleColor(String colorText) {
1323
+ colorText = colorText.toLowerCase().trim();
1324
+ //if (colorNames.containsKey(colorText)) {
1325
+ if (colorNames.hasKey(colorText)) {
1326
+ return colorNames.get(colorText);
1327
+ } else if (colorText.startsWith("#")) {
1328
+ if (colorText.length() == 4) {
1329
+ // Short form: #ABC, transform to long form #AABBCC
1330
+ colorText = colorText.replaceAll("^#(.)(.)(.)$", "#$1$1$2$2$3$3");
1331
+ }
1332
+ return (Integer.parseInt(colorText.substring(1), 16)) & 0xFFFFFF;
1333
+ //System.out.println("hex for fill is " + PApplet.hex(fillColor));
1334
+ } else if (colorText.startsWith("rgb")) {
1335
+ return parseRGB(colorText);
1336
+ } else {
1337
+ System.err.println("Cannot parse \"" + colorText + "\".");
1338
+ return 0;
1339
+ }
1340
+ }
1341
+
1342
+
1343
+ /**
1344
+ * Deliberately conforms to the HTML 4.01 color spec + en-gb grey, rather
1345
+ * than the (unlikely to be useful) entire 147-color system used in SVG.
1346
+ */
1347
+ static protected IntDict colorNames = new IntDict(new Object[][] {
1348
+ { "aqua", 0x00ffff },
1349
+ { "black", 0x000000 },
1350
+ { "blue", 0x0000ff },
1351
+ { "fuchsia", 0xff00ff },
1352
+ { "gray", 0x808080 },
1353
+ { "grey", 0x808080 },
1354
+ { "green", 0x008000 },
1355
+ { "lime", 0x00ff00 },
1356
+ { "maroon", 0x800000 },
1357
+ { "navy", 0x000080 },
1358
+ { "olive", 0x808000 },
1359
+ { "purple", 0x800080 },
1360
+ { "red", 0xff0000 },
1361
+ { "silver", 0xc0c0c0 },
1362
+ { "teal", 0x008080 },
1363
+ { "white", 0xffffff },
1364
+ { "yellow", 0xffff00 }
1365
+ });
1366
+
1367
+ /*
1368
+ static protected Map<String, Integer> colorNames;
1369
+ static {
1370
+ colorNames = new HashMap<String, Integer>();
1371
+ colorNames.put("aqua", 0x00ffff);
1372
+ colorNames.put("black", 0x000000);
1373
+ colorNames.put("blue", 0x0000ff);
1374
+ colorNames.put("fuchsia", 0xff00ff);
1375
+ colorNames.put("gray", 0x808080);
1376
+ colorNames.put("grey", 0x808080);
1377
+ colorNames.put("green", 0x008000);
1378
+ colorNames.put("lime", 0x00ff00);
1379
+ colorNames.put("maroon", 0x800000);
1380
+ colorNames.put("navy", 0x000080);
1381
+ colorNames.put("olive", 0x808000);
1382
+ colorNames.put("purple", 0x800080);
1383
+ colorNames.put("red", 0xff0000);
1384
+ colorNames.put("silver", 0xc0c0c0);
1385
+ colorNames.put("teal", 0x008080);
1386
+ colorNames.put("white", 0xffffff);
1387
+ colorNames.put("yellow", 0xffff00);
1388
+ }
1389
+ */
1390
+
1391
+ /**
1392
+ *
1393
+ * @param what
1394
+ * @return
1395
+ */
1396
+
1397
+
1398
+ static protected int parseRGB(String what) {
1399
+ int leftParen = what.indexOf('(') + 1;
1400
+ int rightParen = what.indexOf(')');
1401
+ String sub = what.substring(leftParen, rightParen);
1402
+ String[] values = PApplet.splitTokens(sub, ", ");
1403
+ int rgbValue = 0;
1404
+ if (values.length == 3) {
1405
+ // Color spec allows for rgb values to be percentages.
1406
+ for (int i = 0; i < 3; i++) {
1407
+ rgbValue <<= 8;
1408
+ if (values[i].endsWith("%")) {
1409
+ rgbValue |= (int)(PApplet.constrain(255*parseFloatOrPercent(values[i]), 0, 255));
1410
+ } else {
1411
+ rgbValue |= PApplet.constrain(PApplet.parseInt(values[i]), 0, 255);
1412
+ }
1413
+ }
1414
+ } else System.err.println("Could not read color \"" + what + "\".");
1415
+
1416
+ return rgbValue;
1417
+ }
1418
+
1419
+
1420
+ //static protected Map<String, String> parseStyleAttributes(String style) {
1421
+
1422
+ /**
1423
+ *
1424
+ * @param style
1425
+ * @return
1426
+ */
1427
+ static protected StringDict parseStyleAttributes(String style) {
1428
+ //Map<String, String> table = new HashMap<String, String>();
1429
+ StringDict table = new StringDict();
1430
+ // if (style == null) return table;
1431
+ if (style != null) {
1432
+ String[] pieces = style.split(";");
1433
+ for (int i = 0; i < pieces.length; i++) {
1434
+ String[] parts = pieces[i].split(":");
1435
+ //table.put(parts[0], parts[1]);
1436
+ table.set(parts[0], parts[1]);
1437
+ }
1438
+ }
1439
+ return table;
1440
+ }
1441
+
1442
+
1443
+ /**
1444
+ * Used in place of element.getFloatAttribute(a) because we can
1445
+ * have a unit suffix (length or coordinate).
1446
+ * @param element what to parse
1447
+ * @param attribute name of the attribute to get
1448
+ * @param relativeTo (float) Used for %. When relative to viewbox, should
1449
+ * be svgWidth for horizontal dimentions, svgHeight for vertical, and
1450
+ * svgXYSize for anything else.
1451
+ * @return unit-parsed version of the data
1452
+ */
1453
+ static protected float getFloatWithUnit(XML element, String attribute, float relativeTo) {
1454
+ String val = element.getString(attribute);
1455
+ return (val == null) ? 0 : parseUnitSize(val, relativeTo);
1456
+ }
1457
+
1458
+
1459
+ /**
1460
+ * Parse a size that may have a suffix for its units.
1461
+ * This assumes 90dpi, which implies, as given in the
1462
+ * <A HREF="http://www.w3.org/TR/SVG/coords.html#Units">units</A> spec:
1463
+ * <UL>
1464
+ * <LI>"1pt" equals "1.25px" (and therefore 1.25 user units)
1465
+ * <LI>"1pc" equals "15px" (and therefore 15 user units)
1466
+ * <LI>"1mm" would be "3.543307px" (3.543307 user units)
1467
+ * <LI>"1cm" equals "35.43307px" (and therefore 35.43307 user units)
1468
+ * <LI>"1in" equals "90px" (and therefore 90 user units)
1469
+ * </UL>
1470
+ * @param text
1471
+ * @param relativeTo (float) Used for %. When relative to viewbox, should
1472
+ * be svgWidth for horizontal dimentions, svgHeight for vertical, and
1473
+ * svgXYSize for anything else.
1474
+ * @return
1475
+ */
1476
+ static protected float parseUnitSize(String text, float relativeTo) {
1477
+ int len = text.length() - 2;
1478
+
1479
+ if (text.endsWith("pt")) {
1480
+ return PApplet.parseFloat(text.substring(0, len)) * 1.25f;
1481
+ } else if (text.endsWith("pc")) {
1482
+ return PApplet.parseFloat(text.substring(0, len)) * 15;
1483
+ } else if (text.endsWith("mm")) {
1484
+ return PApplet.parseFloat(text.substring(0, len)) * 3.543307f;
1485
+ } else if (text.endsWith("cm")) {
1486
+ return PApplet.parseFloat(text.substring(0, len)) * 35.43307f;
1487
+ } else if (text.endsWith("in")) {
1488
+ return PApplet.parseFloat(text.substring(0, len)) * 90;
1489
+ } else if (text.endsWith("px")) {
1490
+ return PApplet.parseFloat(text.substring(0, len));
1491
+ } else if (text.endsWith("%")) {
1492
+ return relativeTo * parseFloatOrPercent(text);
1493
+ } else {
1494
+ return PApplet.parseFloat(text);
1495
+ }
1496
+ }
1497
+
1498
+ /**
1499
+ *
1500
+ * @param text
1501
+ * @return
1502
+ */
1503
+ static protected float parseFloatOrPercent(String text) {
1504
+ text = text.trim();
1505
+ if (text.endsWith("%")) {
1506
+ return Float.parseFloat(text.substring(0, text.length() - 1)) / 100.0f;
1507
+ } else {
1508
+ return Float.parseFloat(text);
1509
+ }
1510
+ }
1511
+
1512
+
1513
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1514
+
1515
+ /**
1516
+ *
1517
+ */
1518
+
1519
+
1520
+ static public class Gradient extends PShapeSVG {
1521
+ AffineTransform transform;
1522
+
1523
+ /**
1524
+ *
1525
+ */
1526
+ public float[] offset;
1527
+
1528
+ /**
1529
+ *
1530
+ */
1531
+ public int[] color;
1532
+
1533
+ /**
1534
+ *
1535
+ */
1536
+ public int count;
1537
+
1538
+ /**
1539
+ *
1540
+ * @param parent
1541
+ * @param properties
1542
+ */
1543
+ public Gradient(PShapeSVG parent, XML properties) {
1544
+ super(parent, properties, true);
1545
+
1546
+ XML elements[] = properties.getChildren();
1547
+ offset = new float[elements.length];
1548
+ color = new int[elements.length];
1549
+
1550
+ // <stop offset="0" style="stop-color:#967348"/>
1551
+ for (int i = 0; i < elements.length; i++) {
1552
+ XML elem = elements[i];
1553
+ String name = elem.getName();
1554
+ if (name.equals("stop")) {
1555
+ String offsetAttr = elem.getString("offset");
1556
+ offset[count] = parseFloatOrPercent(offsetAttr);
1557
+
1558
+ String style = elem.getString("style");
1559
+ //Map<String, String> styles = parseStyleAttributes(style);
1560
+ StringDict styles = parseStyleAttributes(style);
1561
+
1562
+ String colorStr = styles.get("stop-color");
1563
+ if (colorStr == null) {
1564
+ colorStr = elem.getString("stop-color");
1565
+ if (colorStr == null) colorStr = "#000000";
1566
+ }
1567
+ String opacityStr = styles.get("stop-opacity");
1568
+ if (opacityStr == null) {
1569
+ opacityStr = elem.getString("stop-opacity");
1570
+ if (opacityStr == null) opacityStr = "1";
1571
+ }
1572
+ int tupacity = PApplet.constrain(
1573
+ (int)(PApplet.parseFloat(opacityStr) * 255), 0, 255);
1574
+ color[count] = (tupacity << 24) | parseSimpleColor(colorStr);
1575
+ count++;
1576
+ }
1577
+ }
1578
+ offset = PApplet.subset(offset, 0, count);
1579
+ color = PApplet.subset(color, 0, count);
1580
+ }
1581
+ }
1582
+
1583
+ /**
1584
+ *
1585
+ */
1586
+ public class LinearGradient extends Gradient {
1587
+
1588
+ /**
1589
+ *
1590
+ */
1591
+ public float x1,
1592
+
1593
+ /**
1594
+ *
1595
+ */
1596
+ y1,
1597
+
1598
+ /**
1599
+ *
1600
+ */
1601
+ x2,
1602
+
1603
+ /**
1604
+ *
1605
+ */
1606
+ y2;
1607
+
1608
+ /**
1609
+ *
1610
+ * @param parent
1611
+ * @param properties
1612
+ */
1613
+ public LinearGradient(PShapeSVG parent, XML properties) {
1614
+ super(parent, properties);
1615
+
1616
+ this.x1 = getFloatWithUnit(properties, "x1", svgWidth);
1617
+ this.y1 = getFloatWithUnit(properties, "y1", svgHeight);
1618
+ this.x2 = getFloatWithUnit(properties, "x2", svgWidth);
1619
+ this.y2 = getFloatWithUnit(properties, "y2", svgHeight);
1620
+
1621
+ String transformStr =
1622
+ properties.getString("gradientTransform");
1623
+
1624
+ if (transformStr != null) {
1625
+ float t[] = parseTransform(transformStr).get(null);
1626
+ this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]);
1627
+
1628
+ Point2D t1 = transform.transform(new Point2D.Float(x1, y1), null);
1629
+ Point2D t2 = transform.transform(new Point2D.Float(x2, y2), null);
1630
+
1631
+ this.x1 = (float) t1.getX();
1632
+ this.y1 = (float) t1.getY();
1633
+ this.x2 = (float) t2.getX();
1634
+ this.y2 = (float) t2.getY();
1635
+ }
1636
+ }
1637
+ }
1638
+
1639
+ /**
1640
+ *
1641
+ */
1642
+ public class RadialGradient extends Gradient {
1643
+
1644
+ /**
1645
+ *
1646
+ */
1647
+ public float cx,
1648
+
1649
+ /**
1650
+ *
1651
+ */
1652
+ cy,
1653
+
1654
+ /**
1655
+ *
1656
+ */
1657
+ r;
1658
+
1659
+ /**
1660
+ *
1661
+ * @param parent
1662
+ * @param properties
1663
+ */
1664
+ public RadialGradient(PShapeSVG parent, XML properties) {
1665
+ super(parent, properties);
1666
+
1667
+ this.cx = getFloatWithUnit(properties, "cx", svgWidth);
1668
+ this.cy = getFloatWithUnit(properties, "cy", svgHeight);
1669
+ this.r = getFloatWithUnit(properties, "r", svgSizeXY);
1670
+
1671
+ String transformStr =
1672
+ properties.getString("gradientTransform");
1673
+
1674
+ if (transformStr != null) {
1675
+ float t[] = parseTransform(transformStr).get(null);
1676
+ this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]);
1677
+
1678
+ Point2D t1 = transform.transform(new Point2D.Float(cx, cy), null);
1679
+ Point2D t2 = transform.transform(new Point2D.Float(cx + r, cy), null);
1680
+
1681
+ this.cx = (float) t1.getX();
1682
+ this.cy = (float) t1.getY();
1683
+ this.r = (float) (t2.getX() - t1.getX());
1684
+ }
1685
+ }
1686
+ }
1687
+
1688
+
1689
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1690
+
1691
+ /**
1692
+ *
1693
+ */
1694
+
1695
+
1696
+ public static class Font extends PShapeSVG {
1697
+
1698
+ /**
1699
+ *
1700
+ */
1701
+ public FontFace face;
1702
+
1703
+ /**
1704
+ *
1705
+ */
1706
+ public Map<String, FontGlyph> namedGlyphs;
1707
+
1708
+ /**
1709
+ *
1710
+ */
1711
+ public Map<Character, FontGlyph> unicodeGlyphs;
1712
+
1713
+ /**
1714
+ *
1715
+ */
1716
+ public int glyphCount;
1717
+
1718
+ /**
1719
+ *
1720
+ */
1721
+ public FontGlyph[] glyphs;
1722
+
1723
+ /**
1724
+ *
1725
+ */
1726
+ public FontGlyph missingGlyph;
1727
+
1728
+ int horizAdvX;
1729
+
1730
+ /**
1731
+ *
1732
+ * @param parent
1733
+ * @param properties
1734
+ */
1735
+ public Font(PShapeSVG parent, XML properties) {
1736
+ super(parent, properties, false);
1737
+ // handle(parent, properties);
1738
+
1739
+ XML[] elements = properties.getChildren();
1740
+
1741
+ horizAdvX = properties.getInt("horiz-adv-x", 0);
1742
+
1743
+ namedGlyphs = new HashMap<String, FontGlyph>();
1744
+ unicodeGlyphs = new HashMap<Character, FontGlyph>();
1745
+ glyphCount = 0;
1746
+ glyphs = new FontGlyph[elements.length];
1747
+
1748
+ for (int i = 0; i < elements.length; i++) {
1749
+ String name = elements[i].getName();
1750
+ XML elem = elements[i];
1751
+ if (name == null) {
1752
+ // skip it
1753
+ } else if (name.equals("glyph")) {
1754
+ FontGlyph fg = new FontGlyph(this, elem, this);
1755
+ if (fg.isLegit()) {
1756
+ if (fg.name != null) {
1757
+ namedGlyphs.put(fg.name, fg);
1758
+ }
1759
+ if (fg.unicode != 0) {
1760
+ unicodeGlyphs.put(Character.valueOf(fg.unicode), fg);
1761
+ }
1762
+ }
1763
+ glyphs[glyphCount++] = fg;
1764
+
1765
+ } else if (name.equals("missing-glyph")) {
1766
+ // System.out.println("got missing glyph inside <font>");
1767
+ missingGlyph = new FontGlyph(this, elem, this);
1768
+ } else if (name.equals("font-face")) {
1769
+ face = new FontFace(this, elem);
1770
+ } else {
1771
+ System.err.println("Ignoring " + name + " inside <font>");
1772
+ }
1773
+ }
1774
+ }
1775
+
1776
+ /**
1777
+ *
1778
+ */
1779
+ protected void drawShape() {
1780
+ // does nothing for fonts
1781
+ }
1782
+
1783
+ /**
1784
+ *
1785
+ * @param g
1786
+ * @param str
1787
+ * @param x
1788
+ * @param y
1789
+ * @param size
1790
+ */
1791
+ public void drawString(PGraphics g, String str, float x, float y, float size) {
1792
+ // 1) scale by the 1.0/unitsPerEm
1793
+ // 2) scale up by a font size
1794
+ g.pushMatrix();
1795
+ float s = size / face.unitsPerEm;
1796
+ //System.out.println("scale is " + s);
1797
+ // swap y coord at the same time, since fonts have y=0 at baseline
1798
+ g.translate(x, y);
1799
+ g.scale(s, -s);
1800
+ char[] c = str.toCharArray();
1801
+ for (int i = 0; i < c.length; i++) {
1802
+ // call draw on each char (pulling it w/ the unicode table)
1803
+ FontGlyph fg = unicodeGlyphs.get(Character.valueOf(c[i]));
1804
+ if (fg != null) {
1805
+ fg.draw(g);
1806
+ // add horizAdvX/unitsPerEm to the x coordinate along the way
1807
+ g.translate(fg.horizAdvX, 0);
1808
+ } else {
1809
+ System.err.println("'" + c[i] + "' not available.");
1810
+ }
1811
+ }
1812
+ g.popMatrix();
1813
+ }
1814
+
1815
+ /**
1816
+ *
1817
+ * @param g
1818
+ * @param c
1819
+ * @param x
1820
+ * @param y
1821
+ * @param size
1822
+ */
1823
+ public void drawChar(PGraphics g, char c, float x, float y, float size) {
1824
+ g.pushMatrix();
1825
+ float s = size / face.unitsPerEm;
1826
+ g.translate(x, y);
1827
+ g.scale(s, -s);
1828
+ FontGlyph fg = unicodeGlyphs.get(Character.valueOf(c));
1829
+ if (fg != null) g.shape(fg);
1830
+ g.popMatrix();
1831
+ }
1832
+
1833
+ /**
1834
+ *
1835
+ * @param str
1836
+ * @param size
1837
+ * @return
1838
+ */
1839
+ public float textWidth(String str, float size) {
1840
+ float w = 0;
1841
+ char[] c = str.toCharArray();
1842
+ for (int i = 0; i < c.length; i++) {
1843
+ // call draw on each char (pulling it w/ the unicode table)
1844
+ FontGlyph fg = unicodeGlyphs.get(Character.valueOf(c[i]));
1845
+ if (fg != null) {
1846
+ w += (float) fg.horizAdvX / face.unitsPerEm;
1847
+ }
1848
+ }
1849
+ return w * size;
1850
+ }
1851
+ }
1852
+
1853
+
1854
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1855
+
1856
+
1857
+ static class FontFace extends PShapeSVG {
1858
+ int horizOriginX; // dflt 0
1859
+ int horizOriginY; // dflt 0
1860
+ // int horizAdvX; // no dflt?
1861
+ int vertOriginX; // dflt horizAdvX/2
1862
+ int vertOriginY; // dflt ascent
1863
+ int vertAdvY; // dflt 1em (unitsPerEm value)
1864
+
1865
+ String fontFamily;
1866
+ int fontWeight; // can also be normal or bold (also comma separated)
1867
+ String fontStretch;
1868
+ int unitsPerEm; // dflt 1000
1869
+ int[] panose1; // dflt "0 0 0 0 0 0 0 0 0 0"
1870
+ int ascent;
1871
+ int descent;
1872
+ int[] bbox; // spec says comma separated, tho not w/ forge
1873
+ int underlineThickness;
1874
+ int underlinePosition;
1875
+ //String unicodeRange; // gonna ignore for now
1876
+
1877
+
1878
+ public FontFace(PShapeSVG parent, XML properties) {
1879
+ super(parent, properties, true);
1880
+
1881
+ unitsPerEm = properties.getInt("units-per-em", 1000);
1882
+ }
1883
+
1884
+
1885
+ protected void drawShape() {
1886
+ // nothing to draw in the font face attribute
1887
+ }
1888
+ }
1889
+
1890
+
1891
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1892
+
1893
+ /**
1894
+ *
1895
+ */
1896
+
1897
+
1898
+ public static class FontGlyph extends PShapeSVG { // extends Path
1899
+
1900
+ /**
1901
+ *
1902
+ */
1903
+ public String name;
1904
+ char unicode;
1905
+ int horizAdvX;
1906
+
1907
+ /**
1908
+ *
1909
+ * @param parent
1910
+ * @param properties
1911
+ * @param font
1912
+ */
1913
+ public FontGlyph(PShapeSVG parent, XML properties, Font font) {
1914
+ super(parent, properties, true);
1915
+ super.parsePath(); // ??
1916
+
1917
+ name = properties.getString("glyph-name");
1918
+ String u = properties.getString("unicode");
1919
+ unicode = 0;
1920
+ if (u != null) {
1921
+ if (u.length() == 1) {
1922
+ unicode = u.charAt(0);
1923
+ //System.out.println("unicode for " + name + " is " + u);
1924
+ } else {
1925
+ System.err.println("unicode for " + name +
1926
+ " is more than one char: " + u);
1927
+ }
1928
+ }
1929
+ if (properties.hasAttribute("horiz-adv-x")) {
1930
+ horizAdvX = properties.getInt("horiz-adv-x");
1931
+ } else {
1932
+ horizAdvX = font.horizAdvX;
1933
+ }
1934
+ }
1935
+
1936
+ /**
1937
+ *
1938
+ * @return
1939
+ */
1940
+ protected boolean isLegit() { // TODO need a better way to handle this...
1941
+ return vertexCount != 0;
1942
+ }
1943
+ }
1944
+
1945
+
1946
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1947
+
1948
+
1949
+ /**
1950
+ * Get a particular element based on its SVG ID. When editing SVG by hand,
1951
+ * this is the id="" tag on any SVG element. When editing from Illustrator,
1952
+ * these IDs can be edited by expanding the layers palette. The names used
1953
+ * in the layers palette, both for the layers or the shapes and groups
1954
+ * beneath them can be used here.
1955
+ * <PRE>
1956
+ * // This code grabs "Layer 3" and the shapes beneath it.
1957
+ * PShape layer3 = svg.getChild("Layer 3");
1958
+ * </PRE>
1959
+ * @param name
1960
+ * @return
1961
+ */
1962
+ @Override
1963
+ public PShape getChild(String name) {
1964
+ PShape found = super.getChild(name);
1965
+ if (found == null) {
1966
+ // Otherwise try with underscores instead of spaces
1967
+ // (this is how Illustrator handles spaces in the layer names).
1968
+ found = super.getChild(name.replace(' ', '_'));
1969
+ }
1970
+ // Set bounding box based on the parent bounding box
1971
+ if (found != null) {
1972
+ // found.x = this.x;
1973
+ // found.y = this.y;
1974
+ found.width = this.width;
1975
+ found.height = this.height;
1976
+ }
1977
+ return found;
1978
+ }
1979
+
1980
+
1981
+ /**
1982
+ * Prints out the SVG document. Useful for parsing.
1983
+ */
1984
+ public void print() {
1985
+ PApplet.println(element.toString());
1986
+ }
1987
+ }