]> arthur.barton.de Git - netdata.git/blob - src/web_buffer_svg.c
Merge remote-tracking branch 'upstream/master' into health
[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 }