]> mj.ucw.cz Git - leo.git/blob - svg.c
TODO
[leo.git] / svg.c
1 /*
2  *      Hic Est Leo -- SVG Generator
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #include "leo.h"
8 #include "svg.h"
9
10 #include <ucw/gary.h>
11 #include <ucw/mempool.h>
12 #include <ucw-xml/xml.h>
13
14 #include <fcntl.h>
15 #include <stdarg.h>
16 #include <stdio.h>
17
18 static void svg_start_tag(struct svg *svg, struct svg_element *e);
19 static void svg_escape_string(struct svg *svg, const char *str);
20
21 struct svg *svg_open(char *filename)
22 {
23   struct mempool *mp = mp_new(4096);
24   struct svg *svg = mp_alloc_zero(mp, sizeof(*svg));
25
26   svg->pool = mp;
27   svg->fb = bopen_file(filename, O_WRONLY | O_CREAT | O_TRUNC, NULL);
28   svg->scale = 90 / 25.4;
29   // FIXME: Use scale for all operations with dimensions?
30   GARY_INIT(svg->stack, 0);
31
32   svg->fb_pool = mp_alloc_zero(svg->pool, sizeof(*svg->fb_pool));
33   fbpool_init(svg->fb_pool);
34
35   svg_icon_init(svg);
36
37   bputsn(svg->fb, "<?xml version=\"1.0\" standalone=\"no\"?>");
38   bputsn(svg->fb, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
39
40   svg_push_element(svg, "svg");
41   svg_set_attr(svg, "version", "1.1");
42   svg_set_attr(svg, "xmlns", "http://www.w3.org/2000/svg");
43   svg_set_attr(svg, "xmlns:xlink", "http://www.w3.org/1999/xlink");
44
45   return svg;
46 }
47
48 void svg_close(struct svg *svg)
49 {
50   ASSERT(GARY_SIZE(svg->stack) == 1);
51   svg_pop(svg);
52
53   bclose(svg->fb);
54   GARY_FREE(svg->stack);
55   svg_icon_cleanup(svg);
56   mp_delete(svg->pool);
57 }
58
59 static inline struct svg_element *svg_element_this_or_null(struct svg *svg)
60 {
61   uns n = GARY_SIZE(svg->stack);
62   return n ? svg->stack[n-1] : NULL;
63 }
64
65 static inline struct svg_element *svg_element_this(struct svg *svg)
66 {
67   struct svg_element *e = svg_element_this_or_null(svg);
68   ASSERT(e);
69   return e;
70 }
71
72 enum svg_indent_mode {
73   SVG_INDENT_OPEN = 1,
74   SVG_INDENT_CLOSE = 2,
75 };
76
77 static void svg_indent_start(struct svg *svg, struct svg_element *e, enum svg_indent_mode mode)
78 {
79   if (e->indent < 0 || (e->flags & SVG_EF_FLAT) && mode != SVG_INDENT_OPEN)
80     return;
81   for (int i=0; i<e->indent; i++)
82     bputc(svg->fb, '\t');
83 }
84
85 static void svg_indent_end(struct svg *svg, struct svg_element *e, enum svg_indent_mode mode)
86 {
87   if (e->indent < 0 || (e->flags & SVG_EF_FLAT) && mode != SVG_INDENT_CLOSE)
88     return;
89   bputc(svg->fb, '\n');
90 }
91
92 static struct svg_element *svg_push_internal(struct svg *svg, uns type)
93 {
94   ASSERT(!svg->str_fb);
95
96   struct svg_element *parent = svg_element_this_or_null(svg);
97   if (parent)
98     {
99       ASSERT(parent->type == XML_NODE_ELEM);
100       parent->flags |= SVG_EF_HAS_CHILDREN;
101       if (!(parent->flags & SVG_EF_START_TAG))
102         svg_start_tag(svg, parent);
103     }
104
105   mp_push(svg->pool);
106   struct svg_element *e = mp_alloc(svg->pool, sizeof(*e));
107   e->type = type;
108   e->flags = 0;
109   e->name = NULL;
110   clist_init(&e->attrs);
111   *GARY_PUSH(svg->stack) = e;
112
113   if (parent)
114     {
115       if (parent->indent < 0 || (parent->flags & SVG_EF_FLAT))
116         e->indent = -1;
117       else
118         e->indent = parent->indent + 1;
119     }
120   else
121     e->indent = 0;
122
123   return e;
124 }
125
126 struct svg_element *svg_push_element(struct svg *svg, const char *name)
127 {
128   struct svg_element *e = svg_push_internal(svg, XML_NODE_ELEM);
129   e->name = mp_strdup(svg->pool, name);
130   return e;
131 }
132
133 struct svg_element *svg_push_chars(struct svg *svg)
134 {
135   return svg_push_internal(svg, XML_NODE_CHARS);
136 }
137
138 struct svg_element *svg_push_comment(struct svg *svg)
139 {
140   return svg_push_internal(svg, XML_NODE_COMMENT);
141 }
142
143 void svg_pop(struct svg *svg)
144 {
145   struct svg_element *e = svg_element_this(svg);
146   ASSERT(!svg->str_fb);
147
148   switch (e->type)
149     {
150     case XML_NODE_ELEM:
151       if (!(e->flags & SVG_EF_START_TAG))
152         {
153           // Will recognize that the element has no children and an auto-closing tag
154           svg_start_tag(svg, e);
155         }
156       else
157         {
158           svg_indent_start(svg, e, SVG_INDENT_CLOSE);
159           bprintf(svg->fb, "</%s>", e->name);
160           svg_indent_end(svg, e, SVG_INDENT_CLOSE);
161         }
162       break;
163     case XML_NODE_CHARS:
164       ASSERT(e->name);
165       svg_indent_start(svg, e, SVG_INDENT_OPEN);
166       svg_escape_string(svg, e->name);
167       svg_indent_end(svg, e, SVG_INDENT_CLOSE);
168       break;
169     case XML_NODE_COMMENT:
170       ASSERT(e->name);
171       svg_indent_start(svg, e, SVG_INDENT_OPEN);
172       bputs(svg->fb, "<!-- ");
173       bputs(svg->fb, e->name);
174       bputs(svg->fb, " -->");
175       svg_indent_end(svg, e, SVG_INDENT_CLOSE);
176       break;
177     default:
178       ASSERT(0);
179     }
180
181   mp_pop(svg->pool);
182   GARY_POP(svg->stack);
183 }
184
185 static void svg_escape_string(struct svg *svg, const char *str)
186 {
187   for (const char *c = str; *c; c++)
188     switch (*c)
189       {
190       case '"':
191         bputs(svg->fb, "&quot;");
192         break;
193       case '\'':
194         bputs(svg->fb, "&apos;");
195         break;
196       case '<':
197         bputs(svg->fb, "&lt;");
198         break;
199       case '>':
200         bputs(svg->fb, "&gt;");
201         break;
202       case '&':
203         bputs(svg->fb, "&amp;");
204         break;
205       default:
206         bputc(svg->fb, *c);
207       }
208 }
209
210 static void svg_start_tag(struct svg *svg, struct svg_element *e)
211 {
212   ASSERT(!(e->flags & SVG_EF_START_TAG));
213   e->flags |= SVG_EF_START_TAG;
214
215   svg_indent_start(svg, e, SVG_INDENT_OPEN);
216   bputc(svg->fb, '<');
217   bputs(svg->fb, e->name);
218
219   CLIST_FOR_EACH(struct svg_attr *, a, e->attrs)
220     {
221       bputc(svg->fb, ' ');
222       bputs(svg->fb, a->key);
223       bputs(svg->fb, "=\"");
224       svg_escape_string(svg, a->val);
225       bputc(svg->fb, '"');
226     }
227
228   if (e->flags & SVG_EF_HAS_CHILDREN)
229     {
230       bputc(svg->fb, '>');
231       svg_indent_end(svg, e, SVG_INDENT_OPEN);
232     }
233   else
234     {
235       bputs(svg->fb, " />");
236       svg_indent_end(svg, e, SVG_INDENT_CLOSE);
237     }
238 }
239
240 void svg_set_attr_ref(struct svg *svg, const char *key, const char *val)
241 {
242   struct svg_element *e = svg_element_this(svg);
243   ASSERT(e->type == XML_NODE_ELEM);
244   ASSERT(!(e->flags & SVG_EF_START_TAG));
245   ASSERT(!svg->str_fb);
246
247   CLIST_FOR_EACH(struct svg_attr *, a, e->attrs)
248     if (!strcmp(a->key, key))
249       {
250         a->val = val;
251         return;
252       }
253
254   struct svg_attr *a = mp_alloc(svg->pool, sizeof(*a));
255   a->key = mp_strdup(svg->pool, key);
256   a->val = val;
257   clist_add_tail(&e->attrs, &a->n);
258 }
259
260 void svg_set_attr(struct svg *svg, const char *key, const char *val)
261 {
262   svg_set_attr_ref(svg, key, mp_strdup(svg->pool, val));
263 }
264
265 void svg_set_attr_int(struct svg *svg, const char *key, int val)
266 {
267   svg_set_attr_ref(svg, key, mp_printf(svg->pool, "%d", val));
268 }
269
270 void svg_set_attr_float(struct svg *svg, const char *key, double val)
271 {
272   svg_set_attr_ref(svg, key, mp_printf(svg->pool, "%.6g", val));
273 }
274
275 char *svg_format_dimen(struct svg *svg, double val)
276 {
277   return mp_printf(svg->pool, "%.6g", val * svg->scale);
278 }
279
280 void svg_set_attr_dimen(struct svg *svg, const char *key, double val)
281 {
282   svg_set_attr_ref(svg, key, svg_format_dimen(svg, val));
283 }
284
285 void svg_set_attr_color(struct svg *svg, const char *key, color_t color)
286 {
287   svg_set_attr_ref(svg, key, mp_printf(svg->pool, "#%06x", color));
288 }
289
290 void svg_set_attr_format(struct svg *svg, const char *key, const char *fmt, ...)
291 {
292   va_list args;
293   va_start(args, fmt);
294   svg_set_attr_ref(svg, key, mp_vprintf(svg->pool, fmt, args));
295   va_end(args);
296 }
297
298 struct fastbuf *svg_fb_open(struct svg *svg)
299 {
300   ASSERT(!svg->str_fb);
301   fbpool_start(svg->fb_pool, svg->pool, 0);
302   svg->str_fb = (struct fastbuf *) svg->fb_pool;
303   return svg->str_fb;
304 }
305
306 const char *svg_fb_close(struct svg *svg)
307 {
308   ASSERT(svg->str_fb);
309   bputc(svg->str_fb, 0);
310   const char *str = fbpool_end(svg->fb_pool);
311   svg->str_fb = NULL;
312   return str;
313 }
314
315 void svg_fb_close_as_attr(struct svg *svg, const char *key)
316 {
317   const char *val = svg_fb_close(svg);
318   svg_set_attr_ref(svg, key, val);
319 }
320
321 struct svg_element *svg_push_path(struct svg *svg)
322 {
323   struct svg_element *e = svg_push_element(svg, "path");
324   svg_fb_open(svg);
325
326   // Construct the path, then call svg_path_end() and finally svg_pop()
327   // CAVEAT: Do not set attributes before calling svg_path_end()
328   return e;
329 }
330
331 void svg_path_end(struct svg *svg)
332 {
333   svg_fb_close_as_attr(svg, "d");
334 }
335
336 static void svg_path_printf(struct svg *svg, const char *fmt, ...)
337 {
338   va_list args;
339   va_start(args, fmt);
340   if (btell(svg->str_fb))
341     bputc(svg->str_fb, ' ');
342   vbprintf(svg->str_fb, fmt, args);
343   va_end(args);
344 }
345
346 void svg_path_move_to(struct svg *svg, double x, double y)
347 {
348   svg_path_printf(svg, "M %.6g %.6g", x * svg->scale, y * svg->scale);
349 }
350
351 void svg_path_move_to_rel(struct svg *svg, double x, double y)
352 {
353   svg_path_printf(svg, "m %.6g %.6g", x * svg->scale, y * svg->scale);
354 }
355
356 void svg_path_line_to(struct svg *svg, double x, double y)
357 {
358   svg_path_printf(svg, "L %.6g %.6g", x * svg->scale, y * svg->scale);
359 }
360
361 void svg_path_line_to_rel(struct svg *svg, double x, double y)
362 {
363   svg_path_printf(svg, "l %.6g %.6g", x * svg->scale, y * svg->scale);
364 }
365
366 void svg_path_close(struct svg *svg)
367 {
368   svg_path_printf(svg, "Z");
369 }