C++程序  |  691行  |  25.87 KB

///////////////////////////////////////////////////////////////////////
// File:        colpartitionset.cpp
// Description: Class to hold a list of ColPartitions of the page that
//              correspond roughly to columns.
// Author:      Ray Smith
// Created:     Thu Aug 14 10:54:01 PDT 2008
//
// (C) Copyright 2008, Google Inc.
// 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 "colpartitionset.h"
#include "ndminx.h"
#include "workingpartset.h"
#include "tablefind.h"

namespace tesseract {

ELISTIZE(ColPartitionSet)

ColPartitionSet::ColPartitionSet(ColPartition_LIST* partitions) {
  ColPartition_IT it(&parts_);
  it.add_list_after(partitions);
  ComputeCoverage();
}

ColPartitionSet::ColPartitionSet(ColPartition* part) {
  ColPartition_IT it(&parts_);
  it.add_after_then_move(part);
  ComputeCoverage();
}

ColPartitionSet::~ColPartitionSet() {
}

// Return an element of the parts_ list from its index.
ColPartition* ColPartitionSet::GetColumnByIndex(int index) {
  ColPartition_IT it(&parts_);
  it.mark_cycle_pt();
  for (int i = 0; i < index && !it.cycled_list(); ++i, it.forward());
  if (it.cycled_list())
    return NULL;
  return it.data();
}

// Return the ColPartition that contains the given coords, if any, else NULL.
ColPartition* ColPartitionSet::ColumnContaining(int x, int y) {
  ColPartition_IT it(&parts_);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    if (part->ColumnContains(x, y))
      return part;
  }
  return NULL;
}

// Insert the ColPartitions in our list into the given grid.
void ColPartitionSet::ReturnParts(ColPartition_LIST* parts) {
  ColPartition_IT it(parts);
  it.add_list_before(&parts_);
}

// Merge any significantly overlapping partitions within the this and other,
// and unique the boxes so that no two partitions use the same box.
// Return true if any changes were made to either set.
bool ColPartitionSet::MergeOverlaps(ColPartitionSet* other, WidthCallback* cb) {
  bool debug = TabFind::WithinTestRegion(2, bounding_box_.left(),
                                         bounding_box_.bottom()) ||
               TabFind::WithinTestRegion(2, other->bounding_box_.left(),
                                         other->bounding_box_.bottom());
  if (debug) {
    tprintf("Considering merge on:\n");
    Print();
    other->Print();
  }
  ColPartition_IT it1(&parts_);
  ColPartition_IT it2(&other->parts_);
  bool any_merged = false;
  it1.mark_cycle_pt();
  it2.mark_cycle_pt();
  // Iterate the two lists in parallel, using the fact that they are
  // sorted by x-coord to keep the iterators in sync.
  while (!it1.cycled_list() && !it2.cycled_list()) {
    any_merged = false;
    ColPartition* part1 = it1.data();
    ColPartition* part2 = it2.data();
    if (debug) {
      tprintf("Vover=%d, HOver=%d, Hcompatible=%d, typesmatch=%d\n",
              part1->VOverlaps(*part2), part1->HOverlaps(*part2),
              part1->HCompatible(*part2), part1->TypesMatch(*part2));
    }
    if (part1->VOverlaps(*part2) &&
        part1->HCompatible(*part2) && part1->TypesMatch(*part2)) {
      // Partitions seem to be mergeable, so absorb part1 into part2.
      part1->Absorb(it2.extract(), cb);
      any_merged = true;
      it1.forward();
      it2.forward();
    } else if (part1->HOverlaps(*part2) && part1->TypesMatch(*part2) &&
               part1->Unique(part2, cb)) {
      // Unique moved some boxes, so check to see in either partition was
      // left empty. If not, any_merged is not set true.
      if (part1->IsEmpty()) {
        any_merged = true;
        delete it1.extract();
        it1.forward();
        continue;
      }
      if (part2->IsEmpty()) {
        any_merged = true;
        delete it2.extract();
        it2.forward();
        continue;
      }
    }
    if (!any_merged) {
      // Move on the iterator that point to the leftmost partition.
      if (part1->IsLeftOf(*part2)) {
        it1.forward();
      } else {
        it2.forward();
      }
    }
  }
  if (any_merged) {
    ComputeCoverage();
    other->ComputeCoverage();
  }
  return any_merged;
}

