propane 2.7.2-java → 2.8.0.pre-java

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