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

ascii-boxes / boxes / 7314850954

24 Dec 2023 01:12PM UTC coverage: 88.826% (+2.5%) from 86.336%
7314850954

push

github

tsjensen
Use -ggdb3 option for more detailed debug info

2869 of 3408 branches covered (0.0%)

Branch coverage included in aggregate %.

4619 of 5022 relevant lines covered (91.98%)

202175.9 hits per line

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

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

16
/*
17
 * Shape handling and information functions
18
 */
19

20
#include "config.h"
21

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

27
#include "boxes.h"
28
#include "bxstring.h"
29
#include "shape.h"
30
#include "tools.h"
31

32

33

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

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

41
shape_t east_side[SHAPES_PER_SIDE] = {NE, ENE, E, ESE, SE};
42

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

46
shape_t west_side[SHAPES_PER_SIDE] = {SW, WSW, W, WNW, NW};
47

48
shape_t corners[NUM_CORNERS] = {NW, NE, SE, SW};
49

50
shape_t *sides[] = {north_side, east_side, south_side, west_side};
51

52

53

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

69
    for (i = 0; i < num; ++i) {
14,756✔
70
        if (isempty(sarr + i)) {
13,612✔
71
            continue;
11,372✔
72
        } else {
73
            break;
2,240✔
74
        }
75
    }
76

77
    return (shape_t) i;
3,384✔
78
}
79

80

81

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

99
    for (side = 0; side < NUM_SIDES; ++side) {
2,690!
100
        for (i = 0; i < SHAPES_PER_SIDE; ++i) {
8,322✔
101
            if (sides[side][i] == s) {
7,348✔
102
                if (found == idx) {
1,716!
103
                    return side;
1,716✔
104
                } else {
105
                    ++found;
×
106
                }
107
            }
108
        }
2,816✔
109
    }
487✔
110

111
    return NUM_SIDES;
×
112
}
858✔
113

114

115

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

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

140
    *chars = (char **) calloc(height, sizeof(char *));
2,912✔
141
    if (*chars == NULL) {
2,912✔
142
        perror(PROJECT);
×
143
        return 2;
×
144
    }
145

146
    *mbcs = (bxstr_t **) calloc(height, sizeof(bxstr_t *));
2,912✔
147
    if (*mbcs == NULL) {
2,912✔
148
        BFREE(*chars);
×
149
        perror(PROJECT);
×
150
        return 4;
×
151
    }
152

153
    for (j = 0; j < height; ++j) {
6,328✔
154
        (*chars)[j] = nspaces(width);
3,416✔
155
        (*mbcs)[j] = bxs_from_ascii((*chars)[j]);
3,416✔
156
    }
1,708✔
157

158
    return 0;
2,912✔
159
}
1,456✔
160

161

162

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

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

182
    *shape = SENTRY_INITIALIZER;
×
183
}
×
184

185

186

187
int isempty(const sentry_t *shape)
343,912✔
188
/*
189
 *  Return true if shape is empty.
190
 *
191
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
192
 */
193
{
194
    if (shape == NULL) {
343,912✔
195
        return 1;
×
196
    } else if (shape->chars == NULL || shape->mbcs == NULL) {
343,912!
197
        return 1;
151,956✔
198
    } else if (shape->width == 0 || shape->height == 0) {
191,956!
199
        return 1;
×
200
    } else {
201
        return 0;
191,956✔
202
    }
203
}
171,956✔
204

205

206

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

217
    if (isempty(shape)) {
23,436✔
218
        return 1;
1,716✔
219
    }
220

221
    for (j = 0; j < shape->height; ++j) {
33,246✔
222
        if (shape->chars[j]) {
30,522✔
223
            if (strspn(shape->chars[j], " \t") != shape->width) {
30,522✔
224
                return 0;
18,996✔
225
            }
226
        }
5,763✔
227
    }
5,763✔
228

229
    return 1;
2,724✔
230
}
11,718✔
231

232

233

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

251
    #if defined(DEBUG) && 0
252
    fprintf (stderr, "highest (%d, ...)\n", n);
253
    #endif
254

255
    va_start (ap, n);
1,036✔
256

257
    for (i = 0; i < n; ++i) {
6,216✔
258
        shape_t r = va_arg (ap, shape_t);
5,180✔
259
        if (!isempty(sarr + r)) {
5,180✔
260
            if (sarr[r].height > max) {
3,522✔
261
                max = sarr[r].height;
1,036✔
262
            }
518✔
263
        }
1,761✔
264
    }
2,590✔
265

266
    va_end (ap);
1,036✔
267

268
    return max;
1,036✔
269
}
270

271

