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

ascii-boxes / boxes / 7314827362

24 Dec 2023 01:12PM UTC coverage: 88.826% (+0.6%) from 88.199%
7314827362

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

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

20
#include "config.h"
21

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

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

36

37

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

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

46
    /** the length in characters of the matched west shape part */
47
    size_t west_quality;
48

49
    /** index of the first character of the east shape */
50
    size_t east_start;
51

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

56
    /** the length in characters of the matched east shape part */
57
    size_t east_quality;
58

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

63

64

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

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

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

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

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

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

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

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

92
    /** number of lines in `body` */
93
    size_t body_num_lines;
94

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

100

101

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

148

149

150
static void debug_print_shapes_relevant(shape_line_ctx_t *shapes_relevant)
1,590✔
151
{
152
    #ifdef DEBUG
153
        fprintf(stderr, "  shapes_relevant = {");
154
        for (size_t ds = 0; ds < SHAPES_PER_SIDE; ds++) {
155
            if (shapes_relevant[ds].empty) {
156
                fprintf(stderr, "-");
157
            }
158
            else {
159
                char *out_shp_text = bxs_to_output(shapes_relevant[ds].text);
160
                fprintf(stderr, "\"%s\"(%d%s)", out_shp_text, (int) shapes_relevant[ds].text->num_chars,
161
                    shapes_relevant[ds].elastic ? "E" : "");
162
                BFREE(out_shp_text);
163
            }
164
            if (ds < SHAPES_PER_SIDE - 1) {
165
                fprintf(stderr, ", ");
166
            }
167
        }
168
        fprintf(stderr, "}\n");
169
    #else
170
        UNUSED(shapes_relevant);
795✔
171
    #endif
172
}
1,590✔
173

174

175

176
static size_t find_first_line()
320✔
177
{
178
    size_t result = input.num_lines;
320✔
179
    for (size_t line_idx = 0; line_idx < input.num_lines; line_idx++) {
324✔
180
        if (!bxs_is_blank(input.lines[line_idx].text)) {
324✔
181
            result = line_idx;
320✔
182
            break;
320✔
183
        }
184
    }
2✔
185
    return result;
320✔
186
}
187

188

189

190
static size_t find_last_line()
320✔
191
{
192
    size_t result = input.num_lines - 1;
320✔
193
    for (long line_idx = (long) input.num_lines - 1; line_idx >= 0; line_idx--) {
324✔
194
        if (!bxs_is_blank(input.lines[line_idx].text)) {
324✔
195
            result = (size_t) line_idx;
320✔
196
            break;
320✔
197
        }
198
    }
2✔
199
    return result;
320✔
200
}
201

202

203

204
static int is_shape_line_empty(shape_line_ctx_t *shapes_relevant, size_t shape_idx)
2,650✔
205
{
206
    if (shape_idx < SHAPES_PER_SIDE) {
2,650!
207
        return shapes_relevant[shape_idx].empty || bxs_is_blank(shapes_relevant[shape_idx].text);
2,650✔
208
    }
209
    return 1;
×
210
}
1,325✔
211

212

213

214
static int non_empty_shapes_after(shape_line_ctx_t *shapes_relevant, size_t shape_idx)
1,716✔
215
{
216
    /* CHECK Can we use shape->is_blank_rightward? */
217
    for (size_t i = shape_idx + 1; i < SHAPES_PER_SIDE - 1; i++) {
2,584✔
218
        if (!is_shape_line_empty(shapes_relevant, i)) {
922✔
219
            return 1;
54✔
220
        }
221
    }
434✔
222
    return 0;
1,662✔
223
}
858✔
224

225

226

227
static int is_blank_between(uint32_t *start, uint32_t *end)
768✔
228
{
229
    for (uint32_t *p = start; p < end; p++) {
12,480✔
230
        if (!is_blank(*p)) {
11,716✔
231
            return 0;
4✔
232
        }
233
    }
5,856✔
234
    return 1;
764✔
235
}
384✔
236

237

238

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

255
    uint32_t *s = shape_line_ctx->text->memory;
17,192✔
256
    uint32_t *e = shape_line_ctx->text->memory + shape_line_ctx->text->num_chars;
17,192✔
257
    prefer_left = allow_left ? prefer_left : 0;
17,192✔
258
    size_t reduction_steps = shape_line_ctx->text->num_chars - *quality + 1;
17,192✔
259
    for (size_t i = 0; i < reduction_steps; i++) {
66,606✔
260
        if (prefer_left) {
53,854✔
261
            if (s < e && is_blank(*s)) {
17,284!
262
                s++;
8,740✔
263
            }
4,370✔
264
            else if (e > s && allow_right && is_blank(*(e - 1))) {
8,544!
265
                e--;
6,562✔
266
            }
3,281✔
267
            else {
268
                break;
991✔
269
            }
270
        }
7,651✔
271
        else {
272
            if (e > s && allow_right && is_blank(*(e - 1))) {
36,570✔
273
                e--;
26,642✔
274
            }
13,321✔
275
            else if (s < e && allow_left && is_blank(*s)) {
9,928✔
276
                s++;
7,470✔
277
            }
3,735✔
278
            else {
279
                break;
1,229✔
280
            }
281
        }
282
    }
24,707✔
283

284
    uint32_t *result = NULL;
17,192✔
285
    size_t new_quality = e - s;
17,192✔
286
    if (new_quality < *quality) {
17,192✔
287
        result = u32_strdup(s);
12,752✔
288
        set_char_at(result, new_quality, char_nul);
12,752✔
289
        *quality = new_quality;
12,752✔
290
    }
6,376✔
291
    return result;
17,192✔
292
}
8,600✔
293

294

295

296
static int hmm_shiftable(shape_line_ctx_t *shapes_relevant, uint32_t *cur_pos, size_t shape_idx, uint32_t *end_pos,
297
        int anchored_right);
298

299

300

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

327
    int result = 0;
37,224✔
328
    if (!anchored_left) {
37,224✔
329
        result = hmm_shiftable(shapes_relevant, cur_pos, shape_idx, end_pos, anchored_right);
794✔
330
    }
397✔
331
    else if (cur_pos > end_pos) {
36,430✔
332
        /* invalid input */
333
        result = 0;
242✔
334
    }
