11 #include "appconfig.h"
13 #include "web_buffer.h"
14 #include "web_buffer_svg.h"
16 #define BADGE_HORIZONTAL_PADDING 4
17 #define VERDANA_KERNING 0.5
20 * verdana11_widths[] has been generated with this method:
21 * https://github.com/badges/shields/blob/master/measure-text.js
24 double verdana11_widths[256] = {
57 [32] = 3.8671874999999996, //
58 [33] = 4.3291015625, // !
59 [34] = 5.048828125, // "
60 [35] = 9.001953125, // #
61 [36] = 6.9931640625, // $
62 [37] = 11.837890625, // %
63 [38] = 7.992187499999999, // &
64 [39] = 2.9541015625, // '
65 [40] = 4.9951171875, // (
66 [41] = 4.9951171875, // )
67 [42] = 6.9931640625, // *
68 [43] = 9.001953125, // +
69 [44] = 4.00146484375, // ,
70 [45] = 4.9951171875, // -
71 [46] = 4.00146484375, // .
72 [47] = 4.9951171875, // /
73 [48] = 6.9931640625, // 0
74 [49] = 6.9931640625, // 1
75 [50] = 6.9931640625, // 2
76 [51] = 6.9931640625, // 3
77 [52] = 6.9931640625, // 4
78 [53] = 6.9931640625, // 5
79 [54] = 6.9931640625, // 6
80 [55] = 6.9931640625, // 7
81 [56] = 6.9931640625, // 8
82 [57] = 6.9931640625, // 9
83 [58] = 4.9951171875, // :
84 [59] = 4.9951171875, // ;
85 [60] = 9.001953125, // <
86 [61] = 9.001953125, // =
87 [62] = 9.001953125, // >
88 [63] = 5.99951171875, // ?
90 [65] = 7.51953125, // A
91 [66] = 7.541015625, // B
92 [67] = 7.680664062499999, // C
93 [68] = 8.4755859375, // D
94 [69] = 6.95556640625, // E
95 [70] = 6.32177734375, // F
96 [71] = 8.529296875, // G
97 [72] = 8.26611328125, // H
98 [73] = 4.6298828125, // I
99 [74] = 5.00048828125, // J
100 [75] = 7.62158203125, // K
101 [76] = 6.123046875, // L
102 [77] = 9.2705078125, // M
103 [78] = 8.228515625, // N
104 [79] = 8.658203125, // O
105 [80] = 6.63330078125, // P
106 [81] = 8.658203125, // Q
107 [82] = 7.6484375, // R
108 [83] = 7.51953125, // S
109 [84] = 6.7783203125, // T
110 [85] = 8.05126953125, // U
111 [86] = 7.51953125, // V
112 [87] = 10.87646484375, // W
113 [88] = 7.53564453125, // X
114 [89] = 6.767578125, // Y
115 [90] = 7.53564453125, // Z
116 [91] = 4.9951171875, // [
117 [92] = 4.9951171875, // backslash
118 [93] = 4.9951171875, // ]
119 [94] = 9.001953125, // ^
120 [95] = 6.9931640625, // _
121 [96] = 6.9931640625, // `
122 [97] = 6.6064453125, // a
123 [98] = 6.853515625, // b
124 [99] = 5.73095703125, // c
125 [100] = 6.853515625, // d
126 [101] = 6.552734375, // e
127 [102] = 3.8671874999999996, // f
128 [103] = 6.853515625, // g
129 [104] = 6.9609375, // h
130 [105] = 3.0185546875, // i
131 [106] = 3.78662109375, // j
132 [107] = 6.509765625, // k
133 [108] = 3.0185546875, // l
134 [109] = 10.69921875, // m
135 [110] = 6.9609375, // n
136 [111] = 6.67626953125, // o
137 [112] = 6.853515625, // p
138 [113] = 6.853515625, // q
139 [114] = 4.6943359375, // r
140 [115] = 5.73095703125, // s
141 [116] = 4.33447265625, // t
142 [117] = 6.9609375, // u
143 [118] = 6.509765625, // v
144 [119] = 9.001953125, // w
145 [120] = 6.509765625, // x
146 [121] = 6.509765625, // y
147 [122] = 5.779296875, // z
148 [123] = 6.982421875, // {
149 [124] = 4.9951171875, // |
150 [125] = 6.982421875, // }
151 [126] = 9.001953125, // ~
283 // find the width of the string using the verdana 11points font
284 // re-write the string in place, skiping zero-length characters
285 static inline int verdana11_width(char *s) {
290 double t = verdana11_widths[(unsigned char)*s];
294 w += t + VERDANA_KERNING;
303 w -= VERDANA_KERNING;
307 static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
310 // required escapes from
311 // https://github.com/badges/shields/blob/master/badge.js
322 strcpy(dst, "&");
352 strcpy(dst, """);
362 strcpy(dst, "'");
382 static inline const char *fix_units(const char *units) {
383 if(!units || !*units || !strcmp(units, "empty") || !strcmp(units, "null")) return "";
384 if(!strcmp(units, "percentage") || !strcmp(units, "percent") || !strcmp(units, "pcent")) return "%";
388 static inline const char *color_map(const char *color) {
390 // https://github.com/badges/shields/blob/master/colorscheme.json
391 if(!strcmp(color, "brightgreen")) return "#4c1";
392 else if(!strcmp(color, "green")) return "#97CA00";
393 else if(!strcmp(color, "yellow")) return "#dfb317";
394 else if(!strcmp(color, "yellowgreen")) return "#a4a61d";
395 else if(!strcmp(color, "orange")) return "#fe7d37";
396 else if(!strcmp(color, "red")) return "#e05d44";
397 else if(!strcmp(color, "blue")) return "#007ec6";
398 else if(!strcmp(color, "grey")) return "#555";
399 else if(!strcmp(color, "gray")) return "#555";
400 else if(!strcmp(color, "lightgrey")) return "#9f9f9f";
401 else if(!strcmp(color, "lightgray")) return "#9f9f9f";
405 static inline void calc_colorz(const char *color, char *final, size_t len, calculated_number value, int value_is_null) {
406 char color_buffer[256 + 1] = "";
407 char value_buffer[256 + 1] = "";
408 char comparison = '>';
411 // color<max|color>min|color:null...
413 const char *c = color;
415 char *dc = color_buffer, *dv = NULL;
416 size_t ci = 0, vi = 0;
420 while(*t && *t != '|') {
470 // prepare for next iteration
480 if(!*value_buffer || !strcmp(value_buffer, "null"))
484 calculated_number v = strtold(value_buffer, NULL);
486 if(comparison == '<' && value < v) break;
487 else if(comparison == '(' && value <= v) break;
488 else if(comparison == '>' && value > v) break;
489 else if(comparison == ')' && value >= v) break;
490 else if(comparison == '=' && value == v) break;
503 strncpyz(final, b, len);
506 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) {
507 char label_buffer[256 + 1], value_string[512 + 1], value_color_buffer[256 + 1];
508 char label_escaped[256 + 1], value_escaped[512 + 1], label_color_escaped[256 + 1], value_color_escaped[256 + 1];
509 int label_width, value_width, total_width;
511 if(!label_color || !*label_color) label_color = "#555";
512 if(!value_color || !*value_color) value_color = "#4c1";
514 units = fix_units(units);
515 calc_colorz(value_color, value_color_buffer, 256, value, value_is_null);
517 char *separator = "";
518 if(isalnum(*units)) separator = " ";
521 strcpy(value_string, "-");
523 calculated_number abs = (value < (calculated_number)0)?-value:value;
524 if(abs > (calculated_number)1000.0) snprintfz(value_string, 512, "%0.0Lf%s%s", (long double)value, separator, units);
525 else if(abs > (calculated_number)100.0) snprintfz(value_string, 512, "%0.1Lf%s%s", (long double)value, separator, units);
526 else if(abs > (calculated_number)1.0) snprintfz(value_string, 512, "%0.2Lf%s%s", (long double)value, separator, units);
527 else if(abs > (calculated_number)0.1) snprintfz(value_string, 512, "%0.3Lf%s%s", (long double)value, separator, units);
528 else snprintfz(value_string, 512, "%0.4Lf%s%s", (long double)value, separator, units);
531 // we need to copy the label, since verdana11_width may write to it
532 strncpyz(label_buffer, label, 256);
534 label_width = verdana11_width(label_buffer) + (BADGE_HORIZONTAL_PADDING * 2);
535 value_width = verdana11_width(value_string) + (BADGE_HORIZONTAL_PADDING * 2);
536 total_width = label_width + value_width;
538 escape_xmlz(label_escaped, label_buffer, 256);
539 escape_xmlz(value_escaped, value_string, 256);
540 escape_xmlz(label_color_escaped, color_map(label_color), 256);
541 escape_xmlz(value_color_escaped, color_map(value_color_buffer), 256);
543 wb->contenttype = CT_IMAGE_SVG_XML;
545 // svg template from:
546 // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
548 "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%zu\" height=\"20\">"
549 "<linearGradient id=\"smooth\" x2=\"0\" y2=\"100%%\">"
550 "<stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>"
551 "<stop offset=\"1\" stop-opacity=\".1\"/>"
553 "<mask id=\"round\">"
554 "<rect width=\"%zu\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
556 "<g mask=\"url(#round)\">"
557 "<rect width=\"%zu\" height=\"20\" fill=\"%s\"/>"
558 "<rect x=\"%zu\" width=\"%zu\" height=\"20\" fill=\"%s\"/>"
559 "<rect width=\"%zu\" height=\"20\" fill=\"url(#smooth)\"/>"
561 "<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">"
562 "<text x=\"%zu\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
563 "<text x=\"%zu\" y=\"14\">%s</text>"
564 "<text x=\"%zu\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">%s</text>"
565 "<text x=\"%zu\" y=\"14\">%s</text>"
568 total_width, total_width,
569 label_width, label_color_escaped,
570 label_width, value_width, value_color_escaped,
572 label_width / 2, label_escaped,
573 label_width / 2, label_escaped,
574 label_width + value_width / 2 -1, value_escaped,
575 label_width + value_width / 2 -1, value_escaped);