Coverage Report

Created: 2026-10-28 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/textord/fpchop.cpp
Line
Count
Source
1
/**********************************************************************
2
 * File:        fpchop.cpp  (Formerly fp_chop.c)
3
 * Description: Code to chop fixed pitch text into character cells.
4
 * Author:      Ray Smith
5
 *
6
 * (C) Copyright 1993, Hewlett-Packard Ltd.
7
 ** Licensed under the Apache License, Version 2.0 (the "License");
8
 ** you may not use this file except in compliance with the License.
9
 ** You may obtain a copy of the License at
10
 ** http://www.apache.org/licenses/LICENSE-2.0
11
 ** Unless required by applicable law or agreed to in writing, software
12
 ** distributed under the License is distributed on an "AS IS" BASIS,
13
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 ** See the License for the specific language governing permissions and
15
 ** limitations under the License.
16
 *
17
 **********************************************************************/
18
19
// Include automatically generated configuration file if running autoconf.
20
#ifdef HAVE_CONFIG_H
21
#  include "config_auto.h"
22
#endif
23
24
#include "fpchop.h"
25
26
#include "blobbox.h"
27
#include "drawtord.h"
28
#include "statistc.h"
29
#include "topitch.h"
30
#include "tovars.h"
31
32
namespace tesseract {
33
34
INT_VAR(textord_fp_chop_error, 2, "Max allowed bending of chop cells");
35
36
static WERD *add_repeated_word(WERD_IT *rep_it, int16_t &rep_left, int16_t &prev_chop_coord,
37
                               uint8_t &blanks, float pitch, WERD_IT *word_it);
38
39
static void fixed_chop_cblob(C_BLOB *blob, int16_t chop_coord, float pitch_error,
40
                             C_OUTLINE_LIST *left_outlines, C_OUTLINE_LIST *right_outlines);
41
42
static void fixed_split_coutline(C_OUTLINE *srcline, int16_t chop_coord, float pitch_error,
43
                                 C_OUTLINE_IT *left_it, C_OUTLINE_IT *right_it);
44
45
static bool fixed_chop_coutline(C_OUTLINE *srcline, int16_t chop_coord, float pitch_error,
46
                                C_OUTLINE_FRAG_LIST *left_frags, C_OUTLINE_FRAG_LIST *right_frags);
47
48
static void save_chop_cfragment(int16_t head_index, ICOORD head_pos, int16_t tail_index,
49
                                ICOORD tail_pos, C_OUTLINE *srcline, C_OUTLINE_FRAG_LIST *frags);
50
51
static void add_frag_to_list(C_OUTLINE_FRAG *frag, C_OUTLINE_FRAG_LIST *frags);
52
53
static void close_chopped_cfragments(C_OUTLINE_FRAG_LIST *frags, C_OUTLINE_LIST *children,
54
                                     float pitch_error, C_OUTLINE_IT *dest_it);
55
56
static C_OUTLINE *join_chopped_fragments(C_OUTLINE_FRAG *bottom, C_OUTLINE_FRAG *top);
57
58
static void join_segments(C_OUTLINE_FRAG *bottom, C_OUTLINE_FRAG *top);
59
60
/**********************************************************************
61
 * fixed_pitch_words
62
 *
63
 * Make a ROW from a fixed pitch TO_ROW.
64
 **********************************************************************/
65
ROW *fixed_pitch_words( // find lines
66
    TO_ROW *row         // row to do
67
5.60k
) {
68
5.60k
  bool bol;                // start of line
69
5.60k
  uint8_t blanks;          // in front of word
70
5.60k
  uint8_t new_blanks;      // blanks in empty cell
71
5.60k
  int16_t chop_coord;      // chop boundary
72
5.60k
  int16_t prev_chop_coord; // start of cell
73
5.60k
  int16_t rep_left;        // left edge of rep word
74
5.60k
  ROW *real_row;           // output row
75
5.60k
  C_OUTLINE_LIST left_coutlines;
76
5.60k
  C_OUTLINE_LIST right_coutlines;
77
5.60k
  C_BLOB_LIST cblobs;
78
5.60k
  C_BLOB_IT cblob_it = &cblobs;
79
5.60k
  WERD_LIST words;
80
5.60k
  WERD_IT word_it = &words; // new words
81
                            // repeated blobs
82
5.60k
  WERD_IT rep_it = &row->rep_words;
83
5.60k
  WERD *word;         // new word
84
5.60k
  int32_t xstarts[2]; // row ends
85
5.60k
  int32_t prev_x;     // end of prev blob
86
                      // iterator
87
5.60k
  BLOBNBOX_IT box_it = row->blob_list();
88
  // boundaries
89
5.60k
  ICOORDELT_IT cell_it = &row->char_cells;
90
91
#ifndef GRAPHICS_DISABLED
92
  if (textord_show_page_cuts && to_win != nullptr) {
93
    plot_row_cells(to_win, ScrollView::RED, row, 0, &row->char_cells);
94
  }
95
#endif
96
97
5.60k
  prev_x = -INT16_MAX;
98
5.60k
  bol = true;
99
5.60k
  blanks = 0;
100
5.60k
  if (rep_it.empty()) {
101
5.60k
    rep_left = INT16_MAX;
102
5.60k
  } else {
103
0
    rep_left = rep_it.data()->bounding_box().left();
104
0
  }
105
5.60k
  if (box_it.empty()) {
106
0
    return nullptr; // empty row
107
0
  }
108
5.60k
  xstarts[0] = box_it.data()->bounding_box().left();
109
5.60k
  if (rep_left < xstarts[0]) {
110
0
    xstarts[0] = rep_left;
111
0
  }
112
5.60k
  if (cell_it.empty() || row->char_cells.singleton()) {
113
0
    tprintf("Row without enough char cells!\n");
114
0
    tprintf("Leftmost blob is at (%d,%d)\n", box_it.data()->bounding_box().left(),
115
0
            box_it.data()->bounding_box().bottom());
116
0
    return nullptr;
117
0
  }
118
5.60k
  ASSERT_HOST(!cell_it.empty() && !row->char_cells.singleton());
119
5.60k
  prev_chop_coord = cell_it.data()->x();
120
5.60k
  word = nullptr;
121
5.60k
  while (rep_left < cell_it.data()->x()) {
122
0
    word =
123
0
        add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, &word_it);
124
0
  }
125
5.60k
  cell_it.mark_cycle_pt();
126
5.60k
  if (prev_chop_coord >= cell_it.data()->x()) {
127
5.60k
    cell_it.forward();
128
5.60k
  }
129
44.4k
  for (; !cell_it.cycled_list(); cell_it.forward()) {
130
38.8k
    chop_coord = cell_it.data()->x();
131
124k
    while (!box_it.empty() && box_it.data()->bounding_box().left() <= chop_coord) {
132
85.5k
      if (box_it.data()->bounding_box().right() > prev_x) {
133
52.0k
        prev_x = box_it.data()->bounding_box().right();
134
52.0k
      }
135
85.5k
      split_to_blob(box_it.extract(), chop_coord, textord_fp_chop_error + 0.5f, &left_coutlines,
136
85.5k
                    &right_coutlines);
137
85.5k
      box_it.forward();
138
94.9k
      while (!box_it.empty() && box_it.data()->cblob() == nullptr) {
139
9.42k
        delete box_it.extract();
140
9.42k
        box_it.forward();
141
9.42k
      }
142
85.5k
    }
143
38.8k
    if (!right_coutlines.empty() && left_coutlines.empty()) {
144
1.60k
      split_to_blob(nullptr, chop_coord, textord_fp_chop_error + 0.5f, &left_coutlines,
145
1.60k
                    &right_coutlines);
146
1.60k
    }
147
38.8k
    if (!left_coutlines.empty()) {
148
36.8k
      cblob_it.add_after_then_move(new C_BLOB(&left_coutlines));
149
36.8k
    } else {
150
2.03k
      if (rep_left < chop_coord) {
151
0
        if (rep_left > prev_chop_coord) {
152
0
          new_blanks =
153
0
              static_cast<uint8_t>(floor((rep_left - prev_chop_coord) / row->fixed_pitch + 0.5));
154
0
        } else {
155
0
          new_blanks = 0;
156
0
        }
157
2.03k
      } else {
158
2.03k
        if (chop_coord > prev_chop_coord) {
159
2.03k
          new_blanks =
160
2.03k
              static_cast<uint8_t>(floor((chop_coord - prev_chop_coord) / row->fixed_pitch + 0.5));
161
2.03k
        } else {
162
0
          new_blanks = 0;
163
0
        }
164
2.03k
      }
165
2.03k
      if (!cblob_it.empty()) {
166
1.04k
        if (blanks < 1 && word != nullptr && !word->flag(W_REP_CHAR)) {
167
6
          blanks = 1;
168
6
        }
169
1.04k
        word = new WERD(&cblobs, blanks, nullptr);
170
1.04k
        cblob_it.set_to_list(&cblobs);
171
1.04k
        word->set_flag(W_DONT_CHOP, true);
172
1.04k
        word_it.add_after_then_move(word);
173
1.04k
        if (bol) {
174
534
          word->set_flag(W_BOL, true);
175
534
          bol = false;
176
534
        }
177
1.04k
        blanks = new_blanks;
178
1.04k
      } else {
179
991
        blanks += new_blanks;
180
991
      }
181
2.03k
      while (rep_left < chop_coord) {
182
0
        word = add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch,
183
0
                                 &word_it);
184
0
      }
185
2.03k
    }
186
38.8k
    if (prev_chop_coord < chop_coord) {
187
38.8k
      prev_chop_coord = chop_coord;
188
38.8k
    }
189
38.8k
  }