121✔
335
    else if (cur_pos == end_pos) {
36,188✔
336
        /* we are at the end, which is fine if there is nothing else to match */
337
        result = (shape_idx == (SHAPES_PER_SIDE - 1) && anchored_right)
639✔
338
                || ((shapes_relevant[shape_idx].empty || bxs_is_blank(shapes_relevant[shape_idx].text))
750!
339
                    && !non_empty_shapes_after(shapes_relevant, shape_idx) ? 1 : 0);
351✔
340
    }
213✔
341
    else if (shape_idx >= SHAPES_PER_SIDE - 1) {
35,762✔
342
        /* no more shapes to try, which is fine if the rest of the line is blank */
343
        result = u32_is_blank(cur_pos);
110✔
344
    }
55✔
345
    else if (shapes_relevant[shape_idx].empty) {
35,652✔
346
        /* the current shape line is empty, try the next one */
347
        result = hmm(shapes_relevant, cur_pos, shape_idx + 1, end_pos, 1, anchored_right);
470✔
348
    }
235✔
349
    else {
350
        uint32_t *shape_line = u32_strdup(shapes_relevant[shape_idx].text->memory);
35,182✔
351
        size_t quality = shapes_relevant[shape_idx].text->num_chars;
35,182✔
352
        while (shape_line != NULL && quality > 0) {
71,614✔
353
            if (u32_strncmp(cur_pos, shape_line, quality) == 0) {
36,432✔
354
                BFREE(shape_line);
33,664✔
355
                cur_pos = cur_pos + quality;
33,664✔
356
                if (cur_pos == end_pos && !non_empty_shapes_after(shapes_relevant, shape_idx)) {
33,664✔
357
                    result = 1; /* success */
1,094✔
358
                }
547✔
359
                else {
360
                    int rc = 0;
32,570✔
361
                    if (shapes_relevant[shape_idx].elastic) {
32,570✔
362
                        rc = hmm(shapes_relevant, cur_pos, shape_idx, end_pos, 1, anchored_right);
32,098✔
363
                    }
16,049✔
364
                    if (rc == 0) {
32,570✔
365
                        result = hmm(shapes_relevant, cur_pos, shape_idx + 1, end_pos, 1, anchored_right);
2,304✔
366
                    }
1,152✔
367
                    else {
368
                        result = rc;
30,266✔
369
                    }
370
                }
371
            }
16,832✔
372
            else if (!anchored_right) {
2,768✔
373
                shape_line = shorten(shapes_relevant + shape_idx, &quality, 0, 0, 1);
1,580✔
374
                #ifdef DEBUG
375
                    char *out_shape_line = u32_strconv_to_output(shape_line);
376
                    fprintf(stderr, "hmm() - shape_line shortened to %d (\"%s\")\n", (int) quality, out_shape_line);
377
                    BFREE(out_shape_line);
378
                #endif
379
            }
790✔
380
            else {
381
                BFREE(shape_line);
1,188✔
382
            }
383
        }
384
    }
385

386
    #ifdef DEBUG
387
        fprintf(stderr, "hmm() - exit, result = %d\n", result);
388
    #endif
389
    return result;
37,224✔
390
}
391

392

393

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

432

433

434
static shape_line_ctx_t *prepare_comp_shapes_horiz(int hside, comparison_t comp_type, size_t shape_line_idx)
1,590✔
435
{
436
    shape_t *side_shapes = hside == BTOP ? north_side : south_side_rev;
1,590✔
437
    shape_line_ctx_t *shapes_relevant = (shape_line_ctx_t *) calloc(SHAPES_PER_SIDE, sizeof(shape_line_ctx_t));
1,590✔
438

439
    for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
9,540✔
440
        shapes_relevant[i].elastic = opt.design->shape[side_shapes[i]].elastic;
7,950✔
441
        shapes_relevant[i].empty = isempty(opt.design->shape + side_shapes[i]);
7,950✔
442
        if (!shapes_relevant[i].empty) {
7,950✔
443
            uint32_t *s = prepare_comp_shape(opt.design, side_shapes[i], shape_line_idx, comp_type, 0,
10,413✔
444
                    i == SHAPES_PER_SIDE - 1);
3,471✔
445
            shapes_relevant[i].text = bxs_from_unicode(s);
6,942✔
446
            BFREE(s);
6,942✔
447
        }
3,471✔
448
    }
3,975✔
449

450
    return shapes_relevant;
1,590✔
451
}
452

453

454

455
static match_result_t *new_match_result(uint32_t *p, size_t p_idx, size_t len, int shiftable)
3,028✔
456
{
457
    match_result_t *result = (match_result_t *) calloc(1, sizeof(match_result_t));
3,028✔
458
    result->p = p;
3,028✔
459
    result->p_idx = p_idx;
3,028✔
460
    result->len = len;
3,028✔
461
    result->shiftable = shiftable;
3,028✔
462
    return result;
3,028✔
463
}
464

465

466

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

482
    if (vside == BLEF) {
3,046✔
483
        if (bxs_is_blank(shape_line)) {
1,538✔
484
            return new_match_result(input_line->memory, 0, 0, 1);
718✔
485
        }
486
        for (uint32_t *s = shape_line->memory; s == shape_line->memory || is_blank(*s); s++) {
834✔
487
            uint32_t *p = u32_strstr(input_line->memory, s);
826✔
488
            size_t p_idx = p != NULL ? p - input_line->memory : 0;
826✔
489
            if (p == NULL || p_idx > input_line->first_char[input_line->indent]) {
826✔
490
                continue;  /* not found or found too far in */
14✔
491
            }
492
            return new_match_result(p, p_idx, shape_line->num_chars - (s - shape_line->memory), 0);
812✔
493
        }
494
    }
4✔
495
    else {
496
        if (bxs_is_blank(shape_line)) {
1,508✔
497
            uint32_t *p = bxs_last_char_ptr(input_line);
658✔
498
            size_t p_idx = p - input_line->memory;
658✔
499
            return new_match_result(p, p_idx, 0, 1);
658✔
500
        }
501
        int slen = shape_line->num_chars;
850✔
502
        uint32_t *s = u32_strdup(shape_line->memory);
850✔
503
        for (; slen == (int) shape_line->num_chars || is_blank(s[slen]); slen--) {
870✔
504
            s[slen] = char_nul;
860✔
505
            uint32_t *p = u32_strnrstr(input_line->memory, s, slen);
860✔
506
            size_t p_idx = p != NULL ? p - input_line->memory : 0;
860✔
507
            if (p == NULL || p_idx + slen
860✔
508
                    < input_line->first_char[input_line->num_chars_visible - input_line->trailing]) {
844✔
509
                continue; /* not found or found too far in */
20✔
510
            }
511
            BFREE(s);
840!
512
            return new_match_result(p, p_idx, (size_t) slen, 0);
840✔
513
        }
514
        BFREE(s);
10✔
515
    }
