]> arthur.barton.de Git - netdata.git/blob - src/web_buffer_svg.c
087c79a3f037cf6850f4a285adf7913b285494f5
[netdata.git] / src / web_buffer_svg.c
1 #include "common.h"
2
3 #define BADGE_HORIZONTAL_PADDING 4
4 #define VERDANA_KERNING 0.5
5
6 /*
7  * verdana11_widths[] has been generated with this method:
8  * https://github.com/badges/shields/blob/master/measure-text.js
9 */
10
11 double verdana11_widths[256] = {
12         [0] = 0.0,
13         [1] = 0.0,
14         [2] = 0.0,
15         [3] = 0.0,
16         [4] = 0.0,
17         [5] = 0.0,
18         [6] = 0.0,
19         [7] = 0.0,
20         [8] = 0.0,
21         [9] = 0.0,
22         [10] = 0.0,
23         [11] = 0.0,
24         [12] = 0.0,
25         [13] = 0.0,
26         [14] = 0.0,
27         [15] = 0.0,
28         [16] = 0.0,
29         [17] = 0.0,
30         [18] = 0.0,
31         [19] = 0.0,
32         [20] = 0.0,
33         [21] = 0.0,
34         [22] = 0.0,
35         [23] = 0.0,
36         [24] = 0.0,
37         [25] = 0.0,
38         [26] = 0.0,
39         [27] = 0.0,
40         [28] = 0.0,
41         [29] = 0.0,
42         [30] = 0.0,
43         [31] = 0.0,
44         [32] = 3.8671874999999996, //
45         [33] = 4.3291015625, // !
46         [34] = 5.048828125, // "
47         [35] = 9.001953125, // #
48         [36] = 6.9931640625, // $
49         [37] = 11.837890625, // %
50         [38] = 7.992187499999999, // &
51         [39] = 2.9541015625, // '
52         [40] = 4.9951171875, // (
53         [41] = 4.9951171875, // )
54         [42] = 6.9931640625, // *
55         [43] = 9.001953125, // +
56         [44] = 4.00146484375, // ,
57         [45] = 4.9951171875, // -
58         [46] = 4.00146484375, // .
59         [47] = 4.9951171875, // /
60         [48] = 6.9931640625, // 0
61         [49] = 6.9931640625, // 1
62         [50] = 6.9931640625, // 2
63         [51] = 6.9931640625, // 3
64         [52] = 6.9931640625, // 4
65         [53] = 6.9931640625, // 5
66         [54] = 6.9931640625, // 6
67         [55] = 6.9931640625, // 7
68         [56] = 6.9931640625, // 8
69         [57] = 6.9931640625, // 9
70         [58] = 4.9951171875, // :
71         [59] = 4.9951171875, // ;
72         [60] = 9.001953125, // <
73         [61] = 9.001953125, // =
74         [62] = 9.001953125, // >
75         [63] = 5.99951171875, // ?
76         [64] = 11.0, // @
77         [65] = 7.51953125, // A
78         [66] = 7.541015625, // B
79         [67] = 7.680664062499999, // C
80         [68] = 8.4755859375, // D
81         [69] = 6.95556640625, // E
82         [70] = 6.32177734375, // F
83         [71] = 8.529296875, // G
84         [72] = 8.26611328125, // H
85         [73] = 4.6298828125, // I
86         [74] = 5.00048828125, // J
87         [75] = 7.62158203125, // K
88         [76] = 6.123046875, // L
89         [77] = 9.2705078125, // M
90         [78] = 8.228515625, // N
91         [79] = 8.658203125, // O
92         [80] = 6.63330078125, // P
93         [81] = 8.658203125, // Q
94         [82] = 7.6484375, // R
95         [83] = 7.51953125, // S
96         [84] = 6.7783203125, // T
97         [85] = 8.05126953125, // U
98         [86] = 7.51953125, // V
99         [87] = 10.87646484375, // W
100         [88] = 7.53564453125, // X
101         [89] = 6.767578125, // Y
102         [90] = 7.53564453125, // Z
103         [91] = 4.9951171875, // [
104         [92] = 4.9951171875, // backslash
105         [93] = 4.9951171875, // ]
106         [94] = 9.001953125, // ^
107         [95] = 6.9931640625, // _
108         [96] = 6.9931640625, // `
109         [97] = 6.6064453125, // a
110         [98] = 6.853515625, // b
111         [99] = 5.73095703125, // c
112         [100] = 6.853515625, // d
113         [101] = 6.552734375, // e
114         [102] = 3.8671874999999996, // f
115         [103] = 6.853515625, // g
116         [104] = 6.9609375, // h
117         [105] = 3.0185546875, // i
118         [106] = 3.78662109375, // j
119         [107] = 6.509765625, // k
120         [108] = 3.0185546875, // l
121         [109] = 10.69921875, // m
122         [110] = 6.9609375, // n
123         [111] = 6.67626953125, // o
124         [112] = 6.853515625, // p
125         [113] = 6.853515625, // q
126         [114] = 4.6943359375, // r
127         [115] = 5.73095703125, // s
128         [116] = 4.33447265625, // t
129         [117] = 6.9609375, // u
130         [118] = 6.509765625, // v
131         [119] = 9.001953125, // w
132         [120] = 6.509765625, // x
133         [121] = 6.509765625, // y
134         [122] = 5.779296875, // z
135         [123] = 6.982421875, // {
136         [124] = 4.9951171875, // |
137         [125] = 6.982421875, // }
138         [126] = 9.001953125, // ~
139         [127] = 0.0,
140         [128] = 0.0,
141         [129] = 0.0,
142         [130] = 0.0,
143         [131] = 0.0,
144         [132] = 0.0,
145         [133] = 0.0,
146         [134] = 0.0,
147         [135] = 0.0,
148         [136] = 0.0,
149         [137] = 0.0,
150         [138] = 0.0,
151         [139] = 0.0,
152         [140] = 0.0,
153         [141] = 0.0,
154         [142] = 0.0,
155         [143] = 0.0,
156         [144] = 0.0,
157         [145] = 0.0,
158         [146] = 0.0,
159         [147] = 0.0,
160         [148] = 0.0,
161         [149] = 0.0,
162         [150] = 0.0,
163         [151] = 0.0,
164         [152] = 0.0,
165         [153] = 0.0,
166         [154] = 0.0,
167         [155] = 0.0,
168         [156] = 0.0,
169         [157] = 0.0,
170         [158] = 0.0,
171         [159] = 0.0,
172         [160] = 0.0,
173         [161] = 0.0,
174         [162] = 0.0,
175         [163] = 0.0,
176         [164] = 0.0,
177         [165] = 0.0,
178         [166] = 0.0,
179         [167] = 0.0,
180         [168] = 0.0,
181         [169] = 0.0,
182         [170] = 0.0,
183         [171] = 0.0,
184         [172] = 0.0,
185         [173] = 0.0,
186         [174] = 0.0,
187         [175] = 0.0,
188         [176] = 0.0,
189         [177] = 0.0,
190         [178] = 0.0,
191         [179] = 0.0,
192         [180] = 0.0,
193         [181] = 0.0,
194         [182] = 0.0,
195         [183] = 0.0,
196         [184] = 0.0,
197         [185] = 0.0,
198         [186] = 0.0,
199         [187] = 0.0,
200         [188] = 0.0,
201         [189] = 0.0,
202         [190] = 0.0,
203         [191] = 0.0,
204         [192] = 0.0,
205         [193] = 0.0,
206         [194] = 0.0,
207         [195] = 0.0,
208         [196] = 0.0,
209         [197] = 0.0,
210         [198] = 0.0,
211         [199] = 0.0,
212         [200] = 0.0,
213         [201] = 0.0,
214         [202] = 0.0,
215         [203] = 0.0,
216         [204] = 0.0,
217         [205] = 0.0,
218         [206] = 0.0,
219         [207] = 0.0,
220         [208] = 0.0,
221         [209] = 0.0,
222         [210] = 0.0,
223         [211] = 0.0,
224         [212] = 0.0,
225         [213] = 0.0,
226         [214] = 0.0,
227         [215] = 0.0,
228         [216] = 0.0,
229         [217] = 0.0,
230         [218] = 0.0,
231         [219] = 0.0,
232         [220] = 0.0,
233         [221] = 0.0,
234         [222] = 0.0,
235         [223] = 0.0,
236         [224] = 0.0,
237         [225] = 0.0,
238         [226] = 0.0,
239         [227] = 0.0,
240         [228] = 0.0,
241         [229] = 0.0,
242         [230] = 0.0,
243         [231] = 0.0,
244         [232] = 0.0,
245         [233] = 0.0,
246         [234] = 0.0,
247         [235] = 0.0,
248         [236] = 0.0,
249         [237] = 0.0,
250         [238] = 0.0,
251         [239] = 0.0,
252         [240] = 0.0,
253         [241] = 0.0,
254         [242] = 0.0,
255         [243] = 0.0,
256         [244] = 0.0,
257         [245] = 0.0,
258         [246] = 0.0,
259         [247] = 0.0,
260         [248] = 0.0,
261         [249] = 0.0,
262         [250] = 0.0,
263         [251] = 0.0,
264         [252] = 0.0,
265         [253] = 0.0,
266         [254] = 0.0,
267         [255] = 0.0
268 };
269
270 // find the width of the string using the verdana 11points font
271 // re-write the string in place, skiping zero-length characters
272 static inline int verdana11_width(char *s) {
273         double w = 0.0;
274         char *d = s;
275
276         while(*s) {
277                 double t = verdana11_widths[(unsigned char)*s];
278                 if(t == 0.0)
279                         s++;
280                 else {
281                         w += t + VERDANA_KERNING;
282                         if(d != s)
283                                 *d++ = *s++;
284                         else
285                                 d = ++s;
286                 }
287         }
288
289         *d = '\0';
290         w -= VERDANA_KERNING;
291         return ceil(w);
292 }
293
294 static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
295         size_t i = len;
296
297         // required escapes from
298         // https://github.com/badges/shields/blob/master/badge.js
299         while(*src && i) {
300                 switch(*src) {
301                         case '\\':
302                                 *dst++ = '/';
303                                 src++;
304                                 i--;
305                                 break;
306
307                         case '&':
308                                 if(i > 5) {
309                                         strcpy(dst, "&amp;");
310                                         i -= 5;
311                                         dst += 5;
312                                         src++;
313                                 }
314                                 else goto cleanup;
315                                 break;
316
317                         case '<':
318                                 if(i > 4) {
319                                         strcpy(dst, "&lt;");
320                                         i -= 4;
321                                         dst += 4;
322                                         src++;
323                                 }
324                                 else goto cleanup;
325                                 break;
326
327                         case '>':
328                                 if(i > 4) {
329                                         strcpy(dst, "&gt;");
330                                         i -= 4;
331                                         dst += 4;
332                                         src++;
333                                 }
334                                 else goto cleanup;
335                                 break;
336
337                         case '"':
338                                 if(i > 6) {
339                                         strcpy(dst, "&quot;");
340                                         i -= 6;
341                                         dst += 6;
342                                         src++;
343                                 }
344                                 else goto cleanup;
345                                 break;
346
347                         case '\'':
348                                 if(i > 6) {
349                                         strcpy(dst, "&apos;");
350                                         i -= 6;
351                                         dst += 6;
352                                         src++;
353                                 }
354                                 else goto cleanup;
355                                 break;
356
357                         default:
358                                 i--;
359                                 *dst++ = *src++;
360                                 break;
361                 }
362         }
363
364 cleanup:
365         *dst = '\0';
366         return len - i;
367 }
368
369 static inline const char *fix_units(const char *units) {
370         if(!units || !*units || !strcmp(units, "empty") || !strcmp(units, "null")) return "";
371         if(!strcmp(units, "percentage") || !strcmp(units, "percent") || !strcmp(units, "pcent")) return "%";
372         return units;
373 }
374
375 static inline const char *color_map(const char *color) {
376         // colors from:
377         // https://github.com/badges/shields/blob/master/colorscheme.json
378              if(!strcmp(color, "brightgreen")) return "#4c1";
379         else if(!strcmp(color, "green"))       return "#97CA00";
380         else if(!strcmp(color, "yellow"))      return "#dfb317";
381         else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
382         else if(!strcmp(color, "orange"))      return "#fe7d37";
383         else if(!strcmp(color, "red"))         return "#e05d44";
384         else if(!strcmp(color, "blue"))        return "#007ec6";
385         else if(!strcmp(color, "grey"))        return "#555";
386         else if(!strcmp(color, "gray"))        return "#555";
387         else if(!strcmp(color, "lightgrey"))   return "#9f9f9f";
388         else if(!strcmp(color, "lightgray"))   return "#9f9f9f";
389         return color;
390 }
391
392 static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value, int value_is_null) {
393         char color_buffer[256 + 1] = "";
394         char value_buffer[256 + 1] = "";
395         char comparison = '>';
396
397         // example input:
398         // color<max|color>min|color:null...
399
400         const char *c = color;
401         while(*c) {
402                 char *dc = color_buffer, *dv = NULL;
403                 size_t ci = 0, vi = 0;
404
405                 const char *t = c;
406
407                 while(*t && *t != '|') {
408                         switch(*t) {
409                                 case ':':
410                                         comparison = '=';
411                                         dv = value_buffer;
412                                         break;
413
414                                 case '}':
415                                 case ')':
416                                 case '>':
417                                         if(t[1] == '=') {
418                                                 comparison = ')';
419                                                 t++;
420                                         }
421                                         else
422                                                 comparison = '>';
423                                         dv = value_buffer;
424                                         break;
425
426                                 case '{':
427                                 case '(':
428                                 case '<':
429                                         if(t[1] == '=') {
430                                                 comparison = '(';
431                                                 t++;
432                                         }
433                                         else
434                                                 comparison = '<';
435                                         dv = value_buffer;
436                                         break;
437
438                                 default:
439                                         if(dv) {
440                                                 if(vi < 256) {
441                                                         vi++;
442                                                         *dv++ = *t;
443                                                 }
444                                         }
445                                         else {
446                                                 if(ci < 256) {
447                                                         ci++;
448                                                         *dc++ = *t;
449                                                 }
450                                         }
451                                         break;
452                         }
453
454                         t++;
455                 }
456
457                 // prepare for next iteration
458                 if(*t == '|') t++;
459                 c = t;
460
461                 // do the math
462                 *dc = '\0';
463                 if(dv) {
464                         *dv = '\0';
465
466                         if(value_is_null) {
467                                 if(!*value_buffer || !strcmp(value_buffer, "null"))
468                                         break;
469                         }
470                         else {
471                                 calculated_number v = strtold(value_buffer, NULL);
472
473                                      if(comparison == '<' && value < v) break;
474                                 else if(comparison == '(' && value <= v) break;
475                                 else if(comparison == '>' && value > v) break;
476                                 else if(comparison == ')' && value >= v) break;
477                                 else if(comparison == '=' && value == v) break;
478                         }
479                 }
480                 else
481                         break;
482         }
483
484         const char *b;
485         if(color_buffer[0])
486                 b = color_buffer;
487         else
488                 b = color;
489
490         strncpyz(final, b, len);
491 }
492
493 void buffer_svg(BUFFER *wb, const char *label, calculated_number value, const char *units, const char *label_color, const char *value_color, int value_is_null, int precision) {
494         char label_buffer[256 + 1], value_string[512 + 1], value_color_buffer[256 + 1];
495         char label_escaped[256 + 1], value_escaped[512 + 1], label_color_escaped[256 + 1], value_color_escaped[256 + 1];
496         int label_width, value_width, total_width;
497
498         if(!label_color || !*label_color) label_color = "#555";
499         if(!value_color || !*value_color) value_color = (value_is_null)?"#999":"#4c1";
500
501         units = fix_units(units);
502         calc_colorz(value_color, value_color_buffer, 256, value, value_is_null);
503
504         char *separator = "";
505         if(isalnum(*units)) separator = " ";
506
507         if(value_is_null)
508                 strcpy(value_string, "-");
509         else if(precision < 0) {
510                 calculated_number abs = (value < (calculated_number)0)?-value:value;
511                 if(abs > (calculated_number)1000.0)      snprintfz(value_string, 512, "%0.0Lf%s%s", (long double)value, separator, units);
512                 else if(abs > (calculated_number)100.0)  snprintfz(value_string, 512, "%0.1Lf%s%s", (long double)value, separator, units);
513                 else if(abs > (calculated_number)1.0)    snprintfz(value_string, 512, "%0.2Lf%s%s", (long double)value, separator, units);
514                 else if(abs > (calculated_number)0.1)    snprintfz(value_string, 512, "%0.3Lf%s%s", (long double)value, separator, units);
515                 else                                     snprintfz(value_string, 512, "%0.4Lf%s%s", (long double)value, separator, units);
516         }
517         else {
518                 if(precision > 50) precision = 50;
519                 snprintfz(value_string, 512, "%0.*Lf%s%s", precision, (long double)value, separator, units);
520         }
521
522         // we need to copy the label, since verdana11_width may write to it
523         strncpyz(label_buffer, label, 256);
524
525         label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
526         value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
527         total_width = label_width + value_width;
528
529         escape_xmlz(label_escaped, label_buffer, 256);
530         escape_xmlz(value_escaped, value_string, 256);
531         escape_xmlz(label_color_escaped, color_map(label_color), 256);
532         escape_xmlz(value_color_escaped, color_map(value_color_buffer), 256);
533
534         wb->contenttype = CT_IMAGE_SVG_XML;
535
536         // svg template from:
537         // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
538         buffer_sprintf(wb,
539                 "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"20\">"
540                         "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
541                                 "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
542                                 "<stop offset=\"1\" stop-opacity=\".1\"/>"
543                         "</linearGradient>"
544                         "<mask id=\"round\">"
545                                 "<rect width=\"%d\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
546                         "</mask>"
547                         "<g mask=\"url(#round)\">"
548                                 "<rect width=\"%d\" height=\"20\" fill=\"%s\"/>"
549                                 "<rect x=\"%d\" width=\"%d\" height=\"20\" fill=\"%s\"/>"
550                                 "<rect width=\"%d\" height=\"20\" fill=\"url(#smooth)\"/>"
551                         "</g>"
552                         "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
553                                 "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
554                                 "<text x=\"%d\" y=\"14\">%s</text>"
555                                 "<text x=\"%d\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
556                                 "<text x=\"%d\" y=\"14\">%s</text>"
557                         "</g>"
558                 "</svg>",
559                 total_width, total_width,
560                 label_width, label_color_escaped,
561                 label_width, value_width, value_color_escaped,
562                 total_width,
563                 label_width / 2, label_escaped,
564                 label_width / 2, label_escaped,
565                 label_width + value_width / 2 -1, value_escaped,
566                 label_width + value_width / 2 -1, value_escaped);
567 }