272

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

290
    #if defined(DEBUG) && 0
291
    fprintf (stderr, "widest (%d, ...)\n", n);
292
    #endif
293

294
    va_start (ap, n);
1,036✔
295

296
    for (i = 0; i < n; ++i) {
6,216✔
297
        shape_t r = va_arg (ap, shape_t);
5,180✔
298
        if (!isempty(sarr + r)) {
5,180✔
299
            if (sarr[r].width > max) {
3,284✔
300
                max = sarr[r].width;
1,036✔
301
            }
518✔
302
        }
1,642✔
303
    }
2,590✔
304

305
    va_end (ap);
1,036✔
306

307
    return max;
1,036✔
308
}
309

310

311

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

325
    for (i = 0; i < SHAPES_PER_SIDE; ++i) {
9,790✔
326
        if (isdeepempty(sarr + sides[aside][i])) {
9,164✔
327
            continue;
3,902✔
328
        } else {
329
            return 0;
5,262✔
330
        }                    /* side is not empty */
331
    }
332

333
    return 1;                            /* side is empty */
626✔
334
}
2,944✔
335

336

337

338
static int is_west(sentry_t *shape, int include_corners)
7,688✔
339
{
340
    size_t offset = include_corners ? 0 : 1;
7,688✔
341
    for (size_t i = offset; i < SHAPES_PER_SIDE - offset; i++) {
38,440✔
342
        if (west_side[i] == shape->name) {
30,752!
343
            return 1;
×
344
        }
345
    }
15,376✔
346
    return 0;
7,688✔
347
}
3,844✔
348

349

350

351
static int is_east(sentry_t *shape, int include_corners)
7,688✔
352
{
353
    size_t offset = include_corners ? 0 : 1;
7,688✔
354
    for (size_t i = offset; i < SHAPES_PER_SIDE - offset; i++) {
38,440✔
355
        if (east_side[i] == shape->name) {
30,752!
356
            return 1;
×
357
        }
358
    }
15,376✔
359
    return 0;
7,688✔
360
}
3,844✔
361

362

363

364
static int find_in_horiz(shape_t side[], shape_t shape_name)
11,424✔
365
{
366
    int result = -1;
11,424✔
367
    for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
45,396✔
368
        if (side[i] == shape_name) {
41,660✔
369
            result = i;
7,688✔
370
            break;
7,688✔
371
        }
372
    }
16,986✔
373
    return result;
11,424✔
374
}
375

376

377

378
static int find_in_north(shape_t shape_name)
7,688✔
379
{
380
    return find_in_horiz(north_side, shape_name);
7,688✔
381
}
382

383

384

385
static int find_in_south(shape_t shape_name)
3,736✔
386
{
387
    return find_in_horiz(south_side_rev, shape_name);
3,736✔
388
}
389

390

391

392
static int *new_blankward_cache(const size_t shape_height)
3,220✔
393
{
394
    int *result = (int *) calloc(shape_height, sizeof(int));
3,220✔
395
    for (size_t i = 0; i < shape_height; i++) {
15,896✔
396
        result[i] = -1;
12,676✔
397
    }
6,338✔
398
    return result;
3,220✔
399
}
400

401

402

403
static int is_blankward_calc(
7,688✔
404
        design_t *current_design, const shape_t shape, const size_t shape_line_idx, const int is_leftward)
405
{
406
    int result = -1;
7,688✔
407
    sentry_t *shape_data = current_design->shape + shape;
7,688✔
408
    if (is_west(shape_data, is_leftward ? 1 : 0)) {
7,688!
409
        result = is_leftward ? 1 : 0;
×
410
    }
411
    else if (is_east(shape_data, is_leftward ? 0 : 1)) {
7,688!
412
        result = is_leftward ? 0 : 1;
×
413
    }
414
    else {
415
        shape_t *side = north_side;
7,688✔
416
        int pos = find_in_north(shape);
7,688✔
417
        if (pos < 0) {
7,688✔
418
            side = south_side_rev;
3,736✔
419
            pos = find_in_south(shape);
3,736✔
420
        }
1,868✔
421
        result = 1;
7,688✔
422
        size_t loop_init = (size_t) pos + 1;
7,688✔
423
        size_t loop_end = SHAPES_PER_SIDE;
7,688✔
424
        if (is_leftward) {
7,688✔
425
            loop_init = 0;
3,844✔
426
            loop_end = (size_t) pos;
3,844✔
427
        }
1,922✔
428
        for (size_t i = loop_init; i < loop_end; i++) {
13,636✔
429
            sentry_t *tshape = current_design->shape + side[i];
11,108✔
430
            if (tshape->mbcs != NULL && !bxs_is_blank(tshape->mbcs[shape_line_idx])) {
11,108✔
431
                result = 0;
5,160✔
432
                break;
5,160✔
433
            }
434
        }
2,974✔
435
    }
436
    return result;
7,688✔
437
}
438

