]> mj.ucw.cz Git - leo.git/blob - svg-icon.c
Updated UCW::Configure to the version from current LibUCW
[leo.git] / svg-icon.c
1 /*
2  *      Hic Est Leo -- SVG Icon Embedder
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/fastbuf.h>
9 #include <ucw-xml/xml.h>
10
11 #include <fcntl.h>
12 #include <math.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15
16 #include "leo.h"
17 #include "svg.h"
18
19 #define HASH_NODE struct svg_icon
20 #define HASH_PREFIX(x) icon_##x
21 #define HASH_KEY_ENDSTRING name
22 #define HASH_WANT_LOOKUP
23 #define HASH_WANT_CLEANUP
24 #define HASH_ZERO_FILL
25 #define HASH_TABLE_DYNAMIC
26 #include <ucw/hashtable.h>
27
28 static void svg_icon_error(struct xml_context *ctx)
29 {
30   fprintf(stderr, "%s at %u: %s\n", (ctx->err_code < XML_ERR_ERROR) ? "warn" : "error", xml_row(ctx), ctx->err_msg);
31 }
32
33 struct svg_icon *svg_icon_load(struct svg *svg, const char *name)
34 {
35   struct svg_icon *icon = icon_lookup(svg->icon_hash, (char *) name);
36   if (icon->id)
37     return icon;
38   icon->id = ++svg->icon_counter;
39   clist_init(&icon->patterns);
40
41   struct xml_context *ctx = xmalloc(sizeof(*ctx));
42   xml_init(ctx);
43   ctx->h_warn = ctx->h_error = ctx->h_fatal = svg_icon_error;
44   xml_push_fastbuf(ctx, bopen_file(name, O_RDONLY, NULL));
45
46   ctx->flags |= XML_ALLOC_TAGS | XML_ALLOC_CHARS;
47   xml_parse(ctx);
48   if (ctx->err_code)
49     die("Error reading icon %s: Fatal error in XML parser", name);
50
51   icon->ctx = ctx;
52   icon->root = ctx->dom;
53
54   struct xml_node *root = icon->root;
55   ASSERT(root);
56   if (strcmp(root->name, "svg"))
57     die("Error reading icon %s: root element is <%s>, not <svg>", name, root->name);
58
59   struct xml_attr *a_width = xml_attr_find(ctx, root, "width");
60   struct xml_attr *a_height = xml_attr_find(ctx, root, "height");
61   if (!a_width || !a_height)
62     die("Error reading icon %s: cannot determine image dimensions", name);
63   icon->width = atof(a_width->val) / svg->scale;
64   icon->height = atof(a_height->val) / svg->scale;
65   if (icon->width < 0.01 || icon->height < 0.01)
66     die("Error reading icon %s: invalid icon dimensions", name);
67   // FIXME: What if dimensions are given in absolute units?
68
69   msg(L_DEBUG, "Loaded icon %s (%.5g x %.5g)", name, icon->width, icon->height);
70   return icon;
71 }
72
73 static void svg_icon_unload(struct svg_icon *icon)
74 {
75   xml_cleanup(icon->ctx);
76   xfree(icon->ctx);
77 }
78
79 static bool is_white(int c)
80 {
81   return c == ' ' || c == '\t' || c == '\r' || c == '\n';
82 }
83
84 static void normalize_white(char *r)
85 {
86   char *w = r;
87
88   while (is_white(*r))
89     r++;
90   while (*r)
91     {
92       if (is_white(*r))
93         {
94           while (is_white(*r))
95             r++;
96           if (*r)
97             *w++ = *r++;
98         }
99       else
100         *w++ = *r++;
101     }
102   *w = 0;
103 }
104
105 static void svg_embed(struct svg *svg, struct svg_icon *icon, struct xml_node *n)
106 {
107   if (n->type == XML_NODE_CHARS)
108     {
109       char *t = mp_strdup(svg->pool, n->text);
110       normalize_white(t);
111       if (*t)
112         {
113           svg_push_chars(svg)->name = n->text;
114           svg_pop(svg);
115         }
116       return;
117     }
118   ASSERT(n->type == XML_NODE_ELEM);
119
120   svg_push_element(svg, n->name);
121   XML_ATTR_FOR_EACH(a, n)
122     {
123       char *val = a->val;
124       if (!strcmp(a->name, "id"))
125         val = mp_printf(svg->pool, "i%u-%s", icon->id, val);
126       else if (!strcmp(a->name, "xlink:href") && val[0] == '#')
127         val = mp_printf(svg->pool, "#i%u-%s", icon->id, val+1);
128       // FIXME: Some attributes can have values "url(#id)"
129       svg_set_attr(svg, a->name, val);
130     }
131   XML_NODE_FOR_EACH(e, n)
132     svg_embed(svg, icon, e);
133   svg_pop(svg);
134 }
135
136 static void svg_icon_embed(struct svg *svg, struct svg_icon *icon)
137 {
138   svg_push_element(svg, "g");
139   svg_set_attr_format(svg, "id", "icon%u", icon->id);
140
141   svg_push_comment(svg)->name = mp_printf(svg->pool, "Embedded %s", icon->name);
142   svg_pop(svg);
143
144   svg_embed(svg, icon, icon->root);
145   svg_pop(svg);
146 }
147
148 void svg_icon_init(struct svg *svg)
149 {
150   svg->icon_hash = mp_alloc_zero(svg->pool, sizeof(struct icon_table));
151   icon_init(svg->icon_hash);
152 }
153
154 void svg_icon_cleanup(struct svg *svg)
155 {
156   HASH_FOR_ALL_DYNAMIC(icon, svg->icon_hash, icon)
157     {
158       svg_icon_unload(icon);
159     }
160   HASH_END_FOR;
161   icon_cleanup(svg->icon_hash);
162 }
163
164 void svg_icon_put(struct svg *svg, struct svg_icon_request *sir)
165 {
166   struct svg_icon *icon = sir->icon;
167   double scale_x = sir->width / icon->width;
168   double scale_y = sir->height / icon->height;
169
170   // Center
171   // FIXME: More alignment modes?
172   double x = sir->x - sir->width / 2;
173   double y = sir->y - sir->height / 2;
174
175   svg_push_element(svg, "use");
176   svg_set_attr_format(svg, "xlink:href", "#icon%u", icon->id);
177   svg_set_attr_dimen(svg, "x", x / scale_x);
178   svg_set_attr_dimen(svg, "y", y / scale_y);
179   if (fabs(scale_x - 1) > 1e-10 || fabs(scale_y - 1) > 1e-10)
180     svg_set_attr_format(svg, "transform", "scale(%.5g %.5g)", scale_x, scale_y);
181   svg_pop(svg);
182 }
183
184 struct svg_pattern *svg_icon_to_pattern(struct svg *svg, struct svg_pattern_request *spr)
185 {
186   CLIST_FOR_EACH(struct svg_pattern *, patt, spr->icon->patterns)
187     {
188       if (fabs(patt->width - spr->width) < 1e-10 &&
189           fabs(patt->height - spr->height) < 1e-10)
190         return patt;
191     }
192
193   struct svg_pattern *patt = mp_alloc_zero(svg->pool, sizeof(*patt));
194   clist_add_tail(&spr->icon->patterns, &patt->n);
195   patt->id = ++svg->pattern_counter;
196   patt->icon = spr->icon;
197   patt->width = spr->width;
198   patt->height = spr->height;
199   patt->paint_server = mp_printf(svg->pool, "url(#patt%u)", patt->id);
200   return patt;
201 }
202
203 static void svg_pattern_embed(struct svg *svg, struct svg_pattern *patt)
204 {
205   struct svg_icon *icon = patt->icon;
206
207   svg_push_element(svg, "pattern");
208   svg_set_attr_format(svg, "id", "patt%u", patt->id);
209   svg_set_attr(svg, "patternUnits", "userSpaceOnUse");
210   svg_set_attr(svg, "patternContentUnits", "userSpaceOnUse");
211   svg_set_attr_dimen(svg, "width", patt->width);
212   svg_set_attr_dimen(svg, "height", patt->height);
213
214   struct svg_icon_request sir = {
215     .icon = icon,
216     .x = patt->width / 2,
217     .y = patt->height / 2,
218     .width = patt->width,
219     .height = patt->height,
220   };
221   svg_icon_put(svg, &sir);
222
223   svg_pop(svg);
224 }
225
226 void svg_icon_dump_library(struct svg *svg)
227 {
228   svg_push_element(svg, "defs");
229   HASH_FOR_ALL_DYNAMIC(icon, svg->icon_hash, icon)
230     {
231       svg_icon_embed(svg, icon);
232       CLIST_FOR_EACH(struct svg_pattern *, patt, icon->patterns)
233         svg_pattern_embed(svg, patt);
234     }
235   HASH_END_FOR;
236   svg_pop(svg);
237 }