3 #define BADGE_HORIZONTAL_PADDING 4
4 #define VERDANA_KERNING 0.2
5 #define VERDANA_PADDING 1.0
8 * verdana11_widths[] has been generated with this method:
9 * https://github.com/badges/shields/blob/master/measure-text.js
12 double verdana11_widths[256] = {
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, // ?
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, // ~
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) {
278 double t = verdana11_widths[(unsigned char)*s];
282 w += t + VERDANA_KERNING;
291 w -= VERDANA_KERNING;
292 w += VERDANA_PADDING;
296 static inline size_t escape_xmlz(char *dst, const char *src, size_t len) {
299 // required escapes from
300 // https://github.com/badges/shields/blob/master/badge.js
311 strcpy(dst, "&");
341 strcpy(dst, """);
351 strcpy(dst, "'");
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)))
375 char *separator = "";
376 if(unlikely(isalnum(*units)))
380 int len, lstop = 0, trim_zeros = 1;
382 calculated_number abs = value;
383 if(isless(value, 0)) {
388 if(isgreaterequal(abs, 1000)) {
389 len = snprintfz(value_string, value_string_len, "%0.0Lf", (long double) value);
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);
397 if(unlikely(trim_zeros)) {
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';
406 else if(unlikely(value_string[l] == '.')) {
407 value_string[l] = '\0';
417 if(unlikely(len <= 0)) len = 1;
418 snprintfz(&value_string[len], value_string_len - len, "%s%s", separator, units);
421 if(precision > 50) precision = 50;
422 snprintfz(value_string, value_string_len, "%0.*Lf%s%s", precision, (long double) value, separator, units);
428 inline char *format_value_and_unit(char *value_string, size_t value_string_len, calculated_number value, const char *units, int precision) {
431 hash_seconds_ago = 0,
433 hash_minutes_ago = 0,
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");
464 if(unlikely(!units)) units = "";
466 uint32_t hash_units = simple_hash(units);
468 if(unlikely((hash_units == hash_seconds && !strcmp(units, "seconds")) || (hash_units == hash_seconds_ago && !strcmp(units, "seconds ago")))) {
470 snprintfz(value_string, value_string_len, "%s", "now");
473 else if(isnan(value) || isinf(value)) {
474 snprintfz(value_string, value_string_len, "%s", "never");
478 const char *suffix = (hash_units == hash_seconds_ago)?" ago":"";
480 size_t s = (size_t)value;
481 size_t d = s / 86400;
491 snprintfz(value_string, value_string_len, "%zu %s %02zu:%02zu:%02zu%s", d, (d == 1)?"day":"days", h, m, s, suffix);
493 snprintfz(value_string, value_string_len, "%02zu:%02zu:%02zu%s", h, m, s, suffix);
498 else if(unlikely((hash_units == hash_minutes && !strcmp(units, "minutes")) || (hash_units == hash_minutes_ago && !strcmp(units, "minutes ago")))) {
500 snprintfz(value_string, value_string_len, "%s", "now");
503 else if(isnan(value) || isinf(value)) {
504 snprintfz(value_string, value_string_len, "%s", "never");
508 const char *suffix = (hash_units == hash_minutes_ago)?" ago":"";
510 size_t m = (size_t)value;
511 size_t d = m / (60 * 24);
518 snprintfz(value_string, value_string_len, "%zud %02zuh %02zum%s", d, h, m, suffix);
520 snprintfz(value_string, value_string_len, "%zuh %zum%s", h, m, suffix);
525 else if(unlikely((hash_units == hash_hours && !strcmp(units, "hours")) || (hash_units == hash_hours_ago && !strcmp(units, "hours ago")))) {
527 snprintfz(value_string, value_string_len, "%s", "now");
530 else if(isnan(value) || isinf(value)) {
531 snprintfz(value_string, value_string_len, "%s", "never");
535 const char *suffix = (hash_units == hash_hours_ago)?" ago":"";
537 size_t h = (size_t)value;
542 snprintfz(value_string, value_string_len, "%zud %zuh%s", d, h, suffix);
544 snprintfz(value_string, value_string_len, "%zuh%s", h, suffix);
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");
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");
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");
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");
569 else if(unlikely(hash_units == hash_empty && !strcmp(units, "empty")))
572 else if(unlikely(hash_units == hash_null && !strcmp(units, "null")))
575 else if(unlikely(hash_units == hash_percentage && !strcmp(units, "percentage")))
578 else if(unlikely(hash_units == hash_percent && !strcmp(units, "percent")))
581 else if(unlikely(hash_units == hash_pcent && !strcmp(units, "pcent")))
585 if(unlikely(isnan(value) || isinf(value))) {
586 strcpy(value_string, "-");
590 return format_value_with_precision_and_unit(value_string, value_string_len, value, units, precision);
593 static inline const char *color_map(const char *color) {
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";
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)) {
617 char color_buffer[256 + 1] = "";
618 char value_buffer[256 + 1] = "";
619 char comparison = '>';
622 // color<max|color>min|color:null...
624 const char *c = color;
626 char *dc = color_buffer, *dv = NULL;
627 size_t ci = 0, vi = 0;
631 while(*t && *t != '|') {
681 // prepare for next iteration
691 if(!*value_buffer || !strcmp(value_buffer, "null"))
695 calculated_number v = str2l(value_buffer);
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;
714 strncpyz(final, b, len);
718 #define VALUE_STRING_SIZE 100
721 #define LABEL_STRING_SIZE 200
724 #define COLOR_STRING_SIZE 100
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];
735 int label_width, value_width, total_width;
737 if(unlikely(!label_color || !*label_color))
738 label_color = "#555";
740 if(unlikely(!value_color || !*value_color))
741 value_color = (isnan(value) || isinf(value))?"#999":"#4c1";
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);
746 // we need to copy the label, since verdana11_width may write to it
747 strncpyz(label_buffer, label, LABEL_STRING_SIZE);
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;
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);
758 wb->contenttype = CT_IMAGE_SVG_XML;
760 // svg template from:
761 // https://raw.githubusercontent.com/badges/shields/master/templates/flat-template.svg
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\"/>"
768 "<mask id=\"round\">"
769 "<rect width=\"%d\" height=\"20\" rx=\"3\" fill=\"#fff\"/>"
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)\"/>"
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>"
783 total_width, total_width,
784 label_width, label_color_escaped,
785 label_width, value_width, value_color_escaped,
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);