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

ascii-boxes / boxes / 10972725362

21 Sep 2024 12:51PM UTC coverage: 83.099% (-1.9%) from 84.973%
10972725362

push

github

tsjensen
defs

2958 of 3795 branches covered (77.94%)

Branch coverage included in aggregate %.

4796 of 5536 relevant lines covered (86.63%)

176493.95 hits per line

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

63.73
/src/shape.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * Copyright (c) 1999-2024 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 "logging.h"
30
#include "shape.h"
31
#include "tools.h"
32

33

34

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

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

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

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

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

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

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

53

54

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

70
    for (i = 0; i < num; ++i) {
14,828✔
71
        if (isempty(sarr + i)) {
13,678✔
72
            continue;
11,434✔
73
        } else {
74
            break;
2,244✔
75
        }
76
    }
77

78
    return (shape_t) i;
3,394✔
79
}
80

81

82

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

100
    for (side = 0; side < NUM_SIDES; ++side) {
2,698!
101
        for (i = 0; i < SHAPES_PER_SIDE; ++i) {
8,364✔
102
            if (sides[side][i] == s) {
7,384✔
103
                if (found == idx) {
1,718!
104
                    return side;
1,718✔
105
                } else {
106
                    ++found;
×
107
                }
108
            }
109
        }
2,833✔
110
    }
490✔
111

112
    return NUM_SIDES;
×
113
}
859✔
114

115

116

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

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

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

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

154
    for (j = 0; j < height; ++j) {
6,356✔
155
        (*chars)[j] = nspaces(width);
3,430✔
156
        (*mbcs)[j] = bxs_from_ascii((*chars)[j]);
3,430✔
157
    }
1,715✔
158

159
    return 0;
2,926✔
160
}
1,463✔
161

162

163

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

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

183
    *shape = SENTRY_INITIALIZER;
×
184
}
×
185

186

187

188
int isempty(const sentry_t *shape)
344,284✔
189
/*
190
 *  Return true if shape is empty.
191
 *
192
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
193
 */
194
{
195
    if (shape == NULL) {
344,284✔
196
        return 1;
×
197
    } else if (shape->chars == NULL || shape->mbcs == NULL) {
344,284!
198
        return 1;
152,176✔
199
    } else if (shape->width == 0 || shape->height == 0) {
192,108!
200
        return 1;
×
201
    } else {
202
        return 0;
192,108✔
203
    }
204
}
172,142✔
205

206

207

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

218
    if (isempty(shape)) {
23,484✔
219
        return 1;
1,734✔
220
    }
221

222
    for (j = 0; j < shape->height; ++j) {
33,302✔
223
        if (shape->chars[j]) {
30,552✔
224
            if (strspn(shape->chars[j], " \t") != shape->width) {
30,552✔
225
                return 0;
19,000✔
226
            }
227
        }
5,776✔
228
    }
5,776✔
229

230
    return 1;
2,750✔
231
}
11,742✔
232

233

234

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

252
    va_start (ap, n);
1,040✔
253

254
    for (i = 0; i < n; ++i) {
6,240✔
255
        shape_t r = va_arg (ap, shape_t);
5,200✔
256
        if (!isempty(sarr + r)) {
5,200✔
257
            if (sarr[r].height > max) {
3,534✔
258
                max = sarr[r].height;
1,040✔
259
            }
520✔
260
        }
1,767✔
261
    }
2,600✔
262

263
    va_end (ap);
1,040✔
264

265
    return max;
1,040✔
266
}
267

268

269

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

287
    va_start (ap, n);
1,040✔
288

289
    for (i = 0; i < n; ++i) {
6,240✔
290
        shape_t r = va_arg (ap, shape_t);
5,200✔
291
        if (!isempty(sarr + r)) {
5,200✔
292
            if (sarr[r].width > max) {
3,296✔
293
                max = sarr[r].width;
1,040✔
294
            }
520✔
295
        }
1,648✔
296
    }
2,600✔
297

298
    va_end (ap);
1,040✔
299

300
    return max;
1,040✔
301
}
302

303

304

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

318
    for (i = 0; i < SHAPES_PER_SIDE; ++i) {
9,844✔
319
        if (isdeepempty(sarr + sides[aside][i])) {
9,210✔
320
            continue;
3,946✔
321
        } else {
322
            return 0;
5,264✔
323
        }                    /* side is not empty */
324
    }
325

326
    return 1;                            /* side is empty */
634✔
327
}
2,949✔
328

329

330

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

342

343

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

355

356

357
static int find_in_horiz(shape_t side[], shape_t shape_name)
11,424✔
358
{
359
    int result = -1;
11,424✔
360
    for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
45,396✔
361
        if (side[i] == shape_name) {
41,660✔
362
            result = i;
7,688✔
363
            break;
7,688✔
364
        }
365
    }
16,986✔
366
    return result;
11,424✔
367
}
368

369

370

371
static int find_in_north(shape_t shape_name)
7,688✔
372
{
373
    return find_in_horiz(north_side, shape_name);
7,688✔
374
}
375

376

377

378
static int find_in_south(shape_t shape_name)
3,736✔
379
{
380
    return find_in_horiz(south_side_rev, shape_name);
3,736✔
381
}
382

383

384

385
static int *new_blankward_cache(const size_t shape_height)
3,220✔
386
{
387
    int *result = (int *) calloc(shape_height, sizeof(int));
3,220✔
388
    for (size_t i = 0; i < shape_height; i++) {
15,896✔
389
        result[i] = -1;
12,676✔
390
    }
6,338✔
391
    return result;
3,220✔
392
}
393

394

395

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

432

433

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

457
    int result = is_blankward_calc(current_design, shape, shape_line_idx, is_leftward);
7,688✔
458
    blankward_cache[shape_line_idx] = result;
7,688✔
459
    return result;
7,688✔
460
}
3,878✔
461

462

463

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

526

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