2 * Hic Est Leo -- Text Symbolizer
4 * (c) 2014 Martin Mares <mj@ucw.cz>
8 #include <ucw/stkstring.h>
13 #include FT_FREETYPE_H
14 #include <pango/pangoft2.h>
30 PangoFontDescription *pango_font_desc;
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>
42 static PangoFontMap *pango_font_map;
43 static PangoContext *pango_context;
45 static double pt_to_mm(double pt)
47 return pt / 72 * 25.4;
50 static double mm_to_pt(double mm)
52 return mm / 25.4 * 72;
55 static double pango_to_mm(double pango)
57 return pt_to_mm(pango / PANGO_SCALE);
60 static double mm_to_pango(double mm)
62 return mm_to_pt(mm) * PANGO_SCALE;
66 * Pango tries to do pixel-based optimizations in low resolutions.
67 * Work around it by scaling everything up.
69 #define FONT_HACK_FACTOR 30
71 static struct text_font *font_get(struct text_font *req)
73 char *key = stk_printf("%s:%.6g:%u:%u", req->family, req->size, req->weight, req->style);
75 struct text_font *font = text_font_lookup(key, &is_new);
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;
84 PangoFontDescription *desc;
85 desc = pango_font_description_new();
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;
97 PangoFont *pfont = pango_font_map_load_font(pango_font_map, pango_context, desc);
99 PangoFontDescription *d2 = pango_font_describe(pfont);
101 msg(L_DEBUG, "Font desc: %s", pango_font_description_to_string(d2));
103 PangoFontMetrics *fm = pango_font_get_metrics(pfont, NULL);
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)));
111 static void font_init(void)
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);
122 static void text_size(struct sym_text *st)
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);
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;
137 g_object_unref(layout);
140 /*** Elimination of duplicate texts ***/
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;
147 static void text_dup_init(void)
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);
155 static int text_dup_coord(int x, int y)
157 ASSERT(x >= 0 && x < dup_tiles_w && y >= 0 && y < dup_tiles_h);
158 return x + y * dup_tiles_w;
161 static double text_quad_dist(struct sym_text *a, struct sym_text *b)
163 double dx = a->x - b->x;
164 double dy = a->y - b->y;
165 return dx*dx + dy*dy;
168 static bool text_dup_detect(struct sym_text *t, struct style_info *si)
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)
177 int tile_x = x / DUP_TILE_SIZE;
178 int tile_y = y / DUP_TILE_SIZE;
181 style_get_number(si, PROP_TEXT_DUP_THRESHOLD, &radius);
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++)
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 &&
199 t->next_duplicate = d->next_duplicate;
200 d->next_duplicate = t;
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;
212 /*** Core of the symbolizer ***/
214 static void prepare_text_element(struct sym_text *t, struct svg *svg)
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));
228 static void sym_text_draw(struct symbol *sym, struct svg *svg)
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);
233 if (t->next_duplicate)
235 // If there is a cluster of duplicate texts, average their positions
236 double sx = 0, sy = 0;
238 for (struct sym_text *u = t; u; u = u->next_duplicate)
249 bool want_rotate = (fabs(t->rotate) > 1e-5);
250 if (t->opacity != 1 || want_rotate)
253 svg_push_element(svg, "g");
255 svg_set_attr_float(svg, "opacity", t->opacity);
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));
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);
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);
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);
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);
309 static osm_val_t get_text(struct osm_object *o, struct style_info *si)
311 struct style_prop *prop = style_get_and_check(si, PROP_TEXT, (1 << PROP_TYPE_STRING) | (1 << PROP_TYPE_IDENT));
315 if (prop->type == PROP_TYPE_IDENT && prop->val.id == VALUE_AUTO)
317 static const osm_key_t auto_text_keys[] = {
318 KEY_NAME_CZ, // FIXME: This should be configurable
323 KEY_ADDR_HOUSENUMBER,
325 for (uns i=0; i < ARRAY_SIZE(auto_text_keys); i++)
327 osm_val_t val = osm_obj_find_tag(o, auto_text_keys[i]);
334 return osm_obj_find_tag(o, osm_key_encode(osm_val_decode(prop->val.id)));
337 static void get_text_attrs(struct sym_text *st, struct style_info *si)
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?
343 st->text_color = 0xffffff;
344 style_get_color(si, PROP_TEXT_COLOR, &st->text_color);
346 struct text_font f = {
347 .family = "Helvetica",
350 osm_val_t fam = style_get_string(si, PROP_FONT_FAMILY);
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);
356 f.weight = VALUE_NORMAL;
357 if (f.weight != VALUE_NORMAL && f.weight != VALUE_BOLD)
359 osm_obj_warn(o, "Unknown font-weight %s", osm_val_decode(f.weight));
360 f.weight = VALUE_NORMAL;
362 f.style = style_get_ident(si, PROP_FONT_STYLE);
364 f.style = VALUE_NORMAL;
365 if (f.style != VALUE_NORMAL && f.style != VALUE_ITALIC)
367 osm_obj_warn(o, "Unknown font-style %s", osm_val_decode(f.weight));
368 f.style = VALUE_NORMAL;
370 st->font = font_get(&f);
373 style_get_number(si, PROP_TEXT_OPACITY, &st->opacity);
375 st->halo_color = st->text_color ^ 0xffffff;
376 style_get_color(si, PROP_TEXT_HALO_COLOR, &st->halo_color);
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);
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);
390 static void text_fix_placement(struct sym_text *st)
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);
399 static void sym_text_node(struct osm_object *o, struct style_info *si, osm_val_t text)
401 struct osm_node *n = (struct osm_node *) o;
403 struct sym_text *st = sym_text_new(o);
408 get_text_attrs(st, si);
411 osm_val_t ah = style_get_ident(si, PROP_TEXT_ANCHOR_HORIZONTAL);
424 osm_obj_warn(o, "Unknown text-anchor-horizontal: %s", osm_val_decode(ah));
427 osm_val_t av = style_get_ident(si, PROP_TEXT_ANCHOR_VERTICAL);
430 case VALUE_ABOVE: // FIXME: What's the difference between above and top?
435 st->y -= (st->th + st->td) / 2;
443 osm_obj_warn(o, "Unknown text-anchor-vertical: %s", osm_val_decode(av));
446 text_fix_placement(st);
447 if (!text_dup_detect(st, si))
449 msg(L_DEBUG, "Text <%s> dropped as duplicate", osm_val_decode(text));
453 //sym_plan(&st->s, sym_zindex(o, si, 5));
456 static void sym_text_center(struct osm_object *o, struct style_info *si, osm_val_t text, double x, double y)
458 struct sym_text *st = sym_text_new(o);
463 get_text_attrs(st, si);
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))
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));
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));
482 static void sym_text_way(struct osm_object *o, struct style_info *si, osm_val_t text)
485 osm_val_t tp = style_get_ident(si, PROP_TEXT_POSITION);
490 if (osm_obj_center(o, &x, &y))
491 sym_text_center(o, si, text, x, y);
496 osm_obj_warn(o, "Unknown text-position: %s", osm_val_decode(tp));
500 static void sym_text_mpg(struct osm_object *o, struct style_info *si, osm_val_t text)
503 osm_val_t tp = style_get_ident(si, PROP_TEXT_POSITION);
508 if (osm_obj_center(o, &x, &y))
509 sym_text_center(o, si, text, x, y);
514 osm_obj_warn(o, "Unknown text-position: %s", osm_val_decode(tp));
518 static void sym_text_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
520 osm_val_t text = get_text(o, si);
527 sym_text_node(o, si, text);
530 sym_text_way(o, si, text);
532 case OSM_TYPE_MULTIPOLYGON:
533 sym_text_mpg(o, si, text);
536 osm_obj_warn(o, "Text symbolizer does not support this object type");
541 static void sym_text_init(void)
547 struct symbolizer symbolizer_text = {
549 .draw = sym_text_draw,
551 .init = sym_text_init,
554 struct sym_text *sym_text_new(struct osm_object *o)
556 return sym_new(SYMBOLIZER_TEXT, o, sizeof(struct sym_text));
560 void scale_text(struct svg *svg, double x, double y, osm_val_t text)
562 struct sym_text *st = sym_text_new(NULL);
564 struct text_font f = {
566 .weight = VALUE_NORMAL,
567 .style = VALUE_NORMAL,
568 .size = pt_to_mm(10),
575 st->font = font_get(&f);
577 st->halo_color = 0xffffff;
578 st->halo_radius = 0.8;
579 st->halo_opacity = 1;
582 sym_text_draw(&st->s, svg);