• 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

82.72
/src/remove.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
 * Box removal, i.e. the deletion of boxes
19
 */
20

21
#include "config.h"
22

23
#include <stdint.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <unistr.h>
28
#include <uniwidth.h>
29

30
#include "boxes.h"
31
#include "detect.h"
32
#include "logging.h"
33
#include "remove.h"
34
#include "shape.h"
35
#include "tools.h"
36
#include "unicode.h"
37

38

39

40
typedef struct _line_ctx_t {
41
    /** index of the first character of the west shape */
42
    size_t west_start;
43

44
    /** index of the character following the last character of the west shape. If equal to `west_start`, then no west
45
     *  shape was detected. */
46
    size_t west_end;
47

48
    /** the length in characters of the matched west shape part */
49
    size_t west_quality;
50

51
    /** index of the first character of the east shape */
52
    size_t east_start;
53

54
    /** index of the character following the last character of the east shape. If equal to `east_start`, then no east
55
     *  shape was detected.  */
56
    size_t east_end;
57

58
    /** the length in characters of the matched east shape part */
59
    size_t east_quality;
60

61
    /** the input line to which the above values refer. Will look very different depending on comparison type. */
62
    uint32_t *input_line_used;
63
} line_ctx_t;
64

65

66

67
typedef struct _remove_ctx_t {
68
    /** Array of flags indicating which sides of the box design are defined as empty. Access via `BTOP` etc. constants. */
69
    int empty_side[NUM_SIDES];
70

71
    /** Flag indicating that there are no invisible characters in the definition of the design we are removing. */
72
    int design_is_mono;
73

74
    /** Flag indicating that there are no invisible characters in the input. */
75
    int input_is_mono;
76

77
    /** Index into `input.lines` of the first line of the box (topmost box line). Lines above are blank. */
78
    size_t top_start_idx;
79

80
    /** Index into `input.lines` of the line following the last line of the top part of the box. If the top part of the
81
     *  box is empty or missing, this value will be equal to `top_start_idx`. */
82
    size_t top_end_idx;
83

84
    /** Index into `input.lines` of the first line of the bottom side of the box. */
85
    size_t bottom_start_idx;
86

87
    /** Index into `input.lines` of the line following the last line of the bottom part of the box. If the bottom part
88
     *  of the box is empty or missing, this value will be equal to `bottom_start_idx`. Lines below are blank. */
89
    size_t bottom_end_idx;
90

91
    /** The current comparison type. This changes whenever another comparison type is tried. */
92
    comparison_t comp_type;
93

94
    /** number of lines in `body` */
95
    size_t body_num_lines;
96

97
    /** Information on the vertical east and west shapes in body lines, one entry for each line between `top_end_idx`
98
     *  (inclusive) and `bottom_start_idx` (exclusive) */
99
    line_ctx_t *body;
100
} remove_ctx_t;
101

102

103

104
static void debug_print_remove_ctx(remove_ctx_t *ctx, char *heading)
160✔
105
{
106
    if (is_debug_logging(MAIN)) {
160!
107
        log_debug(__FILE__, MAIN, "Remove Context %s:\n", heading);
×
108
        log_debug(__FILE__, MAIN, "    - empty_side[BTOP] = %s\n", ctx->empty_side[BTOP] ? "true" : "false");
×
109
        log_debug(__FILE__, MAIN, "    - empty_side[BRIG] = %s\n", ctx->empty_side[BRIG] ? "true" : "false");
×
110
        log_debug(__FILE__, MAIN, "    - empty_side[BBOT] = %s\n", ctx->empty_side[BBOT] ? "true" : "false");
×
111
        log_debug(__FILE__, MAIN, "    - empty_side[BLEF] = %s\n", ctx->empty_side[BLEF] ? "true" : "false");
×
112
        log_debug(__FILE__, MAIN, "    - design_is_mono = %s\n", ctx->design_is_mono ? "true" : "false");
×
113
        log_debug(__FILE__, MAIN, "    - input_is_mono = %s\n", ctx->input_is_mono ? "true" : "false");
×
114
        log_debug(__FILE__, MAIN, "    - top_start_idx = %d\n", (int) ctx->top_start_idx);
×
115
        log_debug(__FILE__, MAIN, "    - top_end_idx = %d\n", (int) ctx->top_end_idx);
×
116
        log_debug(__FILE__, MAIN, "    - bottom_start_idx = %d\n", (int) ctx->bottom_start_idx);
×
117
        log_debug(__FILE__, MAIN, "    - bottom_end_idx = %d\n", (int) ctx->bottom_end_idx);
×
118
        log_debug(__FILE__, MAIN, "    - comp_type = %s\n", comparison_name[ctx->comp_type]);
×
119
        log_debug(__FILE__, MAIN, "    - body (%d lines):\n", (int) ctx->body_num_lines);
×
120
        for (size_t i = 0; i < ctx->body_num_lines; i++) {
×
121
            if (ctx->body[i].input_line_used != NULL) {
×
122
                char *out_input_line_used = u32_strconv_to_output(ctx->body[i].input_line_used);
×
123
                log_debug(__FILE__, MAIN, "        - lctx: \"%s\" (%d characters)\n", out_input_line_used,
×
124
                    (int) u32_strlen(ctx->body[i].input_line_used));
×
UNCOV
125
                BFREE(out_input_line_used);
×
126
            }
127
            else {
UNCOV
128
                log_debug(__FILE__, MAIN, "        - lctx: (null)\n");
×
129
            }
130
            bxstr_t *orgline = input.lines[ctx->top_end_idx + i].text;
×
131
            if (orgline != NULL) {
×
132
                char *out_orgline = bxs_to_output(orgline);
×
133
                log_debug(__FILE__, MAIN, "          orgl: \"%s\" (%d characters, %d columns)\n", out_orgline,
×
134
                    (int) orgline->num_chars, (int) orgline->num_columns);
×
UNCOV
135
                BFREE(out_orgline);
×
136
            }
137
            else {
UNCOV
138
                log_debug(__FILE__, MAIN, "          orgl: (null)\n");
×
139
            }
140
            log_debug(__FILE__, MAIN, "                west: %d-%d (quality: %d), east: %d-%d (quality: %d)\n",
×
141
                (int) ctx->body[i].west_start, (int) ctx->body[i].west_end, (int) ctx->body[i].west_quality,
×
UNCOV
142
                (int) ctx->body[i].east_start, (int) ctx->body[i].east_end, (int) ctx->body[i].east_quality);
×
143
        }
144
    }
145
}
160✔
146

147

148

149
static void debug_print_shapes_relevant(shape_line_ctx_t *shapes_relevant)
795✔
150
{
151
    if (is_debug_logging(MAIN)) {
795!
152
        log_debug(__FILE__, MAIN, "  shapes_relevant = {");
×
153
        for (size_t ds = 0; ds < SHAPES_PER_SIDE; ds++) {
×
154
            if (shapes_relevant[ds].empty) {
×
UNCOV
155
                log_debug_cont(MAIN, "-");
×
156
            }
157
            else {
158
                char *out_shp_text = bxs_to_output(shapes_relevant[ds].text);
×
159
                log_debug_cont(MAIN, "\"%s\"(%d%s)", out_shp_text, (int) shapes_relevant[ds].text->num_chars,
×
160
                    shapes_relevant[ds].elastic ? "E" : "");
×
UNCOV
161
                BFREE(out_shp_text);
×
162
            }
163
            if (ds < SHAPES_PER_SIDE - 1) {
×
UNCOV
164
                log_debug_cont(MAIN, ", ");
×
165
            }
166
        }
UNCOV
167
        log_debug_cont(MAIN, "}\n");
×
168
    }
169
}
795✔
170