// Attempt to improve this by adding partitions or expanding partitions.
void ColPartitionSet::ImproveColumnCandidate(WidthCallback* cb,
                                             PartSetVector* src_sets) {
  int set_size = src_sets->size();
  // Iterate over the provided column sets, as each one may have something
  // to improve this.
  for (int i = 0; i < set_size; ++i) {
    ColPartitionSet* column_set = src_sets->get(i);
    if (column_set == NULL)
      continue;
    // Iterate over the parts in this and column_set, adding bigger or
    // new parts in column_set to this.
    ColPartition_IT part_it(&parts_);
    ASSERT_HOST(!part_it.empty());
    int prev_right = MIN_INT32;
    part_it.mark_cycle_pt();
    ColPartition_IT col_it(&column_set->parts_);
    for (col_it.mark_cycle_pt(); !col_it.cycled_list(); col_it.forward()) {
      ColPartition* col_part = col_it.data();
      if (col_part->blob_type() < BRT_UNKNOWN)
        continue;  // Ignore image partitions.
      int col_left = col_part->left_key();
      int col_right = col_part->right_key();
      // Sync-up part_it (in this) so it matches the col_part in column_set.
      ColPartition* part = part_it.data();
      while (!part_it.at_last() && part->right_key() < col_left) {
        prev_right = part->right_key();
        part_it.forward();
        part = part_it.data();
      }
      int part_left = part->left_key();
      int part_right = part->right_key();
      if (part_right < col_left || col_right < part_left) {
        // There is no overlap so this is a new partition.
        AddPartition(col_part->ShallowCopy(), &part_it);
        continue;
      }
      // Check the edges of col_part to see if they can improve part.
      bool part_width_ok = cb->Run(part->KeyWidth(part_left, part_right));
      if (col_left < part_left && col_left > prev_right) {
        // The left edge of the column is better and it doesn't overlap,
        // so we can potentially expand it.
        int col_box_left = col_part->BoxLeftKey();
        bool tab_width_ok = cb->Run(part->KeyWidth(col_left, part_right));
        bool box_width_ok = cb->Run(part->KeyWidth(col_box_left, part_right));
        if (tab_width_ok || (!part_width_ok )) {
          // The tab is leaving the good column metric at least as good as
          // it was before, so use the tab.
          part->CopyLeftTab(*col_part, false);
          part->SetColumnGoodness(cb);
        } else if (col_box_left < part_left &&
                   (box_width_ok || !part_width_ok)) {
          // The box is leaving the good column metric at least as good as
          // it was before, so use the box.
          part->CopyLeftTab(*col_part, true);
          part->SetColumnGoodness(cb);
        }
        part_left = part->left_key();
      }
      if (col_right > part_right &&
          (part_it.at_last() ||
           part_it.data_relative(1)->left_key() > col_right)) {
        // The right edge is better, so we can possibly expand it.
        int col_box_right = col_part->BoxRightKey();
        bool tab_width_ok = cb->Run(part->KeyWidth(part_left, col_right));
        bool box_width_ok = cb->Run(part->KeyWidth(part_left, col_box_right));
        if (tab_width_ok || (!part_width_ok )) {
          // The tab is leaving the good column metric at least as good as
          // it was before, so use the tab.
          part->CopyRightTab(*col_part, false);
          part->SetColumnGoodness(cb);
        } else if (col_box_right > part_right &&
                   (box_width_ok || !part_width_ok)) {
          // The box is leaving the good column metric at least as good as
          // it was before, so use the box.
          part->CopyRightTab(*col_part, true);
          part->SetColumnGoodness(cb);
        }
      }
    }
  }
  ComputeCoverage();
}

