]> arthur.barton.de Git - netdata.git/blob - src/web_buffer_svg.c
dns_query_time plugin: replace "." with "_" in dimensions
[netdata.git] / src / web_buffer_svg.c
1 #include "common.h"
2
3 #define BADGE_HORIZONTAL_PADDING 4
4 #define VERDANA_KERNING 0.2
5 #define VERDANA_PADDING 1.0
6
7 /*
8  * verdana11_widths[] has been generated with this method:
9  * https://github.com/badges/shields/blob/master/measure-text.js
10 */
11
12 double verdana11_widths[256] = {
13     [0] = 0.0,
14     [1] = 0.0,
15     [2] = 0.0,
16     [3] = 0.0,
17     [4] = 0.0,
18     [5] = 0.0,
19     [6] = 0.0,
20     [7] = 0.0,
21     [8] = 0.0,
22     [9] = 0.0,
23     [10] = 0.0,
24     [11] = 0.0,
25     [12] = 0.0,
26     [13] = 0.0,
27     [14] = 0.0,
28     [15] = 0.0,
29     [16] = 0.0,
30     [17] = 0.0,
31     [18] = 0.0,
32     [19] = 0.0,
33     [20] = 0.0,
34     [21] = 0.0,
35     [22] = 0.0,
36     [23] = 0.0,
37     [24] = 0.0,
38     [25] = 0.0,
39     [26] = 0.0,
40     [27] = 0.0,
41     [28] = 0.0,
42     [29] = 0.0,
43     [30] = 0.0,
44     [31] = 0.0,
45     [32] = 3.8671874999999996, //
46     [33] = 4.3291015625, // !
47     [34] = 5.048828125, // "
48     [35] = 9.001953125, // #
49     [36] = 6.9931640625, // $
50     [37] = 11.837890625, // %
51     [38] = 7.992187499999999, // &
52     [39] = 2.9541015625, // '
53     [40] = 4.9951171875, // (
54     [41] = 4.9951171875, // )
55     [42] = 6.9931640625, // *
56     [43] = 9.001953125, // +
57     [44] = 4.00146484375, // ,
58     [45] = 4.9951171875, // -
59     [46] = 4.00146484375, // .
60     [47] = 4.9951171875, // /
61     [48] = 6.9931640625, // 0
62     [49] = 6.9931640625, // 1
63     [50] = 6.9931640625, // 2
64     [51] = 6.9931640625, // 3
65     [52] = 6.9931640625, // 4
66     [53] = 6.9931640625, // 5
67     [54] = 6.9931640625, // 6
68     [55] = 6.9931640625, // 7
69     [56] = 6.9931640625, // 8
70     [57] = 6.9931640625, // 9
71     [58] = 4.9951171875, // :
72     [59] = 4.9951171875, // ;
73     [60] = 9.001953125, // <
74     [61] = 9.001953125, // =
75     [62] = 9.001953125, // >
76     [63] = 5.99951171875, // ?
77     [64] = 11.0, // @
78     [65] = 7.51953125, // A
79     [66] = 7.541015625, // B
80     [67] = 7.680664062499999, // C
81     [68] = 8.4755859375, // D
82     [69] = 6.95556640625, // E
83     [70] = 6.32177734375, // F
84     [71] = 8.529296875, // G
85     [72] = 8.26611328125, // H
86     [73] = 4.6298828125, // I
87     [74] = 5.00048828125, // J
88     [75] = 7.62158203125, // K
89     [76] = 6.123046875, // L
90     [77] = 9.2705078125, // M
91     [78] = 8.228515625, // N
92     [79] = 8.658203125, // O
93     [80] = 6.63330078125, // P
94     [81] = 8.658203125, // Q
95     [82] = 7.6484375, // R
96     [83] = 7.51953125, // S
97     [84] = 6.7783203125, // T
98     [85] = 8.05126953125, // U
99     [86] = 7.51953125, // V
100     [87] = 10.87646484375, // W
101     [88] = 7.53564453125, // X
102     [89] = 6.767578125, // Y
103     [90] = 7.53564453125, // Z
104     [91] = 4.9951171875, // [
105     [92] = 4.9951171875, // backslash
106     [93] = 4.9951171875, // ]
107     [94] = 9.001953125, // ^
108     [95] = 6.9931640625, // _
109     [96] = 6.9931640625, // `
110     [97] = 6.6064453125, // a
111     [98] = 6.853515625, // b
112     [99] = 5.73095703125, // c
113     [100] = 6.853515625, // d
114     [101] = 6.552734375, // e
115     [102] = 3.8671874999999996, // f
116     [103] = 6.853515625, // g
117     [104] = 6.9609375, // h
118     [105] = 3.0185546875, // i
119     [106] = 3.78662109375, // j
120     [107] = 6.509765625, // k
121     [108] = 3.0185546875, // l
122     [109] = 10.69921875, // m
123     [110] = 6.9609375, // n
124     [111] = 6.67626953125, // o
125     [112] = 6.853515625, // p
126     [113] = 6.853515625, // q
127     [114] = 4.6943359375, // r
128     [115] = 5.73095703125, // s
129     [116] = 4.33447265625, // t
130     [117] = 6.9609375, // u
131     [118] = 6.509765625, // v
132     [119] = 9.001953125, // w
133     [120] = 6.509765625, // x
134     [121] = 6.509765625, // y
135     [122] = 5.779296875, // z
136     [123] = 6.982421875, // {
137     [124] = 4.9951171875, // |
138     [125] = 6.982421875, // }
139     [126] = 9.001953125, // ~
140     [127] = 0.0,
141     [128] = 0.0,
142     [129] = 0.0,
143     [130] = 0.0,
144     [131] = 0.0,
145     [132] = 0.0,
146     [133] = 0.0,
147     [134] = 0.0,
148     [135] = 0.0,
149     [136] = 0.0,
150     [137] = 0.0,
151     [138] = 0.0,
152     [139] = 0.0,
153     [140] = 0.0,
154     [141] = 0.0,
155     [142] = 0.0,
156     [143] = 0.0,
157     [144] = 0.0,
158     [145] = 0.0,
159     [146] = 0.0,
160     [147] = 0.0,
161     [148] = 0.0,
162     [149] = 0.0,
163     [150] = 0.0,
164     [151] = 0.0,
165     [152] = 0.0,
166     [153] = 0.0,
167     [154] = 0.0,
168     [155] = 0.0,
169     [156] = 0.0,
170     [157] = 0.0,
171     [158] = 0.0,
172     [159] = 0.0,
173     [160] = 0.0,
174     [161] = 0.0,
175     [162] = 0.0,
176     [163] = 0.0,
177     [164] = 0.0,
178     [165] = 0.0,
179     [166] = 0.0,
180     [167] = 0.0,
181     [168] = 0.0,
182     [169] = 0.0,
183     [170] = 0.0,
184     [171] = 0.0,
185     [172] = 0.0,
186     [173] = 0.0,
187     [174] = 0.0,
188     [175] = 0.0,
189     [176] = 0.0,
190     [177] = 0.0,
191     [178] = 0.0,
192     [179] = 0.0,
193     [180] = 0.0,
194     [181] = 0.0,
195     [182] = 0.0,
196     [183] = 0.0,
197     [184] = 0.0,
198     [185] = 0.0,
199     [186] = 0.0,
200     [187] = 0.0,
201     [188] = 0.0,
202     [189] = 0.0,
203     [190] = 0.0,
204     [191] = 0.0,
205     [192] = 0.0,
206     [193] = 0.0,
207     [194] = 0.0,
208     [195] = 0.0,
209     [196] = 0.0,
210     [197] = 0.0,
211     [198] = 0.0,
212     [199] = 0.0,
213     [200] = 0.0,
214     [201] = 0.0,
215     [202] = 0.0,
216     [203] = 0.0,
217     [204] = 0.0,
218     [205] = 0.0,
219     [206] = 0.0,
220     [207] = 0.0,
221     [208] = 0.0,
222     [209] = 0.0,
223     [210] = 0.0,
224     [211] = 0.0,
225     [212] = 0.0,
226     [213] = 0.0,
227     [214] = 0.0,
228     [215] = 0.0,
229     [216] = 0.0,
230     [217] = 0.0,
231     [218] = 0.0,
232     [219] = 0.0,
233     [220] = 0.0,
234     [221] = 0.0,
235     [222] = 0.0,
236     [223] = 0.0,
237     [224] = 0.0,
238     [225] = 0.0,
239     [226] = 0.0,
240     [227] = 0.0,
241     [228] = 0.0,
242     [229] = 0.0,
243     [230] = 0.0,
244     [231] = 0.0,
245     [232] = 0.0,
246     [233] = 0.0,
247     [234] = 0.0,
248     [235] = 0.0,
249     [236] = 0.0,
250     [237] = 0.0,
251     [238] = 0.0,
252     [239] = 0.0,
253     [240] = 0.0,
254     [241] = 0.0,
255     [242] = 0.0,
256     [243] = 0.0,
257     [244] = 0.0,
258     [245] = 0.0,
259     [246] = 0.0,
260     [247] = 0.0,
261     [248] = 0.0,
262     [249] = 0.0,
263     [250] = 0.0,
264     [251] = 0.0,
265     [252] = 0.0,
266     [253] = 0.0,
267     [254] = 0.0,
268     [255] = 0.0
269 };
270
271 // find the width of the string using the verdana 11points font
272 // re-write the string in place, skiping zero-length characters
273 static inline int verdana11_width(char *s) {
274     double w = 0.0;
275     char *d = s;
276
277     while(*s) {
278         double t = verdana11_widths[(unsigned char)*s];
279         if(t == 0.0)
280             s++;
281         else {
282             w += t + VERDANA_KERNING;
283             if(d != s)
284                 *d++ = *s++;
285             else
286                 d = ++s;
287         }
288     }
289
290     *d = '\0';
291     w -= VERDANA_KERNING;
292     w += VERDANA_PADDING;
293     return (int)ceil(w);
294 }
295
296 static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
297     size_t i = len;
298
299     // required escapes from
300     // https://github.com/badges/shields/blob/master/badge.js
301     while(*src && i) {
302         switch(*src) {
303             case '\\':
304                 *dst++ = '/';
305                 src++;
306                 i--;
307                 break;
308
309             case '&':
310                 if(i > 5) {
311                     strcpy(dst, "&amp;");
312                     i -= 5;
313                     dst += 5;
314                     src++;
315                 }
316                 else goto cleanup;
317                 break;
318
319             case '<':
320                 if(i > 4) {
321                     strcpy(dst, "&lt;");
322                     i -= 4;
323                     dst += 4;
324                     src++;
325                 }
326                 else goto cleanup;
327                 break;
328
329             case '>':
330                 if(i > 4) {
331                     strcpy(dst, "&gt;");
332                     i -= 4;
333                     dst += 4;
334                     src++;
335                 }
336                 else goto cleanup;
337                 break;
338
339             case '"':
340                 if(i > 6) {
341                     strcpy(dst, "&quot;");
342                     i -= 6;
343                     dst += 6;
344                     src++;
345                 }
346                 else goto cleanup;
347                 break;
348
349             case '\'':
350                 if(i > 6) {
351                     strcpy(dst, "&apos;");
352                     i -= 6;
353                     dst += 6;
354                     src++;
355                 }
356                 else goto cleanup;
357                 break;
358
359             default:
360                 i--;
361                 *dst++ = *src++;
362                 break;
363         }
364     }
365
366 cleanup:
367     *dst = '\0';
368     return len - i;
369 }
370
371 static inline char *format_value_with_precision_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) {
372     if(unlikely(isnan(value) || isinf(value)))
373         value = 0.0;
374
375     char *separator = "";
376     if(unlikely(isalnum(*units)))
377         separator = " ";
378
379     if(precision < 0) {
380         int len, lstop = 0, trim_zeros = 1;
381
382         calculated_number abs = value;
383         if(isless(value, 0)) {
384             lstop = 1;
385             abs = -value;
386         }
387
388         if(isgreaterequal(abs, 1000)) {
389             len = snprintfz(value_string, value_string_len, "%0.0Lf", (long double) value);
390             trim_zeros = 0;
391         }
392         else if(isgreaterequal(abs, 10))  len = snprintfz(value_string, value_string_len, "%0.1Lf", (long double) value);
393         else if(isgreaterequal(abs, 1))   len = snprintfz(value_string, value_string_len, "%0.2Lf", (long double) value);
394         else if(isgreaterequal(abs, 0.1)) len = snprintfz(value_string, value_string_len, "%0.2Lf", (long double) value);
395         else                              len = snprintfz(value_string, value_string_len, "%0.4Lf", (long double) value);
396
397         if(unlikely(trim_zeros)) {
398             int l;
399             // remove trailing zeros from the decimal part
400             for(l = len - 1; l > lstop; l--) {
401                 if(likely(value_string[l] == '0')) {
402                     value_string[l] = '\0';
403                     len--;
404                 }
405
406                 else if(unlikely(value_string[l] == '.')) {
407                     value_string[l] = '\0';
408                     len--;
409                     break;
410                 }
411
412                 else
413                     break;
414             }
415         }
416
417         if(unlikely(len <= 0)) len = 1;
418         snprintfz(&value_string[len], value_string_len - len, "%s%s", separator, units);
419     }
420     else {
421         if(precision > 50) precision = 50;
422         snprintfz(value_string, value_string_len, "%0.*Lf%s%s", precision, (long double) value, separator, units);
423     }
424
425     return value_string;
426 }
427
428 inline char *format_value_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) {
429     static uint32_t
430             hash_seconds = 0,
431             hash_seconds_ago = 0,
432             hash_minutes = 0,
433             hash_minutes_ago = 0,
434             hash_hours = 0,
435             hash_hours_ago = 0,
436             hash_onoff = 0,
437             hash_updown = 0,
438             hash_okerror = 0,
439             hash_okfailed = 0,
440             hash_empty = 0,
441             hash_null = 0,
442             hash_percentage = 0,
443             hash_percent = 0,
444             hash_pcent = 0;
445
446     if(unlikely(!hash_seconds)) {
447         hash_seconds     = simple_hash("seconds");
448         hash_seconds_ago = simple_hash("seconds ago");
449         hash_minutes     = simple_hash("minutes");
450         hash_minutes_ago = simple_hash("minutes ago");
451         hash_hours       = simple_hash("hours");
452         hash_hours_ago   = simple_hash("hours ago");
453         hash_onoff       = simple_hash("on/off");
454         hash_updown      = simple_hash("up/down");
455         hash_okerror     = simple_hash("ok/error");
456         hash_okfailed    = simple_hash("ok/failed");
457         hash_empty       = simple_hash("empty");
458         hash_null        = simple_hash("null");
459         hash_percentage  = simple_hash("percentage");
460         hash_percent     = simple_hash("percent");
461         hash_pcent       = simple_hash("pcent");
462     }
463
464     if(unlikely(!units)) units = "";
465
466     uint32_t hash_units = simple_hash(units);
467
468     if(unlikely((hash_units == hash_seconds && !strcmp(units, "seconds")) || (hash_units == hash_seconds_ago && !strcmp(units, "seconds ago")))) {
469         if(value == 0.0) {
470             snprintfz(value_string, value_string_len, "%s", "now");
471             return value_string;
472         }
473         else if(isnan(value) || isinf(value)) {
474             snprintfz(value_string, value_string_len, "%s", "never");
475             return value_string;
476         }
477
478         const char *suffix = (hash_units == hash_seconds_ago)?" ago":"";
479
480         size_t s = (size_t)value;
481         size_t d = s / 86400;
482         s = s % 86400;
483
484         size_t h = s / 3600;
485         s = s % 3600;
486
487         size_t m = s / 60;
488         s = s % 60;
489
490         if(d)
491             snprintfz(value_string, value_string_len, "%zu %s %02zu:%02zu:%02zu%s", d, (d == 1)?"day":"days", h, m, s, suffix);
492         else
493             snprintfz(value_string, value_string_len, "%02zu:%02zu:%02zu%s", h, m, s, suffix);
494
495         return value_string;
496     }
497
498     else if(unlikely((hash_units == hash_minutes && !strcmp(units, "minutes")) || (hash_units == hash_minutes_ago && !strcmp(units, "minutes ago")))) {
499         if(value == 0.0) {
500             snprintfz(value_string, value_string_len, "%s", "now");
501             return value_string;
502         }
503         else if(isnan(value) || isinf(value)) {
504             snprintfz(value_string, value_string_len, "%s", "never");
505             return value_string;
506         }
507
508         const char *suffix = (hash_units == hash_minutes_ago)?" ago":"";
509
510         size_t m = (size_t)value;
511         size_t d = m / (60 * 24);
512         m = m % (60 * 24);
513
514         size_t h = m / 60;
515         m = m % 60;
516
517         if(d)
518             snprintfz(value_string, value_string_len, "%zud %02zuh %02zum%s", d, h, m, suffix);
519         else
520             snprintfz(value_string, value_string_len, "%zuh %zum%s", h, m, suffix);
521
522         return value_string;
523     }
524
525     else if(unlikely((hash_units == hash_hours && !strcmp(units, "hours")) || (hash_units == hash_hours_ago && !strcmp(units, "hours ago")))) {
526         if(value == 0.0) {
527             snprintfz(value_string, value_string_len, "%s", "now");
528             return value_string;
529         }
530         else if(isnan(value) || isinf(value)) {
531             snprintfz(value_string, value_string_len, "%s", "never");
532             return value_string;
533         }
534
535         const char *suffix = (hash_units == hash_hours_ago)?" ago":"";
536
537         size_t h = (size_t)value;
538         size_t d = h / 24;
539         h = h % 24;
540
541         if(d)
542             snprintfz(value_string, value_string_len, "%zud %zuh%s", d, h, suffix);
543         else
544             snprintfz(value_string, value_string_len, "%zuh%s", h, suffix);
545
546         return value_string;
547     }
548
549     else if(unlikely(hash_units == hash_onoff && !strcmp(units, "on/off"))) {
550         snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"on":"off");
551         return value_string;
552     }
553
554     else if(unlikely(hash_units == hash_updown && !strcmp(units, "up/down"))) {
555         snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"up":"down");
556         return value_string;
557     }
558
559     else if(unlikely(hash_units == hash_okerror && !strcmp(units, "ok/error"))) {
560         snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"error");
561         return value_string;
562     }
563
564     else if(unlikely(hash_units == hash_okfailed && !strcmp(units, "ok/failed"))) {
565         snprintfz(value_string, value_string_len, "%s", (value != 0.0)?"ok":"failed");
566         return value_string;
567     }
568
569     else if(unlikely(hash_units == hash_empty && !strcmp(units, "empty")))
570         units = "";
571
572     else if(unlikely(hash_units == hash_null && !strcmp(units, "null")))
573         units = "";
574
575     else if(unlikely(hash_units == hash_percentage && !strcmp(units, "percentage")))
576         units = "%";
577
578     else if(unlikely(hash_units == hash_percent && !strcmp(units, "percent")))
579         units = "%";
580
581     else if(unlikely(hash_units == hash_pcent && !strcmp(units, "pcent")))
582         units = "%";
583
584
585     if(unlikely(isnan(value) || isinf(value))) {
586         strcpy(value_string, "-");
587         return value_string;
588     }
589
590     return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision);
591 }
592
593 static inline const char *color_map(const char *color) {
594     // colors from:
595     // https://github.com/badges/shields/blob/master/colorscheme.json
596          if(!strcmp(color, "brightgreen")) return "#4c1";
597     else if(!strcmp(color, "green"))       return "#97CA00";
598     else if(!strcmp(color, "yellow"))      return "#dfb317";
599     else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
600     else if(!strcmp(color, "orange"))      return "#fe7d37";
601     else if(!strcmp(color, "red"))         return "#e05d44";
602     else if(!strcmp(color, "blue"))        return "#007ec6";
603     else if(!strcmp(color, "grey"))        return "#555";
604     else if(!strcmp(color, "gray"))        return "#555";
605     else if(!strcmp(color, "lightgrey"))   return "#9f9f9f";
606     else if(!strcmp(color, "lightgray"))   return "#9f9f9f";
607     return color;
608 }
609
610 static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value) {
611     int value_is_null = 0;
612     if(isnan(value) || isinf(value)) {
613         value = 0.0;
614         value_is_null = 1;
615     }
616
617     char color_buffer[256 + 1] = "";
618     char value_buffer[256 + 1] = "";
619     char comparison = '>';
620
621     // example input:
622     // color<max|color>min|color:null...
623
624     const char *c = color;
625     while(*c) {
626         char *dc = color_buffer, *dv = NULL;
627         size_t ci = 0, vi = 0;
628
629         const char *t = c;
630
631         while(*t && *t != '|') {
632             switch(*t) {
633                 case ':':
634                     comparison = '=';
635                     dv = value_buffer;
636                     break;
637
638                 case '}':
639                 case ')':
640                 case '>':
641                     if(t[1] == '=') {
642                         comparison = ')';
643                         t++;
644                     }
645                     else
646                         comparison = '>';
647                     dv = value_buffer;
648                     break;
649
650                 case '{':
651                 case '(':
652                 case '<':
653                     if(t[1] == '=') {
654                         comparison = '(';
655                         t++;
656                     }
657                     else
658                         comparison = '<';
659                     dv = value_buffer;
660                     break;
661
662                 default:
663                     if(dv) {
664                         if(vi < 256) {
665                             vi++;
666                             *dv++ = *t;
667                         }
668                     }
669                     else {
670                         if(ci < 256) {
671                             ci++;
672                             *dc++ = *t;
673                         }
674                     }
675                     break;
676             }
677
678             t++;
679         }
680
681         // prepare for next iteration
682         if(*t == '|') t++;
683         c = t;
684
685         // do the math
686         *dc = '\0';
687         if(dv) {
688             *dv = '\0';
689
690             if(value_is_null) {
691                 if(!*value_buffer || !strcmp(value_buffer, "null"))
692                     break;
693             }
694             else {
695                 calculated_number v = str2l(value_buffer);
696
697                      if(comparison == '<' && value < v) break;
698                 else if(comparison == '(' && value <= v) break;
699                 else if(comparison == '>' && value > v) break;
700                 else if(comparison == ')' && value >= v) break;
701                 else if(comparison == '=' && value == v) break;
702             }
703         }
704         else
705             break;
706     }
707
708     const char *b;
709     if(color_buffer[0])
710         b = color_buffer;
711     else
712         b = color;
713
714     strncpyz(final, b, len);
715 }
716
717 // value + units
718 #define VALUE_STRING_SIZE 100
719
720 // label
721 #define LABEL_STRING_SIZE 200
722
723 // colors
724 #define COLOR_STRING_SIZE 100
725
726 void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int precision) {
727     char      label_buffer[LABEL_STRING_SIZE + 1]
728             , value_color_buffer[COLOR_STRING_SIZE + 1]
729             , value_string[VALUE_STRING_SIZE + 1]
730             , label_escaped[LABEL_STRING_SIZE + 1]
731             , value_escaped[VALUE_STRING_SIZE + 1]
732             , label_color_escaped[COLOR_STRING_SIZE + 1]
733             , value_color_escaped[COLOR_STRING_SIZE + 1];
734
735     int label_width, value_width, total_width;
736
737     if(unlikely(!label_color || !*label_color))
738         label_color = "#555";
739
740     if(unlikely(!value_color || !*value_color))
741         value_color = (isnan(value) || isinf(value))?"#999":"#4c1";
742
743     calc_colorz(value_color, value_color_buffer, COLOR_STRING_SIZE, value);
744     format_value_and_unit(value_string, VALUE_STRING_SIZE, value, units, precision);
745
746     // we need to copy the label, since verdana11_width may write to it
747     strncpyz(label_buffer, label, LABEL_STRING_SIZE);
748
749     label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
750     value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
751     total_width = label_width + value_width;
752
753     escape_xmlz(label_escaped, label_buffer, LABEL_STRING_SIZE);
754     escape_xmlz(value_escaped, value_string, VALUE_STRING_SIZE);
755     escape_xmlz(label_color_escaped, color_map(label_color), COLOR_STRING_SIZE);
756     escape_xmlz(value_color_escaped, color_map(value_color_buffer), COLOR_STRING_SIZE);
757
758     wb->contenttype = CT_IMAGE_SVG_XML;
759
760     // svg template from:
761     // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
762     buffer_sprintf(wb,
763         "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"20\">"
764             "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
765                 "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
766                 "<stop offset=\"1\" stop-opacity=\".1\"/>"
767             "</linearGradient>"
768             "<mask id=\"round\">"
769                 "<rect width=\"%d\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
770             "</mask>"
771             "<g mask=\"url(#round)\">"
772                 "<rect width=\"%d\" height=\"20\" fill=\"%s\"/>"
773                 "<rect x=\"%d\" width=\"%d\" height=\"20\" fill=\"%s\"/>"
774                 "<rect width=\"%d\" height=\"20\" fill=\"url(#smooth)\"/>"
775             "</g>"
776             "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
777                 "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
778                 "<text x=\"%d\" y=\"14\">%s</text>"
779                 "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
780                 "<text x=\"%d\" y=\"14\">%s</text>"
781             "</g>"
782         "</svg>",
783         total_width, total_width,
784         label_width, label_color_escaped,
785         label_width, value_width, value_color_escaped,
786         total_width,
787         label_width / 2, label_escaped,
788         label_width / 2, label_escaped,
789         label_width + value_width / 2 -1, value_escaped,
790         label_width + value_width / 2 -1, value_escaped);
791 }