1 /*
2 Copyright (C) 2012 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Cairo;
16
17 namespace BirdFont {
18
19 public class Svg {
20
21 /** Export to svg glyph data. */
22 public static string to_svg_glyph (Glyph g) {
23 StringBuilder svg = new StringBuilder ();
24 PathList stroke_list;
25
26 foreach (Path p in g.get_visible_paths ()) {
27 if (p.stroke == 0) {
28 write_path_as_glyph (p, svg, g);
29 } else {
30 stroke_list = p.get_completed_stroke ();
31 write_paths_as_glyph (stroke_list, svg, g);
32 }
33 }
34
35 return svg.str;
36 }
37
38 /** Export to svg-font data. */
39 public static string to_svg_path (Path pl, Glyph g) {
40 StringBuilder svg = new StringBuilder ();
41 pl.create_list ();
42 write_path (pl, svg, g, false);
43 return svg.str;
44 }
45
46 private static void write_paths_as_glyph (PathList pl, StringBuilder svg, Glyph g) {
47 foreach (Path p in pl.paths) {
48 write_path_as_glyph (p, svg, g);
49 }
50 }
51
52 private static void write_path_as_glyph (Path pl, StringBuilder svg, Glyph g) {
53 write_path (pl, svg, g, true);
54 }
55
56 private static void write_path (Path p, StringBuilder svg, Glyph g, bool do_glyph) {
57 int i = 0;
58 EditPoint? n = null;
59 EditPoint m;
60
61 if (p.points.size < 2) {
62 return;
63 }
64
65 p.create_list ();
66
67 foreach (var e in p.points) {
68 if (i == 0) {
69 add_abs_start (e, svg, g, do_glyph);
70 i++;
71 n = e;
72 continue;
73 }
74
75 m = (!) n;
76
77 add_abs_next (m, e, svg, g, do_glyph);
78
79 n = e;
80 i++;
81 }
82
83 if (!p.is_open ()) {
84 m = p.points.get (0);
85 add_abs_next ((!) n, m, svg, g, do_glyph);
86 close_path (svg);
87 }
88 }
89
90 private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph) {
91 if (start.right_handle.type == PointType.LINE_QUADRATIC) {
92 add_abs_line_to (start, end, svg, g, do_glyph);
93 } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) {
94 add_abs_line_to (start, end, svg, g, do_glyph);
95 } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) {
96 add_quadratic_abs_path (start, end, svg, g, do_glyph);
97 } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) {
98 add_double_quadratic_abs_path (start, end, svg, g, do_glyph);
99 } else {
100 add_cubic_abs_path (start, end, svg, g, do_glyph);
101 }
102 }
103
104 private static void add_abs_start (EditPoint ep, StringBuilder svg, Glyph g, bool to_glyph) {
105 double left = g.left_limit;
106 double baseline = -BirdFont.get_current_font ().base_line;
107 Font font = BirdFont.get_current_font ();
108 double height = font.top_limit - font.base_line;
109
110 svg.append_printf ("M");
111
112 if (!to_glyph) {
113 svg.append_printf ("%s ", round (ep.x - left));
114 svg.append_printf ("%s ", round (-ep.y + height));
115 } else {
116 svg.append_printf ("%s ", round (ep.x - left));
117 svg.append_printf ("%s ", round (ep.y + baseline));
118 }
119 }
120
121 private static void close_path (StringBuilder svg) {
122 svg.append ("z");
123 }
124
125 private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg, Glyph g, bool to_glyph) {
126 double baseline = -BirdFont.get_current_font ().base_line;
127 double left = g.left_limit;
128 Font font = BirdFont.get_current_font ();
129 double height = font.top_limit - font.base_line;
130
131
132 double xa, ya, xb, yb;
133
134 Path.get_line_points (start, stop, out xa, out ya, out xb, out yb);
135
136 double center_x = Glyph.xc ();
137 double center_y = Glyph.yc ();
138
139 svg.append ("L");
140
141 if (!to_glyph) {
142 svg.append_printf ("%s ", round (xb - center_x - left));
143 svg.append_printf ("%s ", round (yb - center_y + height));
144 } else {
145 svg.append_printf ("%s ", round (xb - center_x - left));
146 svg.append_printf ("%s ", round (-yb + center_y + baseline));
147 }
148 }
149
150 private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) {
151 EditPoint middle;
152 double x, y;
153
154 x = start.get_right_handle ().x + (end.get_left_handle ().x - start.get_right_handle ().x) / 2;
155 y = start.get_right_handle ().y + (end.get_left_handle ().y - start.get_right_handle ().y) / 2;
156
157 middle = new EditPoint (x, y, PointType.QUADRATIC);
158 middle.right_handle = end.get_left_handle ().copy ();
159
160 add_quadratic_abs_path (start, middle, svg, g, to_glyph);
161 add_quadratic_abs_path (middle, end, svg, g, to_glyph);
162 }
163
164 private static void add_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) {
165 double left = g.left_limit;
166 double baseline = -BirdFont.get_current_font ().base_line;
167 Font font = BirdFont.get_current_font ();
168 double height = font.top_limit - font.base_line;
169
170 double xa, ya, xb, yb, xc, yc, xd, yd;
171
172 Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);
173
174 double center_x = Glyph.xc ();
175 double center_y = Glyph.yc ();
176
177 // cubic path
178 if (!to_glyph) {
179 svg.append_printf ("Q");
180
181 svg.append_printf ("%s ", round (xb - center_x - left));
182 svg.append_printf ("%s ", round (yb - center_y + height));
183
184 svg.append_printf ("%s ", round (xd - center_x - left));
185 svg.append_printf ("%s ", round (yd - center_y + height));
186
187 } else {
188 svg.append_printf ("Q");
189
190 svg.append_printf ("%s ", round (xb - center_x - left));
191 svg.append_printf ("%s ", round (-yb + center_y + baseline));
192
193 svg.append_printf ("%s ", round (xd - center_x - left));
194 svg.append_printf ("%s ", round (-yd + center_y + baseline));
195 }
196 }
197
198 private static void add_cubic_abs_path (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool to_glyph) {
199 double left = g.left_limit;
200 double baseline = -BirdFont.get_current_font ().base_line;
201 Font font = BirdFont.get_current_font ();
202 double height = font.top_limit - font.base_line;
203
204 double xa, ya, xb, yb, xc, yc, xd, yd;
205
206 Path.get_bezier_points (start, end, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);
207
208 double center_x = Glyph.xc ();
209 double center_y = Glyph.yc ();
210
211 // cubic path
212 if (!to_glyph) {
213 svg.append_printf ("C");
214
215 svg.append_printf ("%s ", round (xb - center_x - left));
216 svg.append_printf ("%s ", round (yb - center_y + height));
217
218 svg.append_printf ("%s ", round (xc - center_x - left));
219 svg.append_printf ("%s ", round (yc - center_y + height));
220
221 svg.append_printf ("%s ", round (xd - center_x - left));
222 svg.append_printf ("%s ", round (yd - center_y + height));
223
224 } else {
225 svg.append_printf ("C");
226
227 svg.append_printf ("%s ", round (xb - center_x - left));
228 svg.append_printf ("%s ", round (-yb + center_y + baseline));
229
230 svg.append_printf ("%s ", round (xc - center_x - left));
231 svg.append_printf ("%s ", round (-yc + center_y + baseline));
232
233 svg.append_printf ("%s ", round (xd - center_x - left));
234 svg.append_printf ("%s ", round (-yd + center_y + baseline));
235 }
236 }
237
238 /** Draw path from svg font data. */
239 public static void draw_svg_path (Context cr, string svg, double x, double y) {
240 double x1, x2, x3;
241 double y1, y2, y3;
242 double px, py;
243 string[] d = svg.split (" ");
244
245 if (d.length == 0) {
246 return;
247 }
248
249 px = 0;
250 py = 0;
251
252 cr.save ();
253
254 cr.set_line_width (0);
255
256 if (svg == "") {
257 return;
258 }
259
260 for (int i = 0; i < d.length; i++) {
261
262 // trim off leading white space
263 while (d[i].index_of (" ") == 0) {
264 d[i] = d[i].substring (1); // FIXME: maybe no ascii
265 }
266
267 if (d[i].index_of ("L") == 0) {
268 x1 = double.parse (d[i].substring (1)) + x;
269 y1 = -double.parse (d[i+1]) + y;
270 cr.line_to (x1, y1);
271
272 px = x1;
273 py = y1;
274 continue;
275 }
276
277 if (d[i].index_of ("Q") == 0) {
278 x1 = double.parse (d[i].substring (1)) + x;
279 y1 = -double.parse (d[i+1]) + y;
280
281 x2 = double.parse (d[i+2]) + x;
282 y2 = -double.parse (d[i+3]) + y;
283
284 cr.curve_to ((px + 2 * x1) / 3, (py + 2 * y1) / 3, (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, x2, y2);
285
286 px = x2;
287 py = y2;
288 continue;
289 }
290
291 if (d[i].index_of ("C") == 0) {
292 x1 = double.parse (d[i].substring (1)) + x;
293 y1 = -double.parse (d[i+1]) + y;
294
295 x2 = double.parse (d[i+2]) + x;
296 y2 = -double.parse (d[i+3]) + y;
297
298 x3 = double.parse (d[i+4]) + x;
299 y3 = -double.parse (d[i+5]) + y;
300
301 cr.curve_to (x1, y1, x2, y2, x3, y3);
302
303 px = x3;
304 py = y3;
305 continue;
306 }
307
308 if (d[i].index_of ("M") == 0) {
309 x1 = double.parse (d[i].substring (1)) + x;
310 y1 = -double.parse (d[i+1]) + y;
311
312 cr.move_to (x1, y1);
313
314 px = x1;
315 py = y1;
316 continue;
317 }
318
319 if (d[i].index_of ("zM") == 0) {
320 cr.close_path ();
321
322 x1 = double.parse (d[i].substring (2)) + x;
323 y1 = -double.parse (d[i+1]) + y;
324
325 cr.move_to (x1, y1);
326
327 px = x1;
328 py = y1;
329 continue;
330 }
331
332 if (d[i].index_of ("z") == 0) {
333 cr.close_path ();
334 continue;
335 }
336
337 }
338
339 cr.fill ();
340 cr.restore ();
341 }
342
343 }
344
345 internal static string round (double p) {
346 string v = p.to_string ();
347 char[] c = new char [501];
348
349 v = p.format (c, "%3.15f");
350
351 if (v.index_of ("e") != -1) {
352 return "0.0";
353 }
354
355 return v;
356 }
357
358 }
359