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