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