diff mupdf-source/thirdparty/tesseract/src/textord/fpchop.cpp @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/tesseract/src/textord/fpchop.cpp	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,813 @@
+/**********************************************************************
+ * File:        fpchop.cpp  (Formerly fp_chop.c)
+ * Description: Code to chop fixed pitch text into character cells.
+ * Author:      Ray Smith
+ *
+ * (C) Copyright 1993, Hewlett-Packard Ltd.
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ *
+ **********************************************************************/
+
+// Include automatically generated configuration file if running autoconf.
+#ifdef HAVE_CONFIG_H
+#  include "config_auto.h"
+#endif
+
+#include "fpchop.h"
+
+#include "blobbox.h"
+#include "drawtord.h"
+#include "statistc.h"
+#include "topitch.h"
+#include "tovars.h"
+
+namespace tesseract {
+
+INT_VAR(textord_fp_chop_error, 2, "Max allowed bending of chop cells");
+
+static WERD *add_repeated_word(WERD_IT *rep_it, int16_t &rep_left, int16_t &prev_chop_coord,
+                               uint8_t &blanks, float pitch, WERD_IT *word_it);
+
+static void fixed_chop_cblob(C_BLOB *blob, int16_t chop_coord, float pitch_error,
+                             C_OUTLINE_LIST *left_outlines, C_OUTLINE_LIST *right_outlines);
+
+static void fixed_split_coutline(C_OUTLINE *srcline, int16_t chop_coord, float pitch_error,
+                                 C_OUTLINE_IT *left_it, C_OUTLINE_IT *right_it);
+
+static bool fixed_chop_coutline(C_OUTLINE *srcline, int16_t chop_coord, float pitch_error,
+                                C_OUTLINE_FRAG_LIST *left_frags, C_OUTLINE_FRAG_LIST *right_frags);
+
+static void save_chop_cfragment(int16_t head_index, ICOORD head_pos, int16_t tail_index,
+                                ICOORD tail_pos, C_OUTLINE *srcline, C_OUTLINE_FRAG_LIST *frags);
+
+static void add_frag_to_list(C_OUTLINE_FRAG *frag, C_OUTLINE_FRAG_LIST *frags);
+
+static void close_chopped_cfragments(C_OUTLINE_FRAG_LIST *frags, C_OUTLINE_LIST *children,
+                                     float pitch_error, C_OUTLINE_IT *dest_it);
+
+static C_OUTLINE *join_chopped_fragments(C_OUTLINE_FRAG *bottom, C_OUTLINE_FRAG *top);
+
+static void join_segments(C_OUTLINE_FRAG *bottom, C_OUTLINE_FRAG *top);
+
+/**********************************************************************
+ * fixed_pitch_words
+ *
+ * Make a ROW from a fixed pitch TO_ROW.
+ **********************************************************************/
+ROW *fixed_pitch_words( // find lines
+    TO_ROW *row,        // row to do
+    FCOORD rotation     // for drawing
+) {
+  bool bol;                // start of line
+  uint8_t blanks;          // in front of word
+  uint8_t new_blanks;      // blanks in empty cell
+  int16_t chop_coord;      // chop boundary
+  int16_t prev_chop_coord; // start of cell
+  int16_t rep_left;        // left edge of rep word
+  ROW *real_row;           // output row
+  C_OUTLINE_LIST left_coutlines;
+  C_OUTLINE_LIST right_coutlines;
+  C_BLOB_LIST cblobs;
+  C_BLOB_IT cblob_it = &cblobs;
+  WERD_LIST words;
+  WERD_IT word_it = &words; // new words
+                            // repeated blobs
+  WERD_IT rep_it = &row->rep_words;
+  WERD *word;         // new word
+  int32_t xstarts[2]; // row ends
+  int32_t prev_x;     // end of prev blob
+                      // iterator
+  BLOBNBOX_IT box_it = row->blob_list();
+  // boundaries
+  ICOORDELT_IT cell_it = &row->char_cells;
+
+#ifndef GRAPHICS_DISABLED
+  if (textord_show_page_cuts && to_win != nullptr) {
+    plot_row_cells(to_win, ScrollView::RED, row, 0, &row->char_cells);
+  }
+#endif
+
+  prev_x = -INT16_MAX;
+  bol = true;
+  blanks = 0;
+  if (rep_it.empty()) {
+    rep_left = INT16_MAX;
+  } else {
+    rep_left = rep_it.data()->bounding_box().left();
+  }
+  if (box_it.empty()) {
+    return nullptr; // empty row
+  }
+  xstarts[0] = box_it.data()->bounding_box().left();
+  if (rep_left < xstarts[0]) {
+    xstarts[0] = rep_left;
+  }
+  if (cell_it.empty() || row->char_cells.singleton()) {
+    tprintf("Row without enough char cells!\n");
+    tprintf("Leftmost blob is at (%d,%d)\n", box_it.data()->bounding_box().left(),
+            box_it.data()->bounding_box().bottom());
+    return nullptr;
+  }
+  ASSERT_HOST(!cell_it.empty() && !row->char_cells.singleton());
+  prev_chop_coord = cell_it.data()->x();
+  word = nullptr;
+  while (rep_left < cell_it.data()->x()) {
+    word =
+        add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, &word_it);
+  }
+  cell_it.mark_cycle_pt();
+  if (prev_chop_coord >= cell_it.data()->x()) {
+    cell_it.forward();
+  }
+  for (; !cell_it.cycled_list(); cell_it.forward()) {
+    chop_coord = cell_it.data()->x();
+    while (!box_it.empty() && box_it.data()->bounding_box().left() <= chop_coord) {
+      if (box_it.data()->bounding_box().right() > prev_x) {
+        prev_x = box_it.data()->bounding_box().right();
+      }
+      split_to_blob(box_it.extract(), chop_coord, textord_fp_chop_error + 0.5f, &left_coutlines,
+                    &right_coutlines);
+      box_it.forward();
+      while (!box_it.empty() && box_it.data()->cblob() == nullptr) {
+        delete box_it.extract();
+        box_it.forward();
+      }
+    }
+    if (!right_coutlines.empty() && left_coutlines.empty()) {
+      split_to_blob(nullptr, chop_coord, textord_fp_chop_error + 0.5f, &left_coutlines,
+                    &right_coutlines);
+    }
+    if (!left_coutlines.empty()) {
+      cblob_it.add_after_then_move(new C_BLOB(&left_coutlines));
+    } else {
+      if (rep_left < chop_coord) {
+        if (rep_left > prev_chop_coord) {
+          new_blanks =
+              static_cast<uint8_t>(floor((rep_left - prev_chop_coord) / row->fixed_pitch + 0.5));
+        } else {
+          new_blanks = 0;
+        }
+      } else {
+        if (chop_coord > prev_chop_coord) {
+          new_blanks =
+              static_cast<uint8_t>(floor((chop_coord - prev_chop_coord) / row->fixed_pitch + 0.5));
+        } else {
+          new_blanks = 0;
+        }
+      }
+      if (!cblob_it.empty()) {
+        if (blanks < 1 && word != nullptr && !word->flag(W_REP_CHAR)) {
+          blanks = 1;
+        }
+        word = new WERD(&cblobs, blanks, nullptr);
+        cblob_it.set_to_list(&cblobs);
+        word->set_flag(W_DONT_CHOP, true);
+        word_it.add_after_then_move(word);
+        if (bol) {
+          word->set_flag(W_BOL, true);
+          bol = false;
+        }
+        blanks = new_blanks;
+      } else {
+        blanks += new_blanks;
+      }
+      while (rep_left < chop_coord) {
+        word = add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch,
+                                 &word_it);
+      }
+    }
+    if (prev_chop_coord < chop_coord) {
+      prev_chop_coord = chop_coord;
+    }
+  }
+  if (!cblob_it.empty()) {
+    word = new WERD(&cblobs, blanks, nullptr);
+    word->set_flag(W_DONT_CHOP, true);
+    word_it.add_after_then_move(word);
+    if (bol) {
+      word->set_flag(W_BOL, true);
+    }
+  }
+  ASSERT_HOST(word != nullptr);
+  while (!rep_it.empty()) {
+    add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, &word_it);
+  }
+  // at end of line
+  word_it.data()->set_flag(W_EOL, true);
+  if (prev_chop_coord > prev_x) {
+    prev_x = prev_chop_coord;
+  }
+  xstarts[1] = prev_x + 1;
+  real_row =
+      new ROW(row, static_cast<int16_t>(row->kern_size), static_cast<int16_t>(row->space_size));
+  word_it.set_to_list(real_row->word_list());
+  // put words in row
+  word_it.add_list_after(&words);
+  real_row->recalc_bounding_box();
+  return real_row;
+}
+
+/**********************************************************************
+ * add_repeated_word
+ *
+ * Add repeated word into the row at the given point.
+ **********************************************************************/
+
+static WERD *add_repeated_word( // move repeated word
+    WERD_IT *rep_it,            // repeated words
+    int16_t &rep_left,          // left edge of word
+    int16_t &prev_chop_coord,   // previous word end
+    uint8_t &blanks,            // no of blanks
+    float pitch,                // char cell size
+    WERD_IT *word_it            // list of words
+) {
+  WERD *word;         // word to move
+  int16_t new_blanks; // extra blanks
+
+  if (rep_left > prev_chop_coord) {
+    new_blanks = static_cast<uint8_t>(floor((rep_left - prev_chop_coord) / pitch + 0.5));
+    blanks += new_blanks;
+  }
+  word = rep_it->extract();
+  prev_chop_coord = word->bounding_box().right();
+  word_it->add_after_then_move(word);
+  word->set_blanks(blanks);
+  rep_it->forward();
+  if (rep_it->empty()) {
+    rep_left = INT16_MAX;
+  } else {
+    rep_left = rep_it->data()->bounding_box().left();
+  }
+  blanks = 0;
+  return word;
+}
+
+/**********************************************************************
+ * split_to_blob
+ *
+ * Split a BLOBNBOX across a vertical chop line and put the pieces
+ * into a left outline list and a right outline list.
+ **********************************************************************/
+
+void split_to_blob(                 // split the blob
+    BLOBNBOX *blob,                 // blob to split
+    int16_t chop_coord,             // place to chop
+    float pitch_error,              // allowed deviation
+    C_OUTLINE_LIST *left_coutlines, // for cblobs
+    C_OUTLINE_LIST *right_coutlines) {
+  C_BLOB *real_cblob; // cblob to chop
+
+  if (blob != nullptr) {
+    real_cblob = blob->remove_cblob();
+  } else {
+    real_cblob = nullptr;
+  }
+  if (!right_coutlines->empty() || real_cblob != nullptr) {
+    fixed_chop_cblob(real_cblob, chop_coord, pitch_error, left_coutlines, right_coutlines);
+  }
+
+  delete blob;
+}
+
+/**********************************************************************
+ * fixed_chop_cblob
+ *
+ * Chop the given cblob (if any) and the existing right outlines to
+ * produce a list of outlines left of the chop point and more to the right.
+ **********************************************************************/
+
+static void fixed_chop_cblob(      // split the blob
+    C_BLOB *blob,                  // blob to split
+    int16_t chop_coord,            // place to chop
+    float pitch_error,             // allowed deviation
+    C_OUTLINE_LIST *left_outlines, // left half of chop
+    C_OUTLINE_LIST *right_outlines // right half of chop
+) {
+  C_OUTLINE *old_right;        // already there
+  C_OUTLINE_LIST new_outlines; // new right ones
+                               // output iterator
+  C_OUTLINE_IT left_it = left_outlines;
+  // in/out iterator
+  C_OUTLINE_IT right_it = right_outlines;
+  C_OUTLINE_IT new_it = &new_outlines;
+  C_OUTLINE_IT blob_it; // outlines in blob
+
+  if (!right_it.empty()) {
+    while (!right_it.empty()) {
+      old_right = right_it.extract();
+      right_it.forward();
+      fixed_split_coutline(old_right, chop_coord, pitch_error, &left_it, &new_it);
+    }
+    right_it.add_list_before(&new_outlines);
+  }
+  if (blob != nullptr) {
+    blob_it.set_to_list(blob->out_list());
+    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
+      fixed_split_coutline(blob_it.extract(), chop_coord, pitch_error, &left_it, &right_it);
+    }
+    delete blob;
+  }
+}
+
+/**********************************************************************
+ * fixed_split_outline
+ *
+ * Chop the given outline (if necessary) placing the fragments which
+ * fall either side of the chop line into the appropriate list.
+ **********************************************************************/
+
+static void fixed_split_coutline( // chop the outline
+    C_OUTLINE *srcline,           // source outline
+    int16_t chop_coord,           // place to chop
+    float pitch_error,            // allowed deviation
+    C_OUTLINE_IT *left_it,        // left half of chop
+    C_OUTLINE_IT *right_it        // right half of chop
+) {
+  C_OUTLINE *child;               // child outline
+  TBOX srcbox;                    // box of outline
+  C_OUTLINE_LIST left_ch;         // left children
+  C_OUTLINE_LIST right_ch;        // right children
+  C_OUTLINE_FRAG_LIST left_frags; // chopped fragments
+  C_OUTLINE_FRAG_LIST right_frags;
+  ;
+  C_OUTLINE_IT left_ch_it = &left_ch;
+  // for whole children
+  C_OUTLINE_IT right_ch_it = &right_ch;
+  // for holes
+  C_OUTLINE_IT child_it = srcline->child();
+
+  srcbox = srcline->bounding_box();
+  if (srcbox.left() + srcbox.right() <= chop_coord * 2 &&
+      srcbox.right() < chop_coord + pitch_error) {
+    // Whole outline is in the left side or not far over the chop_coord,
+    // so put the whole thing on the left.
+    left_it->add_after_then_move(srcline);
+  } else if (srcbox.left() + srcbox.right() > chop_coord * 2 &&
+             srcbox.left() > chop_coord - pitch_error) {
+    // Whole outline is in the right side or not far over the chop_coord,
+    // so put the whole thing on the right.
+    right_it->add_before_stay_put(srcline);
+  } else {
+    // Needs real chopping.
+    if (fixed_chop_coutline(srcline, chop_coord, pitch_error, &left_frags, &right_frags)) {
+      for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) {
+        child = child_it.extract();
+        srcbox = child->bounding_box();
+        if (srcbox.right() < chop_coord) {
+          // Whole child is on the left.
+          left_ch_it.add_after_then_move(child);
+        } else if (srcbox.left() > chop_coord) {
+          // Whole child is on the right.
+          right_ch_it.add_after_then_move(child);
+        } else {
+          // No pitch_error is allowed when chopping children to prevent
+          // impossible outlines from being created.
+          if (fixed_chop_coutline(child, chop_coord, 0.0f, &left_frags, &right_frags)) {
+            delete child;
+          } else {
+            if (srcbox.left() + srcbox.right() <= chop_coord * 2) {
+              left_ch_it.add_after_then_move(child);
+            } else {
+              right_ch_it.add_after_then_move(child);
+            }
+          }
+        }
+      }
+      close_chopped_cfragments(&left_frags, &left_ch, pitch_error, left_it);
+      close_chopped_cfragments(&right_frags, &right_ch, pitch_error, right_it);
+      ASSERT_HOST(left_ch.empty() && right_ch.empty());
+      // No children left.
+      delete srcline; // Smashed up.
+    } else {
+      // Chop failed. Just use middle coord.
+      if (srcbox.left() + srcbox.right() <= chop_coord * 2) {
+        left_it->add_after_then_move(srcline); // Stick whole in left.
+      } else {
+        right_it->add_before_stay_put(srcline);
+      }
+    }
+  }
+}
+
+/**********************************************************************
+ * fixed_chop_coutline
+ *
+ * Chop the given coutline (if necessary) placing the fragments which
+ * fall either side of the chop line into the appropriate list.
+ * If the coutline lies too heavily to one side to chop, false is returned.
+ **********************************************************************/
+
+static bool fixed_chop_coutline(     // chop the outline
+    C_OUTLINE *srcline,              // source outline
+    int16_t chop_coord,              // place to chop
+    float pitch_error,               // allowed deviation
+    C_OUTLINE_FRAG_LIST *left_frags, // left half of chop
+    C_OUTLINE_FRAG_LIST *right_frags // right half of chop
+) {
+  bool first_frag;         // fragment
+  int16_t left_edge;       // of outline
+  int16_t startindex;      // in first fragment
+  int32_t length;          // of outline
+  int16_t stepindex;       // into outline
+  int16_t head_index;      // start of fragment
+  ICOORD head_pos;         // start of fragment
+  int16_t tail_index;      // end of fragment
+  ICOORD tail_pos;         // end of fragment
+  ICOORD pos;              // current point
+  int16_t first_index = 0; // first tail
+  ICOORD first_pos;        // first tail
+
+  length = srcline->pathlength();
+  pos = srcline->start_pos();
+  left_edge = pos.x();
+  tail_index = 0;
+  tail_pos = pos;
+  for (stepindex = 0; stepindex < length; stepindex++) {
+    if (pos.x() < left_edge) {
+      left_edge = pos.x();
+      tail_index = stepindex;
+      tail_pos = pos;
+    }
+    pos += srcline->step(stepindex);
+  }
+  if (left_edge >= chop_coord - pitch_error) {
+    return false; // not worth it
+  }
+
+  startindex = tail_index;
+  first_frag = true;
+  head_index = tail_index;
+  head_pos = tail_pos;
+  do {
+    do {
+      tail_pos += srcline->step(tail_index);
+      tail_index++;
+      if (tail_index == length) {
+        tail_index = 0;
+      }
+    } while (tail_pos.x() != chop_coord && tail_index != startindex);
+    if (tail_index == startindex) {
+      if (first_frag) {
+        return false; // doesn't cross line
+      } else {
+        break;
+      }
+    }
+    ASSERT_HOST(head_index != tail_index);
+    if (!first_frag) {
+      save_chop_cfragment(head_index, head_pos, tail_index, tail_pos, srcline, left_frags);
+    } else {
+      first_index = tail_index;
+      first_pos = tail_pos;
+      first_frag = false;
+    }
+    while (srcline->step(tail_index).x() == 0) {
+      tail_pos += srcline->step(tail_index);
+      tail_index++;
+      if (tail_index == length) {
+        tail_index = 0;
+      }
+    }
+    head_index = tail_index;
+    head_pos = tail_pos;
+    while (srcline->step(tail_index).x() > 0) {
+      do {
+        tail_pos += srcline->step(tail_index);
+        tail_index++;
+        if (tail_index == length) {
+          tail_index = 0;
+        }
+      } while (tail_pos.x() != chop_coord);
+      ASSERT_HOST(head_index != tail_index);
+      save_chop_cfragment(head_index, head_pos, tail_index, tail_pos, srcline, right_frags);
+      while (srcline->step(tail_index).x() == 0) {
+        tail_pos += srcline->step(tail_index);
+        tail_index++;
+        if (tail_index == length) {
+          tail_index = 0;
+        }
+      }
+      head_index = tail_index;
+      head_pos = tail_pos;
+    }
+  } while (tail_index != startindex);
+  save_chop_cfragment(head_index, head_pos, first_index, first_pos, srcline, left_frags);
+  return true; // did some chopping
+}
+
+/**********************************************************************
+ * save_chop_cfragment
+ *
+ * Store the given fragment in the given fragment list.
+ **********************************************************************/
+
+static void save_chop_cfragment( // chop the outline
+    int16_t head_index,          // head of fragment
+    ICOORD head_pos,             // head of fragment
+    int16_t tail_index,          // tail of fragment
+    ICOORD tail_pos,             // tail of fragment
+    C_OUTLINE *srcline,          // source of edgesteps
+    C_OUTLINE_FRAG_LIST *frags   // fragment list
+) {
+  int16_t jump;         // gap across end
+  int16_t stepcount;    // total steps
+  C_OUTLINE_FRAG *head; // head of fragment
+  C_OUTLINE_FRAG *tail; // tail of fragment
+  int16_t tail_y;       // ycoord of tail
+
+  ASSERT_HOST(tail_pos.x() == head_pos.x());
+  ASSERT_HOST(tail_index != head_index);
+  stepcount = tail_index - head_index;
+  if (stepcount < 0) {
+    stepcount += srcline->pathlength();
+  }
+  jump = tail_pos.y() - head_pos.y();
+  if (jump < 0) {
+    jump = -jump;
+  }
+  if (jump == stepcount) {
+    return; // its a nop
+  }
+  tail_y = tail_pos.y();
+  head = new C_OUTLINE_FRAG(head_pos, tail_pos, srcline, head_index, tail_index);
+  tail = new C_OUTLINE_FRAG(head, tail_y);
+  head->other_end = tail;
+  add_frag_to_list(head, frags);
+  add_frag_to_list(tail, frags);
+}
+
+/**********************************************************************
+ * C_OUTLINE_FRAG::C_OUTLINE_FRAG
+ *
+ * Constructors for C_OUTLINE_FRAG.
+ **********************************************************************/
+
+C_OUTLINE_FRAG::C_OUTLINE_FRAG( // record fragment
+    ICOORD start_pt,            // start coord
+    ICOORD end_pt,              // end coord
+    C_OUTLINE *outline,         // source of steps
+    int16_t start_index, int16_t end_index) {
+  start = start_pt;
+  end = end_pt;
+  ycoord = start_pt.y();
+  stepcount = end_index - start_index;
+  if (stepcount < 0) {
+    stepcount += outline->pathlength();
+  }
+  ASSERT_HOST(stepcount > 0);
+  steps = new DIR128[stepcount];
+  if (end_index > start_index) {
+    for (int i = start_index; i < end_index; ++i) {
+      steps[i - start_index] = outline->step_dir(i);
+    }
+  } else {
+    int len = outline->pathlength();
+    int i = start_index;
+    for (; i < len; ++i) {
+      steps[i - start_index] = outline->step_dir(i);
+    }
+    if (end_index > 0) {
+      for (; i < end_index + len; ++i) {
+        steps[i - start_index] = outline->step_dir(i - len);
+      }
+    }
+  }
+  other_end = nullptr;
+  delete close();
+}
+
+C_OUTLINE_FRAG::C_OUTLINE_FRAG( // record fragment
+    C_OUTLINE_FRAG *head,       // other end
+    int16_t tail_y) {
+  ycoord = tail_y;
+  other_end = head;
+  start = head->start;
+  end = head->end;
+  steps = nullptr;
+  stepcount = 0;
+}
+
+/**********************************************************************
+ * add_frag_to_list
+ *
+ * Insert the fragment in the list at the appropriate place to keep
+ * them in ascending ycoord order.
+ **********************************************************************/
+
+static void add_frag_to_list(  // ordered add
+    C_OUTLINE_FRAG *frag,      // fragment to add
+    C_OUTLINE_FRAG_LIST *frags // fragment list
+) {
+  // output list
+  C_OUTLINE_FRAG_IT frag_it = frags;
+
+  if (!frags->empty()) {
+    for (frag_it.mark_cycle_pt(); !frag_it.cycled_list(); frag_it.forward()) {
+      if (frag_it.data()->ycoord > frag->ycoord ||
+          (frag_it.data()->ycoord == frag->ycoord && frag->other_end->ycoord < frag->ycoord)) {
+        frag_it.add_before_then_move(frag);
+        return;
+      }
+    }
+  }
+  frag_it.add_to_end(frag);
+}
+
+/**********************************************************************
+ * close_chopped_cfragments
+ *
+ * Clear the given list of fragments joining them up into outlines.
+ * Each outline made soaks up any of the child outlines which it encloses.
+ **********************************************************************/
+
+static void close_chopped_cfragments( // chop the outline
+    C_OUTLINE_FRAG_LIST *frags,       // list to clear
+    C_OUTLINE_LIST *children,         // potential children
+    float pitch_error,                // allowed shrinkage
+    C_OUTLINE_IT *dest_it             // output list
+) {
+  // iterator
+  C_OUTLINE_FRAG_IT frag_it = frags;
+  C_OUTLINE_FRAG *bottom_frag; // bottom of cut
+  C_OUTLINE_FRAG *top_frag;    // top of cut
+  C_OUTLINE *outline;          // new outline
+  C_OUTLINE *child;            // current child
+  C_OUTLINE_IT child_it = children;
+  C_OUTLINE_IT olchild_it; // children of outline
+
+  while (!frag_it.empty()) {
+    frag_it.move_to_first();
+    // get bottom one
+    bottom_frag = frag_it.extract();
+    frag_it.forward();
+    top_frag = frag_it.data(); // look at next
+    if ((bottom_frag->steps == nullptr && top_frag->steps == nullptr) ||
+        (bottom_frag->steps != nullptr && top_frag->steps != nullptr)) {
+      if (frag_it.data_relative(1)->ycoord == top_frag->ycoord) {
+        frag_it.forward();
+      }
+    }
+    top_frag = frag_it.extract();
+    if (top_frag->other_end != bottom_frag) {
+      outline = join_chopped_fragments(bottom_frag, top_frag);
+      ASSERT_HOST(outline == nullptr);
+    } else {
+      outline = join_chopped_fragments(bottom_frag, top_frag);
+      if (outline != nullptr) {
+        olchild_it.set_to_list(outline->child());
+        for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) {
+          child = child_it.data();
+          if (*child < *outline) {
+            olchild_it.add_to_end(child_it.extract());
+          }
+        }
+        if (outline->bounding_box().width() > pitch_error) {
+          dest_it->add_after_then_move(outline);
+        } else {
+          delete outline; // Make it disappear.
+        }
+      }
+    }
+  }
+  while (!child_it.empty()) {
+    dest_it->add_after_then_move(child_it.extract());
+    child_it.forward();
+  }
+}
+
+/**********************************************************************
+ * join_chopped_fragments
+ *
+ * Join the two lists of POLYPTs such that neither OUTLINE_FRAG
+ * operand keeps responsibility for the fragment.
+ **********************************************************************/
+
+static C_OUTLINE *join_chopped_fragments( // join pieces
+    C_OUTLINE_FRAG *bottom,               // bottom of cut
+    C_OUTLINE_FRAG *top                   // top of cut
+) {
+  C_OUTLINE *outline; // closed loop
+
+  if (bottom->other_end == top) {
+    if (bottom->steps == nullptr) {
+      outline = top->close(); // turn to outline
+    } else {
+      outline = bottom->close();
+    }
+    delete top;
+    delete bottom;
+    return outline;
+  }
+  if (bottom->steps == nullptr) {
+    ASSERT_HOST(top->steps != nullptr);
+    join_segments(bottom->other_end, top);
+  } else {
+    ASSERT_HOST(top->steps == nullptr);
+    join_segments(top->other_end, bottom);
+  }
+  top->other_end->other_end = bottom->other_end;
+  bottom->other_end->other_end = top->other_end;
+  delete bottom;
+  delete top;
+  return nullptr;
+}
+
+/**********************************************************************
+ * join_segments
+ *
+ * Join the two edgestep fragments such that the second comes after
+ * the first and the gap between them is closed.
+ **********************************************************************/
+
+static void join_segments(  // join pieces
+    C_OUTLINE_FRAG *bottom, // bottom of cut
+    C_OUTLINE_FRAG *top     // top of cut
+) {
+  DIR128 *steps;      // new steps
+  int32_t stepcount;  // no of steps
+  int16_t fake_count; // fake steps
+  DIR128 fake_step;   // step entry
+
+  ASSERT_HOST(bottom->end.x() == top->start.x());
+  fake_count = top->start.y() - bottom->end.y();
+  if (fake_count < 0) {
+    fake_count = -fake_count;
+    fake_step = 32;
+  } else {
+    fake_step = 96;
+  }
+
+  stepcount = bottom->stepcount + fake_count + top->stepcount;
+  steps = new DIR128[stepcount];
+  memmove(steps, bottom->steps, bottom->stepcount);
+  memset(steps + bottom->stepcount, fake_step.get_dir(), fake_count);
+  memmove(steps + bottom->stepcount + fake_count, top->steps, top->stepcount);
+  delete[] bottom->steps;
+  bottom->steps = steps;
+  bottom->stepcount = stepcount;
+  bottom->end = top->end;
+  bottom->other_end->end = top->end;
+}
+
+/**********************************************************************
+ * C_OUTLINE_FRAG::close
+ *
+ * Join the ends of this fragment and turn it into an outline.
+ **********************************************************************/
+
+C_OUTLINE *C_OUTLINE_FRAG::close() { // join pieces
+  DIR128 *new_steps;                 // new steps
+  int32_t new_stepcount;             // no of steps
+  int16_t fake_count;                // fake steps
+  DIR128 fake_step;                  // step entry
+
+  ASSERT_HOST(start.x() == end.x());
+  fake_count = start.y() - end.y();
+  if (fake_count < 0) {
+    fake_count = -fake_count;
+    fake_step = 32;
+  } else {
+    fake_step = 96;
+  }
+
+  new_stepcount = stepcount + fake_count;
+  if (new_stepcount > C_OUTLINE::kMaxOutlineLength) {
+    return nullptr; // Can't join them
+  }
+  new_steps = new DIR128[new_stepcount];
+  memmove(new_steps, steps, stepcount);
+  memset(new_steps + stepcount, fake_step.get_dir(), fake_count);
+  auto *result = new C_OUTLINE(start, new_steps, new_stepcount);
+  delete[] new_steps;
+  return result;
+}
+
+/**********************************************************************
+ * C_OUTLINE_FRAG::operator=
+ *
+ * Copy this fragment.
+ **********************************************************************/
+
+// join pieces
+C_OUTLINE_FRAG &C_OUTLINE_FRAG::operator=(const C_OUTLINE_FRAG &src // fragment to copy
+) {
+  delete[] steps;
+
+  stepcount = src.stepcount;
+  steps = new DIR128[stepcount];
+  memmove(steps, src.steps, stepcount);
+  start = src.start;
+  end = src.end;
+  ycoord = src.ycoord;
+  return *this;
+}
+
+} // namespace tesseract