// If this set is good enough to represent a new partitioning into columns,
// add it to the vector of sets, otherwise delete it.
void ColPartitionSet::AddToColumnSetsIfUnique(PartSetVector* column_sets,
                                              WidthCallback* cb) {
  bool debug = TabFind::WithinTestRegion(2, bounding_box_.left(),
                                         bounding_box_.bottom());
  if (debug) {
    tprintf("Considering new column candidate:\n");
    Print();
  }
  if (!LegalColumnCandidate()) {
    if (debug) {
      tprintf("Not a legal column candidate:\n");
      Print();
    }
    delete this;
    return;
  }
  for (int i = 0; i < column_sets->size(); ++i) {
    ColPartitionSet* columns = column_sets->get(i);
    // In ordering the column set candidates, total_coverage_ is king,
    // followed by good_column_count_ and then total column_count.
    bool better = total_coverage_ > columns->total_coverage_;
    if (total_coverage_ == columns->total_coverage_) {
      better = good_column_count_ > columns->good_column_count_;
      if (good_column_count_ == columns->good_column_count_) {
          better = parts_.length() > columns->parts_.length();
      }
    }
    if (better) {
      // The new one is better so add it.
      if (debug)
        tprintf("Good one\n");
      column_sets->insert(this, i);
      return;
    }
    if (columns->CompatibleColumns(false, this, cb)) {
      if (debug)
        tprintf("Duplicate\n");
      delete this;
      return;  // It is not unique.
    }
  }
  if (debug)
    tprintf("Added to end\n");
  column_sets->push_back(this);
}

// Return true if the partitions in other are all compatible with the columns
// in this.
bool ColPartitionSet::CompatibleColumns(bool debug, ColPartitionSet* other,
                                        WidthCallback* cb) {
  if (debug) {
    tprintf("CompatibleColumns testing compability\n");
    Print();
    other->Print();
  }
  if (other->parts_.empty()) {
    if (debug)
      tprintf("CompatibleColumns true due to empty other\n");
    return true;
  }
  ColPartition_IT it(&other->parts_);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    if (part->blob_type() < BRT_UNKNOWN) {
      if (debug) {
        tprintf("CompatibleColumns ignoring image partition\n");
        part->Print();
      }
      continue;  // Image partitions are irrelevant to column compability.
    }
    int y = part->MidY();
    int left = part->bounding_box().left();
    int right = part->bounding_box().right();
    ColPartition* left_col = ColumnContaining(left, y);
    ColPartition* right_col = ColumnContaining(right, y);
    if (right_col == NULL || left_col == NULL) {
      if (debug) {
        tprintf("CompatibleColumns false due to partition edge outside\n");
        part->Print();
      }
      return false;  // A partition edge lies outside of all columns
    }
    if (right_col != left_col && cb->Run(right - left)) {
      if (debug) {
        tprintf("CompatibleColumns false due to good width in multiple cols\n");
        part->Print();
      }
      return false;  // Partition with a good width must be in a single column.
    }

    ColPartition_IT it2= it;
    while (!it2.at_last()) {
      it2.forward();
      ColPartition* next_part = it2.data();
      if (next_part->blob_type() <= BRT_UNKNOWN)
        continue;  // Image partitions are irrelevant.
      int next_left = next_part->bounding_box().left();
      if (next_left == right) {
        break;  // They share the same edge, so one must be a pull-out.
      }
      // Search to see if right and next_left fall within a single column.
      ColPartition* next_left_col = ColumnContaining(next_left, y);
      if (right_col == next_left_col) {
        // There is a column break in this column.
        // Check for the difference between different column layout and
        // a pull-out block.
        int part_box_width = part->bounding_box().width();
        int part_margin_width = part->right_margin() - part->left_margin();
        int next_box_width = next_part->bounding_box().width();
        int next_margin_width = next_part->right_margin() -
                                next_part->left_margin();
        int next_right = next_part->bounding_box().right();
        if (part_box_width < next_margin_width &&
            next_box_width < part_margin_width) {
          if (debug) {
            tprintf("CompatibleColumns false due to equal sized columns\n");
            tprintf("part1 %d-%d = %d, part2 %d-%d = %d\n",
                    left, right, part->ColumnWidth(),
                    next_left, next_right, next_part->ColumnWidth());
            right_col->Print();
          }
          return false;  // Must be a new column layout as they are equal size.
        }
        ColPartition* next_right_col = ColumnContaining(next_right, y);
        if (left_col == right_col && next_right_col == next_left_col) {
          // Column completely contains both. Not allowed.
          if (debug) {
            tprintf("CompatibleColumns false due to containing 2 partitions\n");
            tprintf("part1 %d-%d, part2 %d-%d\n",
                    left, right, next_left, next_right);
            right_col->Print();
          }
          return false;
        }
      }
      break;
    }
  }
  if (debug)
    tprintf("CompatibleColumns true!\n");
  return true;
}