516
    return NULL;
18✔
517
}
1,527✔
518

519

520

521
static int match_horiz_line(remove_ctx_t *ctx, int hside, size_t input_line_idx, size_t shape_line_idx)
1,560✔
522
{
523
    #ifdef DEBUG
524
        fprintf(stderr, "match_horiz_line(ctx, %s, %d, %d)\n",
525
                hside == BTOP ? "BTOP" : "BBOT", (int) input_line_idx, (int) shape_line_idx);
526
    #endif
527

528
    int result = 0;
1,560✔
529
    for (comparison_t comp_type = 0; comp_type < NUM_COMPARISON_TYPES; comp_type++) {
1,758✔
530
        if (!comp_type_is_viable(comp_type, ctx->input_is_mono, ctx->design_is_mono)) {
1,758✔
531
            continue;
168✔
532
        }
533
        ctx->comp_type = comp_type;
1,590✔
534
        #ifdef DEBUG
535
            fprintf(stderr, "  Setting comparison type to: %s\n", comparison_name[comp_type]);
536
        #endif
537

538
        shape_line_ctx_t *shapes_relevant = prepare_comp_shapes_horiz(hside, comp_type, shape_line_idx);
1,590✔
539
        debug_print_shapes_relevant(shapes_relevant);
1,590✔
540

541
        bxstr_t *input_prepped1 = bxs_from_unicode(prepare_comp_input(input_line_idx, 0, comp_type, 0, NULL, NULL));
1,590✔
542
        bxstr_t *input_prepped = bxs_rtrim(input_prepped1);
1,590✔
543
        bxs_append_spaces(input_prepped, opt.design->shape[NW].width + opt.design->shape[NE].width);
1,590✔
544
        bxs_free(input_prepped1);
1,590✔
545
        #ifdef DEBUG
546
            char *out_input_prepped = bxs_to_output(input_prepped);
547
            fprintf(stderr, "  input_prepped = \"%s\"\n", out_input_prepped);
548
            BFREE(out_input_prepped);
549
        #endif
550

551
        uint32_t *cur_pos = input_prepped->memory;
1,590✔
552
        match_result_t *mrl = NULL;
1,590✔
553
        if (!ctx->empty_side[BLEF]) {
1,590✔
554
            mrl = match_outer_shape(BLEF, input_prepped, shapes_relevant[0].text);
1,526✔
555
            if (mrl != NULL) {
1,526✔
556
                cur_pos = mrl->p + mrl->len;
1,522✔
557
            }
761✔
558
        }
763✔
559

560
        uint32_t *end_pos = bxs_last_char_ptr(input_prepped);
1,590✔
561
        match_result_t *mrr = NULL;
1,590✔
562
        if (!ctx->empty_side[BRIG]) {
1,590✔
563
            mrr = match_outer_shape(BRIG, input_prepped, shapes_relevant[SHAPES_PER_SIDE - 1].text);
1,498✔
564
            if (mrr != NULL) {
1,498✔
565
                end_pos = mrr->p;
1,488✔
566
            }
744✔
567
        }
749✔
568
        #ifdef DEBUG
569
            char *out_cur_pos = u32_strconv_to_output(cur_pos);
570
            char *out_end_pos = u32_strconv_to_output(end_pos);
571
            fprintf(stderr, "  cur_pos = \"%s\" (index %d)\n", out_cur_pos, (int) BMAX(cur_pos - input_prepped->memory, 0));
572
            fprintf(stderr, "  end_pos = \"%s\" (index %d)\n", out_end_pos, (int) BMAX(end_pos - input_prepped->memory, 0));
573
            BFREE(out_cur_pos);
574
            BFREE(out_end_pos);
575
        #endif
576

577
        result = hmm(shapes_relevant, cur_pos, 1, end_pos, (mrl == NULL) || mrl->shiftable ? 0 : 1,
3,078✔
578
                (mrr == NULL) || mrr->shiftable ? 0 : 1);
1,539✔
579

580
        BFREE(mrl);
1,590✔
581
        BFREE(mrr);
1,590✔
582
        for (size_t i = 0; i < SHAPES_PER_SIDE; i++) {
9,540✔
583
            bxs_free(shapes_relevant[i].text);
7,950✔
584
        }
3,975✔
585
        BFREE(shapes_relevant);
1,590✔
586

587
        if (result) {
1,590✔
588
            #ifdef DEBUG
589
                fprintf(stderr, "Matched %s side line using comp_type=%s and shape_line_idx=%d\n",
590
                    hside == BTOP ? "top" : "bottom", comparison_name[comp_type], (int) shape_line_idx);
591
            #endif
592
            break;
1,560✔
593
        }
594
    }
15✔
595

596
    return result;
1,560✔
597
}
598

599

600

601
static size_t find_top_side(remove_ctx_t *ctx)
272✔
602
{
603
    size_t result = ctx->top_start_idx;
272✔
604
    sentry_t *shapes = opt.design->shape;
272✔
605
    for (size_t input_line_idx = ctx->top_start_idx;
1,188✔
606
            input_line_idx < input.num_lines && input_line_idx < ctx->top_start_idx + shapes[NE].height;
1,052✔
607
            input_line_idx++)
780✔
608
    {
609
        int matched = 0;
780✔
610
        size_t shape_lines_tested = 0;
780✔
611
        for (size_t shape_line_idx = (input_line_idx - ctx->top_start_idx) % shapes[NE].height;
780!
612
                shape_lines_tested < shapes[NE].height;
780!
613
                shape_line_idx = (shape_line_idx + 1) % shapes[NE].height, shape_lines_tested++)
×
614
        {
615
            if (match_horiz_line(ctx, BTOP, input_line_idx, shape_line_idx)) {
780✔
616
                matched = 1;
780✔
617
                break;
780✔
618
            }
619
        }
620
        if (!matched) {
780!
621
            break;
×
622
        }
623
        result = input_line_idx + 1;
780✔
624
    }
390✔
625
    return result;
272✔
626
}
627