190
5.60k
  if (!cblob_it.empty()) {
191
5.55k
    word = new WERD(&cblobs, blanks, nullptr);
192
5.55k
    word->set_flag(W_DONT_CHOP, true);
193
5.55k
    word_it.add_after_then_move(word);
194
5.55k
    if (bol) {
195
5.07k
      word->set_flag(W_BOL, true);
196
5.07k
    }
197
5.55k
  }
198
5.60k
  ASSERT_HOST(word != nullptr);
199
5.60k
  while (!rep_it.empty()) {
200
0
    add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, &word_it);
201
0
  }
202
  // at end of line
203
5.60k
  word_it.data()->set_flag(W_EOL, true);
204
5.60k
  if (prev_chop_coord > prev_x) {
205
2.47k
    prev_x = prev_chop_coord;
206
2.47k
  }
207
5.60k
  xstarts[1] = prev_x + 1;
208
5.60k
  real_row =
209
5.60k
      new ROW(row, static_cast<int16_t>(row->kern_size), static_cast<int16_t>(row->space_size));
210
5.60k
  word_it.set_to_list(real_row->word_list());
211
  // put words in row
212
5.60k
  word_it.add_list_after(&words);
213
5.60k
  real_row->recalc_bounding_box();
214
5.60k
  return real_row;
