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