// Return true if this ColPartitionSet makes a legal column candidate by
// having legal individual partitions and non-overlapping adjacent pairs.
bool ColPartitionSet::LegalColumnCandidate() {
  ColPartition_IT it(&parts_);
  if (it.empty())
    return false;
  int any_text_parts = false;
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    if (part->blob_type() > BRT_UNKNOWN) {
      if (!part->IsLegal())
        return false;  // Individual partition is illegal.
      any_text_parts = true;
    }
    if (!it.at_last()) {
      ColPartition* next_part = it.data_relative(1);
      if (next_part->left_key() < part->right_key()) {
        return false;
      }
    }
  }
  return any_text_parts;
}

// Return a copy of this. If good_only will only copy the Good ColPartitions.
ColPartitionSet* ColPartitionSet::Copy(bool good_only) {
  ColPartition_LIST copy_parts;
  ColPartition_IT src_it(&parts_);
  ColPartition_IT dest_it(&copy_parts);
  for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
    ColPartition* part = src_it.data();
    if (part->blob_type() > BRT_UNKNOWN &&
        (!good_only || part->good_width() || part->good_column()))
      dest_it.add_after_then_move(part->ShallowCopy());
  }
  if (dest_it.empty())
    return NULL;
  return new ColPartitionSet(&copy_parts);
}

// Return the bounding boxes of columns at the given y-range
void ColPartitionSet::GetColumnBoxes(int y_bottom, int y_top,
                                     ColSegment_LIST *segments) {
  ColPartition_IT it(&parts_);
  ColSegment_IT col_it(segments);
  col_it.move_to_last();
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    ICOORD bot_left(part->LeftAtY(y_top), y_bottom);
    ICOORD top_right(part->RightAtY(y_bottom), y_top);
    ColSegment *col_seg = new ColSegment();
    col_seg->InsertBox(TBOX(bot_left, top_right));
    col_it.add_after_then_move(col_seg);
  }
}

// Display the edges of the columns at the given y coords.
void ColPartitionSet::DisplayColumnEdges(int y_bottom, int y_top,
                                         ScrollView* win) {
  ColPartition_IT it(&parts_);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
#ifndef GRAPHICS_DISABLED
    win->Line(part->LeftAtY(y_top), y_top, part->LeftAtY(y_bottom), y_bottom);
    win->Line(part->RightAtY(y_top), y_top, part->RightAtY(y_bottom), y_bottom);
#endif
  }
}