215
5.60k
}
216
217
/**********************************************************************
218
 * add_repeated_word
219
 *
220
 * Add repeated word into the row at the given point.
221
 **********************************************************************/
222
223
static WERD *add_repeated_word( // move repeated word
224
    WERD_IT *rep_it,            // repeated words
225
    int16_t &rep_left,          // left edge of word
226
    int16_t &prev_chop_coord,   // previous word end
227
    uint8_t &blanks,            // no of blanks
228
    float pitch,                // char cell size
229
    WERD_IT *word_it            // list of words
230
0
) {
231
0
  WERD *word;         // word to move
232
0
  int16_t new_blanks; // extra blanks
233
234
0
  if (rep_left > prev_chop_coord) {
235
0
    new_blanks = static_cast<uint8_t>(floor((rep_left - prev_chop_coord) / pitch + 0.5));
236
0
    blanks += new_blanks;
237
0
  }
238
0
  word = rep_it->extract();
239
0
  prev_chop_coord = word->bounding_box().right();
240
0
  word_it->add_after_then_move(word);
241
0
  word->set_blanks(blanks);
242
0
  rep_it->forward();
243
0
  if (rep_it->empty()) {
244
0
    rep_left = INT16_MAX;
245
0
  } else {
246
0
    rep_left = rep_it->data()->bounding_box().left();
247
0
  }
248
0
  blanks = 0;
249
0
  return word;
250
0
}
251
252
/**********************************************************************
253
 * split_to_blob
254
 *
255
 * Split a BLOBNBOX across a vertical chop line and put the pieces
256
 * into a left outline list and a right outline list.
257
 **********************************************************************/
258
259
void split_to_blob(                 // split the blob
260
    BLOBNBOX *blob,                 // blob to split
261
    int16_t chop_coord,             // place to chop
262
    float pitch_error,              // allowed deviation
263
    C_OUTLINE_LIST *left_coutlines, // for cblobs
264
144k
    C_OUTLINE_LIST *right_coutlines) {
265
144k
  C_BLOB *real_cblob; // cblob to chop
266
267
144k
  if (blob != nullptr) {
268
102k
    real_cblob = blob->remove_cblob();
269
102k
  } else {
270
41.4k
    real_cblob = nullptr;
271
41.4k
  }
272
144k
  if (!right_coutlines->empty() || real_cblob != nullptr) {
273
144k
    fixed_chop_cblob(real_cblob, chop_coord, pitch_error, left_coutlines, right_coutlines);
274
144k
  }
275
276
144k
  delete blob;
277
144k
}
278
279
/**********************************************************************
280
 * fixed_chop_cblob
281
 *
282
 * Chop the given cblob (if any) and the existing right outlines to
283
 * produce a list of outlines left of the chop point and more to the right.
284
 **********************************************************************/
