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

ascii-boxes / boxes / 25991660202

17 May 2026 01:04PM UTC coverage: 82.822%. Remained the same
25991660202

push

github

tsjensen
Fix a Heisenbug in u32_insert_space_at() in unicode.c

2776 of 3695 branches covered (75.13%)

Branch coverage included in aggregate %.

5 of 7 new or added lines in 1 file covered. (71.43%)

297 existing lines in 19 files now uncovered.

4345 of 4903 relevant lines covered (88.62%)

98937.49 hits per line

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

73.95
/src/shape.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
 * Shape handling and information functions
19
 */
20

21
#include "config.h"
22

23
#include <stdlib.h>
24
#include <stdio.h>
25
#include <stdarg.h>
26
#include <string.h>
27

28
#include "boxes.h"
29
#include "bxstring.h"
30
#include "logging.h"
31
#include "shape.h"
32
#include "tools.h"
33

34

35

36
char *shape_name[] = {
37
        "NW", "NNW", "N", "NNE", "NE", "ENE", "E", "ESE",
38
        "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW"
39
};
40

41
shape_t north_side[SHAPES_PER_SIDE] = {NW, NNW, N, NNE, NE};  /* clockwise */
42

43
shape_t east_side[SHAPES_PER_SIDE] = {NE, ENE, E, ESE, SE};
44

45
shape_t south_side[SHAPES_PER_SIDE] = {SE, SSE, S, SSW, SW};
46
shape_t south_side_rev[SHAPES_PER_SIDE] = {SW, SSW, S, SSE, SE};
47

48
shape_t west_side[SHAPES_PER_SIDE] = {SW, WSW, W, WNW, NW};
49

50
shape_t corners[NUM_CORNERS] = {NW, NE, SE, SW};
51

52
shape_t *sides[] = {north_side, east_side, south_side, west_side};
53

54

55

56
shape_t findshape(const sentry_t *sarr, const int num)
1,704✔
57
/*
58
 *  Find a non-empty shape and return its name
59
 *
60
 *      sarr    the shape array to check
61
 *      num     number of entries in sarr to be checked
62
 *
63
 *  RETURNS: a shape_name  on success
64
 *           num           on error (e.g. empty shape array)
65
 *
66
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
67
 */
68
{
69
    int i;
70

71
    for (i = 0; i < num; ++i) {
7,452✔
72
        if (isempty(sarr + i)) {
6,874✔
73
            continue;
5,748✔
74
        } else {
75
            break;
1,126✔
76
        }
77
    }
78

79
    return (shape_t) i;
1,704✔
80
}
81

82

83

84
int on_side(const shape_t s, const int idx)
862✔
85
/*
86
 *  Compute the side that shape s is on.
87
 *
88
 *      s    shape to look for
89
 *      idx  which occurence to return (0 == first, 1 == second (for corners)
90
 *
91
 *  RETURNS: side number (BTOP etc.)  on success
92
 *           NUM_SIDES                on error (e.g. idx==1 && s no corner)
93
 *
94
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
95
 */
96
{
97
    int side;
98
    int i;
99
    int found = 0;
862✔
100

101
    for (side = 0; side < NUM_SIDES; ++side) {
1,355!
102
        for (i = 0; i < SHAPES_PER_SIDE; ++i) {
4,205✔
103
            if (sides[side][i] == s) {
3,712✔
104
                if (found == idx) {
862!
105
                    return side;
862✔
106
                } else {
UNCOV
107
                    ++found;
×
108
                }
109
            }
110
        }
111
    }
112

UNCOV
113
    return NUM_SIDES;
×
114
}
115

116

117

118
int genshape(const size_t width, const size_t height, char ***chars, bxstr_t ***mbcs)
1,470✔
119
/*
120
 *  Generate a shape consisting of spaces only.
121
 *
122
 *      width   desired shape width
123
 *      height  desired shape height
124
 *      chars   pointer to the shape lines (should be NULL upon call)
125
 *      mbcs    pointer to the shape lines, MBCS version (should be NULL upon call)
126
 *
127
 *  Memory is allocated for the shape lines which must be freed by the caller.
128
 *
129
 *  RETURNS:  == 0   on success (memory allocated)
130
 *            != 0   on error   (no memory allocated)
131
 *
132
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
133
 */
