2 * Hic Est Leo -- SVG Icon Embedder
4 * (c) 2014 Martin Mares <mj@ucw.cz>
8 #include <ucw/fastbuf.h>
9 #include <ucw-xml/xml.h>
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>
28 static void svg_icon_error(struct xml_context *ctx)
30 fprintf(stderr, "%s at %u: %s\n", (ctx->err_code < XML_ERR_ERROR) ? "warn" : "error", xml_row(ctx), ctx->err_msg);
33 struct svg_icon *svg_icon_load(struct svg *svg, const char *name)
35 struct svg_icon *icon = icon_lookup(svg->icon_hash, (char *) name);
38 icon->id = ++svg->icon_counter;
39 clist_init(&icon->patterns);
41 struct xml_context *ctx = xmalloc(sizeof(*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));
46 ctx->flags |= XML_ALLOC_TAGS | XML_ALLOC_CHARS;
49 die("Error reading icon %s: Fatal error in XML parser", name);
52 icon->root = ctx->dom;
54 struct xml_node *root = icon->root;
56 if (strcmp(root->name, "svg"))
57 die("Error reading icon %s: root element is <%s>, not <svg>", name, root->name);
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?
69 msg(L_DEBUG, "Loaded icon %s (%.5g x %.5g)", name, icon->width, icon->height);
73 static void svg_icon_unload(struct svg_icon *icon)
75 xml_cleanup(icon->ctx);
79 static bool is_white(int c)
81 return c == ' ' || c == '\t' || c == '\r' || c == '\n';
84 static void normalize_white(char *r)
105 static void svg_embed(struct svg *svg, struct svg_icon *icon, struct xml_node *n)
107 if (n->type == XML_NODE_CHARS)
109 char *t = mp_strdup(svg->pool, n->text);
113 svg_push_chars(svg)->name = n->text;
118 ASSERT(n->type == XML_NODE_ELEM);
120 svg_push_element(svg, n->name);
121 XML_ATTR_FOR_EACH(a, n)
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);
131 XML_NODE_FOR_EACH(e, n)
132 svg_embed(svg, icon, e);
136 static void svg_icon_embed(struct svg *svg, struct svg_icon *icon)
138 svg_push_element(svg, "g");
139 svg_set_attr_format(svg, "id", "icon%u", icon->id);
141 svg_push_comment(svg)->name = mp_printf(svg->pool, "Embedded %s", icon->name);
144 svg_embed(svg, icon, icon->root);
148 void svg_icon_init(struct svg *svg)
150 svg->icon_hash = mp_alloc_zero(svg->pool, sizeof(struct icon_table));
151 icon_init(svg->icon_hash);
154 void svg_icon_cleanup(struct svg *svg)
156 HASH_FOR_ALL_DYNAMIC(icon, svg->icon_hash, icon)
158 svg_icon_unload(icon);
161 icon_cleanup(svg->icon_hash);
164 void svg_icon_put(struct svg *svg, struct svg_icon_request *sir)
166 struct svg_icon *icon = sir->icon;
167 double scale_x = sir->width / icon->width;
168 double scale_y = sir->height / icon->height;
171 // FIXME: More alignment modes?
172 double x = sir->x - sir->width / 2;
173 double y = sir->y - sir->height / 2;
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);
184 struct svg_pattern *svg_icon_to_pattern(struct svg *svg, struct svg_pattern_request *spr)
186 CLIST_FOR_EACH(struct svg_pattern *, patt, spr->icon->patterns)
188 if (fabs(patt->width - spr->width) < 1e-10 &&
189 fabs(patt->height - spr->height) < 1e-10)
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);
203 static void svg_pattern_embed(struct svg *svg, struct svg_pattern *patt)
205 struct svg_icon *icon = patt->icon;
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);
214 struct svg_icon_request sir = {
216 .x = patt->width / 2,
217 .y = patt->height / 2,
218 .width = patt->width,
219 .height = patt->height,
221 svg_icon_put(svg, &sir);
226 void svg_icon_dump_library(struct svg *svg)
228 svg_push_element(svg, "defs");
229 HASH_FOR_ALL_DYNAMIC(icon, svg->icon_hash, icon)
231 svg_icon_embed(svg, icon);
232 CLIST_FOR_EACH(struct svg_pattern *, patt, icon->patterns)
233 svg_pattern_embed(svg, patt);