628

629

630
static size_t find_bottom_side(remove_ctx_t *ctx)
276✔
631
{
632
    size_t result = ctx->bottom_end_idx;
276✔
633
    sentry_t *shapes = opt.design->shape;
276✔
634
    for (long input_line_idx = (long) ctx->bottom_end_idx - 1;
1,194✔
635
            input_line_idx >= 0 && input_line_idx >= (long) ctx->bottom_end_idx - (long) shapes[SE].height;
1,056✔
636
            input_line_idx--)
780✔
637
    {
638
        int matched = 0;
780✔
639
        size_t shape_lines_tested = 0;
780✔
640
        for (long shape_line_idx = shapes[SE].height - (ctx->bottom_end_idx - input_line_idx);
1,170!
641
                shape_line_idx >= 0 && shape_lines_tested < shapes[SE].height;
780!
642
                shape_lines_tested++,
×
643
                shape_line_idx = shape_line_idx == 0 ? (long) (shapes[SE].height - 1) : (long) (shape_line_idx - 1))
×
644
        {
645
            if (match_horiz_line(ctx, BBOT, input_line_idx, shape_line_idx)) {
780✔
646
                matched = 1;
780✔
647
                break;
780✔
648
            }
649
        }
650
        if (!matched) {
780!
651
            break;
×
652
        }
653
        result = input_line_idx;
780✔
654
    }
390✔
655
    return result;
276✔
656
}
657

658

659

660
static size_t count_shape_lines(shape_t side_shapes[])
584✔
661
{
662
    size_t result = 0;
584✔
663
    for (size_t i = 0; i < SHAPES_PER_SIDE - CORNERS_PER_SIDE; i++) {
2,336✔
664
        if (!isempty(opt.design->shape + side_shapes[i])) {
1,752✔
665
            result += opt.design->shape[side_shapes[i]].height;
736✔
666
        }
368✔
667
    }
876✔
668
    return result;
584✔
669
}
670

671

672
static shape_line_ctx_t **prepare_comp_shapes_vert(int vside, comparison_t comp_type)
584✔
673
{
674
    shape_t west_side_shapes[SHAPES_PER_SIDE - CORNERS_PER_SIDE] = {WNW, W, WSW};
584✔
675
    shape_t east_side_shapes[SHAPES_PER_SIDE - CORNERS_PER_SIDE] = {ENE, E, ESE};
584✔
676
    shape_t side_shapes[SHAPES_PER_SIDE - CORNERS_PER_SIDE];
677
    if (vside == BLEF) {
584✔
678
        memcpy(side_shapes, west_side_shapes, (SHAPES_PER_SIDE - CORNERS_PER_SIDE) * sizeof(shape_t));
314✔
679
    }
157✔
680
    else {
681
        memcpy(side_shapes, east_side_shapes, (SHAPES_PER_SIDE - CORNERS_PER_SIDE) * sizeof(shape_t));
270✔
682
    }
683

684
    size_t num_shape_lines = count_shape_lines(side_shapes);
584✔
685

686
    shape_line_ctx_t **shape_lines = (shape_line_ctx_t **) calloc(num_shape_lines + 1, sizeof(shape_line_ctx_t *));
584✔
687
    for (size_t i = 0; i < num_shape_lines; i++) {
1,708✔
688
        shape_lines[i] = (shape_line_ctx_t *) calloc(1, sizeof(shape_line_ctx_t));
1,124✔
689
    }
562✔
690

691
    for (size_t shape_idx = 0, i = 0; shape_idx < SHAPES_PER_SIDE - CORNERS_PER_SIDE; shape_idx++) {
2,336✔
692
        if (!isempty(opt.design->shape + side_shapes[shape_idx])) {
1,752✔
693
            int deep_empty = isdeepempty(opt.design->shape + side_shapes[shape_idx]);
736✔
694
            for (size_t slno = 0; slno < opt.design->shape[side_shapes[shape_idx]].height; slno++, i++) {
1,860✔
695
                uint32_t *s = prepare_comp_shape(opt.design, side_shapes[shape_idx], slno, comp_type, 0, 0);
1,124✔
696
                shape_lines[i]->text = bxs_from_unicode(s);
1,124✔
697
                shape_lines[i]->empty = deep_empty;
1,124✔
698
                shape_lines[i]->elastic = opt.design->shape[side_shapes[shape_idx]].elastic;
1,124✔
699
                BFREE(s);
1,124✔
700
            }
562✔
701
        }
368✔
702
    }
876✔
703

704
    return shape_lines;
584✔
705
}
706

707

708

709
static void free_shape_lines(shape_line_ctx_t **shape_lines)
636✔
710
{
711
    if (shape_lines != NULL) {
636✔
712
        for (shape_line_ctx_t **p = shape_lines; *p != NULL; p++) {
1,708✔
713
            bxs_free((*p)->text);
1,124✔
714
            BFREE(*p);
1,124!
715
        }
562✔
716
        BFREE(shape_lines);
584!
717
    }
292✔
718
}
636✔
719

720

721

722
static void match_vertical_side(remove_ctx_t *ctx, int vside, shape_line_ctx_t **shape_lines, uint32_t *input_line,
3,232✔
723
    size_t line_idx, size_t input_length, size_t input_indent, size_t input_trailing)