285
286
static void fixed_chop_cblob(      // split the blob
287
    C_BLOB *blob,                  // blob to split
288
    int16_t chop_coord,            // place to chop
289
    float pitch_error,             // allowed deviation
290
    C_OUTLINE_LIST *left_outlines, // left half of chop
291
    C_OUTLINE_LIST *right_outlines // right half of chop
292
144k
) {
293
144k
  C_OUTLINE *old_right;        // already there
294
144k
  C_OUTLINE_LIST new_outlines; // new right ones
295
                               // output iterator
296
144k
  C_OUTLINE_IT left_it = left_outlines;
297
  // in/out iterator
298
144k
  C_OUTLINE_IT right_it = right_outlines;
299
144k
  C_OUTLINE_IT new_it = &new_outlines;
300
144k
  C_OUTLINE_IT blob_it; // outlines in blob
301
302
144k
  if (!right_it.empty()) {
303
110k
    while (!right_it.empty()) {
304
59.6k
      old_right = right_it.extract();
305
59.6k
      right_it.forward();
306
59.6k
      fixed_split_coutline(old_right, chop_coord, pitch_error, &left_it, &new_it);
307
59.6k
    }
308
50.6k
    right_it.add_list_before(&new_outlines);
309
50.6k
  }
310
144k
  if (blob != nullptr) {
311
102k
    blob_it.set_to_list(blob->out_list());
312
205k
    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
313
103k
      fixed_split_coutline(blob_it.extract(), chop_coord, pitch_error, &left_it, &right_it);
314
103k
    }
315
102k
    delete blob;
316
102k
  }
317
144k
}
318
319
/**********************************************************************
320
 * fixed_split_outline
321
 *
322
 * Chop the given outline (if necessary) placing the fragments which
323
 * fall either side of the chop line into the appropriate list.
324
 **********************************************************************/
325
326
static void fixed_split_coutline( // chop the outline
327
    C_OUTLINE *srcline,           // source outline
328
    int16_t chop_coord,           // place to chop
329
    float pitch_error,            // allowed deviation
330
    C_OUTLINE_IT *left_it,        // left half of chop
331
    C_OUTLINE_IT *right_it        // right half of chop
332
162k
) {
333
162k
  C_OUTLINE *child;               // child outline
334
162k
  TBOX srcbox;                    // box of outline
335
162k
  C_OUTLINE_LIST left_ch;         // left children
336
162k
  C_OUTLINE_LIST right_ch;        // right children
337
162k
  C_OUTLINE_FRAG_LIST left_frags; // chopped fragments
338
162k
  C_OUTLINE_FRAG_LIST right_frags;
339
162k
  ;
340
162k
  C_OUTLINE_IT left_ch_it = &left_ch;
341
  // for whole children
342
162k
  C_OUTLINE_IT right_ch_it = &right_ch;
343
  // for holes
344
162k
  C_OUTLINE_IT child_it = srcline->child();
345
346
162k
  srcbox = srcline->bounding_box();
347
162k
  if (srcbox.left() + srcbox.right() <= chop_coord * 2 &&
348
111k
      srcbox.right() < chop_coord + pitch_error) {
349
    // Whole outline is in the left side or not far over the chop_coord,
350
    // so put the whole thing on the left.
351
105k
    left_it->add_after_then_move(srcline);
352
105k
  } else if (srcbox.left() + srcbox.right() > chop_coord * 2 &&
353
50.9k
             srcbox.left() > chop_coord - pitch_error) {
354
    // Whole outline is in the right side or not far over the chop_coord,
355
    // so put the whole thing on the right.
356
25.6k
    right_it->add_before_stay_put(srcline);
357
31.3k
  } else {
358
    // Needs real chopping.
359
31.3k
    if (fixed_chop_coutline(srcline, chop_coord, pitch_error, &left_frags, &right_frags)) {
360
122k
      for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) {
361
91.5k
        child = child_it.extract();
362
91.5k
        srcbox = child->bounding_box();
363
91.5k
        if (srcbox.right() < chop_coord) {
364
          // Whole child is on the left.
365
12.1k
          left_ch_it.add_after_then_move(child);
366
79.4k
        } else if (srcbox.left() > chop_coord) {
367
          // Whole child is on the right.
368
71.4k
          right_ch_it.add_after_then_move(child);
369
71.4k
        } else {
370
          // No pitch_error is allowed when chopping children to prevent
371
          // impossible outlines from being created.
372
7.95k
          if (fixed_chop_coutline(child, chop_coord, 0.0f, &left_frags, &right_frags)) {
373
5.87k
            delete child;
374
5.87k
          } else {
375
2.07k
            if (srcbox.left() + srcbox.right() <= chop_coord * 2) {
376
0
              left_ch_it.add_after_then_move(child);
377
2.07k
            } else {
378
2.07k
              right_ch_it.add_after_then_move(child);
379
2.07k
            }
380
2.07k
          }
381
7.95k
        }
382
91.5k
      }
383
31.3k
      close_chopped_cfragments(&left_frags, &left_ch, pitch_error, left_it);
384
31.3k
      close_chopped_cfragments(&right_frags, &right_ch, pitch_error, right_it);
385
31.3k
      ASSERT_HOST(left_ch.empty() && right_ch.empty());
386
      // No children left.
387
31.3k
      delete srcline; // Smashed up.
388
31.3k
    } else {
389
      // Chop failed. Just use middle coord.
390
0
      if (srcbox.left() + srcbox.right() <= chop_coord * 2) {
391
0
        left_it->add_after_then_move(srcline); // Stick whole in left.
392
0
      } else {
393
0
        right_it->add_before_stay_put(srcline);
394
0
      }
395
0
    }
396
31.3k
  }