134
{
135
    size_t j;
136

137
    if (width <= 0 || height <= 0 || width > LINE_MAX_BYTES) {
1,470!
138
        fprintf(stderr, "%s: internal error\n", PROJECT);
×
UNCOV
139
        return 1;
×
140
    }
141

142
    *chars = (char **) calloc(height, sizeof(char *));
1,470✔
143
    if (*chars == NULL) {
1,470!
144
        perror(PROJECT);
×
UNCOV
145
        return 2;
×
146
    }
147

148
    *mbcs = (bxstr_t **) calloc(height, sizeof(bxstr_t *));
1,470✔
149
    if (*mbcs == NULL) {
1,470!
150
        BFREE(*chars);
×
151
        perror(PROJECT);
×
UNCOV
152
        return 4;
×
153
    }
154

155
    for (j = 0; j < height; ++j) {
3,192✔
156
        (*chars)[j] = nspaces(width);
1,722✔
157
        (*mbcs)[j] = bxs_from_ascii((*chars)[j]);
1,722✔
158
    }
159

160
    return 0;
1,470✔
161
}
162

163

164

UNCOV
165
void freeshape(sentry_t *shape)
×
166
/*
167
 *  Free all memory allocated by the shape and set the struct to
168
 *  SENTRY_INITIALIZER. Do not free memory of the struct.
169
 *
170
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
171
 */
172
{
173
    size_t j;
174

175
    for (j = 0; j < shape->height; ++j) {
×
176
        BFREE (shape->chars[j]);
×
UNCOV
177
        bxs_free(shape->mbcs[j]);
×
178
    }
179
    BFREE (shape->chars);
×
180
    BFREE (shape->mbcs);
×
181
    BFREE (shape->blank_leftward);
×
UNCOV
182
    BFREE (shape->blank_rightward);
×
183

184
    *shape = SENTRY_INITIALIZER;
×
UNCOV
185
}
×
186

187

188

189
int isempty(const sentry_t *shape)
172,745✔
190
/*
191
 *  Return true if shape is empty.
192
 *
193
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
194
 */
195
{
196
    if (shape == NULL) {
172,745!
UNCOV
197
        return 1;
×
198
    } else if (shape->chars == NULL || shape->mbcs == NULL) {
172,745!
199
        return 1;
76,387✔
200
    } else if (shape->width == 0 || shape->height == 0) {
96,358!
UNCOV
201
        return 1;
×
202
    } else {
203
        return 0;
96,358✔
204
    }
205
}
206

207

208

209
int isdeepempty(const sentry_t *shape)
11,780✔
210
/*
211
 *  Return true if shape is empty, also checking if lines consist of whitespace
212
 *  only.
213
 *
214
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
215
 */
216
{
217
    size_t j;
218

219
    if (isempty(shape)) {
11,780✔
220
        return 1;
872✔
221
    }
222

223
    for (j = 0; j < shape->height; ++j) {
16,726✔
224
        if (shape->chars[j]) {
15,344!
225
            if (strspn(shape->chars[j], " \t") != shape->width) {
15,344✔
226
                return 0;
9,526✔
227
            }
228
        }
229
    }
230

231
    return 1;
1,382✔
232
}
233

234

235

236
size_t highest(const sentry_t *sarr, const int n, ...)
526✔
237
/*
238
 *  Return height (vert.) of highest shape in given list.
239
 *
240
 *  sarr         array of shapes to examine
241
 *  n            number of shapes following
242
 *  ...          the shapes to consider
243
 *
244
 *  RETURNS:     height in lines (may be zero)
245
 *
246
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
247
 */
248
{
249
    va_list ap;
250
    int i;
251
    size_t max = 0;                      /* current maximum height */
526✔
252

253
    va_start (ap, n);
526✔
254

255
    for (i = 0; i < n; ++i) {
3,156✔
256
        shape_t r = va_arg (ap, shape_t);
2,630✔
257
        if (!isempty(sarr + r)) {
2,630✔
258
            if (sarr[r].height > max) {
1,787✔
259
                max = sarr[r].height;
526✔
260
            }
261
        }
262
    }
263

264
    va_end (ap);
526✔
265

266
    return max;
526✔
267
}
268

269

270

271
size_t widest(const sentry_t *sarr, const int n, ...)
526✔
272
/*
273
 *  Return width (horiz.) of widest shape in given list.
274
 *
275
 *  sarr         array of shapes to examine
276
 *  n            number of shapes following
277
 *  ...          the shapes to consider
278
 *
279
 *  RETURNS:     width in chars (may be zero)
280
 *
281
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
282
 */
283
{
284
    va_list ap;
285
    int i;
286
    size_t max = 0;                      /* current maximum width */
526✔
287

288
    va_start (ap, n);
526✔
289

290
    for (i = 0; i < n; ++i) {
3,156✔
291
        shape_t r = va_arg (ap, shape_t);
2,630✔
292
        if (!isempty(sarr + r)) {
2,630✔
293
            if (sarr[r].width > max) {
1,666✔
294
                max = sarr[r].width;
526✔
295
            }
296
        }
297
    }
298

299
    va_end (ap);
526✔
300

301
    return max;
526✔
302
}
303

304

305