724
{
725
    line_ctx_t *line_ctx = ctx->body + (line_idx - ctx->top_end_idx);
3,232✔
726

727
    for (shape_line_ctx_t **shape_line_ctx = shape_lines; *shape_line_ctx != NULL; shape_line_ctx++) {
10,976✔
728
        if ((*shape_line_ctx)->empty) {
6,424!
729
            continue;
×
730
        }
731

732
        size_t max_quality = (*shape_line_ctx)->text->num_chars;
6,424✔
733
        size_t quality = max_quality;
6,424✔
734
        uint32_t *shape_text = (*shape_line_ctx)->text->memory;
6,424✔
735
        uint32_t *to_free = NULL;
6,424✔
736
        while(shape_text != NULL) {
23,030✔
737
            uint32_t *p;
738
            if (vside == BLEF) {
18,578✔
739
                p = u32_strstr(input_line, shape_text);
8,338✔
740
            }
4,169✔
741
            else {
742
                p = u32_strnrstr(input_line, shape_text, quality);
10,240✔
743
            }
744
            BFREE(to_free);
18,578✔
745
            shape_text = NULL;
18,578✔
746

747
            if ((p == NULL)
20,246✔
748
                    || (vside == BLEF && ((size_t) (p - input_line) > input_indent + (max_quality - quality)))
12,117✔
749
                    || (vside == BRIG && ((size_t) (p - input_line) < input_length - input_trailing - quality))) {
5,496✔
750
                shape_text = shorten(*shape_line_ctx, &quality, vside == BLEF, 1, 1);
16,258✔
751
                to_free = shape_text;
16,258✔
752
            }
8,789✔
753
            else if (vside == BLEF) {
3,640✔
754
                if (quality > line_ctx->west_quality) {
2,000✔
755
                    line_ctx->west_start = (size_t) (p - input_line);
1,784✔
756
                    line_ctx->west_end = line_ctx->west_start + quality;
1,784✔
757
                    line_ctx->west_quality = quality;
1,784✔
758
                    BFREE(line_ctx->input_line_used);
1,784✔
759
                    line_ctx->input_line_used = u32_strdup(input_line);
1,784✔
760
                    break;
1,784✔
761
                }
762
            }
108✔
763
            else if (vside == BRIG) {
1,640✔
764
                if (quality > line_ctx->east_quality) {
1,640✔
765
                    line_ctx->east_start = (size_t) (p - input_line);
1,508✔
766
                    line_ctx->east_end = line_ctx->east_start + quality;
1,508✔
767
                    line_ctx->east_quality = quality;
1,508✔
768
                    BFREE(line_ctx->input_line_used);
1,508✔
769
                    line_ctx->input_line_used = u32_strdup(input_line);
1,508✔
770
                    break;
1,508✔
771
                }
772
            }
66✔
773
        }
774
    }
3,872✔
775
}
4,552✔
776

777

778

779
static int sufficient_body_quality(remove_ctx_t *ctx)
318✔
780
{
781
    size_t num_body_lines = ctx->bottom_start_idx - ctx->top_end_idx;
318✔
782
    size_t total_quality = 0;
318✔
783
    line_ctx_t *body = ctx->body;
318✔
784
    for (size_t body_line_idx = 0; body_line_idx < num_body_lines; body_line_idx++) {
2,072✔
785
        line_ctx_t line_ctx = body[body_line_idx];
1,754✔
786
        total_quality += line_ctx.east_quality + line_ctx.west_quality;
1,754✔
787
    }
877✔
788

789
    size_t max_quality = 0;
318✔
790
    if (!ctx->empty_side[BLEF]) {
318✔
791
        max_quality += opt.design->shape[NW].width;
314✔
792
    }
157✔
793
    if (!ctx->empty_side[BRIG]) {
318✔
794
        max_quality += opt.design->shape[NE].width;
270✔
795
    }
135✔
796
    max_quality = max_quality * num_body_lines;
318✔
797

798
    /* If we manage to match 50%, then it is unlikely to improve with a different comparison mode. */
799
    int sufficient = (max_quality == 0 && total_quality == 0)
318!
800
            || (max_quality > 0 && (total_quality > 0.5 * max_quality));
318!
801
    #ifdef DEBUG
802
        fprintf(stderr, "sufficient_body_quality() found body match quality of %d/%d (%s).\n",
803
                (int) total_quality, (int) max_quality, sufficient ? "sufficient" : "NOT sufficient");
804
    #endif
805
    return sufficient;
318✔
806
}
807

808

809

810
static void reset_body(remove_ctx_t *ctx)
318✔
811
{
812
    if (ctx->body != NULL) {
318!
813
        for (size_t i = 0; i < ctx->body_num_lines; i++) {
2,072✔
814
            BFREE(ctx->body[i].input_line_used);
1,754✔
815
        }
877✔
816
        memset(ctx->body, 0, ctx->body_num_lines * sizeof(line_ctx_t));
318✔
817
    }
159✔
818
}
318✔
819

820

821

822
static void find_vertical_shapes(remove_ctx_t *ctx)
320✔
823
{
824
    int west_empty = ctx->empty_side[BLEF];
320✔
825
    int east_empty = ctx->empty_side[BRIG];
320✔
826
    if (west_empty && east_empty) {
320✔
827
        return;
4✔
828
    }
829

830
    for (comparison_t comp_type = 0; comp_type < NUM_COMPARISON_TYPES; comp_type++) {
382✔
831
        if (!comp_type_is_viable(comp_type, ctx->input_is_mono, ctx->design_is_mono)) {
370✔
832
            continue;
52✔
833
        }
834
        ctx->comp_type = comp_type;
318✔
835
        #ifdef DEBUG
836
            fprintf(stderr, "find_vertical_shapes(): comp_type = %s\n", comparison_name[comp_type]);
837
        #endif
838
        reset_body(ctx);
318✔
839

840
        shape_line_ctx_t **shape_lines_west = NULL;
318✔
841
        if (!west_empty) {
318✔
842
            shape_lines_west = prepare_comp_shapes_vert(BLEF, comp_type);
314✔
843
        }
157✔
844
        shape_line_ctx_t **shape_lines_east = NULL;
318✔
845
        if (!east_empty) {
318✔
846
            shape_lines_east = prepare_comp_shapes_vert(BRIG, comp_type);
270✔
847
        }
135✔
848

849
        for (size_t input_line_idx = ctx->top_end_idx; input_line_idx < ctx->bottom_start_idx; input_line_idx++) {
2,072✔
850
            size_t input_indent = 0;
1,754✔
851
            size_t input_trailing = 0;
1,754✔
852
            uint32_t *input_line = prepare_comp_input(input_line_idx, 0, comp_type, 0, &input_indent, &input_trailing);
1,754✔
853
            size_t input_length = u32_strlen(input_line);
1,754✔
854

855
            if (!west_empty) {
1,754✔
856
                match_vertical_side(ctx, BLEF, shape_lines_west,
2,601✔
857
                        input_line, input_line_idx, input_length, input_indent, input_trailing);
867✔
858
            }
867✔
859
            if (!east_empty) {
1,754✔
860
                match_vertical_side(ctx, BRIG, shape_lines_east,
2,247✔
861
                        input_line, input_line_idx, input_length, input_indent, input_trailing);
749✔
862
            }
749✔
863
        }
877✔
864

865
        free_shape_lines(shape_lines_west);
318✔
866
        free_shape_lines(shape_lines_east);
318✔
867

868
        if (sufficient_body_quality(ctx)) {
318✔
869
            break;
304✔
870
        }
871
    }
7✔
872
}
160✔
873