397
162k
}
398
399
/**********************************************************************
400
 * fixed_chop_coutline
401
 *
402
 * Chop the given coutline (if necessary) placing the fragments which
403
 * fall either side of the chop line into the appropriate list.
404
 * If the coutline lies too heavily to one side to chop, false is returned.
405
 **********************************************************************/
406
407
static bool fixed_chop_coutline(     // chop the outline
408
    C_OUTLINE *srcline,              // source outline
409
    int16_t chop_coord,              // place to chop
410
    float pitch_error,               // allowed deviation
411
    C_OUTLINE_FRAG_LIST *left_frags, // left half of chop
412
    C_OUTLINE_FRAG_LIST *right_frags // right half of chop
413
39.3k
) {
414
39.3k
  bool first_frag;         // fragment
415
39.3k
  int16_t left_edge;       // of outline
416
39.3k
  int16_t startindex;      // in first fragment
417
39.3k
  int32_t length;          // of outline
418
39.3k
  int16_t stepindex;       // into outline
419
39.3k
  int16_t head_index;      // start of fragment
420
39.3k
  ICOORD head_pos;         // start of fragment
421
39.3k
  int16_t tail_index;      // end of fragment
422
39.3k
  ICOORD tail_pos;         // end of fragment
423
39.3k
  ICOORD pos;              // current point
424
39.3k
  int16_t first_index = 0; // first tail
425
39.3k
  ICOORD first_pos;        // first tail
426
427
39.3k
  length = srcline->pathlength();
428
39.3k
  pos = srcline->start_pos();
429
39.3k
  left_edge = pos.x();
430
39.3k
  tail_index = 0;
431
39.3k
  tail_pos = pos;
432
13.2M
  for (stepindex = 0; stepindex < length; stepindex++) {
433
13.1M
    if (pos.x() < left_edge) {
434
102k
      left_edge = pos.x();
435
102k
      tail_index = stepindex;
436
102k
      tail_pos = pos;
437
102k
    }
438
13.1M
    pos += srcline->step(stepindex);
439
13.1M
  }
440
39.3k
  if (left_edge >= chop_coord - pitch_error) {
441
2.07k
    return false; // not worth it
442
2.07k
  }
443
444
37.2k
  startindex = tail_index;
445
37.2k
  first_frag = true;
446
37.2k
  head_index = tail_index;
447
37.2k
  head_pos = tail_pos;
448
96.3k
  do {
449
2.29M
    do {
450
2.29M
      tail_pos += srcline->step(tail_index);
451
2.29M
      tail_index++;
452
2.29M
      if (tail_index == length) {
453
32.5k
        tail_index = 0;
454
32.5k
      }
455
2.29M
    } while (tail_pos.x() != chop_coord && tail_index != startindex);
456
96.3k
    if (tail_index == startindex) {
457
37.2k
      if (first_frag) {
458
0
        return false; // doesn't cross line
459
37.2k
      } else {
460
37.2k
        break;
461
37.2k
      }
462
37.2k
    }
463
59.1k
    ASSERT_HOST(head_index != tail_index);
464
59.1k
    if (!first_frag) {
465
21.9k
      save_chop_cfragment(head_index, head_pos, tail_index, tail_pos, srcline, left_frags);
466
37.2k
    } else {
467
37.2k
      first_index = tail_index;
468
37.2k
      first_pos = tail_pos;
469
37.2k
      first_frag = false;
470
37.2k
    }
471
110k
    while (srcline->step(tail_index).x() == 0) {
472
51.4k
      tail_pos += srcline->step(tail_index);
473
51.4k
      tail_index++;
474
51.4k
      if (tail_index == length) {
475
988
        tail_index = 0;
476
988
      }
477
51.4k
    }
478
59.1k
    head_index = tail_index;
479
59.1k
    head_pos = tail_pos;
480
112k
    while (srcline->step(tail_index).x() > 0) {
481
10.7M
      do {
482
10.7M
        tail_pos += srcline->step(tail_index);
483
10.7M
        tail_index++;
484
10.7M
        if (tail_index == length) {
485
3.66k
          tail_index = 0;
486
3.66k
        }
487
10.7M
      } while (tail_pos.x() != chop_coord);
488
53.4k
      ASSERT_HOST(head_index != tail_index);
489
53.4k
      save_chop_cfragment(head_index, head_pos, tail_index, tail_pos, srcline, right_frags);
490
108k
      while (srcline->step(tail_index).x() == 0) {
491
55.2k
        tail_pos += srcline->step(tail_index);
492
55.2k
        tail_index++;
493
55.2k
        if (tail_index == length) {
494
20
          tail_index = 0;
495
20
        }
496
55.2k
      }
497
53.4k
      head_index = tail_index;
498
53.4k
      head_pos = tail_pos;
499
53.4k
    }
500
59.1k
  } while (tail_index != startindex);
501
37.2k
  save_chop_cfragment(head_index, head_pos, first_index, first_pos, srcline, left_frags);
502
37.2k
  return true; // did some chopping
503
37.2k
}
504
505
/**********************************************************************
506
 * save_chop_cfragment
507
 *
508
 * Store the given fragment in the given fragment list.
509
 **********************************************************************/