306
/**
307
 * Return true if the shapes on the given side consist entirely of spaces - and spaces only, tabs are considered
308
 * non-empty.
309
 *
310
 * @param sarr pointer to shape list of design to check
311
 * @param aside the box side (one of `BTOP` etc.)
312
 * @return == 0: side is not empty;
313
 *         \!= 0: side is empty
314
 */
315
int empty_side(sentry_t *sarr, const int aside)
2,958✔
316
{
317
    int i;
318

319
    for (i = 0; i < SHAPES_PER_SIDE; ++i) {
4,943✔
320
        if (isdeepempty(sarr + sides[aside][i])) {
4,624✔
321
            continue;
1,985✔
322
        } else {
323
            return 0;
2,639✔
324
        }                    /* side is not empty */
325
    }
326

327
    return 1;                            /* side is empty */
319✔
328
}
329

330

331

332
static int is_west(sentry_t *shape, int include_corners)
3,844✔
333
{
334
    size_t offset = include_corners ? 0 : 1;
3,844✔
335
    for (size_t i = offset; i < SHAPES_PER_SIDE - offset; i++) {
19,220✔
336
        if (west_side[i] == shape->name) {
15,376!
UNCOV
337
            return 1;
×
338
        }
339
    }
340
    return 0;
3,844✔
341
}
342

343

344

345
static int is_east(sentry_t *shape, int include_corners)
3,844✔
346
{
347
    size_t offset = include_corners ? 0 : 1;
3,844✔
348
    for (size_t i = offset; i < SHAPES_PER_SIDE - offset; i++) {
19,220✔
349
        if (east_side[i] == shape->name) {
15,376!
UNCOV
350
            return 1;
×
351
        }
352
    }
353
    return 0;
3,844✔
354
}
355

356

357

358
static int find_in_horiz(shape_t side[], shape_t shape_name)
5,712✔
359
{
360
    int result = -1;
5,712✔
361
    for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
22,698✔
362
        if (side[i] == shape_name) {
20,830✔
363
            result = i;
3,844✔
364
            break;
3,844✔
365
        }
366
    }
367
    return result;
5,712✔
368
}
369

370

371

372
static int find_in_north(shape_t shape_name)
3,844✔
373
{
374
    return find_in_horiz(north_side, shape_name);
3,844✔
375
}
376

377

378

379
static int find_in_south(shape_t shape_name)
1,868✔
380
{
381
    return find_in_horiz(south_side_rev, shape_name);
1,868✔
382
}
383

384

385

386
static int *new_blankward_cache(const size_t shape_height)
1,610✔
387
{
388
    int *result = (int *) calloc(shape_height, sizeof(int));
1,610✔
389
    for (size_t i = 0; i < shape_height; i++) {
7,948✔
390
        result[i] = -1;
6,338✔
391
    }
392
    return result;
1,610✔
393
}
394

395

396

397
static int is_blankward_calc(
3,844✔
398
        design_t *current_design, const shape_t shape, const size_t shape_line_idx, const int is_leftward)
399
{
400
    int result = -1;
3,844✔
401
    sentry_t *shape_data = current_design->shape + shape;
3,844✔
402
    if (is_west(shape_data, is_leftward ? 1 : 0)) {
3,844!
UNCOV
403
        result = is_leftward ? 1 : 0;
×
404
    }
405
    else if (is_east(shape_data, is_leftward ? 0 : 1)) {
3,844!
UNCOV
406
        result = is_leftward ? 0 : 1;
×
407
    }
408
    else {
409
        shape_t *side = north_side;
3,844✔
410
        int pos = find_in_north(shape);
3,844✔
411
        if (pos < 0) {
3,844✔
412
            side = south_side_rev;
1,868✔
413
            pos = find_in_south(shape);
1,868✔
414
        }
415
        result = 1;
3,844✔
416
        size_t loop_init = (size_t) pos + 1;
3,844✔
417
        size_t loop_end = SHAPES_PER_SIDE;
3,844✔
418
        if (is_leftward) {
3,844✔
419
            loop_init = 0;
1,922✔
420
            loop_end = (size_t) pos;
1,922✔
421
        }
422
        for (size_t i = loop_init; i < loop_end; i++) {
6,818✔
423
            sentry_t *tshape = current_design->shape + side[i];
5,554✔
424
            if (tshape->mbcs != NULL && !bxs_is_blank(tshape->mbcs[shape_line_idx])) {
5,554✔
425
                result = 0;
2,580✔
426
                break;
2,580✔
427
            }
428
        }
429
    }
430
    return result;
3,844✔
431
}
432

433

434

