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

ascii-boxes / boxes / 27499449924

14 Jun 2026 12:33PM UTC coverage: 83.929% (+1.1%) from 82.852%
27499449924

push

github

tsjensen
Fix coverage measurement on macos-26

2624 of 3395 branches covered (77.29%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 2 files covered. (100.0%)

99 existing lines in 6 files now uncovered.

4066 of 4576 relevant lines covered (88.85%)

196072.68 hits per line

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

85.82
/src/unicode.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * SPDX-FileCopyrightText: Copyright (c) 1999-2026 Thomas Jensen and the boxes contributors
4
 * SPDX-License-Identifier: GPL-3.0-only
5
 *
6
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
7
 * License, version 3, as published by the Free Software Foundation.
8
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
10
 * details.
11
 * You should have received a copy of the GNU General Public License along with this program.
12
 * If not, see <https://www.gnu.org/licenses/>.
13
 *
14
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
15
 */
16

17
/*
18
 * Functions and constants for handling unicode strings with libunistring.
19
 */
20

21
#include "config.h"
22
#include <errno.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
#include <strings.h>
27

28
#include <uniconv.h>
29
#include <unictype.h>
30
#include <unistr.h>
31

32
#include "boxes.h"
33
#include "tools.h"
34
#include "unicode.h"
35

36

37

38
/* effective character encoding of input and output text */
39
const char *encoding;
40

41
/* ucs4_t character '\t' (tab)  */
42
const ucs4_t char_tab = 0x00000009;
43

44
/* ucs4_t character ' '  (space) */
45
const ucs4_t char_space = 0x00000020;
46

47
/* ucs4_t character '\r' (carriage return) */
48
const ucs4_t char_cr = 0x0000000d;
49

50
/* ucs4_t character '\n' (newline) */
51
const ucs4_t char_newline = 0x0000000a;
52

53
/* ucs4_t character 0x1b (escape)  */
54
const ucs4_t char_esc = 0x0000001b;
55

56
/* ucs4_t character '\0' (zero) */
57
const ucs4_t char_nul = 0x00000000;
58

59

60

61
#ifdef _WIN32
62
static const char *get_locale_from_environment()
63
{
64
    const char *locale = getenv("LC_ALL");
65
    if (locale == NULL || locale[0] == '\0') {
66
        locale = getenv("LC_CTYPE");
67
    }
68
    if (locale == NULL || locale[0] == '\0') {
69
        locale = getenv("LANG");
70
    }
71
    return locale != NULL && locale[0] != '\0' ? locale : NULL;
72
}
73

74

75
static int is_all_digits(const char *s)
76
{
77
    if (s == NULL || s[0] == '\0') {
78
        return 0;
79
    }
80
    for (const char *p = s; *p != '\0'; p++) {
81
        if (*p < '0' || *p > '9') {
82
            return 0;
83
        }
84
    }
85
    return 1;
86
}
87

88

89
static const char *extract_locale_encoding(const char *locale)
90
{
91
    static char encoding_buffer[32];
92
    if (locale == NULL) {
93
        return NULL;
94
    }
95

96
    const char *dot = strrchr(locale, '.');
97
    if (dot == NULL || dot[1] == '\0') {
98
        return NULL;
99
    }
100

101
    const char *start = dot + 1;
102
    const char *end = strchr(start, '@');
103
    size_t len = end == NULL ? strlen(start) : (size_t) (end - start);
104
    if (len == 0 || len >= sizeof(encoding_buffer)) {
105
        return NULL;
106
    }
107

108
    memcpy(encoding_buffer, start, len);
109
    encoding_buffer[len] = '\0';
110
    if (strcasecmp(encoding_buffer, "UTF8") == 0
111
            || strcasecmp(encoding_buffer, "UTF-8") == 0
112
            || strcasecmp(encoding_buffer, "CP65001") == 0
113
            || strcmp(encoding_buffer, "65001") == 0)
114
    {
115
        return "UTF-8";
116
    }
117
    if ((encoding_buffer[0] == 'C' || encoding_buffer[0] == 'c')
118
            && (encoding_buffer[1] == 'P' || encoding_buffer[1] == 'p')
119
            && is_all_digits(encoding_buffer + 2))
120
    {
121
        encoding_buffer[0] = 'C';
122
        encoding_buffer[1] = 'P';
123
        return encoding_buffer;
124
    }
125
    if (is_all_digits(encoding_buffer)) {
126
        static char codepage_buffer[sizeof(encoding_buffer) + 2];
127
        memcpy(codepage_buffer, "CP", 2);
128
        strcpy(codepage_buffer + 2, encoding_buffer);
129
        return codepage_buffer;
130
    }
131
    return encoding_buffer;
132
}
133
#endif
134

135

136

137
int is_char_at(const uint32_t *text, const size_t idx, const ucs4_t expected_char)
18,313,349✔
138
{
139
    return text != NULL && u32_cmp(text + idx, &expected_char, 1) == 0;
18,313,349!
140
}
141

142

143

144
void set_char_at(uint32_t *text, const size_t idx, const ucs4_t char_to_set)
383,416✔
145
{
146
    u32_set(text + idx, char_to_set, 1);
383,416✔
147
}
383,416✔
148

149

150

151
int is_empty(const uint32_t *text)
18,306,081✔
152
{
153
    return text == NULL || is_char_at(text, 0, char_nul);
18,306,081✔
154
}
155

156

157

158
int u32_is_blank(const uint32_t *text)
112✔
159
{
160
    if (is_empty(text)) {
112✔
161
        return 1;
2✔
162
    }
163

164
    for (const uint32_t *c = text; *c != char_nul; c++) {
594✔
165
        if (!is_blank(*c)) {
484!
UNCOV
166
            return 0;
×
167
        }
168
    }
169
    return 1;
110✔
170
}
171

172

173

174
int is_ascii_printable(const ucs4_t c)
18,136,639✔
175
{
176
    return c >= 0x20 && c < 0x7f;
18,136,639✔
177
}
178

179

180

181
int is_allowed_anywhere(const ucs4_t c)
19,062,249✔
182
{
183
    /* ESC, CR, LF, and TAB are control characters */
184
    return !uc_is_cntrl(c) || c == char_tab || c == char_cr || c == char_newline || c == char_esc;
19,062,249✔
185
}
186

187

188

189
int is_allowed_in_shape(const ucs4_t c)
219,858✔
190
{
191
    return is_allowed_anywhere(c) && c != char_cr && c != char_newline;
219,858✔
192
}
193

194

195

196
int is_allowed_in_sample(const ucs4_t c)
586,690✔
197
{
198
    return is_allowed_anywhere(c);
586,690✔
199
}
200

201

202

203
int is_allowed_in_filename(const ucs4_t c)
562✔
204
{
205
    return is_allowed_anywhere(c) && c != char_cr && c != char_newline && c != char_esc;
562✔
206
}
207

208

209

210
int is_allowed_in_kv_string(const ucs4_t c)
58,998✔
211
{
212
    return is_allowed_anywhere(c) && c != char_cr && c != char_newline && c != char_esc;
58,998✔
213
}
214

215

216

217
int is_blank(const ucs4_t c)
36,330,982✔
218
{
219
    return c == char_tab || uc_is_blank(c);
36,330,982✔
220
}
221

222

223

224
uint32_t *new_empty_string32()
658✔
225
{
226
    return (uint32_t *) calloc(1, sizeof(uint32_t));
658✔
227
}
228

229

230

231
ucs4_t to_utf32(char ascii)
4,600✔
232
{
233
    ucs4_t c = char_nul;
4,600✔
234
    if (ascii >= 0x20 && ascii < 0x7f) {
4,600!
235
        c = (ucs4_t)ascii;  // Store the ASCII value directly in c
4,598✔
236
    }
237
    return c;
4,600✔
238
}
239

240

241

242
uint32_t *advance_next32(const uint32_t *s, size_t *invis)
18,267,125✔
243
{
244
    if (is_empty(s)) {
18,267,125!
UNCOV
245
        return (uint32_t *) s;
×
246
    }
247

248
    int ansipos = 0;
18,267,125✔
249
    (*invis) = 0;
18,267,125✔
250
    ucs4_t c;
251
    const uint32_t *rest = s;
18,267,125✔
252
    while ((rest = u32_next(&c, rest))) {
18,606,567✔
253
        if (ansipos == 0 && c == char_esc) {
18,606,565✔
254
            /* Found an ESC char, count it as invisible and move 1 forward in the detection of CSI sequences */
255
            (*invis)++;
63,404✔
256
            ansipos++;
63,404✔
257
        } else if (ansipos == 1 && (c == '[' || c == '(')) {
18,543,161!
258
            /* Found '[' char after ESC. A CSI sequence has started. */
259
            (*invis)++;
63,404✔
260
            ansipos++;
63,404✔
261
        } else if (ansipos == 1 && c >= 0x40 && c <= 0x5f) { /* between '@' and '_' (mostly uppercase letters) */
18,479,757!
262
            /* Found a byte designating the end of a two-byte escape sequence */
UNCOV
263
            (*invis)++;
×
UNCOV
264
            ansipos = 0;
×
UNCOV
265
            break;
×
266
        } else if (ansipos == 2) {
18,479,757✔
267
            /* Inside CSI sequence - Keep counting chars as invisible */
268
            (*invis)++;
276,036✔
269

270
            /* A char between 0x40 and 0x7e signals the end of an CSI or escape sequence */
271
            if (c >= 0x40 && c <= 0x7e) {
276,036!
272
                ansipos = 0;
63,402✔
273
                break;
63,402✔
274
            }
275
        } else {
276
            break;
18,203,721✔
277
        }
278
    }
279
    if (rest == NULL) {
18,267,125✔
280
        rest = s + u32_strlen(s);
2✔
281
    }
282
    return (uint32_t *) rest;
18,267,125✔
283
}
284

285

286

287
uint32_t *u32_strconv_from_input(const char *src)
5,274✔
288
{
289
    return u32_strconv_from_arg(src, encoding);
5,274✔
290
}
291

292

293

294
uint32_t *u32_strconv_from_arg(const char *src, const char *sourceEncoding)
576,532✔
295
{
296
    if (src == NULL) {
576,532✔
297
        return NULL;
4✔
298
    }
299
    if (src[0] == '\0') {
576,528✔
300
        return new_empty_string32();
126✔
301
    }
302

303
    uint32_t *result = u32_strconv_from_encoding(
576,402✔
304
            src,                    /* the source string to convert */
305
            sourceEncoding,         /* the character encoding from which to convert */
306
            iconveh_question_mark); /* produce one question mark '?' per unconvertible character */
307

308
    if (result == NULL) {
576,402!
UNCOV
309
        fprintf(stderr, "%s: failed to convert from '%s' to UTF-32: %s\n", PROJECT, sourceEncoding, strerror(errno));
×
310
    }
311
    return result;
576,402✔
312
}
313

314

315

316
char *u32_strconv_to_output(const uint32_t *src)
6,746✔
317
{
318
    return u32_strconv_to_arg(src, encoding);
6,746✔
319
}
320

321

322

323
char *u32_strconv_to_arg(const uint32_t *src, const char *targetEncoding)
6,746✔
324
{
325
    if (src == NULL) {
6,746!
UNCOV
326
        return NULL;
×
327
    }
328
    if (is_empty(src)) {
6,746✔
329
        return strdup("");
28✔
330
    }
331

332
    char *result = u32_strconv_to_encoding(
6,718✔
333
            src,                    /* the source string to convert */
334
            targetEncoding,         /* the character encoding to which to convert */
335
            iconveh_question_mark); /* produce one question mark '?' per unconvertible character */
336

337
    if (result == NULL) {
6,718!
UNCOV
338
        fprintf(stderr, "%s: failed to convert from UTF-32 to '%s': %s\n", PROJECT, targetEncoding, strerror(errno));
×
339
    }
340
    return result;
6,718✔
341
}
342

343

344

345
const char *get_default_encoding()
808✔
346
{
347
    const char *system_encoding = locale_charset();
808✔
348

349
    #ifdef _WIN32
350
        const char *env_encoding = extract_locale_encoding(get_locale_from_environment());
351
        if (env_encoding != NULL) {
352
            return check_encoding(env_encoding, system_encoding);
353
        }
354
    #endif
355

356
    return system_encoding;
808✔
357
}
358

359

360

361
const char *check_encoding(const char *manual_encoding, const char *system_encoding)
786✔
362
{
363
    if (manual_encoding != NULL) {
786✔
364
        uint32_t *unicode = u32_strconv_from_encoding(" ", manual_encoding, iconveh_error);
26✔
365
        if (unicode != NULL) {
26✔
366
            BFREE(unicode);
24!
367
            return manual_encoding;
24✔
368
        }
369
        fprintf(stderr, "%s: Invalid character encoding: %s - falling back to %s\n",
2✔
370
                PROJECT, manual_encoding, system_encoding);
371
        fflush(stderr);
2✔
372
    }
373
    return system_encoding;
762✔
374
}
375

376

377

378
char *to_utf8(uint32_t *src)
828✔
379
{
380
    if (src == NULL) {
828!
UNCOV
381
        return NULL;
×
382
    }
383
    if (is_empty(src)) {
828!
UNCOV
384
        return (char *) strdup("");
×
385
    }
386
    char *result = u32_strconv_to_encoding(src, "UTF-8", iconveh_error);
828✔
387
    if (result == NULL) {
828!
UNCOV
388
        bx_fprintf(stderr, "%s: failed to convert a string to UTF-8: %s\n", PROJECT, strerror(errno));
×
UNCOV
389
        return NULL;
×
390
    }
391
    return result;
828✔
392
}
393

394

395

396
uint32_t *u32_nspaces(const size_t n)
3,524✔
397
{
398
    uint32_t *result = (uint32_t *) malloc((n + 1) * sizeof(uint32_t));
3,524✔
399
    if (result == NULL) {
3,524!
UNCOV
400
        perror(PROJECT);
×
UNCOV
401
        return NULL;
×
402
    }
403
    if (n > 0) {
3,524✔
404
        u32_set(result, char_space, n);
2,678✔
405
    }
406
    set_char_at(result, n, char_nul);
3,524✔
407
    return result;
3,524✔
408
}
409

410

411

412
uint32_t *u32_strnrstr(const uint32_t *haystack, const uint32_t *needle, const size_t needle_len)
11,106✔
413
{
414
    if (is_empty(needle)) {
11,106✔
415
        return (uint32_t *) haystack;
98✔
416
    }
417
    if (is_empty(haystack)) {
11,008✔
418
        return NULL;
2✔
419
    }
420

421
    const uint32_t *p = u32_strrchr(haystack, needle[0]);
11,006✔
422
    if (!p) {
11,006✔
423
        return NULL;
1,072✔
424
    }
425

426
    while (p >= haystack) {
455,594✔
427
        if (u32_strncmp(p, needle, needle_len) == 0) {
449,746✔
428
            return (uint32_t *) p;
4,086✔
429
        }
430
        --p;
445,660✔
431
    }
432

433
    return NULL;
5,848✔
434
}
435

436

437

438
void u32_insert_space_at(uint32_t **s, const size_t idx, const size_t n)
8✔
439
{
440
    if (s == NULL || *s == NULL || n == 0) {
8!
441
        return;
4✔
442
    }
443

444
    size_t len = u32_strlen(*s);
4✔
445
    size_t x = idx;
4✔
446
    if (idx > len) {
4✔
447
        x = len;
2✔
448
    }
449

450
    uint32_t *tmp = (uint32_t *) realloc(*s, (len + 1 + n) * sizeof(uint32_t));
4✔
451
    if (tmp == NULL) {
4!
UNCOV
452
        perror(PROJECT);
×
UNCOV
453
        return;
×
454
    }
455

456
    *s = tmp;
4✔
457
    u32_move(*s + x + n, *s + x, len - x + 1);
4✔
458
    u32_set(*s + x, char_space, n);
4✔
459
}
460

461

462
/* vim: set cindent sw=4: */
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