171

172

173
static size_t find_first_line()
160✔
174
{
175
    size_t result = input.num_lines;
160✔
176
    for (size_t line_idx = 0; line_idx < input.num_lines; line_idx++) {
162!
177
        if (!bxs_is_blank(input.lines[line_idx].text)) {
162✔
178
            result = line_idx;
160✔
179
            break;
160✔
180
        }
181
    }
182
    return result;
160✔
183
}
184

185

186

187
static size_t find_last_line()
160✔
188
{
189
    size_t result = input.num_lines - 1;
160✔
190
    for (long line_idx = (long) input.num_lines - 1; line_idx >= 0; line_idx--) {
162!
191
        if (!bxs_is_blank(input.lines[line_idx].text)) {
162✔
192
            result = (size_t) line_idx;
160✔
193
            break;
160✔
194
        }
195
    }
196
    return result;
160✔
197
}
198

199

200

201
static int is_shape_line_empty(shape_line_ctx_t *shapes_relevant, size_t shape_idx)
1,325✔
202
{
203
    if (shape_idx < SHAPES_PER_SIDE) {
1,325!
204
        return shapes_relevant[shape_idx].empty || bxs_is_blank(shapes_relevant[shape_idx].text);
1,325✔
205
    }
UNCOV
206
    return 1;
×
207
}
208

209

210

211
static int non_empty_shapes_after(shape_line_ctx_t *shapes_relevant, size_t shape_idx)
858✔
212
{
213
    /* CHECK Can we use shape->is_blank_rightward? */
214
    for (size_t i = shape_idx + 1; i < SHAPES_PER_SIDE - 1; i++) {
1,292✔
215
        if (!is_shape_line_empty(shapes_relevant, i)) {
461✔
216
            return 1;
27✔
217
        }
218
    }
219
    return 0;
831✔
220
}
221

222

223

224
static int is_blank_between(uint32_t *start, uint32_t *end)
384✔
225
{
226
    for (uint32_t *p = start; p < end; p++) {
6,240✔
227
        if (!is_blank(*p)) {
5,858✔
228
            return 0;
2✔
229
        }
230
    }
231
    return 1;
382✔
232
}
233

234

235

236
/**
237
 * Take a shape line and shorten it by cutting off blanks from both ends.
238
 * @param shape_line_ctx info record on the shape line to work on. Contains the original shape line, unshortened.
239
 * @param quality (IN/OUT) the current quality, here the value that was last tested. We will reduce this by one.
240
 * @param prefer_left if 1, first cut all blanks from the start of the shape line, if 0, first cut at the end
241
 * @param allow_left if 1, blanks may be cut from the left of the shape line, if 0, we never cut from the left
242
 * @param allow_right if 1, blanks may be cut from the right of the shape line, if 0, we never cut from the right
243
 * @return the shortened shape line, in new memory, or NULL if further shortening was not possible
244
 */
245
uint32_t *shorten(shape_line_ctx_t *shape_line_ctx, size_t *quality, int prefer_left, int allow_left, int allow_right)
8,600✔
246
{
247
    if (shape_line_ctx == NULL || shape_line_ctx->text == NULL || quality == NULL
8,600✔
248
            || *quality > shape_line_ctx->text->num_chars) {
8,597✔
249
        return NULL;
4✔
250
    }
251

252
    uint32_t *s = shape_line_ctx->text->memory;
8,596✔
253
    uint32_t *e = shape_line_ctx->text->memory + shape_line_ctx->text->num_chars;
8,596✔
254
    prefer_left = allow_left ? prefer_left : 0;
8,596✔
255
    size_t reduction_steps = shape_line_ctx->text->num_chars - *quality + 1;
8,596✔
256
    for (size_t i = 0; i < reduction_steps; i++) {
33,303✔
257
        if (prefer_left) {
26,927✔
258
            if (s < e && is_blank(*s)) {
8,642!
259
                s++;
4,370✔
260
            }
261
            else if (e > s && allow_right && is_blank(*(e - 1))) {
4,272!
262
                e--;
3,281✔
263
            }
264
            else {
265
                break;
266
            }
267
        }
268
        else {
269
            if (e > s && allow_right && is_blank(*(e - 1))) {
18,285✔
270
                e--;
13,321✔
271
            }
272
            else if (s < e && allow_left && is_blank(*s)) {
4,964✔
273
                s++;
3,735✔
274
            }
275
            else {
276
                break;
277
            }
278
        }
279
    }
280

281
    uint32_t *result = NULL;
8,596✔
282
    size_t new_quality = e - s;
8,596✔
283
    if (new_quality < *quality) {
8,596✔
284
        result = u32_strdup(s);
6,376✔
285
        set_char_at(result, new_quality, char_nul);
6,376✔
286
        *quality = new_quality;
6,376✔
287
    }
288
    return result;
8,596✔
289
}
290

291

292

293
static int hmm_shiftable(shape_line_ctx_t *shapes_relevant, uint32_t *cur_pos, size_t shape_idx, uint32_t *end_pos,
294
        int anchored_right);
295

296

297

298
/**
299
 * (horizontal middle match)
300
 * Recursive helper function for match_horiz_line(), uses backtracking.
301
 * @param shapes_relevant the prepared shape lines to be concatenated
302
 * @param cur_pos current position in the input line being matched
303
 * @param shape_idx index into `shapes_relevant` indicating which shape to try now
304
 * @param end_pos first character of the east corner
305
 * @param anchored_left flag indicating that `cur_pos` is already "anchored" or still "shiftable". "Anchored" means
306
 *      that we have matched a non-blank shape line already (corner shape line was not blank). Else "shiftable".
307
 * @param anchored_right flag indicating that the east corner shape was not blank. If this is `false`, it means that
308
 *      a shape may be shortened right if only blank shape lines follow.
309
 * @return `== 1`: success;
310
 *         `== 0`: failed to match
311
 */
312
int hmm(shape_line_ctx_t *shapes_relevant, uint32_t *cur_pos, size_t shape_idx, uint32_t *end_pos, int anchored_left,
18,612✔
313
        int anchored_right)