435
int is_blankward(design_t *current_design, const shape_t shape, const size_t shape_line_idx, const int is_leftward)
3,878✔
436
{
437
    if (current_design == NULL) {
3,878!
UNCOV
438
        return 0;  /* this would be a bug */
×
439
    }
440
    sentry_t *shape_data = current_design->shape + shape;
3,878✔
441
    if (shape_line_idx >= shape_data->height) {
3,878!
UNCOV
442
        return 0;  /* this would be a bug */
×
443
    }
444
    int *blankward_cache = is_leftward ? shape_data->blank_leftward : shape_data->blank_rightward;
3,878✔
445
    if (blankward_cache != NULL && blankward_cache[shape_line_idx] >= 0) {
3,878✔
446
        return blankward_cache[shape_line_idx];  /* cached value available */
34✔
447
    }
448
    if (blankward_cache == NULL) {
3,844✔
449
        blankward_cache = new_blankward_cache(shape_data->height);
1,610✔
450
        if (is_leftward) {
1,610✔
451
            shape_data->blank_leftward = blankward_cache;
805✔
452
        }
453
        else {
454
            shape_data->blank_rightward = blankward_cache;
805✔
455
        }
456
    }
457

458
    int result = is_blankward_calc(current_design, shape, shape_line_idx, is_leftward);
3,844✔
459
    blankward_cache[shape_line_idx] = result;
3,844✔
460
    return result;
3,844✔
461
}
462

463

464

465
void debug_print_shape(sentry_t *shape)
48✔
466
{
467
    if (is_debug_logging(MAIN)) {
48!
468
        if (shape == NULL) {
48!
469
            log_debug(__FILE__, MAIN, "NULL\n");
×
UNCOV
470
            return;
×
471
        }
472
        log_debug(__FILE__, MAIN, "Shape %3s (%dx%d): elastic=%s,%s bl=",
96✔
473
            shape_name[shape->name], (int) shape->width, (int) shape->height,
48✔
474
            shape->elastic ? "true" : "false", shape->elastic ? " " : "");
96✔
475
        if (shape->blank_leftward == NULL) {
48!
476
            log_debug_cont(MAIN, "NULL");
48✔
477
        }
478
        else {
479
            log_debug_cont(MAIN, "[");
×
480
            for (size_t i = 0; i < shape->height; i++) {
×
481
                log_debug_cont(MAIN, "%d%s", shape->blank_leftward[i],
×
UNCOV
482
                        shape->height > 0 && i < (shape->height - 1) ? ", " : "");
×
483
            }
UNCOV
484
            log_debug_cont(MAIN, "]");
×
485
        }
486
        log_debug_cont(MAIN, ", br=");
48✔
487
        if (shape->blank_rightward == NULL) {
48!
488
            log_debug_cont(MAIN, "NULL");
48✔
489
        }
490
        else {
491
            log_debug_cont(MAIN, "[");
×
492
            for (size_t i = 0; i < shape->height; i++) {
×
493
                log_debug_cont(MAIN, "%d%s", shape->blank_rightward[i],
×
UNCOV
494
                        shape->height > 0 && i < (shape->height - 1) ? ", " : "");
×
495
            }
UNCOV
496
            log_debug_cont(MAIN, "]");
×
497
        }
498
        log_debug_cont(MAIN, ", ascii=");
48✔
499
        if (shape->chars == NULL) {
48✔
500
            log_debug_cont(MAIN, "NULL");
22✔
501
        }
502
        else {
503
            log_debug_cont(MAIN, "[");
26✔
504
            for (size_t i = 0; i < shape->height; i++) {
96✔
505
                log_debug_cont(MAIN, "%s%s%s%s", shape->chars[i] != NULL ? "\"" : "", shape->chars[i],
140!
506
                        shape->chars[i] != NULL ? "\"" : "", (int) i < ((int) shape->height) - 1 ? ", " : "");
140!
507
            }
508
            log_debug_cont(MAIN, "]");
26✔
509
        }
510
        log_debug_cont(MAIN, ", mbcs=");
48✔
511
        if (shape->mbcs == NULL) {
48✔
512
            log_debug_cont(MAIN, "NULL");
22✔
513
        }
514
        else {
515
            log_debug_cont(MAIN, "[");
26✔
516
            for (size_t i = 0; i < shape->height; i++) {
96✔
517
                char *out_mbcs = bxs_to_output(shape->mbcs[i]);
70✔
518
                log_debug_cont(MAIN, "%s%s%s%s", shape->mbcs[i] != NULL ? "\"" : "", out_mbcs,
140!
519
                        shape->mbcs[i] != NULL ? "\"" : "", shape->height > 0 && i < (shape->height - 1) ? ", " : "");
140!
520
                BFREE(out_mbcs);
70!
521
            }
522
            log_debug_cont(MAIN, "]");
26✔
523
        }
524
        log_debug_cont(MAIN, "\n");
48✔
525
    }
526
}
527

528

529
/* vim: set 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