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