314
{
315
    if (is_debug_logging(MAIN)) {
18,612!
316
        char *out_cur_pos = u32_strconv_to_output(cur_pos);
×
317
        char *out_end_pos = u32_strconv_to_output(end_pos);
×
UNCOV
318
        log_debug(__FILE__, MAIN, "hmm(shapes_relevant, \"%s\", %d, \"%s\", %s, %s) - enter\n", out_cur_pos,
×
319
                (int) shape_idx, out_end_pos, anchored_left ? "true" : "false", anchored_right ? "true" : "false");
320
        BFREE(out_cur_pos);
×
UNCOV
321
        BFREE(out_end_pos);
×
322
    }
323

324
    int result = 0;
18,612✔
325
    if (!anchored_left) {
18,612✔
326
        result = hmm_shiftable(shapes_relevant, cur_pos, shape_idx, end_pos, anchored_right);
397✔
327
    }
328
    else if (cur_pos > end_pos) {
18,215✔
329
        /* invalid input */
330
        result = 0;
121✔
331
    }
332
    else if (cur_pos == end_pos) {
18,094✔
333
        /* we are at the end, which is fine if there is nothing else to match */
334
        result = (shape_idx == (SHAPES_PER_SIDE - 1) && anchored_right)
229!
335
                || ((shapes_relevant[shape_idx].empty || bxs_is_blank(shapes_relevant[shape_idx].text))
383!
336
                    && !non_empty_shapes_after(shapes_relevant, shape_idx) ? 1 : 0);
154✔
337
    }
338
    else if (shape_idx >= SHAPES_PER_SIDE - 1) {
17,881✔
339
        /* no more shapes to try, which is fine if the rest of the line is blank */
340
        result = u32_is_blank(cur_pos);
55✔
341
    }
342
    else if (shapes_relevant[shape_idx].empty) {
17,826✔
343
        /* the current shape line is empty, try the next one */
344
        result = hmm(shapes_relevant, cur_pos, shape_idx + 1, end_pos, 1, anchored_right);
235✔
345
    }
346
    else {
347
        uint32_t *shape_line = u32_strdup(shapes_relevant[shape_idx].text->memory);
17,591✔
348
        size_t quality = shapes_relevant[shape_idx].text->num_chars;
17,591✔
349
        while (shape_line != NULL && quality > 0) {
35,807✔
350
            if (u32_strncmp(cur_pos, shape_line, quality) == 0) {
18,216✔
351
                BFREE(shape_line);
16,832!
352
                cur_pos = cur_pos + quality;
16,832✔
353
                if (cur_pos == end_pos && !non_empty_shapes_after(shapes_relevant, shape_idx)) {
16,832✔
354
                    result = 1; /* success */
547✔
355
                }
356
                else {
357
                    int rc = 0;
16,285✔
358
                    if (shapes_relevant[shape_idx].elastic) {
16,285✔
359
                        rc = hmm(shapes_relevant, cur_pos, shape_idx, end_pos, 1, anchored_right);
16,049✔
360
                    }
361
                    if (rc == 0) {
16,285✔
362
                        result = hmm(shapes_relevant, cur_pos, shape_idx + 1, end_pos, 1, anchored_right);
1,152✔
363
                    }
364
                    else {
365
                        result = rc;
15,133✔
366
                    }
367
                }
368
            }
369
            else if (!anchored_right) {
1,384✔
370
                shape_line = shorten(shapes_relevant + shape_idx, &quality, 0, 0, 1);
790✔
371
                if (is_debug_logging(MAIN)) {
790!
372
                    char *out_shape_line = u32_strconv_to_output(shape_line);
×
UNCOV
373
                    log_debug(__FILE__, MAIN, "hmm() - shape_line shortened to %d (\"%s\")\n",
×
374
                            (int) quality, out_shape_line);
UNCOV
375
                    BFREE(out_shape_line);
×
376
                }
377
            }
378
            else {
379
                BFREE(shape_line);
594!
380
            }
381
        }
382
    }
383

384
    log_debug(__FILE__, MAIN, "hmm() - exit, result = %d\n", result);
18,612✔
385
    return result;
18,612✔
386
}
387

388

389

390
static int hmm_shiftable(shape_line_ctx_t *shapes_relevant, uint32_t *cur_pos, size_t shape_idx, uint32_t *end_pos,
397✔
391
        int anchored_right)
392
{
393
    int result = 0;
397✔
394
    int shapes_are_empty = 1;
397✔
395
    for (size_t i = shape_idx; i < SHAPES_PER_SIDE - 1; i++) {
752✔
396
        if (!is_shape_line_empty(shapes_relevant, i)) {
734✔
397
            shapes_are_empty = 0;
379✔
398
            int can_shorten_right = -1;
379✔
399
            size_t quality = shapes_relevant[i].text->num_chars;
379✔
400
            uint32_t *shape_line = shapes_relevant[i].text->memory;
379✔
401
            while (shape_line != NULL) {
699✔
402
                uint32_t *p = u32_strstr(cur_pos, shape_line);
685✔
403
                if (p != NULL && p < end_pos && is_blank_between(cur_pos, p)) {
685!
404
                    result = hmm(shapes_relevant, p + quality, i + (shapes_relevant[i].elastic ? 0 : 1),
365✔
405
                            end_pos, 1, anchored_right);
406
                    if (result == 0 && shapes_relevant[i].elastic) {
365!
407
                        result = hmm(shapes_relevant, p + quality, i + 1, end_pos, 1, anchored_right);
4✔
408
                    }
409
                    break;
365✔
410
                }
411
                if (can_shorten_right == -1) {
320✔
412
                    /* we can only shorten right if the east corner shape line is also empty */
413
                    can_shorten_right = non_empty_shapes_after(shapes_relevant, i)
264✔
414
                            || !is_shape_line_empty(shapes_relevant, SHAPES_PER_SIDE - 1) ? 0 : 1;
132✔
415
                }
416
                shape_line = shorten(shapes_relevant + i, &quality, 0, 1, can_shorten_right);
320✔
417
            }
418
            break;
379✔
419
        }
420
    }
421
    if (shapes_are_empty) {
397✔
422
        /* all shapes were empty, which is fine if line was blank */
423
        result = is_blank_between(cur_pos, end_pos);
18✔
424
    }
425
    return result;
397✔
426
}
427

428

429

430
static shape_line_ctx_t *prepare_comp_shapes_horiz(int hside, comparison_t comp_type, size_t shape_line_idx)
795✔
431
{
432
    shape_t *side_shapes = hside == BTOP ? north_side : south_side_rev;
795✔
433
    shape_line_ctx_t *shapes_relevant = (shape_line_ctx_t *) calloc(SHAPES_PER_SIDE, sizeof(shape_line_ctx_t));
795✔
434

435
    for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
4,770✔
436
        shapes_relevant[i].elastic = opt.design->shape[side_shapes[i]].elastic;
3,975✔
437
        shapes_relevant[i].empty = isempty(opt.design->shape + side_shapes[i]);
3,975✔
438
        if (!shapes_relevant[i].empty) {
3,975✔
439
            uint32_t *s = prepare_comp_shape(opt.design, side_shapes[i], shape_line_idx, comp_type, 0,
3,471✔
440
                    i == SHAPES_PER_SIDE - 1);
441
            shapes_relevant[i].text = bxs_from_unicode(s);
3,471✔
442
            BFREE(s);
3,471!
443
        }
444
    }
445

446
    return shapes_relevant;
795✔
447
}
448

449

450

451
static match_result_t *new_match_result(uint32_t *p, size_t p_idx, size_t len, int shiftable)
1,514✔
452
{
453
    match_result_t *result = (match_result_t *) calloc(1, sizeof(match_result_t));
1,514✔
454
    result->p = p;
1,514✔
455
    result->p_idx = p_idx;
1,514✔
456
    result->len = len;
1,514✔
457
    result->shiftable = shiftable;
1,514✔
458
    return result;
1,514✔
459
}
460

461

462

463
/**
464
 * Match a `shape_line` at the beginning (`vside` == `BLEF`) or the end (`vside` == `BRIG`) of an `input_line`.
465
 * Both `input_line` and `shape_line` may contain invisible characters, who are then matched, too, just like any other
466
 * characters.
467
 * @param vside BLEF or BRIG
468
 * @param input_line the input line to examine. We expect that it was NOT trimmed.
469
 * @param shape_line the shape line to match, also NOT trimmed
470
 * @return pointer to the match result (in existing memory of `input_line->memory`), or `NULL` if no match
471
 */