439

440

441
int is_blankward(design_t *current_design, const shape_t shape, const size_t shape_line_idx, const int is_leftward)
7,756✔
442
{
443
    if (current_design == NULL) {
7,756✔
444
        return 0;  /* this would be a bug */
×
445
    }
446
    sentry_t *shape_data = current_design->shape + shape;
7,756✔
447
    if (shape_line_idx >= shape_data->height) {
7,756!
448
        return 0;  /* this would be a bug */
×
449
    }
450
    int *blankward_cache = is_leftward ? shape_data->blank_leftward : shape_data->blank_rightward;
7,756✔
451
    if (blankward_cache != NULL && blankward_cache[shape_line_idx] >= 0) {
7,756✔
452
        return blankward_cache[shape_line_idx];  /* cached value available */
68✔
453
    }
454
    if (blankward_cache == NULL) {
7,688✔
455
        blankward_cache = new_blankward_cache(shape_data->height);
3,220✔
456
        if (is_leftward) {
3,220✔
457
            shape_data->blank_leftward = blankward_cache;
1,610✔
458
        }
805✔
459
        else {
460
            shape_data->blank_rightward = blankward_cache;
1,610✔
461
        }
462
    }
1,610✔
463

464
    int result = is_blankward_calc(current_design, shape, shape_line_idx, is_leftward);
7,688✔
465
    blankward_cache[shape_line_idx] = result;
7,688✔
466
    return result;
7,688✔
467
}
3,878✔
468

469

470

471
void debug_print_shape(sentry_t *shape)
×
472
{
473
    #ifdef DEBUG
474
        if (shape == NULL) {
475
            fprintf(stderr, "NULL\n");
476
            return;
477
        }
478
        fprintf(stderr, "Shape %3s (%dx%d): elastic=%s, bl=",
479
            shape_name[shape->name], (int) shape->width, (int) shape->height, shape->elastic ? "true" : "false");
480
        if (shape->blank_leftward == NULL) {
481
            fprintf(stderr, "NULL");
482
        }
483
        else {
484
            fprintf(stderr, "[");
485
            for (size_t i = 0; i < shape->height; i++) {
486
                fprintf(stderr, "%d%s", shape->blank_leftward[i],
487
                        shape->height > 0 && i < (shape->height - 1) ? ", " : "");
488
            }
489
            fprintf(stderr, "]");
490
        }
491
        fprintf(stderr, ", br=");
492
        if (shape->blank_rightward == NULL) {
493
            fprintf(stderr, "NULL");
494
        }
495
        else {
496
            fprintf(stderr, "[");
497
            for (size_t i = 0; i < shape->height; i++) {
498
                fprintf(stderr, "%d%s", shape->blank_rightward[i],
499
                        shape->height > 0 && i < (shape->height - 1) ? ", " : "");
500
            }
501
            fprintf(stderr, "]");
502
        }
503
        fprintf(stderr, ", ascii=");
504
        if (shape->chars == NULL) {
505
            fprintf(stderr, "NULL");
506
        }
507
        else {
508
            fprintf(stderr, "[");
509
            for (size_t i = 0; i < shape->height; i++) {
510
                fprintf(stderr, "%s%s%s%s", shape->chars[i] != NULL ? "\"" : "", shape->chars[i],
511
                        shape->chars[i] != NULL ? "\"" : "", (int) i < ((int) shape->height) - 1 ? ", " : "");
512
            }
513
            fprintf(stderr, "]");
514
        }
515
        fprintf(stderr, ", mbcs=");
516
        if (shape->mbcs == NULL) {
517
            fprintf(stderr, "NULL");
518
        }
519
        else {
520
            fprintf(stderr, "[");
521
            for (size_t i = 0; i < shape->height; i++) {
522
                char *out_mbcs = bxs_to_output(shape->mbcs[i]);
523
                fprintf(stderr, "%s%s%s%s", shape->mbcs[i] != NULL ? "\"" : "", out_mbcs,
524
                        shape->mbcs[i] != NULL ? "\"" : "", shape->height > 0 && i < (shape->height - 1) ? ", " : "");
525
                BFREE(out_mbcs);
526
            }
527
            fprintf(stderr, "]");
528
        }
529
        fprintf(stderr, "\n");
530
    #else
531
        UNUSED(shape);
532
    #endif
533
}
×
534

535

536
/* 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