]> mj.ucw.cz Git - leo.git/blob - sym-text.c
Labelling: randdouble implementation
[leo.git] / sym-text.c
1 /*
2  *      Hic Est Leo -- Text Symbolizer
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/stkstring.h>
9
10 #include <math.h>
11 #include <stdio.h>
12 #include <ft2build.h>
13 #include FT_FREETYPE_H
14 #include <pango/pangoft2.h>
15
16 #include "leo.h"
17 #include "osm.h"
18 #include "sym.h"
19 #include "map.h"
20
21 #include "labeller.h"
22
23 /*** Fonts ***/
24
25 struct text_font {
26   const char *family;
27   double size;
28   osm_val_t weight;
29   osm_val_t style;
30   PangoFontDescription *pango_font_desc;
31   char key[1];
32 };
33
34 #define HASH_NODE struct text_font
35 #define HASH_PREFIX(x) text_font_##x
36 #define HASH_KEY_ENDSTRING key
37 #define HASH_WANT_LOOKUP
38 #define HASH_LOOKUP_DETECT_NEW
39 #define HASH_USE_POOL sym_mp
40 #include <ucw/hashtable.h>
41
42 static PangoFontMap *pango_font_map;
43 static PangoContext *pango_context;
44
45 static double pt_to_mm(double pt)
46 {
47   return pt / 72 * 25.4;
48 }
49
50 static double mm_to_pt(double mm)
51 {
52   return mm / 25.4 * 72;
53 }
54
55 static double pango_to_mm(double pango)
56 {
57   return pt_to_mm(pango / PANGO_SCALE);
58 }
59
60 static double mm_to_pango(double mm)
61 {
62   return mm_to_pt(mm) * PANGO_SCALE;
63 }
64
65 /*
66  * Pango tries to do pixel-based optimizations in low resolutions.
67  * Work around it by scaling everything up.
68  */
69 #define FONT_HACK_FACTOR 30
70
71 static struct text_font *font_get(struct text_font *req)
72 {
73   char *key = stk_printf("%s:%.6g:%u:%u", req->family, req->size, req->weight, req->style);
74   int is_new = 0;
75   struct text_font *font = text_font_lookup(key, &is_new);
76   if (is_new)
77     {
78       msg(L_DEBUG, "Loading font %s (size %.6g, weight %s, style %s)", req->family, req->size, osm_val_decode(req->weight), osm_val_decode(req->style));
79       font->family = mp_strdup(sym_mp, req->family);
80       font->size = req->size;
81       font->weight = req->weight;
82       font->style = req->style;
83
84       PangoFontDescription *desc;
85       desc = pango_font_description_new();
86       ASSERT(desc);
87       pango_font_description_set_family(desc, font->family);
88       if (font->weight != VALUE_NORMAL)
89         pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
90       if (font->style != VALUE_NORMAL)
91         pango_font_description_set_style(desc, PANGO_STYLE_ITALIC);
92       pango_font_description_set_size(desc, mm_to_pango(font->size));
93       font->pango_font_desc = desc;
94
95 #if 1
96       // FIXME
97       PangoFont *pfont = pango_font_map_load_font(pango_font_map, pango_context, desc);
98       ASSERT(pfont);
99       PangoFontDescription *d2 = pango_font_describe(pfont);
100       ASSERT(d2);
101       msg(L_DEBUG, "Font desc: %s", pango_font_description_to_string(d2));
102
103       PangoFontMetrics *fm = pango_font_get_metrics(pfont, NULL);
104       ASSERT(fm);
105       msg(L_DEBUG, "Font metrics: asc=%.6g desc=%.6g", pango_to_mm(pango_font_metrics_get_ascent(fm)), pango_to_mm(pango_font_metrics_get_descent(fm)));
106 #endif
107     }
108   return font;
109 }
110
111 static void font_init(void)
112 {
113   text_font_init();
114
115   pango_font_map = pango_ft2_font_map_new();
116   ASSERT(pango_font_map);
117   pango_ft2_font_map_set_resolution((PangoFT2FontMap *) pango_font_map, 72 * FONT_HACK_FACTOR, 72 * FONT_HACK_FACTOR);
118   pango_context = pango_font_map_create_context(PANGO_FONT_MAP(pango_font_map));
119   ASSERT(pango_context);
120 }
121
122 static void text_size(struct sym_text *st)
123 {
124   PangoLayout *layout = pango_layout_new(pango_context);
125   pango_layout_set_font_description(layout, st->font->pango_font_desc);
126   pango_layout_set_text(layout, osm_val_decode(st->text), -1);
127   pango_layout_context_changed(layout);
128
129   PangoRectangle ext;
130   pango_layout_get_extents(layout, NULL, &ext);
131   // st->tx = pango_to_mm(ext.x) / FONT_HACK_FACTOR;
132   // st->ty = pango_to_mm(ext.y) / FONT_HACK_FACTOR;
133   st->tw = pango_to_mm(ext.width) / FONT_HACK_FACTOR;
134   st->th = pango_to_mm(pango_layout_get_baseline(layout)) / FONT_HACK_FACTOR;
135   st->td = pango_to_mm(ext.height) / FONT_HACK_FACTOR - st->th;
136
137   g_object_unref(layout);
138 }
139
140 /*** Elimination of duplicate texts ***/
141
142 // FIXME: Get rid of globals
143 #define DUP_TILE_SIZE 10
144 static int dup_tiles_w, dup_tiles_h;
145 static struct sym_text **dup_tiles;
146
147 static void text_dup_init(void)
148 {
149   dup_tiles_w = 1 + page_map_width / DUP_TILE_SIZE;
150   dup_tiles_h = 1 + page_map_height / DUP_TILE_SIZE;
151   dup_tiles = xmalloc_zero(sizeof(struct sym_text *) * dup_tiles_w * dup_tiles_h);
152   msg(L_DEBUG, "Allocated text tiles: %u x %u", dup_tiles_w, dup_tiles_h);
153 }
154
155 static int text_dup_coord(int x, int y)
156 {
157   ASSERT(x >= 0 && x < dup_tiles_w && y >= 0 && y < dup_tiles_h);
158   return x + y * dup_tiles_w;
159 }
160
161 static double text_quad_dist(struct sym_text *a, struct sym_text *b)
162 {
163   double dx = a->x - b->x;
164   double dy = a->y - b->y;
165   return dx*dx + dy*dy;
166 }
167
168 static bool text_dup_detect(struct sym_text *t, struct style_info *si)
169 {
170   // Out-of-frame texts are dropped immediately
171   double x = t->x - page_offset_x;
172   double y = t->y - page_offset_y;
173   if (x < 0 || x >= page_map_width ||
174       y < 0 || y >= page_map_height)
175         return 0;
176
177   int tile_x = x / DUP_TILE_SIZE;
178   int tile_y = y / DUP_TILE_SIZE;
179
180   double radius = 0;
181   style_get_number(si, PROP_TEXT_DUP_THRESHOLD, &radius);
182   if (radius)
183     {
184       // A rather simple-minded algorithm, but believed to be efficient enough.
185       int r_tiles = 1 + radius / DUP_TILE_SIZE;
186       int x_start = MAX(0, tile_x - r_tiles);
187       int x_stop = MIN(dup_tiles_w - 1, tile_x + r_tiles);
188       int y_start = MAX(0, tile_y - r_tiles);
189       int y_stop = MIN(dup_tiles_h - 1, tile_y + r_tiles);
190       for (int x = x_start; x <= x_stop; x++)
191         for (int y = y_start; y <= y_stop; y++)
192           {
193             for (struct sym_text *d = dup_tiles[text_dup_coord(x, y)]; d; d = d->next_in_tile)
194               if (d->text == t->text &&
195                   text_quad_dist(d, t) <= radius*radius &&
196                   d->text_color == t->text_color &&
197                   d->font == t->font)
198                 {
199                   t->next_duplicate = d->next_duplicate;
200                   d->next_duplicate = t;
201                   return 0;
202                 }
203           }
204     }
205
206   int tile_i = text_dup_coord(tile_x, tile_y);
207   t->next_in_tile = dup_tiles[tile_i];
208   dup_tiles[tile_i] = t;
209   return 1;
210 }
211
212 /*** Core of the symbolizer ***/
213
214 static void prepare_text_element(struct sym_text *t, struct svg *svg)
215 {
216   struct text_font *font = t->font;
217   svg_push_element(svg, "text");
218   svg_set_attr_dimen(svg, "x", t->x);
219   svg_set_attr_dimen(svg, "y", t->y);
220   svg_set_attr(svg, "font-family", font->family);
221   svg_set_attr_dimen(svg, "font-size", font->size);
222   if (font->weight != VALUE_NORMAL)
223     svg_set_attr(svg, "font-weight", osm_val_decode(font->weight));
224   if (font->style != VALUE_NORMAL)
225     svg_set_attr(svg, "font-style", osm_val_decode(font->style));
226 }
227
228 static void sym_text_draw(struct symbol *sym, struct svg *svg)
229 {
230   struct sym_text *t = (struct sym_text *) sym;
231 printf("Drawing %s at [%.2f; %.2f]\n", osm_val_decode(t->text), t->x, t->y);
232
233   if (t->next_duplicate)
234     {
235       // If there is a cluster of duplicate texts, average their positions
236       double sx = 0, sy = 0;
237       uns nn = 0;
238       for (struct sym_text *u = t; u; u = u->next_duplicate)
239         {
240           sx += u->x;
241           sy += u->y;
242           nn++;
243         }
244       t->x = sx / nn;
245       t->y = sy / nn;
246     }
247
248   bool use_group = 0;
249   bool want_rotate = (fabs(t->rotate) > 1e-5);
250   if (t->opacity != 1 || want_rotate)
251     {
252       use_group = 1;
253       svg_push_element(svg, "g");
254       if (t->opacity != 1)
255         svg_set_attr_float(svg, "opacity", t->opacity);
256       if (want_rotate)
257         svg_set_attr_format(svg, "transform", "rotate(%.2f %s %s)", -t->rotate, svg_format_dimen(svg, t->x), svg_format_dimen(svg, t->y));
258     }
259
260   if (t->halo_radius)
261     {
262       prepare_text_element(t, svg);
263       svg_set_attr(svg, "fill", "none");
264       svg_set_attr_color(svg, "stroke", t->halo_color);
265       svg_set_attr_dimen(svg, "stroke-width", t->halo_radius);
266       svg_set_attr(svg, "stroke-linecap", "round");
267       svg_set_attr(svg, "stroke-linejoin", "round");
268       if (t->halo_opacity != 1)
269         svg_set_attr_float(svg, "stroke-opacity", t->halo_opacity);
270       svg_push_chars(svg)->name = osm_val_decode(t->text);
271       svg_pop(svg);
272       svg_pop(svg);
273     }
274
275   prepare_text_element(t, svg);
276   svg_set_attr_color(svg, "fill", t->text_color);
277   svg_set_attr(svg, "stroke", "none");
278   svg_push_chars(svg)->name = osm_val_decode(t->text);
279   svg_pop(svg);
280   svg_pop(svg);
281
282 #if 0
283   // Draw bounding box for debugging
284   svg_push_element(svg, "rect");
285   svg_set_attr(svg, "fill", "none");
286   svg_set_attr_color(svg, "stroke", 0x0000ff);
287   svg_set_attr_dimen(svg, "stroke-width", 0.2);
288   svg_set_attr_dimen(svg, "x", t->x);
289   svg_set_attr_dimen(svg, "y", t->y - t->th);
290   svg_set_attr_dimen(svg, "width", t->tw);
291   svg_set_attr_dimen(svg, "height", t->th + t->td);
292   svg_pop(svg);
293
294   svg_push_element(svg, "line");
295   svg_set_attr(svg, "fill", "none");
296   svg_set_attr_color(svg, "stroke", 0x0000ff);
297   svg_set_attr_dimen(svg, "stroke-width", 0.2);
298   svg_set_attr_dimen(svg, "x1", t->x);
299   svg_set_attr_dimen(svg, "y1", t->y);
300   svg_set_attr_dimen(svg, "x2", t->x + t->tw);
301   svg_set_attr_dimen(svg, "y2", t->y);
302   svg_pop(svg);
303 #endif
304
305   if (use_group)
306     svg_pop(svg);
307 }
308
309 static osm_val_t get_text(struct osm_object *o, struct style_info *si)
310 {
311   struct style_prop *prop = style_get_and_check(si, PROP_TEXT, (1 << PROP_TYPE_STRING) | (1 << PROP_TYPE_IDENT));
312   if (!prop)
313     return 0;
314
315   if (prop->type == PROP_TYPE_IDENT && prop->val.id == VALUE_AUTO)
316     {
317       static const osm_key_t auto_text_keys[] = {
318         KEY_NAME_CZ,            // FIXME: This should be configurable
319         KEY_NAME,
320         KEY_REF,
321         KEY_OPERATOR,
322         KEY_BRAND,
323         KEY_ADDR_HOUSENUMBER,
324       };
325       for (uns i=0; i < ARRAY_SIZE(auto_text_keys); i++)
326         {
327           osm_val_t val = osm_obj_find_tag(o, auto_text_keys[i]);
328           if (val)
329             return val;
330         }
331       return 0;
332     }
333
334   return osm_obj_find_tag(o, osm_key_encode(osm_val_decode(prop->val.id)));
335 }
336
337 static void get_text_attrs(struct sym_text *st, struct style_info *si)
338 {
339   struct osm_object *o = st->s.o;
340   if (o->type == OSM_TYPE_WAY && osm_way_cyclic_p((struct osm_way *) o))
341     st->text_color = 0xc0c0c0;  // FIXME: This is an ugly hack, do we need it?
342   else
343     st->text_color = 0xffffff;
344   style_get_color(si, PROP_TEXT_COLOR, &st->text_color);
345
346   struct text_font f = {
347     .family = "Helvetica",
348     .size = pt_to_mm(8),
349   };
350   osm_val_t fam = style_get_string(si, PROP_FONT_FAMILY);
351   if (fam)
352     f.family = osm_val_decode(fam);
353   style_get_number(si, PROP_FONT_SIZE, &f.size);
354   f.weight = style_get_ident(si, PROP_FONT_WEIGHT);
355   if (!f.weight)
356     f.weight = VALUE_NORMAL;
357   if (f.weight != VALUE_NORMAL && f.weight != VALUE_BOLD)
358     {
359       osm_obj_warn(o, "Unknown font-weight %s", osm_val_decode(f.weight));
360       f.weight = VALUE_NORMAL;
361     }
362   f.style = style_get_ident(si, PROP_FONT_STYLE);
363   if (!f.style)
364     f.style = VALUE_NORMAL;
365   if (f.style != VALUE_NORMAL && f.style != VALUE_ITALIC)
366     {
367       osm_obj_warn(o, "Unknown font-style %s", osm_val_decode(f.weight));
368       f.style = VALUE_NORMAL;
369     }
370   st->font = font_get(&f);
371
372   st->opacity = 1;
373   style_get_number(si, PROP_TEXT_OPACITY, &st->opacity);
374
375   st->halo_color = st->text_color ^ 0xffffff;
376   style_get_color(si, PROP_TEXT_HALO_COLOR, &st->halo_color);
377   st->halo_radius = 0;
378   style_get_number(si, PROP_TEXT_HALO_RADIUS, &st->halo_radius);
379   st->halo_opacity = 1;
380   style_get_number(si, PROP_TEXT_HALO_OPACITY, &st->halo_opacity);
381
382   double dx = 0, dy = 0;
383   style_get_number(si, PROP_TEXT_OFFSET_X, &dx);
384   style_get_number(si, PROP_TEXT_OFFSET_Y, &dy);
385   style_get_number(si, PROP_TEXT_OFFSET, &dy);
386   st->x += dx;
387   st->y -= dy;
388 }
389
390 static void text_fix_placement(struct sym_text *st)
391 {
392   // Fix texts which do not fit on the paper
393   st->x = MIN(st->x, page_offset_x + page_map_width - st->tw);
394   st->x = MAX(st->x, page_offset_x);
395   st->y = MIN(st->y, page_offset_y + page_map_height - st->th);
396   st->y = MAX(st->y, page_offset_y + st->td);
397 }
398
399 static void sym_text_node(struct osm_object *o, struct style_info *si, osm_val_t text)
400 {
401   struct osm_node *n = (struct osm_node *) o;
402
403   struct sym_text *st = sym_text_new(o);
404   st->text = text;
405   st->x = n->x;
406   st->y = n->y;
407
408   get_text_attrs(st, si);
409   text_size(st);
410
411   osm_val_t ah = style_get_ident(si, PROP_TEXT_ANCHOR_HORIZONTAL);
412   switch (ah)
413     {
414     case VALUE_LEFT:
415       st->x -= st->tw;
416       break;
417     case VALUE_CENTER:
418       st->x -= st->tw / 2;
419       break;
420     case 0:
421     case VALUE_RIGHT:
422       break;
423     default:
424       osm_obj_warn(o, "Unknown text-anchor-horizontal: %s", osm_val_decode(ah));
425     }
426
427   osm_val_t av = style_get_ident(si, PROP_TEXT_ANCHOR_VERTICAL);
428   switch (av)
429     {
430     case VALUE_ABOVE:           // FIXME: What's the difference between above and top?
431     case VALUE_TOP:
432       st->y -= st->td;
433       break;
434     case VALUE_CENTER:
435       st->y -= (st->th + st->td) / 2;
436       // Fall thru
437     case 0:
438     case VALUE_BOTTOM:
439     case VALUE_BELOW:
440       st->y += st->th;
441       break;
442     default:
443       osm_obj_warn(o, "Unknown text-anchor-vertical: %s", osm_val_decode(av));
444     }
445
446   text_fix_placement(st);
447   if (!text_dup_detect(st, si))
448     {
449       msg(L_DEBUG, "Text <%s> dropped as duplicate", osm_val_decode(text));
450       return;
451     }
452
453   //sym_plan(&st->s, sym_zindex(o, si, 5));
454 }
455
456 static void sym_text_center(struct osm_object *o, struct style_info *si, osm_val_t text, double x, double y)
457 {
458   struct sym_text *st = sym_text_new(o);
459   st->text = text;
460   st->x = x;
461   st->y = y;
462
463   get_text_attrs(st, si);
464   text_size(st);
465   st->x -= st->tw / 2;
466   st->y += st->th - (st->th + st->td) / 2;
467   text_fix_placement(st);
468   if (o->type == OSM_TYPE_WAY && !osm_way_cyclic_p((struct osm_way *) o))
469   {
470     //sym_plan(&st->s, sym_zindex(o, si, 4.9));
471     printf("[Sym] Labelling way %ju with %s\n", o->id, osm_val_decode(st->text));
472     labeller_add_linelabel(&st->s, o, sym_zindex(o, si, 4.9));
473   }
474   else
475   {
476     //sym_plan(&st->s, sym_zindex(o, si, 4.9));
477     printf("[Sym] Labelling area %ju with %s\n", o->id, osm_val_decode(st->text));
478     labeller_add_arealabel(&st->s, o, sym_zindex(o, si, 4.9));
479   }
480 }
481
482 static void sym_text_way(struct osm_object *o, struct style_info *si, osm_val_t text)
483 {
484   double x, y;
485   osm_val_t tp = style_get_ident(si, PROP_TEXT_POSITION);
486
487   switch (tp)
488     {
489     case VALUE_CENTER:
490       if (osm_obj_center(o, &x, &y))
491         sym_text_center(o, si, text, x, y);
492       break;
493     case VALUE_LINE:
494       // FIXME
495     default:
496       osm_obj_warn(o, "Unknown text-position: %s", osm_val_decode(tp));
497     }
498 }
499
500 static void sym_text_mpg(struct osm_object *o, struct style_info *si, osm_val_t text)
501 {
502   double x, y;
503   osm_val_t tp = style_get_ident(si, PROP_TEXT_POSITION);
504
505   switch (tp)
506     {
507     case VALUE_CENTER:
508       if (osm_obj_center(o, &x, &y))
509         sym_text_center(o, si, text, x, y);
510       break;
511     case VALUE_LINE:
512       // FIXME
513     default:
514       osm_obj_warn(o, "Unknown text-position: %s", osm_val_decode(tp));
515     }
516 }
517
518 static void sym_text_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
519 {
520   osm_val_t text = get_text(o, si);
521   if (!text)
522     return;
523
524   switch (o->type)
525     {
526     case OSM_TYPE_NODE:
527       sym_text_node(o, si, text);
528       break;
529     case OSM_TYPE_WAY:
530       sym_text_way(o, si, text);
531       break;
532     case OSM_TYPE_MULTIPOLYGON:
533       sym_text_mpg(o, si, text);
534       break;
535     default:
536       osm_obj_warn(o, "Text symbolizer does not support this object type");
537       return;
538     }
539 }
540
541 static void sym_text_init(void)
542 {
543   font_init();
544   text_dup_init();
545 }
546
547 struct symbolizer symbolizer_text = {
548   .name = "text",
549   .draw = sym_text_draw,
550   .gen = sym_text_gen,
551   .init = sym_text_init,
552 };
553
554 struct sym_text *sym_text_new(struct osm_object *o)
555 {
556   return sym_new(SYMBOLIZER_TEXT, o, sizeof(struct sym_text));
557 }
558
559 // FIXME: Hack
560 void scale_text(struct svg *svg, double x, double y, osm_val_t text)
561 {
562   struct sym_text *st = sym_text_new(NULL);
563
564   struct text_font f = {
565     .family = "Times",
566     .weight = VALUE_NORMAL,
567     .style = VALUE_NORMAL,
568     .size = pt_to_mm(10),
569   };
570
571   st->text = text;
572   st->text_color = 0;
573   st->x = x;
574   st->y = y;
575   st->font = font_get(&f);
576   st->opacity = 1;
577   st->halo_color = 0xffffff;
578   st->halo_radius = 0.8;
579   st->halo_opacity = 1;
580   text_size(st);
581   st->x -= st->tw / 2;
582   sym_text_draw(&st->s, svg);
583 }