472
match_result_t *match_outer_shape(int vside, bxstr_t *input_line, bxstr_t *shape_line)
1,527✔
473
{
474
    if (input_line == NULL || input_line->num_chars == 0 || shape_line == NULL || shape_line->num_chars == 0) {
1,527!
475
        return NULL;
4✔
476
    }
477

478
    if (vside == BLEF) {
1,523✔
479
        if (bxs_is_blank(shape_line)) {
769✔
480
            return new_match_result(input_line->memory, 0, 0, 1);
359✔
481
        }
482
        for (uint32_t *s = shape_line->memory; s == shape_line->memory || is_blank(*s); s++) {
417✔
483
            uint32_t *p = u32_strstr(input_line->memory, s);
413✔
484
            size_t p_idx = p != NULL ? p - input_line->memory : 0;
413✔
485
            if (p == NULL || p_idx > input_line->first_char[input_line->indent]) {
413✔
486
                continue;  /* not found or found too far in */
7✔
487
            }
488
            return new_match_result(p, p_idx, shape_line->num_chars - (s - shape_line->memory), 0);
406✔
489
        }
490
    }
491
    else {
492
        if (bxs_is_blank(shape_line)) {
754✔
493
            uint32_t *p = bxs_last_char_ptr(input_line);
329✔
494
            size_t p_idx = p - input_line->memory;
329✔
495
            return new_match_result(p, p_idx, 0, 1);
329✔
496
        }
497
        int slen = shape_line->num_chars;
425✔
498
        uint32_t *s = u32_strdup(shape_line->memory);
425✔
499
        for (; slen == (int) shape_line->num_chars || is_blank(s[slen]); slen--) {
435✔
500
            s[slen] = char_nul;
430✔
501
            uint32_t *p = u32_strnrstr(input_line->memory, s, slen);
430✔
502
            size_t p_idx = p != NULL ? p - input_line->memory : 0;
430✔
503
            if (p == NULL || p_idx + slen
430✔
504
                    < input_line->first_char[input_line->num_chars_visible - input_line->trailing]) {
422✔
505
                continue; /* not found or found too far in */
10✔
506
            }
507
            BFREE(s);
420!
508
            return new_match_result(p, p_idx, (size_t) slen, 0);
420✔
509
        }
510
        BFREE(s);
5!
511
    }
512
    return NULL;
9✔
513
}
514

515

516

517
static int match_horiz_line(remove_ctx_t *ctx, int hside, size_t input_line_idx, size_t shape_line_idx)
780✔
518
{
519
    log_debug(__FILE__, MAIN, "match_horiz_line(ctx, %s, %d, %d)\n",
780✔
520
                hside == BTOP ? "BTOP" : "BBOT", (int) input_line_idx, (int) shape_line_idx);
521

522
    int result = 0;
780✔
523
    for (comparison_t comp_type = 0; comp_type < NUM_COMPARISON_TYPES; comp_type++) {
879!
524
        if (!comp_type_is_viable(comp_type, ctx->input_is_mono, ctx->design_is_mono)) {
879✔
525
            continue;
84✔
526
        }
527
        ctx->comp_type = comp_type;
795✔
528
        log_debug(__FILE__, MAIN, "  Setting comparison type to: %s\n", comparison_name[comp_type]);
795✔
529

530
        shape_line_ctx_t *shapes_relevant = prepare_comp_shapes_horiz(hside, comp_type, shape_line_idx);
795✔
531
        debug_print_shapes_relevant(shapes_relevant);
795✔
532

533
        bxstr_t *input_prepped1 = bxs_from_unicode(prepare_comp_input(input_line_idx, 0, comp_type, 0, NULL, NULL));
795✔
534
        bxstr_t *input_prepped = bxs_rtrim(input_prepped1);
795✔
535
        bxs_append_spaces(input_prepped, opt.design->shape[NW].width + opt.design->shape[NE].width);
795✔
536
        bxs_free(input_prepped1);
795✔
537
        if (is_debug_logging(MAIN)) {
795!
538
            char *out_input_prepped = bxs_to_output(input_prepped);
×
539
            log_debug(__FILE__, MAIN, "  input_prepped = \"%s\"\n", out_input_prepped);
×
UNCOV
540
            BFREE(out_input_prepped);
×
541
        }
542

543
        uint32_t *cur_pos = input_prepped->memory;
795✔
544
        match_result_t *mrl = NULL;
795✔
545
        if (!ctx->empty_side[BLEF]) {
795✔
546
            mrl = match_outer_shape(BLEF, input_prepped, shapes_relevant[0].text);
763✔
547
            if (mrl != NULL) {
763✔
548
                cur_pos = mrl->p + mrl->len;
761✔
549
            }
550
        }
551

552
        uint32_t *end_pos = bxs_last_char_ptr(input_prepped);
795✔
553
        match_result_t *mrr = NULL;
795✔
554
        if (!ctx->empty_side[BRIG]) {
795✔
555
            mrr = match_outer_shape(BRIG, input_prepped, shapes_relevant[SHAPES_PER_SIDE - 1].text);
749✔
556
            if (mrr != NULL) {
749✔
557
                end_pos = mrr->p;
744✔
558
            }
559
        }
560
        if (is_debug_logging(MAIN)) {
795!
561
            char *out_cur_pos = u32_strconv_to_output(cur_pos);
×
562
            char *out_end_pos = u32_strconv_to_output(end_pos);
×
563
            log_debug(__FILE__, MAIN, "  cur_pos = \"%s\" (index %d)\n",
×
564
                    out_cur_pos, (int) BMAX(cur_pos - input_prepped->memory, 0));
×
565
            log_debug(__FILE__, MAIN, "  end_pos = \"%s\" (index %d)\n",
×
566
                    out_end_pos, (int) BMAX(end_pos - input_prepped->memory, 0));
×
567
            BFREE(out_cur_pos);
×
UNCOV
568
            BFREE(out_end_pos);
×
569
        }
570

571
        result = hmm(shapes_relevant, cur_pos, 1, end_pos, (mrl == NULL) || mrl->shiftable ? 0 : 1,
1,539✔
572
                (mrr == NULL) || mrr->shiftable ? 0 : 1);
744✔
573

574
        BFREE(mrl);
795✔
575
        BFREE(mrr);
795✔
576
        for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
4,770✔
577
            bxs_free(shapes_relevant[i].text);
3,975✔
578
        }
579
        BFREE(shapes_relevant);
795!
580

581
        if (result) {
795✔
582
            log_debug(__FILE__, MAIN, "Matched %s side line using comp_type=%s and shape_line_idx=%d\n",
780✔
583
                    hside == BTOP ? "top" : "bottom", comparison_name[comp_type], (int) shape_line_idx);
584
            break;
780✔
585
        }
586
    }
587

588
    return result;
780✔
589
}
590

591

592

593
static size_t find_top_side(remove_ctx_t *ctx)
136✔
594
{
595
    size_t result = ctx->top_start_idx;
136✔
596
    sentry_t *shapes = opt.design->shape;
136✔
597
    for (size_t input_line_idx = ctx->top_start_idx;
136✔
598
            input_line_idx < input.num_lines && input_line_idx < ctx->top_start_idx + shapes[NE].height;
526!
599
            input_line_idx++)
390✔
600
    {
601
        int matched = 0;
390✔
602
        size_t shape_lines_tested = 0;
390✔
603
        for (size_t shape_line_idx = (input_line_idx - ctx->top_start_idx) % shapes[NE].height;
390✔
604
                shape_lines_tested < shapes[NE].height;
390!
UNCOV
605
                shape_line_idx = (shape_line_idx + 1) % shapes[NE].height, shape_lines_tested++)
×
606
        {
607
            if (match_horiz_line(ctx, BTOP, input_line_idx, shape_line_idx)) {
390!
608
                matched = 1;
390✔
609
                break;
390✔
610
            }
611
        }
612
        if (!matched) {
390!
UNCOV
613
            break;
×
614
        }
615
        result = input_line_idx + 1;
390✔
616
    }
617
    return result;
136✔
618
}
619

620

621

