1 /**
2 Copyright: Copyright (c) 2015-2016 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 }