• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gansm / finalcut / #760

06 Feb 2026 01:45AM UTC coverage: 69.064% (-0.02%) from 69.083%
#760

push

travis-ci

gansm
Reduce pointer arithmetic and use safe iterators more often

206 of 432 new or added lines in 27 files covered. (47.69%)

25 existing lines in 6 files now uncovered.

37622 of 54474 relevant lines covered (69.06%)

243.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.67
/final/util/fstring.cpp
1
/***********************************************************************
2
* fstring.cpp - Unicode string class with UTF-8 support                *
3
*                                                                      *
4
* This file is part of the FINAL CUT widget toolkit                    *
5
*                                                                      *
6
* Copyright 2012-2026 Markus Gans                                      *
7
*                                                                      *
8
* FINAL CUT is free software; you can redistribute it and/or modify    *
9
* it under the terms of the GNU Lesser General Public License as       *
10
* published by the Free Software Foundation; either version 3 of       *
11
* the License, or (at your option) any later version.                  *
12
*                                                                      *
13
* FINAL CUT is distributed in the hope that it will be useful, but     *
14
* WITHOUT ANY WARRANTY; without even the implied warranty of           *
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
16
* GNU Lesser General Public License for more details.                  *
17
*                                                                      *
18
* You should have received a copy of the GNU Lesser General Public     *
19
* License along with this program.  If not, see                        *
20
* <http://www.gnu.org/licenses/>.                                      *
21
***********************************************************************/
22

23
#include <algorithm>
24
#include <string>
25
#include <utility>
26
#include <vector>
27

28
#include "final/fapplication.h"
29
#include "final/util/flog.h"
30
#include "final/util/fstring.h"
31