622
static size_t find_bottom_side(remove_ctx_t *ctx)
138✔
623
{
624
    size_t result = ctx->bottom_end_idx;
138✔
625
    sentry_t *shapes = opt.design->shape;
138✔
626
    for (long input_line_idx = (long) ctx->bottom_end_idx - 1;
138✔
627
            input_line_idx >= 0 && input_line_idx >= (long) ctx->bottom_end_idx - (long) shapes[SE].height;
528!
628
            input_line_idx--)
390✔
629
    {
630
        int matched = 0;
390✔
631
        size_t shape_lines_tested = 0;
390✔
632
        for (long shape_line_idx = shapes[SE].height - (ctx->bottom_end_idx - input_line_idx);
390✔
633
                shape_line_idx >= 0 && shape_lines_tested < shapes[SE].height;
390!
634
                shape_lines_tested++,
×
UNCOV
635
                shape_line_idx = shape_line_idx == 0 ? (long) (shapes[SE].height - 1) : (long) (shape_line_idx - 1))
×
636
        {
637
            if (match_horiz_line(ctx, BBOT, input_line_idx, shape_line_idx)) {
390!
638
                matched = 1;
390✔
639
                break;
390✔
640
            }
641
        }
642
        if (!matched) {
390!
UNCOV
643
            break;
×
644
        }
645
        result = input_line_idx;
390✔
646
    }
647
    return result;
138✔
648
}
649

650

651

652
static size_t count_shape_lines(shape_t side_shapes[])
292✔
653
{
654
    size_t result = 0;
292✔
655
    for (size_t i = 0; i < SHAPES_PER_SIDE - CORNERS_PER_SIDE; i++) {
1,168✔
656
        if (!isempty(opt.design->shape + side_shapes[i])) {
876✔
657
            result += opt.design->shape[side_shapes[i]].height;
368✔
658
        }
659
    }
660
    return result;
292✔
661
}
662

663

664
static shape_line_ctx_t **prepare_comp_shapes_vert(int vside, comparison_t comp_type)
292✔
665
{
666
    shape_t west_side_shapes[SHAPES_PER_SIDE - CORNERS_PER_SIDE] = {WNW, W, WSW};
292✔
667
    shape_t east_side_shapes[SHAPES_PER_SIDE - CORNERS_PER_SIDE] = {ENE, E, ESE};
292✔
668
    shape_t side_shapes[SHAPES_PER_SIDE - CORNERS_PER_SIDE];
669
    if (vside == BLEF) {
292✔
670
        memcpy(side_shapes, west_side_shapes, (SHAPES_PER_SIDE - CORNERS_PER_SIDE) * sizeof(shape_t));
157✔
671
    }
672
    else {
673
        memcpy(side_shapes, east_side_shapes, (SHAPES_PER_SIDE - CORNERS_PER_SIDE) * sizeof(shape_t));
135✔
674
    }
675

676
    size_t num_shape_lines = count_shape_lines(side_shapes);
292✔
677

678
    shape_line_ctx_t **shape_lines = (shape_line_ctx_t **) calloc(num_shape_lines + 1, sizeof(shape_line_ctx_t *));
292✔
679
    for (size_t i = 0; i < num_shape_lines; i++) {
854✔
680
        shape_lines[i] = (shape_line_ctx_t *) calloc(1, sizeof(shape_line_ctx_t));
562✔
681
    }
682

683
    for (size_t shape_idx = 0, i = 0; shape_idx < SHAPES_PER_SIDE - CORNERS_PER_SIDE; shape_idx++) {
1,168✔
684
        if (!isempty(opt.design->shape + side_shapes[shape_idx])) {
876✔
685
            int deep_empty = isdeepempty(opt.design->shape + side_shapes[shape_idx]);
368✔
686
            for (size_t slno = 0; slno < opt.design->shape[side_shapes[shape_idx]].height; slno++, i++) {
930✔
687
                uint32_t *s = prepare_comp_shape(opt.design, side_shapes[shape_idx], slno, comp_type, 0, 0);
562✔
688
                shape_lines[i]->text = bxs_from_unicode(s);
562✔
689
                shape_lines[i]->empty = deep_empty;
562✔
690
                shape_lines[i]->elastic = opt.design->shape[side_shapes[shape_idx]].elastic;
562✔
691
                BFREE(s);
562!
692
            }
693
        }
694
    }
695

696
    return shape_lines;
292✔
697
}
698

699

700

701
static void free_shape_lines(shape_line_ctx_t **shape_lines)
318✔
702
{
703
    if (shape_lines != NULL) {
318✔
704
        for (shape_line_ctx_t **p = shape_lines; *p != NULL; p++) {
854✔
705
            bxs_free((*p)->text);
562✔
706
            BFREE(*p);
562!
707
        }
708
        BFREE(shape_lines);
292!
709
    }
710
}
318✔
711

712

713

714
static void match_vertical_side(remove_ctx_t *ctx, int vside, shape_line_ctx_t **shape_lines, uint32_t *input_line,
1,616✔
715
    size_t line_idx, size_t input_length, size_t input_indent, size_t input_trailing)
716
{
717
    line_ctx_t *line_ctx = ctx->body + (line_idx - ctx->top_end_idx);
1,616✔
718

719
    for (shape_line_ctx_t **shape_line_ctx = shape_lines; *shape_line_ctx != NULL; shape_line_ctx++) {
5,488✔
720
        if ((*shape_line_ctx)->empty) {
3,872!
UNCOV
721
            continue;
×
722
        }
723

724
        size_t max_quality = (*shape_line_ctx)->text->num_chars;
3,872✔
725
        size_t quality = max_quality;
3,872✔
726
        uint32_t *shape_text = (*shape_line_ctx)->text->memory;
3,872✔
727
        uint32_t *to_free = NULL;
3,872✔
728
        while(shape_text != NULL) {
11,515✔
729
            uint32_t *p;
730
            if (vside == BLEF) {
9,289✔
731
                p = u32_strstr(input_line, shape_text);
4,169✔
732
            }
733
            else {
734
                p = u32_strnrstr(input_line, shape_text, quality);
5,120✔
735
            }
736
            BFREE(to_free);
9,289✔
737
            shape_text = NULL;
9,289✔
738

739
            if ((p == NULL)
9,289✔
740
                    || (vside == BLEF && ((size_t) (p - input_line) > input_indent + (max_quality - quality)))
2,828✔
741
                    || (vside == BRIG && ((size_t) (p - input_line) < input_length - input_trailing - quality))) {
2,668✔
742
                shape_text = shorten(*shape_line_ctx, &quality, vside == BLEF, 1, 1);
7,469✔
743
                to_free = shape_text;
7,469✔
744
            }
745
            else if (vside == BLEF) {
1,820✔
746
                if (quality > line_ctx->west_quality) {
1,000✔
747
                    line_ctx->west_start = (size_t) (p - input_line);
892✔
748
                    line_ctx->west_end = line_ctx->west_start + quality;
892✔
749
                    line_ctx->west_quality = quality;
892✔
750
                    BFREE(line_ctx->input_line_used);
892✔
751
                    line_ctx->input_line_used = u32_strdup(input_line);
892✔
752
                    break;
892✔
753
                }
754
            }
755
            else if (vside == BRIG) {
820!
756
                if (quality > line_ctx->east_quality) {
820✔
757
                    line_ctx->east_start = (size_t) (p - input_line);
754✔
758
                    line_ctx->east_end = line_ctx->east_start + quality;
754✔
759
                    line_ctx->east_quality = quality;
754✔
760
                    BFREE(line_ctx->input_line_used);
754✔
761
                    line_ctx->input_line_used = u32_strdup(input_line);
754✔
762
                    break;
754✔
763
                }
764
            }
765
        }
766
    }
767
}
1,616✔
768

