1 /*
2 mkvmerge -- utility for splicing together matroska files
3 from component media subtypes
4
5 Distributed under the GPL v2
6 see the file COPYING for details
7 or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
8
9 definitions used in all programs, helper functions
10
11 Written by Moritz Bunkus <moritz@bunkus.org>.
12 */
13
14 #include "common/common_pch.h"
15
16 #include <cctype>
17
18 #include "common/list_utils.h"
19 #include "common/strings/formatting.h"
20 #include "common/strings/utf8.h"
21 #include "common/terminal.h"
22 #include "common/translation.h"
23
24 namespace mtx::string {
25
26 std::string
format_timestamp(int64_t timestamp,unsigned int precision)27 format_timestamp(int64_t timestamp,
28 unsigned int precision) {
29 bool negative = 0 > timestamp;
30 if (negative)
31 timestamp *= -1;
32
33 if (9 > precision) {
34 auto shift = 5ll;
35 for (int shift_idx = 9 - precision; shift_idx > 1; --shift_idx)
36 shift *= 10;
37 timestamp += shift;
38 }
39
40 auto result = fmt::format("{0}{1:02}:{2:02}:{3:02}",
41 negative ? "-" : "",
42 timestamp / 60 / 60 / 1'000'000'000,
43 (timestamp / 60 / 1'000'000'000) % 60,
44 (timestamp / 1'000'000'000) % 60);
45
46 if (9 < precision)
47 precision = 9;
48
49 if (precision) {
50 auto decimals = fmt::format(".{0:09}", timestamp % 1'000'000'000);
51
52 if (decimals.length() > (precision + 1))
53 decimals.erase(precision + 1);
54
55 result += decimals;
56 }
57
58 return result;
59 }
60
61 std::string
format_timestamp(int64_t timestamp,std::string const & format)62 format_timestamp(int64_t timestamp,
63 std::string const &format) {
64 auto result = std::string{};
65 auto width = 0u;
66 auto escaped = false;
67
68 for (auto const &c : format) {
69 if (escaped) {
70 if (std::isdigit(c))
71 width = width * 10 + (c - '0');
72
73 else if (mtx::included_in(c, 'h', 'm', 's', 'H', 'M', 'S', 'n')) {
74 auto lc = std::tolower(c);
75 auto value = lc == 'h' ? (timestamp / 60 / 60 / 1000000000ll)
76 : lc == 'm' ? ((timestamp / 60 / 1000000000ll) % 60)
77 : lc == 's' ? ((timestamp / 1000000000ll) % 60)
78 : (timestamp % 1000000000ll);
79
80 if (c == 'n') {
81 auto temp = fmt::format("{0:09}", value);
82 if (width && (temp.length() > width))
83 temp.erase(width);
84
85 result += temp;
86
87 } else
88 result += fmt::format(std::isupper(c) ? "{0:02}" : "{0}", value);
89
90 } else {
91 result += c;
92 escaped = false;
93 }
94
95 } else if (c == '%') {
96 escaped = true;
97 width = 0;
98
99 } else
100 result += c;
101 }
102
103 return result;
104 }
105
106 std::string
format_rational(int64_t numerator,int64_t denominator,unsigned int precision)107 format_rational(int64_t numerator,
108 int64_t denominator,
109 unsigned int precision) {
110 auto output = fmt::to_string(numerator / denominator);
111 int64_t fractional_part = numerator % denominator;
112
113 if (0 == fractional_part)
114 return output;
115
116 output += fmt::format(".{0:0{1}}", fractional_part, precision);
117 std::string::iterator end = output.end() - 1;
118
119 while (*end == '0')
120 --end;
121 if (*end == '.')
122 --end;
123
124 output.erase(end + 1, output.end());
125
126 return output;
127 }
128
129 static std::wstring
format_paragraph(const std::wstring & text_to_wrap,int indent_column,const std::wstring & indent_first_line,std::wstring indent_following_lines,int wrap_column,const std::wstring & break_chars)130 format_paragraph(const std::wstring &text_to_wrap,
131 int indent_column,
132 const std::wstring &indent_first_line,
133 std::wstring indent_following_lines,
134 int wrap_column,
135 const std::wstring &break_chars) {
136 std::wstring text = indent_first_line;
137 int current_column = get_width_in_em(text);
138 bool break_anywhere = translation_c::get_active_translation().m_line_breaks_anywhere;
139
140 if (WRAP_AT_TERMINAL_WIDTH == wrap_column)
141 wrap_column = get_terminal_columns() - 1;
142
143 if ((0 != indent_column) && (current_column >= indent_column)) {
144 text += L"\n";
145 current_column = 0;
146 }
147
148 if (indent_following_lines.empty())
149 indent_following_lines = std::wstring(indent_column, L' ');
150
151 text += std::wstring(indent_column - current_column, L' ');
152 current_column = indent_column;
153 std::wstring::size_type current_pos = 0;
154 bool first_word_in_line = true;
155 bool needs_space = false;
156
157 while (text_to_wrap.length() > current_pos) {
158 std::wstring::size_type word_start = text_to_wrap.find_first_not_of(L" ", current_pos);
159 if (std::string::npos == word_start)
160 break;
161
162 if (word_start != current_pos)
163 needs_space = true;
164
165 std::wstring::size_type word_end = text_to_wrap.find_first_of(break_chars, word_start);
166 bool next_needs_space = false;
167 if (std::wstring::npos == word_end)
168 word_end = text_to_wrap.length();
169
170 else if (text_to_wrap[word_end] != L' ')
171 ++word_end;
172
173 else
174 next_needs_space = true;
175
176 std::wstring word = text_to_wrap.substr(word_start, word_end - word_start);
177 bool needs_space_now = needs_space && (text_to_wrap.substr(word_start, 1).find_first_of(break_chars) == std::wstring::npos);
178 size_t word_length = get_width_in_em(word);
179 size_t new_column = current_column + (needs_space_now ? 0 : 1) + word_length;
180
181 if (break_anywhere && (new_column >= static_cast<size_t>(wrap_column))) {
182 size_t offset = 0;
183 while (((word_end - 1) > word_start) && ((128 > text_to_wrap[word_end - 1]) || ((new_column - offset) >= static_cast<size_t>(wrap_column)))) {
184 offset += get_width_in_em(text_to_wrap[word_end - 1]);
185 word_end -= 1;
186 }
187
188 if (0 != offset)
189 next_needs_space = false;
190
191 word_length -= offset;
192 new_column -= offset;
193 word.erase(word_end - word_start);
194 }
195
196 if (!first_word_in_line && (new_column >= static_cast<size_t>(wrap_column))) {
197 text += L"\n" + indent_following_lines;
198 current_column = indent_column;
199 first_word_in_line = true;
200 }
201
202 if (!first_word_in_line && needs_space_now) {
203 text += L" ";
204 ++current_column;
205 }
206
207 text += word;
208 current_column += word_length;
209 current_pos = word_end;
210 first_word_in_line = false;
211 needs_space = next_needs_space;
212 }
213
214 text += L"\n";
215
216 return text;
217 }
218
219 std::string
format_paragraph(const std::string & text_to_wrap,int indent_column,const std::string & indent_first_line,std::string indent_following_lines,int wrap_column,const char * break_chars)220 format_paragraph(const std::string &text_to_wrap,
221 int indent_column,
222 const std::string &indent_first_line,
223 std::string indent_following_lines,
224 int wrap_column,
225 const char *break_chars) {
226 return to_utf8(format_paragraph(to_wide(text_to_wrap), indent_column, to_wide(indent_first_line), to_wide(indent_following_lines), wrap_column, to_wide(break_chars)));
227 }
228
229 std::string
to_hex(const unsigned char * buf,size_t size,bool compact)230 to_hex(const unsigned char *buf,
231 size_t size,
232 bool compact) {
233 if (!buf || !size)
234 return {};
235
236 std::string hex;
237 for (int idx = 0; idx < static_cast<int>(size); ++idx)
238 hex += (compact || hex.empty() ? ""s : " "s) + fmt::format(compact ? "{0:02x}" : "0x{0:02x}", static_cast<unsigned int>(buf[idx]));
239
240 return hex;
241 }
242
243 std::string
create_minutes_seconds_time_string(unsigned int seconds,bool omit_minutes_if_zero)244 create_minutes_seconds_time_string(unsigned int seconds,
245 bool omit_minutes_if_zero) {
246 unsigned int minutes = seconds / 60;
247 seconds = seconds % 60;
248
249 std::string result = fmt::format(NY("{0} second", "{0} seconds", seconds), seconds);
250
251 if (!minutes && omit_minutes_if_zero)
252 return result;
253
254 return fmt::format("{0} {1}", fmt::format(NY("{0} minute", "{0} minutes", minutes), minutes), result);
255 }
256
257 std::string
format_file_size(int64_t size)258 format_file_size(int64_t size) {
259 return size < 1024ll ? fmt::format(NY("{0} byte", "{0} bytes", size), size)
260 : size < 1048576ll ? fmt::format(Y("{0}.{1} KiB"), size / 1024, (size * 10 / 1024) % 10)
261 : size < 1073741824ll ? fmt::format(Y("{0}.{1} MiB"), size / 1024 / 1024, (size * 10 / 1024 / 1024) % 10)
262 : fmt::format(Y("{0}.{1} GiB"), size / 1024 / 1024 / 1024, (size * 10 / 1024 / 1024 / 1024) % 10);
263 }
264
265 std::string
format_number(uint64_t number)266 format_number(uint64_t number) {
267 std::string output;
268
269 if (number == 0)
270 return "0";
271
272 while (number != 0) {
273 if (((output.size() + 1) % 4) == 0)
274 output += '.';
275
276 output += ('0' + (number % 10));
277 number /= 10;
278 }
279
280 std::reverse(output.begin(), output.end());
281
282 return output;
283 }
284
285 std::string
format_number(int64_t n)286 format_number(int64_t n) {
287 auto sign = std::string{ n < 0 ? "-" : "" };
288 return sign + format_number(static_cast<uint64_t>(std::abs(n)));
289 }
290
291 std::string
elide_string(std::string s,unsigned int max_length)292 elide_string(std::string s,
293 unsigned int max_length) {
294 if ((s.size() < max_length) || !max_length)
295 return s;
296
297 s.resize(max_length - 1);
298 s += u8"…";
299
300 return s;
301 }
302
303 static std::string
to_upper_or_lower_ascii(std::string const & src,bool downcase)304 to_upper_or_lower_ascii(std::string const &src,
305 bool downcase) {
306 std::string dst;
307
308 char diff = downcase ? 'a' - 'A' : 'A' - 'a';
309 char min = downcase ? 'A' : 'a';
310 char max = downcase ? 'Z' : 'z';
311
312 dst.reserve(src.size());
313
314 for (auto c : src)
315 dst += (c >= min) && (c <= max) ? static_cast<char>(c + diff) : c;
316
317 return dst;
318 }
319
320 std::string
to_upper_ascii(std::string const & src)321 to_upper_ascii(std::string const &src) {
322 return to_upper_or_lower_ascii(src, false);
323 }
324
325 std::string
to_lower_ascii(std::string const & src)326 to_lower_ascii(std::string const &src) {
327 return to_upper_or_lower_ascii(src, true);
328 }
329
330 std::vector<std::string>
to_lower_upper_ascii(std::vector<std::string> const & elements,std::function<std::string (std::string const &)> converter)331 to_lower_upper_ascii(std::vector<std::string> const &elements,
332 std::function<std::string(std::string const &)> converter) {
333 std::vector<std::string> result;
334
335 result.reserve(elements.size());
336
337 for (auto const &element : elements)
338 result.emplace_back(converter(element));
339
340 return result;
341 }
342
343 std::vector<std::string>
to_lower_ascii(std::vector<std::string> const & src)344 to_lower_ascii(std::vector<std::string> const &src) {
345 return to_lower_upper_ascii(src, [](auto const &elt) { return to_lower_ascii(elt); });
346 }
347
348 std::vector<std::string>
to_upper_ascii(std::vector<std::string> const & src)349 to_upper_ascii(std::vector<std::string> const &src) {
350 return to_lower_upper_ascii(src, [](auto const &elt) { return to_upper_ascii(elt); });
351 }
352
353 } // mtx::string
354