874

875

876
/**
877
 * If the user didn't specify a design to remove, autodetect it.
878
 * Since this requires knowledge of all available designs, the entire config file had to be parsed (earlier).
879
 */
880
static void detect_design_if_needed()
322✔
881
{
882
    if (opt.design_choice_by_user == 0) {
322✔
883
        design_t *tmp = autodetect_design();
16✔
884
        if (tmp) {
16✔
885
            opt.design = tmp;
14✔
886
            #ifdef DEBUG
887
                fprintf(stderr, "Design autodetection: Removing box of design \"%s\".\n", opt.design->name);
888
            #endif
889
        }
7✔
890
        else {
891
            fprintf(stderr, "%s: Box design autodetection failed. Use -d option.\n", PROJECT);
2✔
892
            exit(EXIT_FAILURE);
2✔
893
        }
894
    }
7✔
895
    #ifdef DEBUG
896
    else {
897
        fprintf(stderr, "Design was chosen by user: %s\n", opt.design->name);
898
    }
899
    #endif
900
}
320✔
901

902

903

904
static void free_line_text(line_t *line)
3,558✔
905
{
906
    BFREE(line->cache_visible);
3,558✔
907
    bxs_free(line->text);
3,558✔
908
    line->text = NULL;
3,558✔
909
}
3,558✔
910

911

912

913
static void free_line(line_t *line)
1,762✔
914
{
915
    free_line_text(line);
1,762✔
916
    BFREE(line->tabpos);
1,762!
917
    line->tabpos_len = 0;
1,762✔
918
}
1,762✔
919

920

921

922
static void killblank(remove_ctx_t *ctx)
320✔
923
{
924
    size_t lines_removed = 0;
320✔
925
    size_t max_lines_removable = opt.mend && !opt.killblank ? (size_t) BMAX(opt.design->padding[BTOP], 0) : SIZE_MAX;
320!
926
    while (ctx->top_end_idx < ctx->bottom_start_idx && lines_removed < max_lines_removable
704✔
927
            && empty_line(input.lines + ctx->top_end_idx))
549✔
928
    {
929
        #ifdef DEBUG
930
            fprintf(stderr, "Killing leading blank line in box body.\n");
931
        #endif
932
        ++(ctx->top_end_idx);
102✔
933
        --(ctx->body_num_lines);
102✔
934
        ++lines_removed;
102✔
935
    }
936

937
    lines_removed = 0;
320✔
938
    max_lines_removable = opt.mend && !opt.killblank ? (size_t) BMAX(opt.design->padding[BBOT], 0) : SIZE_MAX;
320!
939
    while (ctx->bottom_start_idx > ctx->top_end_idx && lines_removed < max_lines_removable
702✔
940
            && empty_line(input.lines + ctx->bottom_start_idx - 1))
546✔
941
    {
942
        #ifdef DEBUG
943
            fprintf(stderr, "Killing trailing blank line in box body.\n");
944
        #endif
945
        --(ctx->bottom_start_idx);
100✔
946
        --(ctx->body_num_lines);
100✔
947
        ++lines_removed;
100✔
948
    }
949
}
320✔
950

951

952

953
static int org_is_not_blank(bxstr_t *org_line, comparison_t comp_type, size_t idx)
1,920✔
954
{
955
    if (comp_type == literal || comp_type == ignore_invisible_shape) {
1,920!
956
        return !is_blank(org_line->memory[idx]);
1,904✔
957
    }
958
    return !is_blank(org_line->memory[org_line->visible_char[idx]]);
16✔
959
}
960✔
960

961

962

963
static size_t max_chars_line(bxstr_t *org_line, comparison_t comp_type)
2,030✔
964
{
965
    return (comp_type == literal || comp_type == ignore_invisible_shape)
1,032✔
966
            ? org_line->num_chars : org_line->num_chars_visible;
2,047✔
967
}
968

969

970

971
static size_t confirmed_padding(bxstr_t *org_line, comparison_t comp_type, size_t start_idx, size_t num_padding)
1,732✔
972
{
973
    size_t result = 0;
1,732✔
974
    size_t max_chars = max_chars_line(org_line, comp_type);
1,732✔
975
    for (size_t i = start_idx; i < BMIN(max_chars, start_idx + num_padding); i++) {
3,652✔
976
        if (org_is_not_blank(org_line, comp_type, i)) {
1,920!
977
            break;
×
978
        }
979
        result++;
1,920✔
980
    }
960✔
981
    return result;
1,732✔
982
}
983

984

985

986
static void remove_top_from_input(remove_ctx_t *ctx)
320✔
987
{
988
    if (ctx->top_end_idx > ctx->top_start_idx) {
320✔
989
        for (size_t j = ctx->top_start_idx; j < ctx->top_end_idx; ++j) {
1,156✔
990
            free_line(input.lines + j);
882✔
991
        }
441✔
992
        memmove(input.lines + ctx->top_start_idx, input.lines + ctx->top_end_idx,
274✔
993
                (input.num_lines - ctx->top_end_idx) * sizeof(line_t));
137✔
994
        input.num_lines -= ctx->top_end_idx - ctx->top_start_idx;
274✔
995
    }
137✔
996
}
320✔
997

998

999

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

1006
    size_t s_idx = 0;
1,776✔
1007
    if (lctx->west_quality > 0) {
1,776✔
1008
        s_idx = lctx->west_end + confirmed_padding(org_line, ctx->comp_type, lctx->west_end, opt.design->padding[BLEF]);
1,732✔
1009
    }
866✔
1010
    if (ctx->comp_type == ignore_invisible_input || ctx->comp_type == ignore_invisible_all) {
1,776✔
1011
        /* our line context worked with visible characters only, convert back to org_line */
1012
        s_idx = org_line->first_char[s_idx];
30✔
1013
    }
15✔
1014
    return s_idx;
1,776✔
1015
}
1016

1017

1018