769

770

771
static int sufficient_body_quality(remove_ctx_t *ctx)
159✔
772
{
773
    size_t num_body_lines = ctx->bottom_start_idx - ctx->top_end_idx;
159✔
774
    size_t total_quality = 0;
159✔
775
    line_ctx_t *body = ctx->body;
159✔
776
    for (size_t body_line_idx = 0; body_line_idx < num_body_lines; body_line_idx++) {
1,036✔
777
        line_ctx_t line_ctx = body[body_line_idx];
877✔
778
        total_quality += line_ctx.east_quality + line_ctx.west_quality;
877✔
779
    }
780

781
    size_t max_quality = 0;
159✔
782
    if (!ctx->empty_side[BLEF]) {
159✔
783
        max_quality += opt.design->shape[NW].width;
157✔
784
    }
785
    if (!ctx->empty_side[BRIG]) {
159✔
786
        max_quality += opt.design->shape[NE].width;
135✔
787
    }
788
    max_quality = max_quality * num_body_lines;
159✔
789

790
    /* If we manage to match 50%, then it is unlikely to improve with a different comparison mode. */
UNCOV
791
    int sufficient = (max_quality == 0 && total_quality == 0)
×
792
            || (max_quality > 0 && (total_quality > 0.5 * max_quality));
159!
793
    log_debug(__FILE__, MAIN, "sufficient_body_quality() found body match quality of %d/%d (%s).\n",
159✔
794
            (int) total_quality, (int) max_quality, sufficient ? "sufficient" : "NOT sufficient");
795

796
    return sufficient;
159✔
797
}
798

799

800

801
static void reset_body(remove_ctx_t *ctx)
159✔
802
{
803
    if (ctx->body != NULL) {
159!
804
        for (size_t i = 0; i < ctx->body_num_lines; i++) {
1,036✔
805
            BFREE(ctx->body[i].input_line_used);
877!
806
        }
807
        memset(ctx->body, 0, ctx->body_num_lines * sizeof(line_ctx_t));
159✔
808
    }
809
}
159✔
810

811

812

813
static void find_vertical_shapes(remove_ctx_t *ctx)
160✔
814
{
815
    int west_empty = ctx->empty_side[BLEF];
160✔
816
    int east_empty = ctx->empty_side[BRIG];
160✔
817
    if (west_empty && east_empty) {
160✔
818
        return;
2✔
819
    }
820

821
    for (comparison_t comp_type = 0; comp_type < NUM_COMPARISON_TYPES; comp_type++) {
191✔
822
        if (!comp_type_is_viable(comp_type, ctx->input_is_mono, ctx->design_is_mono)) {
185✔
823
            continue;
26✔
824
        }
825
        ctx->comp_type = comp_type;
159✔
826
        log_debug(__FILE__, MAIN, "find_vertical_shapes(): comp_type = %s\n", comparison_name[comp_type]);
159✔
827
        reset_body(ctx);
159✔
828

829
        shape_line_ctx_t **shape_lines_west = NULL;
159✔
830
        if (!west_empty) {
159✔
831
            shape_lines_west = prepare_comp_shapes_vert(BLEF, comp_type);
157✔
832
        }
833
        shape_line_ctx_t **shape_lines_east = NULL;
159✔
834
        if (!east_empty) {
159✔
835
            shape_lines_east = prepare_comp_shapes_vert(BRIG, comp_type);
135✔
836
        }
837

838
        for (size_t input_line_idx = ctx->top_end_idx; input_line_idx < ctx->bottom_start_idx; input_line_idx++) {
1,036✔
839
            size_t input_indent = 0;
877✔
840
            size_t input_trailing = 0;
877✔
841
            uint32_t *input_line = prepare_comp_input(input_line_idx, 0, comp_type, 0, &input_indent, &input_trailing);
877✔
842
            size_t input_length = u32_strlen(input_line);
877✔
843

844
            if (!west_empty) {
877✔
845
                match_vertical_side(ctx, BLEF, shape_lines_west,
867✔
846
                        input_line, input_line_idx, input_length, input_indent, input_trailing);
847
            }
848
            if (!east_empty) {
877✔
849
                match_vertical_side(ctx, BRIG, shape_lines_east,
749✔
850
                        input_line, input_line_idx, input_length, input_indent, input_trailing);
851
            }
852
        }
853

854
        free_shape_lines(shape_lines_west);
159✔
855
        free_shape_lines(shape_lines_east);
159✔
856

857
        if (sufficient_body_quality(ctx)) {
159✔
858
            break;
152✔
859
        }
860
    }
861
}
862

863

864

865
/**
866
 * If the user didn't specify a design to remove, autodetect it.
867
 * Since this requires knowledge of all available designs, the entire config file had to be parsed (earlier).
868
 */
869
static void detect_design_if_needed()
161✔
870
{
871
    if (opt.design_choice_by_user == 0) {
161✔
872
        design_t *tmp = autodetect_design();
8✔
873
        if (tmp) {
8✔
874
            opt.design = tmp;
7✔
875
            log_debug(__FILE__, MAIN, "Design autodetection: Removing box of design \"%s\".\n", opt.design->name);
7✔
876
        }
877
        else {
878
            fprintf(stderr, "%s: Box design autodetection failed. Use -d option.\n", PROJECT);
1✔
879
            exit(EXIT_FAILURE);
1✔
880
        }
881
    }
882
    else {
883
        log_debug(__FILE__, MAIN, "Design was chosen by user: %s\n", opt.design->name);
153✔
884
    }
885
}
160✔
886

887

888

889
static void free_line_text(line_t *line)
1,779✔
890
{
891
    BFREE(line->cache_visible);
1,779✔
892
    bxs_free(line->text);
1,779✔
893
    line->text = NULL;
1,779✔
894
}
1,779✔
895

896

897

898
static void free_line(line_t *line)
881✔
899
{
900
    free_line_text(line);
881✔
901
    BFREE(line->tabpos);
881!
902
    line->tabpos_len = 0;
881✔
903
}
881✔
904

905

906

907
static void killblank(remove_ctx_t *ctx)
160✔
908
{
909
    size_t lines_removed = 0;
160✔
910
    size_t max_lines_removable = opt.mend && !opt.killblank ? (size_t) BMAX(opt.design->padding[BTOP], 0) : SIZE_MAX;
160!
911
    while (ctx->top_end_idx < ctx->bottom_start_idx && lines_removed < max_lines_removable
371✔
912
            && empty_line(input.lines + ctx->top_end_idx))
338!
913
    {
914
        log_debug(__FILE__, MAIN, "Killing leading blank line in box body.\n");
51✔
915
        ++(ctx->top_end_idx);
51✔
916
        --(ctx->body_num_lines);
51✔
917
        ++lines_removed;
51✔
918
    }
919

920
    lines_removed = 0;
160✔
921
    max_lines_removable = opt.mend && !opt.killblank ? (size_t) BMAX(opt.design->padding[BBOT], 0) : SIZE_MAX;
160!
922
    while (ctx->bottom_start_idx > ctx->top_end_idx && lines_removed < max_lines_removable
370✔
923
            && empty_line(input.lines + ctx->bottom_start_idx - 1))
336!
924
    {
925
        log_debug(__FILE__, MAIN, "Killing trailing blank line in box body.\n");
50✔
926
        --(ctx->bottom_start_idx);
50✔
927
        --(ctx->body_num_lines);
50✔
928
        ++lines_removed;
50✔
929
    }
930
}
160✔
931