// Return the PolyBlockType that best explains the columns overlapped
// by the given coords(left,right,y), with the given margins.
// Also return the first and last column index touched by the coords and
// the leftmost and rightmost spanned columns.
// Column indices are 2n + 1 for real colums (0 based) and even values
// represent the gaps in between columns, with 0 being left of the leftmost.
PolyBlockType ColPartitionSet::SpanningType(BlobRegionType type,
                                            int left, int right, int y,
                                            int left_margin, int right_margin,
                                            int* first_col, int* last_col,
                                            int* first_spanned_col,
                                            int* last_spanned_col) {
  *first_col = -1;
  *last_col = -1;
  *first_spanned_col = -1;
  *last_spanned_col = -1;
  int columns_spanned = 0;
  ColPartition_IT it(&parts_);
  int col_index = 1;
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward(), col_index += 2) {
    ColPartition* part = it.data();
    if (part->ColumnContains(left, y)) {
      // In the default case, first_col is set, but columns_spanned remains
      // zero, so first_col will get reset in the first column genuinely
      // spanned, but we can tell the difference from a noise partition
      // that touches no column.
      *first_col = col_index;
      if (part->ColumnContains(right, y)) {
        // Both within a single column.
        *last_col = col_index;
        if (type == BRT_HLINE)
          return PT_FLOWING_LINE;
        else if (type > BRT_UNKNOWN)
          return type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT : PT_FLOWING_TEXT;
        else
          return PT_FLOWING_IMAGE;
      }
      if (left_margin <= part->LeftAtY(y)) {
        // It completely spans this column.
        *last_col = col_index;
        *first_spanned_col = col_index;
        *last_spanned_col = col_index;
        columns_spanned = 1;
      }
    } else if (part->ColumnContains(right, y)) {
      if (*first_col < 0) {
        // It started in-between.
        *first_col = col_index - 1;
      }
      if (right_margin >= part->RightAtY(y)) {
        // It completely spans this column.
        if (columns_spanned == 0)
          *first_spanned_col = col_index;
        *last_spanned_col = col_index;
        ++columns_spanned;
      }
      *last_col = col_index;
      break;
    } else if (left < part->LeftAtY(y) && right > part->RightAtY(y)) {
      // Neither left nor right are contained within, so it spans this
      // column.
      if (columns_spanned == 0) {
        *first_col = col_index;
        *first_spanned_col = col_index;
      }
      *last_col = col_index;
      *last_spanned_col = col_index;
      ++columns_spanned;
    } else if (right < part->LeftAtY(y)) {
      // We have gone past the end.
      *last_col = col_index - 1;
      if (*first_col < 0) {
        // It must lie completely between columns =>noise.
        *first_col = col_index - 1;
      }
      break;
    }
  }
  if (*first_col < 0)
    *first_col = col_index - 1;  // The last in-between.
  if (*last_col < 0)
    *last_col = col_index - 1;  // The last in-between.
  ASSERT_HOST(*first_col >= 0 && *last_col >= 0);
  ASSERT_HOST(*first_col <= *last_col);
  if (columns_spanned == 0 && *first_col == *last_col) {
    // Neither end was in a column, and it didn't span any, so it lies
    // entirely between columns, therefore noise.
    return PT_NOISE;
  } else if (columns_spanned <= 1) {
    // It is a pullout, as left and right were not in the same column.
    if (type == BRT_HLINE)
      return PT_PULLOUT_LINE;
    else if (type > BRT_UNKNOWN)
      return type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT : PT_PULLOUT_TEXT;
    else
      return PT_PULLOUT_IMAGE;
  }
  // It completely spanned more than one column. Always a heading.
  if (type == BRT_HLINE)
    return PT_HEADING_LINE;
  else if (type > BRT_UNKNOWN)
    return type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT : PT_HEADING_TEXT;
  else
    return PT_HEADING_IMAGE;
}