1019
static size_t calculate_end_idx(remove_ctx_t *ctx, size_t body_line_idx)
1,776✔
1020
{
1021
    size_t input_line_idx = ctx->top_end_idx + body_line_idx;
1,776✔
1022
    line_ctx_t *lctx = ctx->body + body_line_idx;
1,776✔
1023
    bxstr_t *org_line = input.lines[input_line_idx].text;
1,776✔
1024

1025
    size_t e_idx = lctx->east_quality > 0 ? lctx->east_start : max_chars_line(org_line, ctx->comp_type);
1,776✔
1026
    if (ctx->comp_type == ignore_invisible_input || ctx->comp_type == ignore_invisible_all) {
1,776✔
1027
        e_idx = org_line->first_char[e_idx];
30✔
1028
    }
15✔
1029
    return e_idx;
1,776✔
1030
}
1031

1032

1033

1034
static void remove_vertical_from_input(remove_ctx_t *ctx)
320✔
1035
{
1036
    for (size_t body_line_idx = 0; body_line_idx < ctx->body_num_lines; body_line_idx++) {
2,096✔
1037
        size_t input_line_idx = ctx->top_end_idx + body_line_idx;
1,776✔
1038
        bxstr_t *org_line = input.lines[input_line_idx].text;
1,776✔
1039
        size_t s_idx = calculate_start_idx(ctx, body_line_idx);
1,776✔
1040
        size_t e_idx = calculate_end_idx(ctx, body_line_idx);
1,776✔
1041
        #ifdef DEBUG
1042
            fprintf(stderr, "remove_vertical_from_input(): body_line_idx=%d, input_line_idx=%d, s_idx=%d, e_idx=%d, "
1043
                    "input.indent=%d\n", (int) body_line_idx, (int) input_line_idx, (int) s_idx, (int) e_idx,
1044
                    (int) input.indent);
1045
        #endif
1046

1047
        bxstr_t *temp2 = bxs_substr(org_line, s_idx, e_idx);
1,776✔
1048
        if (opt.indentmode == 'b' || opt.indentmode == '\0') {
2,663!
1049
            /* restore indentation */
1050
            bxstr_t *temp = bxs_prepend_spaces(temp2, input.indent);
1,774✔
1051
            free_line_text(input.lines + input_line_idx);
1,774✔
1052
            input.lines[input_line_idx].text = temp;
1,774✔
1053
            bxs_free(temp2);
1,774✔
1054
        }
887✔
1055
        else {
1056
            /* remove indentation */
1057
            free_line_text(input.lines + input_line_idx);
2✔
1058
            input.lines[input_line_idx].text = temp2;
2✔
1059
        }
1060
    }
888✔
1061
}
320✔
1062

1063

1064

1065
static void remove_bottom_from_input(remove_ctx_t *ctx)
320✔
1066
{
1067
    if (ctx->bottom_end_idx > ctx->bottom_start_idx) {
320✔
1068
        for (size_t j = ctx->bottom_start_idx; j < ctx->bottom_end_idx; ++j) {
1,158✔
1069
            free_line(input.lines + j);
880✔
1070
        }
440✔
1071
        if (ctx->bottom_end_idx < input.num_lines) {
278✔
1072
            memmove(input.lines + ctx->bottom_start_idx, input.lines + ctx->bottom_end_idx,
2✔
1073
                    (input.num_lines - ctx->bottom_end_idx) * sizeof(line_t));
1✔
1074
        }
1✔
1075
        input.num_lines -= ctx->bottom_end_idx - ctx->bottom_start_idx;
278✔
1076
    }
139✔
1077
}
320✔
1078

1079

1080

1081
static void remove_default_padding(remove_ctx_t *ctx, int num_blanks)
8✔
1082
{
1083
    if (num_blanks > 0) {
8✔
1084
        for (size_t body_line_idx = 0; body_line_idx < ctx->body_num_lines; body_line_idx++) {
24✔
1085
            size_t input_line_idx = ctx->top_start_idx + body_line_idx; /* top_start_idx, because top was removed! */
20✔
1086
            bxstr_t *temp = bxs_cut_front(input.lines[input_line_idx].text, (size_t) num_blanks);
20✔
1087
            free_line_text(input.lines + input_line_idx);
20✔
1088
            input.lines[input_line_idx].text = temp;
20✔
1089
        }
10✔
1090
        input.indent -= (size_t) num_blanks;
4✔
1091
        input.maxline -= (size_t) num_blanks;
4✔
1092
    }
2✔
1093
}
8✔
1094

1095

1096

1097
static void apply_results_to_input(remove_ctx_t *ctx)
320✔
1098
{
1099
    remove_vertical_from_input(ctx);
320✔
1100

1101
    if (opt.killblank || opt.mend) {
320!
1102
        killblank(ctx);
320✔
1103
    }
160✔
1104
    remove_bottom_from_input(ctx);
320✔
1105
    remove_top_from_input(ctx);
320✔
1106

1107
    input.maxline = 0;
320✔
1108
    input.indent = SIZE_MAX;
320✔
1109
    for (size_t j = 0; j < input.num_lines; ++j) {
1,902✔
1110
        if (input.lines[j].text->num_columns > input.maxline) {
1,582✔
1111
            input.maxline = input.lines[j].text->num_columns;
652✔
1112
        }
326✔
1113
        if (input.lines[j].text->indent < input.indent) {
1,582✔
1114
            input.indent = input.lines[j].text->indent;
322✔
1115
        }
161✔
1116
    }
791✔
1117
    if (ctx->empty_side[BLEF]) {
320✔
1118
        /* If the side were not open, default padding would have been removed when the side was removed. */
1119
        remove_default_padding(ctx, BMIN((int) input.indent, opt.design->padding[BLEF]));
8!
1120
    }
4✔
1121

1122
    size_t num_lines_removed = BMAX(ctx->top_end_idx - ctx->top_start_idx, (size_t) 0)
480✔
1123
            + BMAX(ctx->bottom_end_idx - ctx->bottom_start_idx, (size_t) 0);
320✔
1124
    memset(input.lines + input.num_lines, 0, num_lines_removed * sizeof(line_t));
320✔
1125

1126
    #ifdef DEBUG
1127
        print_input_lines(" (remove_box) after box removal");
1128
        fprintf(stderr, "Number of lines shrunk by %d.\n", (int) num_lines_removed);
1129
    #endif
1130
}
320✔
1131