32
namespace finalcut
33
{
34

35
// Herper functions
36
template <typename T>
37
constexpr auto isExactlyEqual (T rhs, T lhs) noexcept -> bool
5✔
38
{
39
#if defined(__clang__)
40
  #pragma clang diagnostic push
41
  #pragma clang diagnostic ignored "-Wfloat-equal"
42
#endif
43

44
  return rhs == lhs;
5✔
45

46
#if defined(__clang__)
47
  #pragma clang diagnostic pop
48
#endif
49
}
50

51
// static class attributes
52
wchar_t       FString::null_char{L'\0'};
53
const wchar_t FString::const_null_char{L'\0'};
54

55
//----------------------------------------------------------------------
56
// class FString
57
//----------------------------------------------------------------------
58

59
// constructors and destructor
60
//----------------------------------------------------------------------
61
FString::FString (int len)
11✔
62
{
63
  if ( len > 0 )
11✔
64
    string = std::wstring(size_type(len), L'\0');
14✔
65
}
11✔
66

67
//----------------------------------------------------------------------
68
FString::FString (size_type len)
1✔
69
  : string{std::wstring(len, L'\0')}
3✔
70
{ }
1✔
71

72
//----------------------------------------------------------------------
73
FString::FString (size_type len, wchar_t c)
14✔
74
  : string{std::wstring(len, c)}
42✔
75
{ }
14✔
76

77
//----------------------------------------------------------------------
78
FString::FString (size_type len, const UniChar& c)
2✔
79
  : FString(len, wchar_t(c))
2✔
80
{ }
2✔
81

82
//----------------------------------------------------------------------
83
FString::FString (const FString& s)  // copy constructor
754✔
84
  : string{s.string}
754✔
85
{ }
754✔
86

87
//----------------------------------------------------------------------
88
FString::FString (FString&& s) noexcept  // move constructor
2,668✔
89
  : string{std::move(s.string)}
2,668✔
90
{ }
2,668✔
91

92
//----------------------------------------------------------------------
93
FString::FString (const std::wstring& s)
865✔
94
{
95
  internal_assign(std::wstring{s});
865✔
96
}
865✔
97

98
//----------------------------------------------------------------------
99
#if __cplusplus >= 201703L
100
FString::FString (const std::wstring_view& s)
101
{
102
  if ( ! s.empty() )
103
    internal_assign(std::wstring{s});
104
}
105
#endif
106

107
//----------------------------------------------------------------------
108
FString::FString (std::wstring&& s)
2,706✔
109
  : string{std::move(s)}
2,706✔
110
{ }
2,706✔
111

112
//----------------------------------------------------------------------
113
FString::FString (const wchar_t s[])
3,448✔
114
{
115
  if ( s )
3,448✔
116
    internal_assign(std::wstring{s});
10,329✔
117
}
3,448✔
118

119
//----------------------------------------------------------------------
120
FString::FString (const std::string& s)
58✔
121
{
122
  if ( ! s.empty() )
58✔
123
    internal_assign(internal_toWideString(s.c_str()));
55✔
124
}
58✔
125

126
//----------------------------------------------------------------------
127
#if __cplusplus >= 201703L
128
FString::FString (const std::string_view& s)
129
{
130
  if ( ! s.empty() )
131
    internal_assign(internal_toWideString(s.data()));
132
}
133
#endif
134

135
//----------------------------------------------------------------------
136
FString::FString (const char s[])
2,837✔
137
{
138
  if ( s )
2,837✔
139
    internal_assign(internal_toWideString(s));
2,492✔
140
}
2,837✔
141

142
//----------------------------------------------------------------------
143
FString::FString (const UniChar& c)
1✔
144
{
145
  internal_assign(std::wstring{static_cast<wchar_t>(c)});
2✔
146
}
1✔
147

148
//----------------------------------------------------------------------
149
FString::FString (const wchar_t c)
290✔
150
{
151
  if ( c )
290✔
152
    internal_assign(std::wstring{c});
861✔
153
}
290✔
154

155
//----------------------------------------------------------------------
156
FString::FString (const char c)
129✔
157
{
158
  if ( c )
129✔
159
    internal_assign(std::wstring{wchar_t(uChar(c))});
366✔
160
}
129✔
161

162
//----------------------------------------------------------------------
163
FString::~FString() noexcept = default;  // destructor
19,371✔
164

165

166
// FString operators
167
//----------------------------------------------------------------------
168
auto FString::operator = (const FString& s) -> FString&
100✔
169
{
170
  if ( &s != this )
100✔
171
    internal_assign(std::wstring{s.string});
100✔
172

173
  return *this;
100✔
174
}
175

176
//----------------------------------------------------------------------
177
auto FString::operator = (FString&& s) noexcept -> FString&
775✔
178
{
179
  if ( &s != this )
775✔
180
    string = s.string;
775✔
181

182
  return *this;
775✔
183
}
184

185
//----------------------------------------------------------------------
186
auto FString::operator += (const FString& s) -> const FString&
18✔
187
{
188
  string.append(s.string);
18✔
189
  return *this;
18✔
190
}
191

192
//----------------------------------------------------------------------
193
auto FString::operator << (const FString& s) -> FString&
8✔
194
{
195
  string.append(s.string);
8✔
196
  return *this;
8✔
197
}
198

199
//----------------------------------------------------------------------
200
auto FString::operator << (const UniChar& c) -> FString&
1✔
201
{
202
  FString s{static_cast<wchar_t>(c)};
1✔
203
  string.append(s.string);
1✔
204
  return *this;
1✔
205
}
1✔
206

207
//----------------------------------------------------------------------
208
auto FString::operator << (const wchar_t c) -> FString&
1✔
209
{
210
  FString s{c};
1✔
211
  string.append(s.string);
1✔
212
  return *this;
1✔
213
}
1✔
214

215
//----------------------------------------------------------------------
216
auto FString::operator << (const char c) -> FString&
1✔
217
{
218
  FString s{c};
1✔
219
  string.append(s.string);
1✔
220
  return *this;
1✔
221
}
1✔
222

223
//----------------------------------------------------------------------
224
FString::operator bool () const noexcept
83✔
225
{
226
  return ! isEmpty();
83✔
227
}
228

229
//----------------------------------------------------------------------
230
auto FString::operator () () const noexcept -> const FString&
3✔
231
{
232
  return *this;
3✔
233
}
234

235

236
// public methods of FString
237
//----------------------------------------------------------------------
238
auto FString::clear() noexcept -> FString&
45✔
239
{
240
  string.clear();
45✔
241
  return *this;
45✔
242
}
243

244
//----------------------------------------------------------------------
245
auto FString::reserve (size_type new_capacity) -> FString&
1✔
246
{
247
  string.reserve(new_capacity);
1✔
248
  return *this;
1✔
249
}
250

251
//----------------------------------------------------------------------
252
auto FString::shrink_to_fit() -> FString&
1✔
253
{
254
  string.shrink_to_fit();
1✔
255
  return *this;
1✔
256
}
257

258
//----------------------------------------------------------------------
259
auto FString::resize (size_type count) -> FString&
1✔
260
{
261
  string.resize(count);
1✔
262
  return *this;
1✔
263
}
264

265
//----------------------------------------------------------------------
266
auto FString::resize (size_type count, wchar_t c) -> FString&
1✔
267
{
268
  string.resize(count, c);
1✔
269
  return *this;
1✔
270
}
271

272
//----------------------------------------------------------------------
273
auto FString::wc_str() const noexcept -> const wchar_t*
73✔
274
{
275
  // Returns a constant wide character string
276

277
  return string.c_str();
73✔
278
}
279

280
//----------------------------------------------------------------------
281
auto FString::wc_str() noexcept -> wchar_t*
63✔
282
{
283
  // Returns a wide character string
284

285
  return const_cast<wchar_t*>(string.c_str());
63✔
286
}
287

288
//----------------------------------------------------------------------
289
auto FString::c_str() const -> const char*
14✔
290
{
291
  // Returns a constant c-string
292

293
  if ( isEmpty() )
14✔
294
    return "";
3✔
295

296
  char_string = internal_toCharString(string);
11✔
297
  return char_string.c_str();
11✔
298
}
299

300
//----------------------------------------------------------------------
301
auto FString::c_str() -> char*
87✔
302
{
303
  // Returns a c-string
304

305
  if ( isEmpty() )
87✔
306
    return const_cast<char*>("");
7✔
307

308
  char_string = internal_toCharString(string);
80✔
309
  return const_cast<char*>(char_string.c_str());
80✔
310
}
311

312
//----------------------------------------------------------------------
313
auto FString::toWString() const -> std::wstring
3,221✔
314
{
315
  return string;
3,221✔
316
}
317

318
//----------------------------------------------------------------------
319
auto FString::toString() const -> std::string
13✔
320
{
321
  return internal_toCharString(string);
13✔
322
}
323

324
//----------------------------------------------------------------------
325
auto FString::toLower() const -> FString
1✔
326
{
327
  FString str;
1✔
328
  str.string.reserve(string.length());
1✔
329
  constexpr wchar_t offset = L'a' - L'A';
1✔
330

331
  for (wchar_t c : string)
4✔
332
  {
333
    if ( c < 128 )  // Fast path for ASCII
3✔
334
    {
335
      const bool is_upper = (c >= L'A') && (c <= L'Z');
3✔
336
      str.string.push_back(is_upper ? c + offset : c);
3✔
337
    }
338
    else  // Slow path for non-ASCII (uses locale)
339
    {
340
      str.string.push_back(wchar_t(std::towupper(std::wint_t(c))));
×
341
    }
342
  }
343

344
  return str;
1✔
345
}
×
346

347
//----------------------------------------------------------------------
348
auto FString::toUpper() const -> FString
1✔
349
{
350
  FString str;
1✔
351
  str.string.reserve(string.length());
1✔
352
  constexpr wchar_t offset = L'a' - L'A';
1✔
353

354
  for (wchar_t c : string)
4✔
355
  {
356
    if ( c < 128 )  // Fast path for ASCII
3✔
357
    {
358
      const bool is_lower = (c >= L'a') && (c <= L'z');
3✔
359
      str.string.push_back(is_lower ? c - offset : c);
3✔
360
    }
361
    else  // Slow path for non-ASCII (uses locale)
362
    {
363
      str.string.push_back(wchar_t(std::towupper(std::wint_t(c))));
×
364
    }
365
  }
366

367
  return str;
1✔
368
}
×
369

370
//----------------------------------------------------------------------
371
auto FString::toShort() const -> sInt16
4✔
372
{
373
  const long value = toLong();
4✔
374

375
  if ( value > SHRT_MAX )
4✔
376
    throw std::overflow_error ("overflow");
1✔
377

378
  if ( value < SHRT_MIN )
3✔
379
    throw std::underflow_error ("underflow");
1✔
380

381
  return static_cast<sInt16>(value);
2✔
382
}
383

384
//----------------------------------------------------------------------
385
auto FString::toUShort() const -> uInt16
4✔
386
{
387
  const uLong value = toULong();
4✔
388

389
  if ( value > USHRT_MAX )
3✔
390
    throw std::overflow_error ("overflow");
1✔
391

392
  return static_cast<uInt16>(value);
2✔
393
}
394

395
//----------------------------------------------------------------------
396
auto FString::toInt() const -> int
95✔
397
{
398
  const long value = toLong();
95✔
399

400
  if ( value > INT_MAX )
95✔
401
    throw std::overflow_error ("overflow");
1✔
402

403
  if ( value < INT_MIN )
94✔
404
    throw std::underflow_error ("underflow");
1✔
405

406
  return static_cast<int>(value);
93✔
407
}
408

409
//----------------------------------------------------------------------
410
auto FString::toUInt() const -> uInt
12✔
411
{
412
  const uLong value = toULong();
12✔
413

414
  if ( value > UINT_MAX )
7✔
415
    throw std::overflow_error ("overflow");
1✔
416

417
  return static_cast<uInt>(value);
6✔
418
}
419

420
//----------------------------------------------------------------------
421
auto FString::toLong() const -> long
107✔
422
{
423
  // Find actual start and end (skip whitespace)
424
  auto iter = string.begin();
107✔
425
  const auto end = string.end();
107✔
426
  internal_skipLeadingWs(iter, end);  // Skip leading whitespace
107✔
427

428
  if ( iter == end )
107✔
429
    throw std::invalid_argument("empty value");
2✔
430

431
  const Sign sign = internal_parseSign(iter);
105✔
432

433
  if ( iter == end )
105✔
434
    throw std::invalid_argument ("no valid number");
×
435

436
  const long value = internal_parseDigits(iter, end, sign);
105✔
437

438
  while ( iter != end )
109✔
439
  {
440
    // Skip trailing whitespace
441
    if ( ! std::iswspace(static_cast<std::wint_t>(*iter)) )
7✔
442
      throw std::invalid_argument("no valid number");  // Trailing non-whitespace
1✔
443

444
    ++iter;
6✔
445
  }
446

447
  return sign == Sign::Negative ? (~value) + 1u : value;
204✔
448
}
449

450
//----------------------------------------------------------------------
451
auto FString::toULong() const -> uLong
27✔
452
{
453
  // Find actual start and end (skip whitespace)
454
  auto iter = string.begin();
27✔
455
  const auto end = string.end();
27✔
456
  internal_skipLeadingWs(iter, end);  // Skip leading whitespace
27✔
457

458

459
  if ( iter == end )
27✔
460
    throw std::invalid_argument("empty value");
4✔
461

462
  if ( internal_parseSign(iter) == Sign::Negative )  // Handle '-' sign
23✔
463
    throw std::underflow_error ("underflow");
5✔
464

465
  if ( iter == end  )
18✔
466
    throw std::invalid_argument("no valid number");
×
467

468
  const uLong value = internal_parseDigits(iter, end);
18✔
469

470
  if ( iter == string.begin() + ((*string.data() == L'+') ? 1 : 0) )
15✔
471
    throw std::invalid_argument("no valid number");  // No digits parsed
2✔
472

473
  while ( iter != end )
19✔
474
  {
475
    // Skip trailing whitespace
476
    if ( ! std::iswspace(static_cast<std::wint_t>(*iter)) )
6✔
NEW
477
      throw std::invalid_argument("no valid number");  // Trailing non-whitespace
×
478

479
    ++iter;
6✔
480
  }
481

482
  return value;
13✔
483
}
484

485
//----------------------------------------------------------------------
486
auto FString::toFloat() const -> float
6✔
487
{
488
  const double value{toDouble()};
6✔
489

490
  if ( value > double(FLT_MAX) || value < double(-FLT_MAX) )
6✔
491
    throw std::overflow_error ("overflow");
2✔
492

493
  if ( std::fabs(value) < double(FLT_EPSILON) )  // value == 0.0f
4✔
494
    throw std::underflow_error ("underflow");
1✔
495

496
  return static_cast<float>(value);
3✔
497
}
498

499
//----------------------------------------------------------------------
500
auto FString::toDouble() const -> double
15✔
501
{
502
  if ( isEmpty() )
15✔
503
    throw std::invalid_argument ("empty value");
2✔
504

505
  errno = 0;
13✔
506
  wchar_t* p{nullptr};
13✔
507
  const double ret = std::wcstod(string.c_str(), &p);
13✔
508

509
  if ( p == string.c_str() )
13✔
510
    throw std::invalid_argument("no valid floating point value");
1✔
511

512
  if ( p != nullptr && *p != L'\0' )
12✔
513
    throw std::invalid_argument ("no valid floating point value");
×
514

515
  if ( errno == ERANGE )
12✔
516
  {
517
    if ( isExactlyEqual(ret, HUGE_VAL) || isExactlyEqual(ret, -HUGE_VAL) )
3✔
518
      throw std::overflow_error ("overflow");
2✔
519

520
    if ( std::fabs(ret) < DBL_EPSILON )  // ret == 0.0l
1✔
521
      throw std::underflow_error ("underflow");
1✔
522
  }
523

524
  return ret;
9✔
525
}
526

527
//----------------------------------------------------------------------
528
auto FString::ltrim() const -> FString
20✔
529
{
530
  if ( isEmpty() )  // Handle empty string
20✔
531
    return {};
3✔
532

533
  // Find last non-whitespace character
534
  const auto pos = string.find_first_not_of(L" \t\n\r\f\v");
17✔
535

536
  if ( pos == npos )  // All whitespace
17✔
537
    return {};
7✔
538

539
  return string.substr(pos);
10✔
540
}
541

542
//----------------------------------------------------------------------
543
auto FString::rtrim() const -> FString
21✔
544
{
545
  if ( isEmpty() )  // Handle empty string
21✔
546
    return {};
4✔
547

548
  // Find last non-whitespace character
549
  const auto pos = string.find_last_not_of(L" \t\n\r\f\v");
17✔
550

551
  if ( pos == npos )  // All whitespace
17✔
552
    return {};
7✔
553

554
  return string.substr(0, pos + 1);
10✔
555
}
556

557
//----------------------------------------------------------------------
558
auto FString::trim() const -> FString
13✔
559
{
560
  if ( isEmpty() )  // Handle empty string
13✔
561
    return {};
3✔
562

563
  const auto first = string.find_first_not_of(L" \t\n\r\f\v");
10✔
564

565
  if ( first == npos )  // All whitespace
10✔
566
    return {};
×
567

568
  const auto last = string.find_last_not_of(L" \t\n\r\f\v");
10✔
569
  return string.substr(first, last - first + 1);
10✔
570
}
571

572
//----------------------------------------------------------------------
573
auto FString::left (size_type len) const -> FString
18✔
574
{
575
  // Handle empty and too long strings
576
  if ( isEmpty() || len > getLength() )
18✔
577
    return *this;
7✔
578

579
  return string.substr(0, len);
11✔
580
}
581

582
//----------------------------------------------------------------------
583
auto FString::right (size_type len) const -> FString
43✔
584
{
585
  // Handle empty and too long strings
586
  if ( isEmpty() || len > getLength() )
43✔
587
    return *this;
7✔
588

589
  return string.substr(string.size() - len, len);
36✔
590
}
591

592
//----------------------------------------------------------------------
593
auto FString::mid (size_type pos, size_type len) const -> FString
23✔
594
{
595
  // Handle empty string
596
  if ( isEmpty() || len == 0 )
23✔
597
    return {};
7✔
598

599
  if ( pos == 0 )
16✔
600
    pos = 1;
2✔
601

602
  const size_type start_index{pos - 1};
16✔
603
  const auto length{string.length()};
16✔
604

605
  if ( start_index >= length )
16✔
606
    return {};
2✔
607

608
  FString str{};
14✔
609
  len = std::min(len, length - start_index);
14✔
610
  str.string.reserve (len);
14✔
611
  str.string.assign (string, start_index, len);
14✔
612
  return str;
14✔
613
}
14✔
614

615
//----------------------------------------------------------------------
616
auto FString::split (const FString& delimiter) const -> FStringList
35✔
617
{
618
  if ( isEmpty() )
35✔
619
    return {};
2✔
620

621
  if ( delimiter.isEmpty() )
33✔
622
    return {*this};
3✔
623

624
  FStringList string_list{};
32✔
625
  const auto delimiter_length{delimiter.getLength()};
32✔
626
  std::wstring::size_type start{0};
32✔
627
  std::wstring::size_type pos{0};
32✔
628

629
  // Calculate result size and reserve
630
  size_type count{1};
32✔
631

632
  while ( (pos = string.find(delimiter.string, pos)) != npos )
101✔
633
  {
634
    count++;
69✔
635
    pos += delimiter_length;
69✔
636
  }
637

638
  if ( count == 1 )  // 'delimiter' not found
32✔
639
  {
640
    string_list.emplace_back(std::wstring(string));
1✔
641
    return string_list;
1✔
642
  }
643

644
  string_list.reserve(count);
31✔
645

646
  // Perform split
647
  while ( (pos = string.find(delimiter.string, start)) != npos )
100✔
648
  {
649
    string_list.emplace_back(std::wstring(string, start, pos - start));
69✔
650
    start = pos + delimiter_length;
69✔
651
  }
652

653
  string_list.emplace_back(std::wstring(string, start));
31✔
654
  return string_list;
31✔
655
}
33✔
656

657
//----------------------------------------------------------------------
658
auto FString::setString (const FString& s) -> FString&
68✔
659
{
660
  std::wstring str{s.string};
68✔
661
  internal_assign (std::move(str));
68✔
662
  return *this;
68✔
663
}
68✔
664

665
//----------------------------------------------------------------------
666
auto FString::setNumber (sInt64 value) -> FString&
13✔
667
{
668
  std::array<wchar_t, NUMBER_BUFFER_SIZE> buf{};
13✔
669
  std::size_t pos = NUMBER_BUFFER_SIZE - 1;  // Last character pos
13✔
670
  const auto is_negative{ bool( value < 0 ) };
13✔
671
  auto abs_value = is_negative ? ~static_cast<uInt64>(value) + 1
13✔
672
                               : static_cast<uInt64>(value);
673
  buf[pos] = L'\0';
13✔
674

675
  do
676
  {
677
    buf[--pos] = L'0' + wchar_t(abs_value % 10);
109✔
678
    abs_value /= 10;
109✔
679
  }
680
  while ( abs_value );
109✔
681

682
  if ( is_negative )
13✔
683
  {
684
    buf[--pos] = L'-';
12✔
685
  }
686

687
  std::wstring str{&buf[pos]};
26✔
688
  internal_assign (std::move(str));
13✔
689
  return *this;
13✔
690
}
13✔
691

692
//----------------------------------------------------------------------
693
auto FString::setNumber (uInt64 value) -> FString&
21✔
694
{
695
  std::array<wchar_t, NUMBER_BUFFER_SIZE> buf{};
21✔
696
  std::size_t pos = NUMBER_BUFFER_SIZE - 1;  // Last character pos
21✔
697
  buf[pos] = L'\0';
21✔
698

699
  do
700
  {
701
    buf[--pos] = L'0' + wchar_t(value % 10);
154✔
702
    value /= 10;
154✔
703
  }
704
  while ( value );
154✔
705

706
  std::wstring str{&buf[pos]};
42✔
707
  internal_assign (std::move(str));
21✔
708
  return *this;
21✔
709
}
21✔
710

711
//----------------------------------------------------------------------
712
auto FString::setNumber (lDouble f_value, int precision) -> FString&
12✔
713
{
714
  // The precision can not have more than 2 digits
715
  precision = std::min(precision, 99);
12✔
716

717
  std::array<wchar_t, 8> format{};  // = "%.<precision>Lg"
12✔
718
  std::size_t pos{0};
12✔
719
  format[pos++] = L'%';
12✔
720
  format[pos++] = L'.';
12✔
721

722
  if ( precision >= 10 )
12✔
723
  {
724
    // The precision value is 2 digits long
725
    format[pos++] = precision / 10 + L'0';
7✔
726
    format[pos++] = precision % 10 + L'0';
7✔
727
  }
728
  else
729
  {
730
    // The precision value has only 1 digit
731
    format[pos++] = precision + L'0';
5✔
732
  }
733

734
  format[pos++] = L'L';
12✔
735
  format[pos++] = L'g';
12✔
736
  format[pos]   = L'\0';
12✔
737

738
  return sprintf(format.data(), f_value);
12✔
739
}
740

741
//----------------------------------------------------------------------
742
auto FString::setFormatedNumber (sInt64 value, FString separator) -> FString&
5✔
743
{
744
  return internal_setFormatedNumber (value, separator);
5✔
745
}
746

747
//----------------------------------------------------------------------
748
auto FString::setFormatedNumber (uInt64 value, FString separator) -> FString&
6✔
749
{
750
  return internal_setFormatedNumber (value, separator);
6✔
751
}
752

753
// FString operators
754
//----------------------------------------------------------------------
755
auto FString::insert (const FString& s, int pos) -> const FString&
23✔
756
{
757
  if ( isNegative(pos) || uInt(pos) > string.length() )
23✔
758
    throw std::out_of_range("FString::insert index out of range");
4✔
759

760
  string.insert(uInt(pos), s.string, 0, s.getLength());
19✔
761
  return *this;
19✔
762
}
763

764
//----------------------------------------------------------------------
765
auto FString::insert (const FString& s, size_type pos) -> const FString&
1✔
766
{
767
  if ( pos > string.length() )
1✔
768
    throw std::out_of_range("");
×
769

770
  string.insert(uInt(pos), s.string, 0, s.getLength());
1✔
771
  return *this;
1✔
772
}
773

774
//----------------------------------------------------------------------
775
auto FString::replace (const FString& from, const FString& to) const -> FString
78✔
776
{
777
  // Handle empty strings
778
  if ( isEmpty() || from.isEmpty() )
78✔
779
    return *this;
7✔
780

781
  // Count occurrences to reserve exact capacity
782
  size_type count{0};
71✔
783
  std::wstring::size_type pos{0};
71✔
784

785
  while ( (pos = string.find(from.string, pos)) != npos )
136✔
786
  {
787
    count++;
65✔
788
    pos += from.getLength();
65✔
789
  }
790

791
  if ( count == 0 )  //  String 'from' not found
71✔
792
    return *this;
12✔
793

794
  // Calculate result size and reserve
795
  const size_type from_len{from.getLength()};
59✔
796
  const size_type to_len{to.getLength()};
59✔
797
  const size_type new_size{string.length() + count * (to_len - from_len)};
59✔
798

799
  FString str{};
59✔
800
  str.string.reserve(new_size);
59✔
801
  size_type last_pos{0};
59✔
802
  pos = 0;
59✔
803

804
  while ( (pos = string.find(from.string, pos)) != npos )
124✔
805
  {
806
    str.string.append (string, last_pos, pos - last_pos);  // Before
65✔
807
    str.string.append (to.string);  // The string replacement
65✔
808
    pos += from_len;
65✔
809
    last_pos = pos;
65✔
810
  }
811

812
  str.string.append (string, last_pos, npos);  // After
59✔
813
  return str;
59✔
814
}
59✔
815

816
//----------------------------------------------------------------------
817
auto FString::replaceControlCodes() const -> FString
24✔
818
{
819
  FString str{};
24✔
820
  str.string.reserve(string.length());
24✔
821

822
  for (auto c : string)
224✔
823
  {
824
    if ( c <= L'\x1f' )
200✔
825
    {
826
      str.string.push_back(c + L'\x2400');
72✔
827
    }
828
    else if ( c == L'\x7f' )
128✔
829
    {
830
      str.string.push_back(L'\x2421');
4✔
831
    }
832
    else if ( (c >= L'\x80' && c <= L'\x9f') || ! isPrintable(c) )
124✔
833
    {
834
      str.string.push_back(L' ');
36✔
835
    }
836
    else
837
      str.string.push_back(c);
88✔
838
  }
839

840
  return str;
24✔
841
}
×
842

843
//----------------------------------------------------------------------
844
auto FString::expandTabs (int tabstop) const -> FString
50✔
845
{
846
  if (tabstop <= 0)
50✔
847
    return *this;
2✔
848

849
  FString str{};
48✔
850
  auto tab_stop = size_type(tabstop);
48✔
851
  auto tab_count = size_type(std::count(string.begin(), string.end(), L'\t'));
48✔
852
  str.string.reserve(string.length() + (tab_count * tab_stop));
48✔
853
  size_type column{0};
48✔
854

855
  for (wchar_t c : string)
564✔
856
  {
857
    if ( c == L'\t' )
516✔
858
    {
859
      // Calculate spaces needed to reach next tab stop
860
      const size_type spaces{tab_stop - (column % tab_stop)};
76✔
861
      str.string.append(spaces, L' ');
76✔
862
      column += spaces;
76✔
863
    }
864
    else if ( c == L'\n' || c == L'\r' )
440✔
865
    {
866
      str.string.push_back(c);
×
867
      column = 0;  // Reset column on newline
×
868
    }
869
    else
870
    {
871
      str.string.push_back(c);
440✔
872
      ++column;
440✔
873
    }
874
  }
875

876
  return str;
48✔
877
}
48✔
878

879
//----------------------------------------------------------------------
880
auto FString::removeDel() const -> FString
23✔
881
{
882
  FString str{};
23✔
883
  str.string.reserve(string.length());
23✔
884
  size_type del_count{0};
23✔
885

886
  for (const auto c : string)
151✔
887
  {
888
    if ( c == L'\x7f' )
128✔
889
    {
890
      del_count++;
48✔
891
    }
892
    else if ( del_count > 0 )
80✔
893
    {
894
      del_count--;
33✔
895
    }
896
    else  // del_count == 0
897
    {
898
      str.string.push_back(c);
47✔
899
    }
900
  }
901

902
  return str;
23✔
903
}
×
904

905

906
//----------------------------------------------------------------------
907
auto FString::removeBackspaces() const -> FString
27✔
908
{
909
  FString str{};
27✔
910
  str.string.reserve(string.length());
27✔
911

912
  for (const auto c : string)
159✔
913
  {
914
    if ( c != L'\b' )
132✔
915
    {
916
      str.string.push_back(c);
78✔
917
    }
918
    else if ( ! str.string.empty() )
54✔
919
      str.string.pop_back();
30✔
920
  }
921

922
  return str;
27✔
923
}
×
924

925
//----------------------------------------------------------------------
926
auto FString::overwrite (const FString& s, int pos) -> FString&
14✔
927
{
928
  if ( pos < 0 )
14✔
929
    return overwrite (s, 0);
3✔
930

931
  return overwrite (s, size_type(pos));
11✔
932
}
933

934
//----------------------------------------------------------------------
935
auto FString::overwrite (const FString& s, size_type pos) -> FString&
16✔
936
{
937
  const auto length{string.length()};
16✔
938

939
  if ( pos > length )
16✔
940
    pos = length;
3✔
941

942
  string.replace(pos, s.getLength(), s.string);
16✔
943
  return *this;
16✔
944
}
945

946
//----------------------------------------------------------------------
947
auto FString::remove (size_type pos, size_type len) -> FString&
33✔
948
{
949
  const auto length{string.length()};
33✔
950

951
  if ( pos > length )
33✔
952
    return *this;
1✔
953

954
  if ( pos + len > length )
32✔
955
    len = length - pos;
3✔
956

957
  string.erase (pos, len);
32✔
958
  return *this;
32✔
959
}
960

961
//----------------------------------------------------------------------
962
auto FString::erase (size_type pos, size_type len) -> FString&
5✔
963
{
964
  string.erase (pos, len);
5✔
965
  return *this;
5✔
966
}
967

968
//----------------------------------------------------------------------
969
auto FString::includes (const FString& s) const noexcept -> bool
60✔
970
{
971
  if ( s.isEmpty() )
60✔
972
    return false;
8✔
973

974
  return string.find(s.string) != npos;
52✔
975
}
976

977
//----------------------------------------------------------------------
978
auto FString::contains (const FString& s) const noexcept -> bool
4✔
979
{
980
  return includes(s);
4✔
981
}
982

983
//----------------------------------------------------------------------
984
auto FString::find (const FString& s, size_type pos) const noexcept -> size_type
5✔
985
{
986
  return string.find(s.string, pos);
5✔
987
}
988

989
//----------------------------------------------------------------------
990
auto FString::rfind (const FString& s, size_type pos) const noexcept -> size_type
5✔
991
{
992
  return string.rfind(s.string, pos);
5✔
993
}
994

995

996
// private methods of FString
997
//----------------------------------------------------------------------
998
auto FString::internal_toCharString (const std::wstring& s) const -> std::string
1,050✔
999
{
1000
  if ( s.empty() )
1,050✔
1001
    return {};
157✔
1002

1003
  auto state{std::mbstate_t()};
893✔
1004
  const wchar_t* src = s.c_str();
893✔
1005
  const auto size = std::wcsrtombs(nullptr, &src, 0, &state);
893✔
1006

1007
  if ( size == MALFORMED_STRING )
893✔
1008
    return {};
×
1009

1010
  std::string dest{};
893✔
1011
  dest.resize(size);
893✔
1012
  auto dest_data = const_cast<char*>(dest.data());
893✔
1013
  const auto mblength = std::wcsrtombs (dest_data, &src, size + 1, &state);
893✔
1014

1015
  if ( mblength == MALFORMED_STRING && errno != EILSEQ )
893✔
1016
    return {};
×
1017

1018
  return dest;
893✔
1019
}
893✔
1020

1021
//----------------------------------------------------------------------
1022
auto FString::internal_toWideString (const char src[]) const -> std::wstring
2,548✔
1023
{
1024
  if ( ! src || *src == '\0' )
2,548✔
1025
    return {};
64✔
1026

1027
  auto state{std::mbstate_t()};
2,484✔
1028
  auto size = std::mbsrtowcs(nullptr, &src, 0, &state);
2,484✔
1029

1030
  if ( size == MALFORMED_STRING )
2,484✔
1031
    return {};
×
1032

1033
  std::wstring dest(size, L'\0');
2,484✔
1034
  auto dest_data = const_cast<wchar_t*>(dest.data());
2,484✔
1035
  const auto wide_length = std::mbsrtowcs (dest_data, &src, size + 1, &state);
2,484✔
1036

1037
  if ( wide_length == MALFORMED_STRING )
2,484✔
1038
    return {};
×
1039

1040
  return dest;
2,484✔
1041
}
2,484✔
1042

1043
//----------------------------------------------------------------------
1044
inline void FString::internal_skipLeadingWs ( const_iterator& iter
134✔
1045
                                            , const_iterator end ) const noexcept
1046
{
1047
  // Skip over leading whitespace in a string
1048

1049
  while ( iter != end && std::iswspace(static_cast<std::wint_t>(*iter)) )
146✔
1050
    ++iter;
12✔
1051
}
134✔
1052

1053
//----------------------------------------------------------------------
1054
inline auto FString::internal_parseSign (const_iterator& iter) const noexcept -> Sign
128✔
1055
{
1056
  // Extract sign and increment pointer
1057

1058
  if ( *iter == L'-' )  // Handle '-' sign
128✔
1059
  {
1060
    ++iter;
14✔
1061
    return Sign::Negative;
14✔
1062
  }
1063

1064
  if ( *iter == L'+' )  // Handle '+' sign
114✔
1065
    ++iter;
2✔
1066

1067
  return Sign::Positive;
114✔
1068
}
1069

1070
//----------------------------------------------------------------------
1071
inline auto FString::internal_parseDigits ( const_iterator& iter
105✔
1072
                                          , const_iterator end
1073
                                          , Sign sign ) const -> long
1074
{
1075
  // Parse digits and detect overflow/underflow
1076

1077
  long value{0};
105✔
1078

1079
  while ( iter != end )
392✔
1080
  {
1081
    const wchar_t c{*iter};
291✔
1082

1083
    if ( c < L'0' || c > L'9' )  // Digit check
291✔
1084
      break;
1085

1086
    const auto digit{long(c - L'0')};
289✔
1087

1088
    if ( internal_isOverflowed(sign, value, digit) )
289✔
1089
    {
1090
      if ( sign == Sign::Negative )
2✔
1091
        throw std::underflow_error("underflow");
1✔
1092

1093
      throw std::overflow_error("overflow");
1✔
1094
    }
1095

1096
    value = (value * 10) + digit;
287✔
1097
    ++iter;
287✔
1098
  }
1099

1100
  return value;
103✔
1101
}
1102

1103
//----------------------------------------------------------------------
1104
inline auto FString::internal_parseDigits ( const_iterator& iter
18✔
1105
                                          , const_iterator end ) const -> uLong
1106
{
1107
  // Parse digits and detect overflow/underflow
1108

1109
  uLong value{0};
18✔
1110

1111
  while ( iter != end )
170✔
1112
  {
1113
    const wchar_t c{*iter};
158✔
1114

1115
    if ( c < L'0' || c > L'9' )  // Digit check
158✔
1116
      break;
1117

1118
    const auto digit{uLong(c - L'0')};
155✔
1119

1120
    if ( internal_isOverflowed(value, digit) )
155✔
1121
      throw std::overflow_error("overflow");
3✔
1122

1123
    value = (value * 10) + digit;
152✔
1124
    ++iter;
152✔
1125
  }
1126

1127
  return value;
15✔
1128
}
1129

1130
//----------------------------------------------------------------------
1131
inline auto FString::internal_isOverflowed ( Sign sign
289✔
1132
                                           , long value
1133
                                           , long digit ) const noexcept -> bool
1134
{
1135
  const long limit_digit = ( sign == Sign::Negative ) ? LONG_MIN_LIMIT_DIGIT
289✔
1136
                                                      : LONG_MAX_LIMIT_DIGIT;
1137
  return ( value > LONG_LIMIT )
1138
      || ( value == LONG_LIMIT && digit > limit_digit );
289✔
1139
}
1140

1141
//----------------------------------------------------------------------
1142
inline auto FString::internal_isOverflowed ( uLong value
155✔
1143
                                           , uLong digit ) const noexcept -> bool
1144
{
1145
  return ( value > ULONG_LIMIT )
1146
      || ( value == ULONG_LIMIT && digit > ULONG_LIMIT_DIGIT );
155✔
1147
}
1148

1149
//----------------------------------------------------------------------
1150
template <typename NumT>
1151
inline auto FString::internal_setFormatedNumber (NumT value, FString separator) -> FString&
11✔
1152
{
1153
  std::array<wchar_t, NUMBER_BUFFER_SIZE> buf{};
11✔
1154
  std::size_t pos{buf.size() - 1};  // Last character position
11✔
1155
  int n{0};
11✔
1156

1157
  const auto is_negative = bool( value < 0 );
11✔
1158
  auto abs_value = is_negative ? uInt64(-(value + 1)) + 1 : uInt64(value);
11✔
1159
  const wchar_t sep_char = ( separator[0] == 0 ) ? L' ' : separator[0];
11✔
1160

1161
  buf[pos] = L'\0';  // Null termination at the last index
11✔
1162

1163
  do
1164
  {
1165
    buf[--pos] = L'0' + static_cast<wchar_t>(abs_value % 10);
149✔
1166
    abs_value /= 10;
149✔
1167
    n++;
149✔
1168

1169
    if ( abs_value && n % 3 == 0 )
149✔
1170
      buf[--pos] = sep_char;
44✔
1171
  }
1172
  while ( abs_value );
149✔
1173

1174
  if ( is_negative )
5✔
1175
    buf[--pos] = L'-';
3✔
1176

1177
  std::wstring str{&buf[pos]};
22✔
1178
  internal_assign(std::move(str));
11✔
1179
  return *this;
11✔
1180
}
11✔
1181

1182

1183
// FString non-member operators
1184
//----------------------------------------------------------------------
1185
auto FStringCaseCompare (const FString& s1, const FString& s2) -> int
31✔
1186
{
1187
  if ( &s1 == &s2 )
31✔
1188
    return 0;
1✔
1189

1190
  auto iter1 = s1.cbegin();
30✔
1191
  auto iter2 = s2.cbegin();
30✔
1192
  const auto end1 = s1.cend();
30✔
1193
  const auto end2 = s2.cend();
30✔
1194

1195
  while ( iter1 != end1 && iter2 != end2 )
127✔
1196
  {
1197
    auto c1 = *iter1;
113✔
1198
    auto c2 = *iter2;
113✔
1199

1200
    // Convert to lowercase
1201
    if ( c1 >= L'A' && c1 <= L'Z' )
113✔
1202
      c1 += 32;
28✔
1203

1204
    if ( c2 >= L'A' && c2 <= L'Z' )
113✔
1205
      c2 += 32;
15✔
1206

1207
    if ( c1 != c2 )
113✔
1208
      return c1 - c2;
16✔
1209

1210
    ++iter1;
97✔
1211
    ++iter2;
97✔
1212
  }
1213

1214
  // Handle remaining characters
1215
  if ( iter1 != end1 )
14✔
1216
    return 1;  // s1 is longer
4✔
1217

1218
  if ( iter2 != end2 )
10✔
1219
    return -1; // s2 is longer
4✔
1220

1221
  return 0;    // Equal
6✔
1222
}
1223

1224

1225
}  // namespace finalcut
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc