propane 3.9.0-java → 3.10.0-java

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