]> mj.ucw.cz Git - leo.git/blob - sym-line.c
TODO
[leo.git] / sym-line.c
1 /*
2  *      Hic Est Leo -- Line and Area Symbolizer
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #include "leo.h"
8 #include "osm.h"
9 #include "sym.h"
10
11 #include <ucw/fastbuf.h>
12 #include <ucw/mempool.h>
13
14 #include <math.h>
15 #include <stdio.h>
16
17 static void sym_line_attrs(struct sym_line *l, struct svg *svg)
18 {
19   svg_set_attr(svg, "fill", "none");
20   svg_set_attr_color(svg, "stroke", l->color);
21   svg_set_attr_dimen(svg, "stroke-width", l->width);
22   if (l->opacity != 1)
23     svg_set_attr_float(svg, "stroke-opacity", l->opacity);
24
25   switch (l->line_cap)
26     {
27     case VALUE_NONE:
28       // This is the default (butt)
29       break;
30     case VALUE_ROUND:
31     case VALUE_SQUARE:
32       svg_set_attr(svg, "stroke-linecap", osm_val_decode(l->line_cap));
33       break;
34     default:
35       osm_obj_warn(l->s.o, "Unknown stroke-linecap: %s", osm_val_decode(l->line_cap));
36     }
37
38   switch (l->line_join)
39     {
40     case VALUE_MITER:
41       // This is the default
42       if (l->miter_limit != 4)
43         svg_set_attr_float(svg, "stroke-miterlimit", l->miter_limit);
44       break;
45     case VALUE_ROUND:
46     case VALUE_BEVEL:
47       svg_set_attr(svg, "stroke-linejoin", osm_val_decode(l->line_join));
48       break;
49     default:
50       osm_obj_warn(l->s.o, "Unknown stroke-linejoin: %s", osm_val_decode(l->line_join));
51     }
52
53   if (l->dash_pattern)
54     {
55       struct fastbuf *fb = svg_fb_open(svg);
56       for (uns i=0; i < GARY_SIZE(l->dash_pattern); i++)
57         {
58           if (i)
59             bputc(fb, ',');
60           // FIXME: This is dimension-sensitive
61           // FIXME: Also, inkscape doesn't handle units in dash lengths
62           bprintf(fb, "%.6g", l->dash_pattern[i]);
63         }
64       svg_fb_close_as_attr(svg, "stroke-dasharray");
65       if (l->dash_offset)
66         svg_set_attr_float(svg, "stroke-dashoffset", l->dash_offset);
67     }
68 }
69
70 static void append_node_list(struct svg *svg, clist *list)
71 {
72   struct osm_node *first = NULL;
73   OSM_FOR_EACH_BEGIN(struct osm_node *, n, *list)
74     {
75       if (!first)
76         {
77           first = n;
78           svg_path_move_to(svg, n->x, n->y);
79         }
80       else if (n == first)
81         svg_path_close(svg);
82       else
83         svg_path_line_to(svg, n->x, n->y);
84     }
85   OSM_FOR_EACH_END;
86 }
87
88 static void make_path(struct osm_object *o, struct svg *svg)
89 {
90   svg_push_path(svg);
91   switch (o->type)
92     {
93     case OSM_TYPE_WAY:
94       {
95         struct osm_way *w = (struct osm_way *) o;
96         append_node_list(svg, &w->nodes);
97         break;
98       }
99     case OSM_TYPE_MULTIPOLYGON:
100       {
101         struct osm_multipolygon *m = (struct osm_multipolygon *) o;
102         CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries)
103           append_node_list(svg, &b->nodes);
104         break;
105       }
106     default:
107       ASSERT(0);
108     }
109   svg_path_end(svg);
110 }
111
112 static void sym_line_draw(struct symbol *sym, struct svg *svg)
113 {
114   struct sym_line *l = (struct sym_line *) sym;
115   make_path(sym->o, svg);
116   sym_line_attrs(l, svg);
117   svg_pop(svg);
118 }
119
120 static void sym_line_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
121 {
122   if (o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON)
123     return;
124
125   struct sym_line *main_sl = NULL;
126
127   // Line and its casing are two similar objects
128   for (uns casing=0; casing<2; casing++)
129     {
130       double w;
131       if (!style_get_number(si, PROP_WIDTH + casing, &w))
132         continue;
133
134       struct sym_line *sl = sym_line_new(o);
135       sl->width = w;
136
137       if (casing)
138         {
139           if (!main_sl)
140             {
141               osm_obj_warn(o, "Casing around no line");
142               continue;
143             }
144           sl->width += main_sl->width;
145         }
146       else
147         main_sl = sl;
148
149       sl->color = 0x808080;
150       style_get_color(si, PROP_FILL_COLOR, &sl->color);
151       style_get_color(si, PROP_COLOR + casing, &sl->color);
152
153       sl->opacity = 1;
154       style_get_number(si, PROP_OPACITY + casing, &sl->opacity);
155
156       sl->line_cap = style_get_ident(si, PROP_LINECAP + casing) ? : VALUE_NONE;
157       sl->line_join = style_get_ident(si, PROP_LINEJOIN + casing) ? : VALUE_ROUND;
158       sl->miter_limit = 10;
159       style_get_number(si, PROP_MITERLIMIT + casing, &sl->miter_limit);
160
161       struct style_prop *dashes = style_get(si, PROP_DASHES + casing);
162       if (!dashes || dashes->type == PROP_TYPE_IDENT && dashes->val.id == VALUE_NONE)
163         ;
164       else if (dashes->type == PROP_TYPE_LIST)
165         {
166           GARY_INIT_ALLOC(sl->dash_pattern, 0, mp_get_allocator(sym_mp));
167           CLIST_FOR_EACH(struct style_val_list_entry *, e, *dashes->val.list)
168             {
169               if (e->val.type == PROP_TYPE_NUMBER)
170                 *GARY_PUSH(sl->dash_pattern) = e->val.val.number;
171               else
172                 {
173                   osm_obj_warn(o, "Invalid dash pattern");
174                   break;
175                 }
176             }
177           style_get_number(si, PROP_DASHES_OFFSET + casing, &sl->dash_offset);
178         }
179       else
180         osm_obj_warn(o, "Invalid dash pattern");
181
182       sym_plan(&sl->s, sym_zindex(o, si, casing ? 2 : 3));
183     }
184 }
185
186 struct symbolizer symbolizer_line = {
187   .name = "line",
188   .draw = sym_line_draw,
189   .gen = sym_line_gen,
190 };
191
192 struct sym_line *sym_line_new(struct osm_object *o)
193 {
194   return sym_new(SYMBOLIZER_LINE, o, sizeof(struct sym_line));
195 }
196
197 /*** Images along line ***/
198
199 static void lineimg_node_list(struct sym_lineimg *sli, clist *nodes, struct svg *svg)
200 {
201   double phase = sli->phase;
202   double step = sli->sir.width + sli->spacing;
203   struct osm_node *prev = NULL;
204   OSM_FOR_EACH_BEGIN(struct osm_node *, n, *nodes)
205     {
206       if (prev)
207         {
208           double dx = n->x - prev->x;
209           double dy = n->y - prev->y;
210           double len = hypot(dx, dy);
211           double dist = 0;
212           while (len - dist > 1e-10)
213             {
214               // FIXME: Rotate images along the path?
215               double next = MIN(len - dist, step - phase);
216               dist += next;
217               phase += next;
218               if (step - phase < 1e-10)
219                 {
220                   struct svg_icon_request sir = sli->sir;
221                   sir.x = prev->x + dx * dist / len;
222                   sir.y = prev->y + dy * dist / len;
223                   svg_icon_put(svg, &sir);
224                   phase -= step;
225                 }
226             }
227         }
228       prev = n;
229     }
230   OSM_FOR_EACH_END;
231 }
232
233 static void sym_lineimg_draw(struct symbol *sym, struct svg *svg)
234 {
235   struct sym_lineimg *li = (struct sym_lineimg *) sym;
236   struct osm_object *o = li->s.o;
237
238   switch (o->type)
239     {
240     case OSM_TYPE_WAY:
241       {
242         struct osm_way *w = (struct osm_way *) o;
243         lineimg_node_list(li, &w->nodes, svg);
244         break;
245       }
246     case OSM_TYPE_MULTIPOLYGON:
247       {
248         struct osm_multipolygon *m = (struct osm_multipolygon *) o;
249         CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries)
250           lineimg_node_list(li, &b->nodes, svg);
251         break;
252       }
253     default:
254       ASSERT(0);
255     }
256 }
257
258 static void sym_lineimg_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
259 {
260   if (o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON)
261     return;
262
263   osm_val_t icon_name = style_get_string(si, PROP_REPEAT_IMAGE);
264   if (!icon_name)
265     return;
266
267   struct sym_lineimg *sli = sym_lineimg_new(o);
268
269   struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(icon_name));
270   sli->sir.icon = icon;
271   sli->sir.width = icon->width;
272   sli->sir.height = icon->height;
273   style_scale(si, &sli->sir.width, &sli->sir.height, PROP_REPEAT_IMAGE_WIDTH, PROP_REPEAT_IMAGE_HEIGHT);
274
275   // FIXME: Better handling of defaults in style_get_number()
276 #if 0
277   // FIXME: align and offset are not supported yet
278   sli->align = style_get_ident(si, PROP_REPEAT_IMAGE_ALIGN);
279   sli->offset = 0;
280   style_get_number(si, PROP_REPEAT_IMAGE_OFFSET, &sli->offset);
281 #endif
282   sli->spacing = 0;
283   style_get_number(si, PROP_REPEAT_IMAGE_SPACING, &sli->spacing);
284   sli->phase = 0;
285   style_get_number(si, PROP_REPEAT_IMAGE_PHASE, &sli->phase);
286
287   sym_plan(&sli->s, sym_zindex(o, si, 3));
288 }
289
290 struct symbolizer symbolizer_lineimg = {
291   .name = "lineimg",
292   .draw = sym_lineimg_draw,
293   .gen = sym_lineimg_gen,
294 };
295
296 struct sym_lineimg *sym_lineimg_new(struct osm_object *o)
297 {
298   return sym_new(SYMBOLIZER_LINEIMG, o, sizeof(struct sym_lineimg));
299 }
300
301 /*** Areas ***/
302
303 static void sym_area_draw(struct symbol *sym, struct svg *svg)
304 {
305   struct sym_area *a = (struct sym_area *) sym;
306
307   for (int i=0; i<2; i++)
308     {
309       if (!i)
310         {
311           if (a->fill_color == COLOR_NONE)
312             continue;
313           make_path(sym->o, svg);
314           svg_set_attr_color(svg, "fill", a->fill_color);
315         }
316       else
317         {
318           if (!a->fill_pattern)
319             continue;
320           make_path(sym->o, svg);
321           svg_set_attr(svg, "fill", a->fill_pattern->paint_server);
322         }
323       if (a->fill_opacity != 1)
324         svg_set_attr_float(svg, "opacity", a->fill_opacity);
325       if (sym->o->type == OSM_TYPE_MULTIPOLYGON)
326         svg_set_attr(svg, "fill-rule", "evenodd");
327       svg_pop(svg);
328     }
329 }
330
331 static void sym_area_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
332 {
333   if (!(o->type == OSM_TYPE_WAY && osm_way_cyclic_p((struct osm_way *) o) ||
334         o->type == OSM_TYPE_MULTIPOLYGON))
335     return;
336
337   color_t color;
338   if (!style_get_color(si, PROP_FILL_COLOR, &color))
339     color = COLOR_NONE;
340
341   osm_val_t pattern = style_get_string(si, PROP_FILL_PATTERN);
342   struct svg_pattern *patt = NULL;
343   if (pattern)
344     {
345       struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(pattern));
346       struct svg_pattern_request spr = {
347         .icon = icon,
348         .width = icon->width,
349         .height = icon->height
350       };
351       style_scale(si, &spr.width, &spr.height, PROP_FILL_PATTERN_WIDTH, PROP_FILL_PATTERN_HEIGHT);
352       patt = svg_icon_to_pattern(svg, &spr);
353     }
354
355   if (color == COLOR_NONE && !patt)
356     return;
357
358   struct sym_area *sa = sym_area_new(o);
359   sa->fill_color = color;
360   sa->fill_pattern = patt;
361
362   sa->fill_opacity = 1;
363   style_get_number(si, PROP_FILL_OPACITY, &sa->fill_opacity);
364
365   sym_plan(&sa->s, sym_zindex(o, si, 1));
366 }
367
368 struct symbolizer symbolizer_area = {
369   .name = "area",
370   .draw = sym_area_draw,
371   .gen = sym_area_gen,
372 };
373
374 struct sym_area *sym_area_new(struct osm_object *o)
375 {
376   return sym_new(SYMBOLIZER_AREA, o, sizeof(struct sym_area));
377 }