510
511
static void save_chop_cfragment( // chop the outline
512
    int16_t head_index,          // head of fragment
513
    ICOORD head_pos,             // head of fragment
514
    int16_t tail_index,          // tail of fragment
515
    ICOORD tail_pos,             // tail of fragment
516
    C_OUTLINE *srcline,          // source of edgesteps
517
    C_OUTLINE_FRAG_LIST *frags   // fragment list
518
112k
) {
519
112k
  int16_t jump;         // gap across end
520
112k
  int16_t stepcount;    // total steps
521
112k
  C_OUTLINE_FRAG *head; // head of fragment
522
112k
  C_OUTLINE_FRAG *tail; // tail of fragment
523
112k
  int16_t tail_y;       // ycoord of tail
524
525
112k
  ASSERT_HOST(tail_pos.x() == head_pos.x());
526
112k
  ASSERT_HOST(tail_index != head_index);
527
112k
  stepcount = tail_index - head_index;
528
112k
  if (stepcount < 0) {
529
36.2k
    stepcount += srcline->pathlength();
530
36.2k
  }
531
112k
  jump = tail_pos.y() - head_pos.y();
532
112k
  if (jump < 0) {
533
55.3k
    jump = -jump;
534
55.3k
  }
535
112k
  if (jump == stepcount) {
536
0
    return; // its a nop
537
0
  }
538
112k
  tail_y = tail_pos.y();
539
112k
  head = new C_OUTLINE_FRAG(head_pos, tail_pos, srcline, head_index, tail_index);
540
112k
  tail = new C_OUTLINE_FRAG(head, tail_y);
541
112k
  head->other_end = tail;
542
112k
  add_frag_to_list(head, frags);
543
112k
  add_frag_to_list(tail, frags);
544
112k
}
545
546
/**********************************************************************
547
 * C_OUTLINE_FRAG::C_OUTLINE_FRAG
548
 *
549
 * Constructors for C_OUTLINE_FRAG.
550
 **********************************************************************/
551
552
C_OUTLINE_FRAG::C_OUTLINE_FRAG( // record fragment
553
    ICOORD start_pt,            // start coord
554
    ICOORD end_pt,              // end coord
555
    C_OUTLINE *outline,         // source of steps
556
112k
    int16_t start_index, int16_t end_index) {
557
112k
  start = start_pt;
558
112k
  end = end_pt;
559
112k
  ycoord = start_pt.y();
560
112k
  stepcount = end_index - start_index;
561
112k
  if (stepcount < 0) {
562
36.2k
    stepcount += outline->pathlength();
563
36.2k
  }
564
112k
  ASSERT_HOST(stepcount > 0);
565
112k
  steps = new DIR128[stepcount];
566
112k
  if (end_index > start_index) {
567
10.5M
    for (int i = start_index; i < end_index; ++i) {
568
10.4M
      steps[i - start_index] = outline->step_dir(i);
569
10.4M
    }
570
76.3k
  } else {
571
36.2k
    int len = outline->pathlength();
572
36.2k
    int i = start_index;
573
1.60M
    for (; i < len; ++i) {
574
1.57M
      steps[i - start_index] = outline->step_dir(i);
575
1.57M
    }
576
36.2k
    if (end_index > 0) {
577
1.00M
      for (; i < end_index + len; ++i) {
578
973k
        steps[i - start_index] = outline->step_dir(i - len);
579
973k
      }
580
34.6k
    }
581
36.2k
  }
582
112k
  other_end = nullptr;
583
112k
  delete close();
584
112k
}
585
586
C_OUTLINE_FRAG::C_OUTLINE_FRAG( // record fragment
587
    C_OUTLINE_FRAG *head,       // other end
588
112k
    int16_t tail_y) {
589
112k
  ycoord = tail_y;
590
112k
  other_end = head;
591
112k
  start = head->start;
592
112k
  end = head->end;
593
112k
  steps = nullptr;
594
112k
  stepcount = 0;
595
112k
}
596
597
/**********************************************************************
598
 * add_frag_to_list
599
 *
600
 * Insert the fragment in the list at the appropriate place to keep
601
 * them in ascending ycoord order.
602
 **********************************************************************/
