• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 ///////////////////////////////////////////////////////////////////////
2 // File:        colpartitionset.cpp
3 // Description: Class to hold a list of ColPartitions of the page that
4 //              correspond roughly to columns.
5 // Author:      Ray Smith
6 // Created:     Thu Aug 14 10:54:01 PDT 2008
7 //
8 // (C) Copyright 2008, Google Inc.
9 // Licensed under the Apache License, Version 2.0 (the "License");
10 // you may not use this file except in compliance with the License.
11 // You may obtain a copy of the License at
12 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 //
19 ///////////////////////////////////////////////////////////////////////
20 
21 #include "colpartitionset.h"
22 #include "ndminx.h"
23 #include "workingpartset.h"
24 #include "tablefind.h"
25 
26 namespace tesseract {
27 
ELISTIZE(ColPartitionSet)28 ELISTIZE(ColPartitionSet)
29 
30 ColPartitionSet::ColPartitionSet(ColPartition_LIST* partitions) {
31   ColPartition_IT it(&parts_);
32   it.add_list_after(partitions);
33   ComputeCoverage();
34 }
35 
ColPartitionSet(ColPartition * part)36 ColPartitionSet::ColPartitionSet(ColPartition* part) {
37   ColPartition_IT it(&parts_);
38   it.add_after_then_move(part);
39   ComputeCoverage();
40 }
41 
~ColPartitionSet()42 ColPartitionSet::~ColPartitionSet() {
43 }
44 
45 // Return an element of the parts_ list from its index.
GetColumnByIndex(int index)46 ColPartition* ColPartitionSet::GetColumnByIndex(int index) {
47   ColPartition_IT it(&parts_);
48   it.mark_cycle_pt();
49   for (int i = 0; i < index && !it.cycled_list(); ++i, it.forward());
50   if (it.cycled_list())
51     return NULL;
52   return it.data();
53 }
54 
55 // Return the ColPartition that contains the given coords, if any, else NULL.
ColumnContaining(int x,int y)56 ColPartition* ColPartitionSet::ColumnContaining(int x, int y) {
57   ColPartition_IT it(&parts_);
58   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
59     ColPartition* part = it.data();
60     if (part->ColumnContains(x, y))
61       return part;
62   }
63   return NULL;
64 }
65 
66 // Insert the ColPartitions in our list into the given grid.
ReturnParts(ColPartition_LIST * parts)67 void ColPartitionSet::ReturnParts(ColPartition_LIST* parts) {
68   ColPartition_IT it(parts);
69   it.add_list_before(&parts_);
70 }
71 
72 // Merge any significantly overlapping partitions within the this and other,
73 // and unique the boxes so that no two partitions use the same box.
74 // Return true if any changes were made to either set.
MergeOverlaps(ColPartitionSet * other,WidthCallback * cb)75 bool ColPartitionSet::MergeOverlaps(ColPartitionSet* other, WidthCallback* cb) {
76   bool debug = TabFind::WithinTestRegion(2, bounding_box_.left(),
77                                          bounding_box_.bottom()) ||
78                TabFind::WithinTestRegion(2, other->bounding_box_.left(),
79                                          other->bounding_box_.bottom());
80   if (debug) {
81     tprintf("Considering merge on:\n");
82     Print();
83     other->Print();
84   }
85   ColPartition_IT it1(&parts_);
86   ColPartition_IT it2(&other->parts_);
87   bool any_merged = false;
88   it1.mark_cycle_pt();
89   it2.mark_cycle_pt();
90   // Iterate the two lists in parallel, using the fact that they are
91   // sorted by x-coord to keep the iterators in sync.
92   while (!it1.cycled_list() && !it2.cycled_list()) {
93     any_merged = false;
94     ColPartition* part1 = it1.data();
95     ColPartition* part2 = it2.data();
96     if (debug) {
97       tprintf("Vover=%d, HOver=%d, Hcompatible=%d, typesmatch=%d\n",
98               part1->VOverlaps(*part2), part1->HOverlaps(*part2),
99               part1->HCompatible(*part2), part1->TypesMatch(*part2));
100     }
101     if (part1->VOverlaps(*part2) &&
102         part1->HCompatible(*part2) && part1->TypesMatch(*part2)) {
103       // Partitions seem to be mergeable, so absorb part1 into part2.
104       part1->Absorb(it2.extract(), cb);
105       any_merged = true;
106       it1.forward();
107       it2.forward();
108     } else if (part1->HOverlaps(*part2) && part1->TypesMatch(*part2) &&
109                part1->Unique(part2, cb)) {
110       // Unique moved some boxes, so check to see in either partition was
111       // left empty. If not, any_merged is not set true.
112       if (part1->IsEmpty()) {
113         any_merged = true;
114         delete it1.extract();
115         it1.forward();
116         continue;
117       }
118       if (part2->IsEmpty()) {
119         any_merged = true;
120         delete it2.extract();
121         it2.forward();
122         continue;
123       }
124     }
125     if (!any_merged) {
126       // Move on the iterator that point to the leftmost partition.
127       if (part1->IsLeftOf(*part2)) {
128         it1.forward();
129       } else {
130         it2.forward();
131       }
132     }
133   }
134   if (any_merged) {
135     ComputeCoverage();
136     other->ComputeCoverage();
137   }
138   return any_merged;
139 }
140 
141 // Attempt to improve this by adding partitions or expanding partitions.
ImproveColumnCandidate(WidthCallback * cb,PartSetVector * src_sets)142 void ColPartitionSet::ImproveColumnCandidate(WidthCallback* cb,
143                                              PartSetVector* src_sets) {
144   int set_size = src_sets->size();
145   // Iterate over the provided column sets, as each one may have something
146   // to improve this.
147   for (int i = 0; i < set_size; ++i) {
148     ColPartitionSet* column_set = src_sets->get(i);
149     if (column_set == NULL)
150       continue;
151     // Iterate over the parts in this and column_set, adding bigger or
152     // new parts in column_set to this.
153     ColPartition_IT part_it(&parts_);
154     ASSERT_HOST(!part_it.empty());
155     int prev_right = MIN_INT32;
156     part_it.mark_cycle_pt();
157     ColPartition_IT col_it(&column_set->parts_);
158     for (col_it.mark_cycle_pt(); !col_it.cycled_list(); col_it.forward()) {
159       ColPartition* col_part = col_it.data();
160       if (col_part->blob_type() < BRT_UNKNOWN)
161         continue;  // Ignore image partitions.
162       int col_left = col_part->left_key();
163       int col_right = col_part->right_key();
164       // Sync-up part_it (in this) so it matches the col_part in column_set.
165       ColPartition* part = part_it.data();
166       while (!part_it.at_last() && part->right_key() < col_left) {
167         prev_right = part->right_key();
168         part_it.forward();
169         part = part_it.data();
170       }
171       int part_left = part->left_key();
172       int part_right = part->right_key();
173       if (part_right < col_left || col_right < part_left) {
174         // There is no overlap so this is a new partition.
175         AddPartition(col_part->ShallowCopy(), &part_it);
176         continue;
177       }
178       // Check the edges of col_part to see if they can improve part.
179       bool part_width_ok = cb->Run(part->KeyWidth(part_left, part_right));
180       if (col_left < part_left && col_left > prev_right) {
181         // The left edge of the column is better and it doesn't overlap,
182         // so we can potentially expand it.
183         int col_box_left = col_part->BoxLeftKey();
184         bool tab_width_ok = cb->Run(part->KeyWidth(col_left, part_right));
185         bool box_width_ok = cb->Run(part->KeyWidth(col_box_left, part_right));
186         if (tab_width_ok || (!part_width_ok )) {
187           // The tab is leaving the good column metric at least as good as
188           // it was before, so use the tab.
189           part->CopyLeftTab(*col_part, false);
190           part->SetColumnGoodness(cb);
191         } else if (col_box_left < part_left &&
192                    (box_width_ok || !part_width_ok)) {
193           // The box is leaving the good column metric at least as good as
194           // it was before, so use the box.
195           part->CopyLeftTab(*col_part, true);
196           part->SetColumnGoodness(cb);
197         }
198         part_left = part->left_key();
199       }
200       if (col_right > part_right &&
201           (part_it.at_last() ||
202            part_it.data_relative(1)->left_key() > col_right)) {
203         // The right edge is better, so we can possibly expand it.
204         int col_box_right = col_part->BoxRightKey();
205         bool tab_width_ok = cb->Run(part->KeyWidth(part_left, col_right));
206         bool box_width_ok = cb->Run(part->KeyWidth(part_left, col_box_right));
207         if (tab_width_ok || (!part_width_ok )) {
208           // The tab is leaving the good column metric at least as good as
209           // it was before, so use the tab.
210           part->CopyRightTab(*col_part, false);
211           part->SetColumnGoodness(cb);
212         } else if (col_box_right > part_right &&
213                    (box_width_ok || !part_width_ok)) {
214           // The box is leaving the good column metric at least as good as
215           // it was before, so use the box.
216           part->CopyRightTab(*col_part, true);
217           part->SetColumnGoodness(cb);
218         }
219       }
220     }
221   }
222   ComputeCoverage();
223 }
224 
225 // If this set is good enough to represent a new partitioning into columns,
226 // add it to the vector of sets, otherwise delete it.
AddToColumnSetsIfUnique(PartSetVector * column_sets,WidthCallback * cb)227 void ColPartitionSet::AddToColumnSetsIfUnique(PartSetVector* column_sets,
228                                               WidthCallback* cb) {
229   bool debug = TabFind::WithinTestRegion(2, bounding_box_.left(),
230                                          bounding_box_.bottom());
231   if (debug) {
232     tprintf("Considering new column candidate:\n");
233     Print();
234   }
235   if (!LegalColumnCandidate()) {
236     if (debug) {
237       tprintf("Not a legal column candidate:\n");
238       Print();
239     }
240     delete this;
241     return;
242   }
243   for (int i = 0; i < column_sets->size(); ++i) {
244     ColPartitionSet* columns = column_sets->get(i);
245     // In ordering the column set candidates, total_coverage_ is king,
246     // followed by good_column_count_ and then total column_count.
247     bool better = total_coverage_ > columns->total_coverage_;
248     if (total_coverage_ == columns->total_coverage_) {
249       better = good_column_count_ > columns->good_column_count_;
250       if (good_column_count_ == columns->good_column_count_) {
251           better = parts_.length() > columns->parts_.length();
252       }
253     }
254     if (better) {
255       // The new one is better so add it.
256       if (debug)
257         tprintf("Good one\n");
258       column_sets->insert(this, i);
259       return;
260     }
261     if (columns->CompatibleColumns(false, this, cb)) {
262       if (debug)
263         tprintf("Duplicate\n");
264       delete this;
265       return;  // It is not unique.
266     }
267   }
268   if (debug)
269     tprintf("Added to end\n");
270   column_sets->push_back(this);
271 }
272 
273 // Return true if the partitions in other are all compatible with the columns
274 // in this.
CompatibleColumns(bool debug,ColPartitionSet * other,WidthCallback * cb)275 bool ColPartitionSet::CompatibleColumns(bool debug, ColPartitionSet* other,
276                                         WidthCallback* cb) {
277   if (debug) {
278     tprintf("CompatibleColumns testing compability\n");
279     Print();
280     other->Print();
281   }
282   if (other->parts_.empty()) {
283     if (debug)
284       tprintf("CompatibleColumns true due to empty other\n");
285     return true;
286   }
287   ColPartition_IT it(&other->parts_);
288   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
289     ColPartition* part = it.data();
290     if (part->blob_type() < BRT_UNKNOWN) {
291       if (debug) {
292         tprintf("CompatibleColumns ignoring image partition\n");
293         part->Print();
294       }
295       continue;  // Image partitions are irrelevant to column compability.
296     }
297     int y = part->MidY();
298     int left = part->bounding_box().left();
299     int right = part->bounding_box().right();
300     ColPartition* left_col = ColumnContaining(left, y);
301     ColPartition* right_col = ColumnContaining(right, y);
302     if (right_col == NULL || left_col == NULL) {
303       if (debug) {
304         tprintf("CompatibleColumns false due to partition edge outside\n");
305         part->Print();
306       }
307       return false;  // A partition edge lies outside of all columns
308     }
309     if (right_col != left_col && cb->Run(right - left)) {
310       if (debug) {
311         tprintf("CompatibleColumns false due to good width in multiple cols\n");
312         part->Print();
313       }
314       return false;  // Partition with a good width must be in a single column.
315     }
316 
317     ColPartition_IT it2= it;
318     while (!it2.at_last()) {
319       it2.forward();
320       ColPartition* next_part = it2.data();
321       if (next_part->blob_type() <= BRT_UNKNOWN)
322         continue;  // Image partitions are irrelevant.
323       int next_left = next_part->bounding_box().left();
324       if (next_left == right) {
325         break;  // They share the same edge, so one must be a pull-out.
326       }
327       // Search to see if right and next_left fall within a single column.
328       ColPartition* next_left_col = ColumnContaining(next_left, y);
329       if (right_col == next_left_col) {
330         // There is a column break in this column.
331         // Check for the difference between different column layout and
332         // a pull-out block.
333         int part_box_width = part->bounding_box().width();
334         int part_margin_width = part->right_margin() - part->left_margin();
335         int next_box_width = next_part->bounding_box().width();
336         int next_margin_width = next_part->right_margin() -
337                                 next_part->left_margin();
338         int next_right = next_part->bounding_box().right();
339         if (part_box_width < next_margin_width &&
340             next_box_width < part_margin_width) {
341           if (debug) {
342             tprintf("CompatibleColumns false due to equal sized columns\n");
343             tprintf("part1 %d-%d = %d, part2 %d-%d = %d\n",
344                     left, right, part->ColumnWidth(),
345                     next_left, next_right, next_part->ColumnWidth());
346             right_col->Print();
347           }
348           return false;  // Must be a new column layout as they are equal size.
349         }
350         ColPartition* next_right_col = ColumnContaining(next_right, y);
351         if (left_col == right_col && next_right_col == next_left_col) {
352           // Column completely contains both. Not allowed.
353           if (debug) {
354             tprintf("CompatibleColumns false due to containing 2 partitions\n");
355             tprintf("part1 %d-%d, part2 %d-%d\n",
356                     left, right, next_left, next_right);
357             right_col->Print();
358           }
359           return false;
360         }
361       }
362       break;
363     }
364   }
365   if (debug)
366     tprintf("CompatibleColumns true!\n");
367   return true;
368 }
369 
370 // Return true if this ColPartitionSet makes a legal column candidate by
371 // having legal individual partitions and non-overlapping adjacent pairs.
LegalColumnCandidate()372 bool ColPartitionSet::LegalColumnCandidate() {
373   ColPartition_IT it(&parts_);
374   if (it.empty())
375     return false;
376   int any_text_parts = false;
377   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
378     ColPartition* part = it.data();
379     if (part->blob_type() > BRT_UNKNOWN) {
380       if (!part->IsLegal())
381         return false;  // Individual partition is illegal.
382       any_text_parts = true;
383     }
384     if (!it.at_last()) {
385       ColPartition* next_part = it.data_relative(1);
386       if (next_part->left_key() < part->right_key()) {
387         return false;
388       }
389     }
390   }
391   return any_text_parts;
392 }
393 
394 // Return a copy of this. If good_only will only copy the Good ColPartitions.
Copy(bool good_only)395 ColPartitionSet* ColPartitionSet::Copy(bool good_only) {
396   ColPartition_LIST copy_parts;
397   ColPartition_IT src_it(&parts_);
398   ColPartition_IT dest_it(&copy_parts);
399   for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
400     ColPartition* part = src_it.data();
401     if (part->blob_type() > BRT_UNKNOWN &&
402         (!good_only || part->good_width() || part->good_column()))
403       dest_it.add_after_then_move(part->ShallowCopy());
404   }
405   if (dest_it.empty())
406     return NULL;
407   return new ColPartitionSet(&copy_parts);
408 }
409 
410 // Return the bounding boxes of columns at the given y-range
GetColumnBoxes(int y_bottom,int y_top,ColSegment_LIST * segments)411 void ColPartitionSet::GetColumnBoxes(int y_bottom, int y_top,
412                                      ColSegment_LIST *segments) {
413   ColPartition_IT it(&parts_);
414   ColSegment_IT col_it(segments);
415   col_it.move_to_last();
416   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
417     ColPartition* part = it.data();
418     ICOORD bot_left(part->LeftAtY(y_top), y_bottom);
419     ICOORD top_right(part->RightAtY(y_bottom), y_top);
420     ColSegment *col_seg = new ColSegment();
421     col_seg->InsertBox(TBOX(bot_left, top_right));
422     col_it.add_after_then_move(col_seg);
423   }
424 }
425 
426 // Display the edges of the columns at the given y coords.
DisplayColumnEdges(int y_bottom,int y_top,ScrollView * win)427 void ColPartitionSet::DisplayColumnEdges(int y_bottom, int y_top,
428                                          ScrollView* win) {
429   ColPartition_IT it(&parts_);
430   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
431     ColPartition* part = it.data();
432 #ifndef GRAPHICS_DISABLED
433     win->Line(part->LeftAtY(y_top), y_top, part->LeftAtY(y_bottom), y_bottom);
434     win->Line(part->RightAtY(y_top), y_top, part->RightAtY(y_bottom), y_bottom);
435 #endif
436   }
437 }
438 
439 // Return the PolyBlockType that best explains the columns overlapped
440 // by the given coords(left,right,y), with the given margins.
441 // Also return the first and last column index touched by the coords and
442 // the leftmost and rightmost spanned columns.
443 // Column indices are 2n + 1 for real colums (0 based) and even values
444 // represent the gaps in between columns, with 0 being left of the leftmost.
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)445 PolyBlockType ColPartitionSet::SpanningType(BlobRegionType type,
446                                             int left, int right, int y,
447                                             int left_margin, int right_margin,
448                                             int* first_col, int* last_col,
449                                             int* first_spanned_col,
450                                             int* last_spanned_col) {
451   *first_col = -1;
452   *last_col = -1;
453   *first_spanned_col = -1;
454   *last_spanned_col = -1;
455   int columns_spanned = 0;
456   ColPartition_IT it(&parts_);
457   int col_index = 1;
458   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward(), col_index += 2) {
459     ColPartition* part = it.data();
460     if (part->ColumnContains(left, y)) {
461       // In the default case, first_col is set, but columns_spanned remains
462       // zero, so first_col will get reset in the first column genuinely
463       // spanned, but we can tell the difference from a noise partition
464       // that touches no column.
465       *first_col = col_index;
466       if (part->ColumnContains(right, y)) {
467         // Both within a single column.
468         *last_col = col_index;
469         if (type == BRT_HLINE)
470           return PT_FLOWING_LINE;
471         else if (type > BRT_UNKNOWN)
472           return type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT : PT_FLOWING_TEXT;
473         else
474           return PT_FLOWING_IMAGE;
475       }
476       if (left_margin <= part->LeftAtY(y)) {
477         // It completely spans this column.
478         *last_col = col_index;
479         *first_spanned_col = col_index;
480         *last_spanned_col = col_index;
481         columns_spanned = 1;
482       }
483     } else if (part->ColumnContains(right, y)) {
484       if (*first_col < 0) {
485         // It started in-between.
486         *first_col = col_index - 1;
487       }
488       if (right_margin >= part->RightAtY(y)) {
489         // It completely spans this column.
490         if (columns_spanned == 0)
491           *first_spanned_col = col_index;
492         *last_spanned_col = col_index;
493         ++columns_spanned;
494       }
495       *last_col = col_index;
496       break;
497     } else if (left < part->LeftAtY(y) && right > part->RightAtY(y)) {
498       // Neither left nor right are contained within, so it spans this
499       // column.
500       if (columns_spanned == 0) {
501         *first_col = col_index;
502         *first_spanned_col = col_index;
503       }
504       *last_col = col_index;
505       *last_spanned_col = col_index;
506       ++columns_spanned;
507     } else if (right < part->LeftAtY(y)) {
508       // We have gone past the end.
509       *last_col = col_index - 1;
510       if (*first_col < 0) {
511         // It must lie completely between columns =>noise.
512         *first_col = col_index - 1;
513       }
514       break;
515     }
516   }
517   if (*first_col < 0)
518     *first_col = col_index - 1;  // The last in-between.
519   if (*last_col < 0)
520     *last_col = col_index - 1;  // The last in-between.
521   ASSERT_HOST(*first_col >= 0 && *last_col >= 0);
522   ASSERT_HOST(*first_col <= *last_col);
523   if (columns_spanned == 0 && *first_col == *last_col) {
524     // Neither end was in a column, and it didn't span any, so it lies
525     // entirely between columns, therefore noise.
526     return PT_NOISE;
527   } else if (columns_spanned <= 1) {
528     // It is a pullout, as left and right were not in the same column.
529     if (type == BRT_HLINE)
530       return PT_PULLOUT_LINE;
531     else if (type > BRT_UNKNOWN)
532       return type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT : PT_PULLOUT_TEXT;
533     else
534       return PT_PULLOUT_IMAGE;
535   }
536   // It completely spanned more than one column. Always a heading.
537   if (type == BRT_HLINE)
538     return PT_HEADING_LINE;
539   else if (type > BRT_UNKNOWN)
540     return type == BRT_VERT_TEXT ? PT_VERTICAL_TEXT : PT_HEADING_TEXT;
541   else
542     return PT_HEADING_IMAGE;
543 }
544 
545 // The column_set has changed. Close down all in-progress WorkingPartSets in
546 // columns that do not match and start new ones for the new columns in this.
547 // As ColPartitions are turned into BLOCKs, the used ones are put in
548 // used_parts, as they still need to be referenced in the grid.
ChangeWorkColumns(const ICOORD & bleft,const ICOORD & tright,int resolution,ColPartition_LIST * used_parts,WorkingPartSet_LIST * working_set_list)549 void ColPartitionSet::ChangeWorkColumns(const ICOORD& bleft,
550                                         const ICOORD& tright,
551                                         int resolution,
552                                         ColPartition_LIST* used_parts,
553                                         WorkingPartSet_LIST* working_set_list) {
554   // Move the input list to a temporary location so we can delete its elements
555   // as we add them to the output working_set.
556   WorkingPartSet_LIST work_src;
557   WorkingPartSet_IT src_it(&work_src);
558   src_it.add_list_after(working_set_list);
559   src_it.move_to_first();
560   WorkingPartSet_IT dest_it(working_set_list);
561   // Completed blocks and to_blocks are accumulated and given to the first new
562   // one  whenever we keep a column, or at the end.
563   BLOCK_LIST completed_blocks;
564   TO_BLOCK_LIST to_blocks;
565   WorkingPartSet* first_new_set = NULL;
566   WorkingPartSet* working_set = NULL;
567   ColPartition_IT col_it(&parts_);
568   for (col_it.mark_cycle_pt(); !col_it.cycled_list(); col_it.forward()) {
569     ColPartition* column = col_it.data();
570     // Any existing column to the left of column is completed.
571     while (!src_it.empty() &&
572            ((working_set = src_it.data())->column() == NULL ||
573             working_set->column()->right_key() <= column->left_key())) {
574       src_it.extract();
575       working_set->ExtractCompletedBlocks(bleft, tright, resolution,
576                                           used_parts, &completed_blocks,
577                                           &to_blocks);
578       delete working_set;
579       src_it.forward();
580     }
581     // Make a new between-column WorkingSet for before the current column.
582     working_set = new WorkingPartSet(NULL);
583     dest_it.add_after_then_move(working_set);
584     if (first_new_set == NULL)
585       first_new_set = working_set;
586     // A matching column gets to stay, and first_new_set gets all the
587     // completed_sets.
588     working_set = src_it.empty() ? NULL : src_it.data();
589     if (working_set != NULL &&
590         working_set->column()->MatchingColumns(*column)) {
591       working_set->set_column(column);
592       dest_it.add_after_then_move(src_it.extract());
593       src_it.forward();
594       first_new_set->InsertCompletedBlocks(&completed_blocks, &to_blocks);
595       first_new_set = NULL;
596     } else {
597       // Just make a new working set for the current column.
598       working_set = new WorkingPartSet(column);
599       dest_it.add_after_then_move(working_set);
600     }
601   }
602   // Complete any remaining src working sets.
603   while (!src_it.empty()) {
604     working_set = src_it.extract();
605     working_set->ExtractCompletedBlocks(bleft, tright, resolution,
606                                         used_parts, &completed_blocks,
607                                         &to_blocks);
608     delete working_set;
609     src_it.forward();
610   }
611   // Make a new between-column WorkingSet for after the last column.
612   working_set = new WorkingPartSet(NULL);
613   dest_it.add_after_then_move(working_set);
614   if (first_new_set == NULL)
615     first_new_set = working_set;
616   // The first_new_set now gets any accumulated completed_parts/blocks.
617   first_new_set->InsertCompletedBlocks(&completed_blocks, &to_blocks);
618 }
619 
620 // Accumulate the widths and gaps into the given variables.
AccumulateColumnWidthsAndGaps(int * total_width,int * width_samples,int * total_gap,int * gap_samples)621 void ColPartitionSet::AccumulateColumnWidthsAndGaps(int* total_width,
622                                                     int* width_samples,
623                                                     int* total_gap,
624                                                     int* gap_samples) {
625   ColPartition_IT it(&parts_);
626   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
627     ColPartition* part = it.data();
628     *total_width += part->ColumnWidth();
629     ++*width_samples;
630     if (!it.at_last()) {
631       ColPartition* next_part = it.data_relative(1);
632       int gap = part->KeyWidth(part->right_key(), next_part->left_key());
633       *total_gap += gap;
634       ++*gap_samples;
635     }
636   }
637 }
638 
639 // Provide debug output for this ColPartitionSet and all the ColPartitions.
Print()640 void ColPartitionSet::Print() {
641   ColPartition_IT it(&parts_);
642   tprintf("Partition set of %d parts, %d good, coverage=%d (%d,%d)->(%d,%d)\n",
643           it.length(), good_column_count_, total_coverage_,
644           bounding_box_.left(), bounding_box_.bottom(),
645           bounding_box_.right(), bounding_box_.top());
646   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
647     ColPartition* part = it.data();
648     part->Print();
649   }
650 }
651 
652 // PRIVATE CODE.
653 
654 // Add the given partition to the list in the appropriate place.
AddPartition(ColPartition * new_part,ColPartition_IT * it)655 void ColPartitionSet::AddPartition(ColPartition* new_part,
656                                    ColPartition_IT* it) {
657   bounding_box_ += new_part->bounding_box();
658   if (new_part->good_column() || new_part->good_width()) {
659     total_coverage_ += new_part->ColumnWidth();
660     ++good_column_count_;
661     if (new_part->good_width())
662       ++good_column_count_;
663   }
664   int new_right = new_part->right_key();
665   if (it->data()->left_key() >= new_right)
666     it->add_before_stay_put(new_part);
667   else
668     it->add_after_stay_put(new_part);
669 }
670 
671 // Compute the coverage and good column count.
ComputeCoverage()672 void ColPartitionSet::ComputeCoverage() {
673   // Count the number of good columns and sum their width.
674   ColPartition_IT it(&parts_);
675   good_column_count_ = 0;
676   total_coverage_ = 0;
677   bounding_box_ = TBOX();
678   for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
679     ColPartition* part = it.data();
680     bounding_box_ += part->bounding_box();
681     if (part->good_column() || part->good_width()) {
682       total_coverage_ += part->ColumnWidth();
683       ++good_column_count_;
684       if (part->good_width())
685         ++good_column_count_;
686     }
687   }
688 }
689 
690 }  // namespace tesseract.
691