932

933

934
static int org_is_not_blank(bxstr_t *org_line, comparison_t comp_type, size_t idx)
960✔
935
{
936
    if (comp_type == literal || comp_type == ignore_invisible_shape) {
960!
937
        return !is_blank(org_line->memory[idx]);
952✔
938
    }
939
    return !is_blank(org_line->memory[org_line->visible_char[idx]]);
8✔
940
}
941

942

943

944
static size_t max_chars_line(bxstr_t *org_line, comparison_t comp_type)
1,015✔
945
{
946
    return (comp_type == literal || comp_type == ignore_invisible_shape)
17✔
947
            ? org_line->num_chars : org_line->num_chars_visible;
1,032✔
948
}
949

950

951

952
static size_t confirmed_padding(bxstr_t *org_line, comparison_t comp_type, size_t start_idx, size_t num_padding)
866✔
953
{
954
    size_t result = 0;
866✔
955
    size_t max_chars = max_chars_line(org_line, comp_type);
866✔
956
    for (size_t i = start_idx; i < BMIN(max_chars, start_idx + num_padding); i++) {
1,826✔
957
        if (org_is_not_blank(org_line, comp_type, i)) {
960!
UNCOV
958
            break;
×
959
        }
960
        result++;
960✔
961
    }
962
    return result;
866✔
963
}
964

965

966

967
static void remove_top_from_input(remove_ctx_t *ctx)
160✔
968
{
969
    if (ctx->top_end_idx > ctx->top_start_idx) {
160✔
970
        for (size_t j = ctx->top_start_idx; j < ctx->top_end_idx; ++j) {
578✔
971
            free_line(input.lines + j);
441✔
972
        }
973
        memmove(input.lines + ctx->top_start_idx, input.lines + ctx->top_end_idx,
137✔
974
                (input.num_lines - ctx->top_end_idx) * sizeof(line_t));
137✔
975
        input.num_lines -= ctx->top_end_idx - ctx->top_start_idx;
137✔
976
    }
977
}
160✔
978

979

980

981
static size_t calculate_start_idx(remove_ctx_t *ctx, size_t body_line_idx)
888✔
982
{
983
    size_t input_line_idx = ctx->top_end_idx + body_line_idx;
888✔
984
    line_ctx_t *lctx = ctx->body + body_line_idx;
888✔
985
    bxstr_t *org_line = input.lines[input_line_idx].text;
888✔
986

987
    size_t s_idx = 0;
888✔
988
    if (lctx->west_quality > 0) {
888✔
989
        s_idx = lctx->west_end + confirmed_padding(org_line, ctx->comp_type, lctx->west_end, opt.design->padding[BLEF]);
866✔
990
    }
991
    if (ctx->comp_type == ignore_invisible_input || ctx->comp_type == ignore_invisible_all) {
888✔
992
        /* our line context worked with visible characters only, convert back to org_line */
993
        s_idx = org_line->first_char[s_idx];
15✔
994
    }
995
    return s_idx;
888✔
996
}
997

998

999

1000
static size_t calculate_end_idx(remove_ctx_t *ctx, size_t body_line_idx)
888✔
1001
{
1002
    size_t input_line_idx = ctx->top_end_idx + body_line_idx;
888✔
1003
    line_ctx_t *lctx = ctx->body + body_line_idx;
888✔
1004
    bxstr_t *org_line = input.lines[input_line_idx].text;
888✔
1005

1006
    size_t e_idx = lctx->east_quality > 0 ? lctx->east_start : max_chars_line(org_line, ctx->comp_type);
888✔
1007
    if (ctx->comp_type == ignore_invisible_input || ctx->comp_type == ignore_invisible_all) {
888✔
1008
        e_idx = org_line->first_char[e_idx];
15✔
1009
    }
1010
    return e_idx;
888✔
1011
}
1012

1013

1014

1015
static void remove_vertical_from_input(remove_ctx_t *ctx)
160✔
1016
{
1017
    for (size_t body_line_idx = 0; body_line_idx < ctx->body_num_lines; body_line_idx++) {
1,048✔
1018
        size_t input_line_idx = ctx->top_end_idx + body_line_idx;
888✔
1019
        bxstr_t *org_line = input.lines[input_line_idx].text;
888✔
1020
        size_t s_idx = calculate_start_idx(ctx, body_line_idx);
888✔
1021
        size_t e_idx = calculate_end_idx(ctx, body_line_idx);
888✔
1022
        log_debug(__FILE__, MAIN, "remove_vertical_from_input(): body_line_idx=%d, input_line_idx=%d, s_idx=%d, "
888✔
1023
                "e_idx=%d, input.indent=%d\n", (int) body_line_idx, (int) input_line_idx, (int) s_idx, (int) e_idx,
1024
                (int) input.indent);
888✔
1025

1026
        bxstr_t *temp2 = bxs_substr(org_line, s_idx, e_idx);
888✔
1027
        if (opt.indentmode == 'b' || opt.indentmode == '\0') {
1,775!
1028
            /* restore indentation */
1029
            bxstr_t *temp = bxs_prepend_spaces(temp2, input.indent);
887✔
1030
            free_line_text(input.lines + input_line_idx);
887✔
1031
            input.lines[input_line_idx].text = temp;
887✔
1032
            bxs_free(temp2);
887✔
1033
        }
1034
        else {
1035
            /* remove indentation */
1036
            free_line_text(input.lines + input_line_idx);
1✔
1037
            input.lines[input_line_idx].text = temp2;
1✔
1038
        }
1039
    }
1040
}
160✔
1041

1042

1043

1044
static void remove_bottom_from_input(remove_ctx_t *ctx)
160✔
1045
{
1046
    if (ctx->bottom_end_idx > ctx->bottom_start_idx) {
160✔
1047
        for (size_t j = ctx->bottom_start_idx; j < ctx->bottom_end_idx; ++j) {
579✔
1048
            free_line(input.lines + j);
440✔
1049
        }
1050
        if (ctx->bottom_end_idx < input.num_lines) {
139✔
1051
            memmove(input.lines + ctx->bottom_start_idx, input.lines + ctx->bottom_end_idx,
1✔
1052
                    (input.num_lines - ctx->bottom_end_idx) * sizeof(line_t));
1✔
1053
        }
1054
        input.num_lines -= ctx->bottom_end_idx - ctx->bottom_start_idx;
139✔
1055
    }
1056
}
160✔
1057

1058

1059

1060
static void remove_default_padding(remove_ctx_t *ctx, int num_blanks)
4✔
1061
{
1062
    if (num_blanks > 0) {
4✔
1063
        for (size_t body_line_idx = 0; body_line_idx < ctx->body_num_lines; body_line_idx++) {
12✔
1064
            size_t input_line_idx = ctx->top_start_idx + body_line_idx; /* top_start_idx, because top was removed! */
10✔
1065
            bxstr_t *temp = bxs_cut_front(input.lines[input_line_idx].text, (size_t) num_blanks);
10✔
1066
            free_line_text(input.lines + input_line_idx);
10✔
1067
            input.lines[input_line_idx].text = temp;
10✔
1068
        }
1069
        input.indent -= (size_t) num_blanks;
2✔
1070
        input.maxline -= (size_t) num_blanks;
2✔
1071
    }
1072
}
4✔
1073

1074

1075