1132

1133

1134
int remove_box()
321✔
1135
{
1136
    detect_design_if_needed();
321✔
1137

1138
    remove_ctx_t *ctx = (remove_ctx_t *) calloc(1, sizeof(remove_ctx_t));
320✔
1139
    ctx->empty_side[BTOP] = empty_side(opt.design->shape, BTOP);
320✔
1140
    ctx->empty_side[BRIG] = empty_side(opt.design->shape, BRIG);
320✔
1141
    ctx->empty_side[BBOT] = empty_side(opt.design->shape, BBOT);
320✔
1142
    ctx->empty_side[BLEF] = empty_side(opt.design->shape, BLEF);
320✔
1143
    #ifdef DEBUG
1144
        fprintf(stderr, "Empty sides? Top: %d, Right: %d, Bottom: %d, Left: %d\n",
1145
                ctx->empty_side[BTOP], ctx->empty_side[BRIG], ctx->empty_side[BBOT], ctx->empty_side[BLEF]);
1146
    #endif
1147

1148
    ctx->design_is_mono = design_is_mono(opt.design);
320✔
1149
    ctx->input_is_mono = input_is_mono();
320✔
1150

1151
    ctx->top_start_idx = find_first_line();
320✔
1152
    if (ctx->top_start_idx >= input.num_lines) {
320!
1153
        return 0;  /* all lines were already blank, so there is no box to remove */
×
1154
    }
1155

1156
    if (ctx->empty_side[BTOP]) {
320✔
1157
        ctx->top_end_idx = ctx->top_start_idx;
48✔
1158
    }
24✔
1159
    else {
1160
        ctx->top_end_idx = find_top_side(ctx);
272✔
1161
    }
1162
    #ifdef DEBUG
1163
        fprintf(stderr, "ctx->top_start_idx = %d, ctx->top_end_idx = %d\n", (int) ctx->top_start_idx,
1164
                (int) ctx->top_end_idx);
1165
    #endif
1166

1167
    ctx->bottom_end_idx = find_last_line() + 1;
320✔
1168
    if (ctx->empty_side[BBOT]) {
320✔
1169
        ctx->bottom_start_idx = ctx->bottom_end_idx;
44✔
1170
    }
22✔
1171
    else {
1172
        ctx->bottom_start_idx = find_bottom_side(ctx);
276✔
1173
    }
1174
    #ifdef DEBUG
1175
        fprintf(stderr, "ctx->bottom_start_idx = %d, ctx->bottom_end_idx = %d\n", (int) ctx->bottom_start_idx,
1176
                (int) ctx->bottom_end_idx);
1177
    #endif
1178
    if (ctx->bottom_start_idx > ctx->top_end_idx) {
320✔
1179
        ctx->body_num_lines = ctx->bottom_start_idx - ctx->top_end_idx;
320✔
1180
    }
160✔
1181

1182
    if (ctx->body_num_lines > 0) {
320✔
1183
        ctx->body = (line_ctx_t *) calloc(ctx->body_num_lines, sizeof(line_ctx_t));
320✔
1184
        find_vertical_shapes(ctx);
320✔
1185
    }
160✔
1186

1187
    debug_print_remove_ctx(ctx, "before apply_results_to_input()");
320✔
1188
    apply_results_to_input(ctx);
320✔
1189

1190
    if (ctx->body != NULL) {
320✔
1191
        for (size_t i = 0; i < ctx->body_num_lines; i++) {
1,894✔
1192
            BFREE(ctx->body[i].input_line_used);
1,574✔
1193
        }
787✔
1194
        BFREE(ctx->body);
320✔
1195
    }
160✔
1196
    BFREE(ctx);
320✔
1197
    return 0;
320✔
1198
}
160✔
1199

1200

1201

1202
void output_input(const int trim_only)
320✔
1203
{
1204
    size_t indent;
1205
    int ntabs, nspcs;
1206

1207
    #ifdef DEBUG
1208
        fprintf(stderr, "output_input() - enter (trim_only=%d)\n", trim_only);
1209
    #endif
1210
    for (size_t j = 0; j < input.num_lines; ++j) {
1,902✔
1211
        if (input.lines[j].text == NULL) {
1,582✔
1212
            continue;
×
1213
        }
1214
        bxstr_t *temp = bxs_rtrim(input.lines[j].text);
1,582✔
1215
        bxs_free(input.lines[j].text);
1,582✔
1216
        input.lines[j].text = temp;
1,582✔
1217
        if (trim_only) {
1,582✔
1218
            continue;
776✔
1219
        }
1220

1221
        char *indentspc = NULL;
806✔
1222
        if (opt.tabexp == 'u') {
806✔
1223
            indent = input.lines[j].text->indent;
8✔
1224
            ntabs = indent / opt.tabstop;
8✔
1225
            nspcs = indent % opt.tabstop;
8✔
1226
            indentspc = (char *) malloc(ntabs + nspcs + 1);
8✔
1227
            if (indentspc == NULL) {
8!
1228
                perror(PROJECT);
×
1229
                return;
×
1230
            }
1231
            memset(indentspc, (int) '\t', ntabs);
8✔
1232
            memset(indentspc + ntabs, (int) ' ', nspcs);
8✔
1233
            indentspc[ntabs + nspcs] = '\0';
8✔
1234
        }
4✔
1235
        else if (opt.tabexp == 'k') {
798!
1236
            uint32_t *indent32 = tabbify_indent(j, NULL, input.indent);
×
1237
            indentspc = u32_strconv_to_output(indent32);
×
1238
            BFREE(indent32);
×
1239
            indent = input.indent;
×
1240
        }
1241
        else {
1242
            indentspc = (char *) strdup("");
798✔
1243
            indent = 0;
798✔
1244
        }
1245

1246
        char *outtext = u32_strconv_to_output(bxs_first_char_ptr(input.lines[j].text, indent));
806✔
1247
        fprintf(opt.outfile, "%s%s%s", indentspc, outtext,
1,209✔
1248
                (input.final_newline || j < input.num_lines - 1 ? opt.eol : ""));
806!
1249
        BFREE(outtext);
806✔
1250
        BFREE(indentspc);
806✔
1251
    }
403✔
1252
}
160✔
1253

1254

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