1 /** 2 Copyright: Copyright (c) 2015-2017 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.utils.textformatter; 7 8 import std.array; 9 import std.format; 10 import std.range; 11 12 char[4*1024] buf; 13 Appender!(char[]) app; 14 15 static this() 16 { 17 app = appender(buf[]); 18 } 19 20 static struct TextPtrs { 21 char* start; 22 char* end; 23 } 24 25 const(char)[] makeFormattedText(Args ...)(string fmt, Args args) { 26 app.clear(); 27 formattedWrite(app, fmt, args); 28 return app.data; 29 } 30 31 TextPtrs makeFormattedTextPtrs(Args ...)(string fmt, Args args) { 32 app.clear(); 33 formattedWrite(app, fmt, args); 34 app.put("\0"); 35 return TextPtrs(app.data.ptr, app.data.ptr + app.data.length - 1); 36 } 37 38 void igTextf(Args ...)(string fmt, Args args) 39 { 40 import derelict.imgui.imgui : igTextUnformatted; 41 TextPtrs pair = makeFormattedTextPtrs(fmt, args); 42 igTextUnformatted(pair.start, pair.end); 43 } 44 45 struct DigitSeparator(T, uint groupSize, char groupSeparator) 46 { 47 T value; 48 void toString(scope void delegate(const(char)[]) sink, 49 FormatSpec!char fmt) const 50 { 51 uint base = 52 fmt.spec == 'x' || fmt.spec == 'X' ? 16 : 53 fmt.spec == 'o' ? 8 : 54 fmt.spec == 'b' ? 2 : 55 fmt.spec == 's' || fmt.spec == 'd' || fmt.spec == 'u' ? 10 : 56 0; 57 assert(base > 0); 58 formatIntegral(sink, value, fmt, base, groupSize, groupSeparator, ulong.max); 59 } 60 } 61 62 // Modified version from std.format. 63 private void formatIntegral(Writer, T, Char)(Writer w, const(T) val, const ref FormatSpec!Char fmt, uint base, uint groupSize, char groupSeparator, ulong mask) 64 { 65 T arg = val; 66 67 bool negative = (base == 10 && arg < 0); 68 if (negative) 69 { 70 arg = -arg; 71 } 72 73 // All unsigned integral types should fit in ulong. 74 static if (is(ucent) && is(typeof(arg) == ucent)) 75 formatUnsigned(w, (cast(ucent) arg) & mask, fmt, base, groupSize, groupSeparator, negative); 76 else 77 formatUnsigned(w, (cast(ulong) arg) & mask, fmt, base, groupSize, groupSeparator, negative); 78 } 79 80 // Modified version from std.format. 81 private void formatUnsigned(Writer, T, Char)(Writer w, T arg, const ref FormatSpec!Char fmt, uint base, uint groupSize, char groupSeparator, bool negative) 82 { 83 /* Write string: 84 * leftpad prefix1 prefix2 zerofill digits rightpad 85 */ 86 87 /* Convert arg to digits[]. 88 * Note that 0 becomes an empty digits[] 89 */ 90 char[128] buffer = void; // 64 bits in base 2 at most and 1 separator for each 91 char[] digits; 92 { 93 size_t i = buffer.length; 94 size_t curGroupDigits = 0; 95 while (arg) 96 { 97 --i; 98 char c = cast(char) (arg % base); 99 arg /= base; 100 101 if (curGroupDigits == groupSize) { 102 buffer[i] = groupSeparator; 103 --i; 104 curGroupDigits = 0; 105 } 106 107 if (c < 10) 108 buffer[i] = cast(char)(c + '0'); 109 else 110 buffer[i] = cast(char)(c + (fmt.spec == 'x' ? 'a' - 10 : 'A' - 10)); 111 112 ++curGroupDigits; 113 } 114 digits = buffer[i .. $]; // got the digits without the sign 115 } 116 117 118 int precision = (fmt.precision == fmt.UNSPECIFIED) ? 1 : fmt.precision; 119 120 char padChar = 0; 121 if (!fmt.flDash) 122 { 123 padChar = (fmt.flZero && fmt.precision == fmt.UNSPECIFIED) ? '0' : ' '; 124 } 125 126 // Compute prefix1 and prefix2 127 char prefix1 = 0; 128 char prefix2 = 0; 129 if (base == 10) 130 { 131 if (negative) 132 prefix1 = '-'; 133 else if (fmt.flPlus) 134 prefix1 = '+'; 135 else if (fmt.flSpace) 136 prefix1 = ' '; 137 } 138 else if (base == 16 && fmt.flHash && digits.length) 139 { 140 prefix1 = '0'; 141 prefix2 = fmt.spec == 'x' ? 'x' : 'X'; 142 } 143 // adjust precision to print a '0' for octal if alternate format is on 144 else if (base == 8 && fmt.flHash && 145 (precision <= 1 || precision <= digits.length)) // too low precision 146 prefix1 = '0'; 147 148 size_t zerofill = precision > digits.length ? precision - digits.length : 0; 149 size_t leftpad = 0; 150 size_t rightpad = 0; 151 152 ptrdiff_t spacesToPrint = fmt.width - ((prefix1 != 0) + (prefix2 != 0) + zerofill + digits.length); 153 if (spacesToPrint > 0) // need to do some padding 154 { 155 if (padChar == '0') 156 zerofill += spacesToPrint; 157 else if (padChar) 158 leftpad = spacesToPrint; 159 else 160 rightpad = spacesToPrint; 161 } 162 163 /**** Print ****/ 164 165 foreach (i ; 0 .. leftpad) 166 put(w, ' '); 167 168 if (prefix1) put(w, prefix1); 169 if (prefix2) put(w, prefix2); 170 171 foreach (i ; 0 .. zerofill) 172 put(w, '0'); 173 174 put(w, digits); 175 176 foreach (i ; 0 .. rightpad) 177 put(w, ' '); 178 }