1076
static void apply_results_to_input(remove_ctx_t *ctx)
160✔
1077
{
1078
    remove_vertical_from_input(ctx);
160✔
1079

1080
    if (opt.killblank || opt.mend) {
160!
1081
        killblank(ctx);
160✔
1082
    }
1083
    remove_bottom_from_input(ctx);
160✔
1084
    remove_top_from_input(ctx);
160✔
1085

1086
    input.maxline = 0;
160✔
1087
    input.indent = SIZE_MAX;
160✔
1088
    for (size_t j = 0; j < input.num_lines; ++j) {
951✔
1089
        if (input.lines[j].text->num_columns > input.maxline) {
791✔
1090
            input.maxline = input.lines[j].text->num_columns;
326✔
1091
        }
1092
        if (input.lines[j].text->indent < input.indent) {
791✔
1093
            input.indent = input.lines[j].text->indent;
161✔
1094
        }
1095
    }
1096
    if (ctx->empty_side[BLEF]) {
160✔
1097
        /* If the side were not open, default padding would have been removed when the side was removed. */
1098
        remove_default_padding(ctx, BMIN((int) input.indent, opt.design->padding[BLEF]));
4✔
1099
    }
1100

1101
    size_t num_lines_removed = BMAX(ctx->top_end_idx - ctx->top_start_idx, (size_t) 0)
160✔
1102
            + BMAX(ctx->bottom_end_idx - ctx->bottom_start_idx, (size_t) 0);
160✔
1103
    memset(input.lines + input.num_lines, 0, num_lines_removed * sizeof(line_t));
160✔
1104

1105
    if (is_debug_logging(MAIN)) {
160!
1106
        print_input_lines(" (remove_box) after box removal");
×
UNCOV
1107
        log_debug(__FILE__, MAIN, "Number of lines shrunk by %d.\n", (int) num_lines_removed);
×
1108
    }
1109
}
160✔
1110

1111

1112

1113
int remove_box()
161✔
1114
{
1115
    detect_design_if_needed();
161✔
1116

1117
    remove_ctx_t *ctx = (remove_ctx_t *) calloc(1, sizeof(remove_ctx_t));
160✔
1118
    ctx->empty_side[BTOP] = empty_side(opt.design->shape, BTOP);
160✔
1119
    ctx->empty_side[BRIG] = empty_side(opt.design->shape, BRIG);
160✔
1120
    ctx->empty_side[BBOT] = empty_side(opt.design->shape, BBOT);
160✔
1121
    ctx->empty_side[BLEF] = empty_side(opt.design->shape, BLEF);
160✔
1122
    log_debug(__FILE__, MAIN, "Empty sides? Top: %d, Right: %d, Bottom: %d, Left: %d\n",
160✔
1123
            ctx->empty_side[BTOP], ctx->empty_side[BRIG], ctx->empty_side[BBOT], ctx->empty_side[BLEF]);
1124

1125
    ctx->design_is_mono = design_is_mono(opt.design);
160✔
1126
    ctx->input_is_mono = input_is_mono();
160✔
1127

1128
    ctx->top_start_idx = find_first_line();
160✔
1129
    if (ctx->top_start_idx >= input.num_lines) {
160!
UNCOV
1130
        return 0;  /* all lines were already blank, so there is no box to remove */
×
1131
    }
1132

1133
    if (ctx->empty_side[BTOP]) {
160✔
1134
        ctx->top_end_idx = ctx->top_start_idx;
24✔
1135
    }
1136
    else {
1137
        ctx->top_end_idx = find_top_side(ctx);
136✔
1138
    }
1139
    log_debug(__FILE__, MAIN, "ctx->top_start_idx = %d, ctx->top_end_idx = %d\n", (int) ctx->top_start_idx,
160✔
1140
            (int) ctx->top_end_idx);
160✔
1141

1142
    ctx->bottom_end_idx = find_last_line() + 1;
160✔
1143
    if (ctx->empty_side[BBOT]) {
160✔
1144
        ctx->bottom_start_idx = ctx->bottom_end_idx;
22✔
1145
    }
1146
    else {
1147
        ctx->bottom_start_idx = find_bottom_side(ctx);
138✔
1148
    }
1149
    log_debug(__FILE__, MAIN, "ctx->bottom_start_idx = %d, ctx->bottom_end_idx = %d\n", (int) ctx->bottom_start_idx,
160✔
1150
            (int) ctx->bottom_end_idx);
160✔
1151
    if (ctx->bottom_start_idx > ctx->top_end_idx) {
160!
1152
        ctx->body_num_lines = ctx->bottom_start_idx - ctx->top_end_idx;
160✔
1153
    }
1154

1155
    if (ctx->body_num_lines > 0) {
160!
1156
        ctx->body = (line_ctx_t *) calloc(ctx->body_num_lines, sizeof(line_ctx_t));
160✔
1157
        find_vertical_shapes(ctx);
160✔
1158
    }
1159

1160
    debug_print_remove_ctx(ctx, "before apply_results_to_input()");
160✔
1161
    apply_results_to_input(ctx);
160✔
1162

1163
    if (ctx->body != NULL) {
160!
1164
        for (size_t i = 0; i < ctx->body_num_lines; i++) {
947✔
1165
            BFREE(ctx->body[i].input_line_used);
787✔
1166
        }
1167
        BFREE(ctx->body);
160!
1168
    }
1169
    BFREE(ctx);
160!
1170
    return 0;
160✔
1171
}
1172

1173

1174

1175
void output_input(const int trim_only)
160✔
1176
{
1177
    size_t indent;
1178
    int ntabs, nspcs;
1179

1180
    log_debug(__FILE__, MAIN, "output_input() - enter (trim_only=%d)\n", trim_only);
160✔
1181

1182
    for (size_t j = 0; j < input.num_lines; ++j) {
951✔
1183
        if (input.lines[j].text == NULL) {
791!
UNCOV
1184
            continue;
×
1185
        }
1186
        bxstr_t *temp = bxs_rtrim(input.lines[j].text);
791✔
1187
        bxs_free(input.lines[j].text);
791✔
1188
        input.lines[j].text = temp;
791✔
1189
        if (trim_only) {
791✔
1190
            continue;
388✔
1191
        }
1192

1193
        char *indentspc = NULL;
403✔
1194
        if (opt.tabexp == 'u') {
403✔
1195
            indent = input.lines[j].text->indent;
4✔
1196
            ntabs = indent / opt.tabstop;
4✔
1197
            nspcs = indent % opt.tabstop;
4✔
1198
            indentspc = (char *) malloc(ntabs + nspcs + 1);
4✔
1199
            if (indentspc == NULL) {
4!
1200
                perror(PROJECT);
×
UNCOV
1201
                return;
×
1202
            }
1203
            memset(indentspc, (int) '\t', ntabs);
4✔
1204
            memset(indentspc + ntabs, (int) ' ', nspcs);
4✔
1205
            indentspc[ntabs + nspcs] = '\0';
4✔
1206
        }
1207
        else if (opt.tabexp == 'k') {
399!
1208
            uint32_t *indent32 = tabbify_indent(j, NULL, input.indent);
×
1209
            indentspc = u32_strconv_to_output(indent32);
×
1210
            BFREE(indent32);
×
UNCOV
1211
            indent = input.indent;
×
1212
        }
1213
        else {
1214
            indentspc = (char *) strdup("");
399✔
1215
            indent = 0;
399✔
1216
        }
1217

1218
        char *outtext = u32_strconv_to_output(bxs_first_char_ptr(input.lines[j].text, indent));
403✔
1219
        fprintf(opt.outfile, "%s%s%s", indentspc, outtext,
403✔
1220
                (input.final_newline || j < input.num_lines - 1 ? opt.eol : ""));
403!
1221
        BFREE(outtext);
403!
1222
        BFREE(indentspc);
403!
1223
    }
1224
}
1225

1226

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