603
604
static void add_frag_to_list(  // ordered add
605
    C_OUTLINE_FRAG *frag,      // fragment to add
606
    C_OUTLINE_FRAG_LIST *frags // fragment list
607
225k
) {
608
  // output list
609
225k
  C_OUTLINE_FRAG_IT frag_it = frags;
610
611
225k
  if (!frags->empty()) {
612
494k
    for (frag_it.mark_cycle_pt(); !frag_it.cycled_list(); frag_it.forward()) {
613
423k
      if (frag_it.data()->ycoord > frag->ycoord ||
614
332k
          (frag_it.data()->ycoord == frag->ycoord && frag->other_end->ycoord < frag->ycoord)) {
615
90.7k
        frag_it.add_before_then_move(frag);
616
90.7k
        return;
617
90.7k
      }
618
423k
    }
619
162k
  }
620
134k
  frag_it.add_to_end(frag);
621
134k
}
622
623
/**********************************************************************
624
 * close_chopped_cfragments
625
 *
626
 * Clear the given list of fragments joining them up into outlines.
627
 * Each outline made soaks up any of the child outlines which it encloses.
628
 **********************************************************************/
629
630
static void close_chopped_cfragments( // chop the outline
631
    C_OUTLINE_FRAG_LIST *frags,       // list to clear
632
    C_OUTLINE_LIST *children,         // potential children
633
    float pitch_error,                // allowed shrinkage
634
    C_OUTLINE_IT *dest_it             // output list
635
62.7k
) {
636
  // iterator
637
62.7k
  C_OUTLINE_FRAG_IT frag_it = frags;
638
62.7k
  C_OUTLINE_FRAG *bottom_frag; // bottom of cut
639
62.7k
  C_OUTLINE_FRAG *top_frag;    // top of cut
640
62.7k
  C_OUTLINE *outline;          // new outline
641
62.7k
  C_OUTLINE *child;            // current child
642
62.7k
  C_OUTLINE_IT child_it = children;
643
62.7k
  C_OUTLINE_IT olchild_it; // children of outline
644
645
175k
  while (!frag_it.empty()) {
646
112k
    frag_it.move_to_first();
647
    // get bottom one
648
112k
    bottom_frag = frag_it.extract();
649
112k
    frag_it.forward();
650
112k
    top_frag = frag_it.data(); // look at next
651
112k
    if ((bottom_frag->steps == nullptr && top_frag->steps == nullptr) ||
652
112k
        (bottom_frag->steps != nullptr && top_frag->steps != nullptr)) {
653
0
      if (frag_it.data_relative(1)->ycoord == top_frag->ycoord) {
654
0
        frag_it.forward();
655
0
      }
656
0
    }
657
112k
    top_frag = frag_it.extract();
658
112k
    if (top_frag->other_end != bottom_frag) {
659
36.0k
      outline = join_chopped_fragments(bottom_frag, top_frag);
660
36.0k
      ASSERT_HOST(outline == nullptr);
661
76.5k
    } else {
662
76.5k
      outline = join_chopped_fragments(bottom_frag, top_frag);
663
76.5k
      if (outline != nullptr) {
664
76.5k
        olchild_it.set_to_list(outline->child());
665
172k
        for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) {
666
96.3k
          child = child_it.data();
667
96.3k
          if (*child < *outline) {
668
85.6k
            olchild_it.add_to_end(child_it.extract());
669
85.6k
          }
670
96.3k
        }
671
76.5k
        if (outline->bounding_box().width() > pitch_error) {
672
70.1k
          dest_it->add_after_then_move(outline);
673
70.1k
        } else {
674
6.34k
          delete outline; // Make it disappear.
675
6.34k
        }
676
76.5k
      }
677
76.5k
    }
678
112k
  }
679
62.7k
  while (!child_it.empty()) {
680
0
    dest_it->add_after_then_move(child_it.extract());
681
0
    child_it.forward();
682
0
  }
683
62.7k
}
684
685
/**********************************************************************
686
 * join_chopped_fragments
687
 *
688
 * Join the two lists of POLYPTs such that neither OUTLINE_FRAG
689
 * operand keeps responsibility for the fragment.
690
 **********************************************************************/
