propane 3.4.1-java → 3.7.1-java

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