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