// The column_set has changed. Close down all in-progress WorkingPartSets in
// columns that do not match and start new ones for the new columns in this.
// As ColPartitions are turned into BLOCKs, the used ones are put in
// used_parts, as they still need to be referenced in the grid.
void ColPartitionSet::ChangeWorkColumns(const ICOORD& bleft,
                                        const ICOORD& tright,
                                        int resolution,
                                        ColPartition_LIST* used_parts,
                                        WorkingPartSet_LIST* working_set_list) {
  // Move the input list to a temporary location so we can delete its elements
  // as we add them to the output working_set.
  WorkingPartSet_LIST work_src;
  WorkingPartSet_IT src_it(&work_src);
  src_it.add_list_after(working_set_list);
  src_it.move_to_first();
  WorkingPartSet_IT dest_it(working_set_list);
  // Completed blocks and to_blocks are accumulated and given to the first new
  // one  whenever we keep a column, or at the end.
  BLOCK_LIST completed_blocks;
  TO_BLOCK_LIST to_blocks;
  WorkingPartSet* first_new_set = NULL;
  WorkingPartSet* working_set = NULL;
  ColPartition_IT col_it(&parts_);
  for (col_it.mark_cycle_pt(); !col_it.cycled_list(); col_it.forward()) {
    ColPartition* column = col_it.data();
    // Any existing column to the left of column is completed.
    while (!src_it.empty() &&
           ((working_set = src_it.data())->column() == NULL ||
            working_set->column()->right_key() <= column->left_key())) {
      src_it.extract();
      working_set->ExtractCompletedBlocks(bleft, tright, resolution,
                                          used_parts, &completed_blocks,
                                          &to_blocks);
      delete working_set;
      src_it.forward();
    }
    // Make a new between-column WorkingSet for before the current column.
    working_set = new WorkingPartSet(NULL);
    dest_it.add_after_then_move(working_set);
    if (first_new_set == NULL)
      first_new_set = working_set;
    // A matching column gets to stay, and first_new_set gets all the
    // completed_sets.
    working_set = src_it.empty() ? NULL : src_it.data();
    if (working_set != NULL &&
        working_set->column()->MatchingColumns(*column)) {
      working_set->set_column(column);
      dest_it.add_after_then_move(src_it.extract());
      src_it.forward();
      first_new_set->InsertCompletedBlocks(&completed_blocks, &to_blocks);
      first_new_set = NULL;
    } else {
      // Just make a new working set for the current column.
      working_set = new WorkingPartSet(column);
      dest_it.add_after_then_move(working_set);
    }
  }
  // Complete any remaining src working sets.
  while (!src_it.empty()) {
    working_set = src_it.extract();
    working_set->ExtractCompletedBlocks(bleft, tright, resolution,
                                        used_parts, &completed_blocks,
                                        &to_blocks);
    delete working_set;
    src_it.forward();
  }
  // Make a new between-column WorkingSet for after the last column.
  working_set = new WorkingPartSet(NULL);
  dest_it.add_after_then_move(working_set);
  if (first_new_set == NULL)
    first_new_set = working_set;
  // The first_new_set now gets any accumulated completed_parts/blocks.
  first_new_set->InsertCompletedBlocks(&completed_blocks, &to_blocks);
}

// Accumulate the widths and gaps into the given variables.
void ColPartitionSet::AccumulateColumnWidthsAndGaps(int* total_width,
                                                    int* width_samples,
                                                    int* total_gap,
                                                    int* gap_samples) {
  ColPartition_IT it(&parts_);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    *total_width += part->ColumnWidth();
    ++*width_samples;
    if (!it.at_last()) {
      ColPartition* next_part = it.data_relative(1);
      int gap = part->KeyWidth(part->right_key(), next_part->left_key());
      *total_gap += gap;
      ++*gap_samples;
    }
  }
}

// Provide debug output for this ColPartitionSet and all the ColPartitions.
void ColPartitionSet::Print() {
  ColPartition_IT it(&parts_);
  tprintf("Partition set of %d parts, %d good, coverage=%d (%d,%d)->(%d,%d)\n",
          it.length(), good_column_count_, total_coverage_,
          bounding_box_.left(), bounding_box_.bottom(),
          bounding_box_.right(), bounding_box_.top());
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    part->Print();
  }
}

// PRIVATE CODE.

// Add the given partition to the list in the appropriate place.
void ColPartitionSet::AddPartition(ColPartition* new_part,
                                   ColPartition_IT* it) {
  bounding_box_ += new_part->bounding_box();
  if (new_part->good_column() || new_part->good_width()) {
    total_coverage_ += new_part->ColumnWidth();
    ++good_column_count_;
    if (new_part->good_width())
      ++good_column_count_;
  }
  int new_right = new_part->right_key();
  if (it->data()->left_key() >= new_right)
    it->add_before_stay_put(new_part);
  else
    it->add_after_stay_put(new_part);
}

// Compute the coverage and good column count.
void ColPartitionSet::ComputeCoverage() {
  // Count the number of good columns and sum their width.
  ColPartition_IT it(&parts_);
  good_column_count_ = 0;
  total_coverage_ = 0;
  bounding_box_ = TBOX();
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    ColPartition* part = it.data();
    bounding_box_ += part->bounding_box();
    if (part->good_column() || part->good_width()) {
      total_coverage_ += part->ColumnWidth();
      ++good_column_count_;
      if (part->good_width())
        ++good_column_count_;
    }
  }
}

}  // namespace tesseract.