691
692
static C_OUTLINE *join_chopped_fragments( // join pieces
693
    C_OUTLINE_FRAG *bottom,               // bottom of cut
694
    C_OUTLINE_FRAG *top                   // top of cut
695
112k
) {
696
112k
  C_OUTLINE *outline; // closed loop
697
698
112k
  if (bottom->other_end == top) {
699
76.5k
    if (bottom->steps == nullptr) {
700
39.2k
      outline = top->close(); // turn to outline
701
39.2k
    } else {
702
37.3k
      outline = bottom->close();
703
37.3k
    }
704
76.5k
    delete top;
705
76.5k
    delete bottom;
706
76.5k
    return outline;
707
76.5k
  }
708
36.0k
  if (bottom->steps == nullptr) {
709
19.9k
    ASSERT_HOST(top->steps != nullptr);
710
19.9k
    join_segments(bottom->other_end, top);
711
19.9k
  } else {
712
16.1k
    ASSERT_HOST(top->steps == nullptr);
713
16.1k
    join_segments(top->other_end, bottom);
714
16.1k
  }
715
36.0k
  top->other_end->other_end = bottom->other_end;
716
36.0k
  bottom->other_end->other_end = top->other_end;
717
36.0k
  delete bottom;
718
36.0k
  delete top;
719
36.0k
  return nullptr;
720
112k
}
721
722
/**********************************************************************
723
 * join_segments
724
 *
725
 * Join the two edgestep fragments such that the second comes after
726
 * the first and the gap between them is closed.
727
 **********************************************************************/
728
729
static void join_segments(  // join pieces
730
    C_OUTLINE_FRAG *bottom, // bottom of cut
731
    C_OUTLINE_FRAG *top     // top of cut
732
36.0k
) {
733
36.0k
  DIR128 *steps;      // new steps
734
36.0k
  int32_t stepcount;  // no of steps
735
36.0k
  int16_t fake_count; // fake steps
736
36.0k
  DIR128 fake_step;   // step entry
737
738
36.0k
  ASSERT_HOST(bottom->end.x() == top->start.x());
739
36.0k
  fake_count = top->start.y() - bottom->end.y();
740
36.0k
  if (fake_count < 0) {
741
16.1k
    fake_count = -fake_count;
742
16.1k
    fake_step = 32;
743
19.9k
  } else {
744
19.9k
    fake_step = 96;
745
19.9k
  }
746
747
36.0k
  stepcount = bottom->stepcount + fake_count + top->stepcount;
748
36.0k
  steps = new DIR128[stepcount];
749
36.0k
  memmove(steps, bottom->steps, bottom->stepcount);
750
36.0k
  memset(steps + bottom->stepcount, fake_step.get_dir(), fake_count);
751
36.0k
  memmove(steps + bottom->stepcount + fake_count, top->steps, top->stepcount);
752
36.0k
  delete[] bottom->steps;
753
36.0k
  bottom->steps = steps;
754
36.0k
  bottom->stepcount = stepcount;
755
36.0k
  bottom->end = top->end;
756
36.0k
  bottom->other_end->end = top->end;
757
36.0k
}
758
759
/**********************************************************************
760
 * C_OUTLINE_FRAG::close
761
 *
762
 * Join the ends of this fragment and turn it into an outline.
763
 **********************************************************************/
764
765
189k
C_OUTLINE *C_OUTLINE_FRAG::close() { // join pieces
766
189k
  DIR128 *new_steps;                 // new steps
767
189k
  int32_t new_stepcount;             // no of steps
768
189k
  int16_t fake_count;                // fake steps
769
189k
  DIR128 fake_step;                  // step entry
770
771
189k
  ASSERT_HOST(start.x() == end.x());
772
189k
  fake_count = start.y() - end.y();
773
189k
  if (fake_count < 0) {
774
94.5k
    fake_count = -fake_count;
775
94.5k
    fake_step = 32;
776
94.5k
  } else {
777
94.5k
    fake_step = 96;
778
94.5k
  }
779
780
189k
  new_stepcount = stepcount + fake_count;
781
189k
  if (new_stepcount > C_OUTLINE::kMaxOutlineLength) {
782
0
    return nullptr; // Can't join them
783
0
  }
784
189k
  new_steps = new DIR128[new_stepcount];
785
189k
  memmove(new_steps, steps, stepcount);
786
189k
  memset(new_steps + stepcount, fake_step.get_dir(), fake_count);
787
189k
  auto *result = new C_OUTLINE(start, new_steps, static_cast<int16_t>(new_stepcount));
788
189k
  delete[] new_steps;
789
189k
  return result;
790
189k
}
791
792
/**********************************************************************
793
 * C_OUTLINE_FRAG::operator=
794
 *
795
 * Copy this fragment.
796
 **********************************************************************/
797
798
// join pieces
799
C_OUTLINE_FRAG &C_OUTLINE_FRAG::operator=(const C_OUTLINE_FRAG &src // fragment to copy
800
0
) {
801
0
  delete[] steps;
802
803
0
  stepcount = src.stepcount;
804
0
  steps = new DIR128[stepcount];
805
0
  memmove(steps, src.steps, stepcount);
806
0
  start = src.start;
807
0
  end = src.end;
808
0
  ycoord = src.ycoord;
809
0
  return *this;
810
0
}
811
812
} // namespace tesseract