Tesseract 3.02

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
colpartitionset.cpp
Go to the documentation of this file.
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 //
20
21 #include "colpartitionset.h"
22 #include "ndminx.h"
23 #include "workingpartset.h"
24 #include "tablefind.h"
25
26 namespace tesseract {
27
28 // Minimum width of a column to be interesting as a multiple of resolution.
29 const double kMinColumnWidth = 2.0 / 3;
30
31 ELISTIZE(ColPartitionSet)
32
33 ColPartitionSet::ColPartitionSet(ColPartition_LIST* partitions) {
34 ColPartition_IT it(&parts_);
35 it.add_list_after(partitions);
36 ComputeCoverage();
37 }
38
39 ColPartitionSet::ColPartitionSet(ColPartition* part) {
40 ColPartition_IT it(&parts_);
41 it.add_after_then_move(part);
42 ComputeCoverage();
43 }
44
45 ColPartitionSet::~ColPartitionSet() {
46 }
47
48 // Return an element of the parts_ list from its index.
49 ColPartition* ColPartitionSet::GetColumnByIndex(int index) {
50 ColPartition_IT it(&parts_);
51 it.mark_cycle_pt();
52 for (int i = 0; i < index && !it.cycled_list(); ++i, it.forward());
53 if (it.cycled_list())
54 return NULL;
55 return it.data();
56 }
57
58 // Return the ColPartition that contains the given coords, if any, else NULL.
59 ColPartition* ColPartitionSet::ColumnContaining(int x, int y) {
60 ColPartition_IT it(&parts_);
61 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
62 ColPartition* part = it.data();
63 if (part->ColumnContains(x, y))
64 return part;
65 }
66 return NULL;
67 }
68
69 // Extract all the parts from the list, relinquishing ownership.
70 void ColPartitionSet::RelinquishParts() {
71 ColPartition_IT it(&parts_);
72 while (!it.empty()) {
73 it.extract();
74 it.forward();
75 }
76 }
77
78 // Attempt to improve this by adding partitions or expanding partitions.
79 void ColPartitionSet::ImproveColumnCandidate(WidthCallback* cb,
80 PartSetVector* src_sets) {
81 int set_size = src_sets->size();
82 // Iterate over the provided column sets, as each one may have something
83 // to improve this.
84 for (int i = 0; i < set_size; ++i) {
85 ColPartitionSet* column_set = src_sets->get(i);
86 if (column_set == NULL)
87 continue;
88 // Iterate over the parts in this and column_set, adding bigger or
89 // new parts in column_set to this.
90 ColPartition_IT part_it(&parts_);
91 ASSERT_HOST(!part_it.empty());
92 int prev_right = MIN_INT32;
93 part_it.mark_cycle_pt();
94 ColPartition_IT col_it(&column_set->parts_);
95 for (col_it.mark_cycle_pt(); !col_it.cycled_list(); col_it.forward()) {
96 ColPartition* col_part = col_it.data();
97 if (col_part->blob_type() < BRT_UNKNOWN)
98 continue; // Ignore image partitions.
99 int col_left = col_part->left_key();
100 int col_right = col_part->right_key();
101 // Sync-up part_it (in this) so it matches the col_part in column_set.
102 ColPartition* part = part_it.data();
103 while (!part_it.at_last() && part->right_key() < col_left) {
104 prev_right = part->right_key();
105 part_it.forward();
106 part = part_it.data();
107 }
108 int part_left = part->left_key();
109 int part_right = part->right_key();
110 if (part_right < col_left || col_right < part_left) {
111 // There is no overlap so this is a new partition.
112 AddPartition(col_part->ShallowCopy(), &part_it);
113 continue;
114 }
115 // Check the edges of col_part to see if they can improve part.
116 bool part_width_ok = cb->Run(part->KeyWidth(part_left, part_right));
117 if (col_left < part_left && col_left > prev_right) {
118 // The left edge of the column is better and it doesn't overlap,
119 // so we can potentially expand it.
120 int col_box_left = col_part->BoxLeftKey();
121 bool tab_width_ok = cb->Run(part->KeyWidth(col_left, part_right));
122 bool box_width_ok = cb->Run(part->KeyWidth(col_box_left, part_right));
123 if (tab_width_ok || (!part_width_ok )) {
124 // The tab is leaving the good column metric at least as good as
125 // it was before, so use the tab.
126 part->CopyLeftTab(*col_part, false);
127 part->SetColumnGoodness(cb);
128 } else if (col_box_left < part_left &&
129 (box_width_ok || !part_width_ok)) {
130 // The box is leaving the good column metric at least as good as
131 // it was before, so use the box.
132 part->CopyLeftTab(*col_part, true);
133 part->SetColumnGoodness(cb);
134 }
135 part_left = part->left_key();
136 }
137 if (col_right > part_right &&
138 (part_it.at_last() ||
139 part_it.data_relative(1)->left_key() > col_right)) {
140 // The right edge is better, so we can possibly expand it.
141 int col_box_right = col_part->BoxRightKey();
142 bool tab_width_ok = cb->Run(part->KeyWidth(part_left, col_right));
143 bool box_width_ok = cb->Run(part->KeyWidth(part_left, col_box_right));
144 if (tab_width_ok || (!part_width_ok )) {
145 // The tab is leaving the good column metric at least as good as
146 // it was before, so use the tab.
147 part->CopyRightTab(*col_part, false);
148 part->SetColumnGoodness(cb);
149 } else if (col_box_right > part_right &&
150 (box_width_ok || !part_width_ok)) {
151 // The box is leaving the good column metric at least as good as
152 // it was before, so use the box.
153 part->CopyRightTab(*col_part, true);
154 part->SetColumnGoodness(cb);
155 }
156 }
157 }
158 }
159 ComputeCoverage();
160 }
161
162 // If this set is good enough to represent a new partitioning into columns,
163 // add it to the vector of sets, otherwise delete it.
164 void ColPartitionSet::AddToColumnSetsIfUnique(PartSetVector* column_sets,
165 WidthCallback* cb) {
166 bool debug = TabFind::WithinTestRegion(2, bounding_box_.left(),
167 bounding_box_.bottom());
168 if (debug) {
169 tprintf("Considering new column candidate:\n");
170 Print();
171 }
172 if (!LegalColumnCandidate()) {
173 if (debug) {
174 tprintf("Not a legal column candidate:\n");
175 Print();
176 }
177 delete this;
178 return;
179 }
180 for (int i = 0; i < column_sets->size(); ++i) {
181 ColPartitionSet* columns = column_sets->get(i);
182 // In ordering the column set candidates, good_coverage_ is king,
183 // followed by good_column_count_ and then bad_coverage_.
184 bool better = good_coverage_ > columns->good_coverage_;
185 if (good_coverage_ == columns->good_coverage_) {
186 better = good_column_count_ > columns->good_column_count_;
187 if (good_column_count_ == columns->good_column_count_) {
188 better = bad_coverage_ > columns->bad_coverage_;
189 }
190 }
191 if (better) {
192 // The new one is better so add it.
193 if (debug)
194 tprintf("Good one\n");
195 column_sets->insert(this, i);
196 return;
197 }
198 if (columns->CompatibleColumns(false, this, cb)) {
199 if (debug)
200 tprintf("Duplicate\n");
201 delete this;
202 return; // It is not unique.
203 }
204 }
205 if (debug)
206 tprintf("Added to end\n");
207 column_sets->push_back(this);
208 }
209
210 // Return true if the partitions in other are all compatible with the columns
211 // in this.
212 bool ColPartitionSet::CompatibleColumns(bool debug, ColPartitionSet* other,
213 WidthCallback* cb) {
214 if (debug) {
215 tprintf("CompatibleColumns testing compatibility\n");
216 Print();
217 other->Print();
218 }
219 if (other->parts_.empty()) {
220 if (debug)
221 tprintf("CompatibleColumns true due to empty other\n");
222 return true;
223 }
224 ColPartition_IT it(&other->parts_);
225 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
226 ColPartition* part = it.data();
227 if (part->blob_type() < BRT_UNKNOWN) {
228 if (debug) {
229 tprintf("CompatibleColumns ignoring image partition\n");
230 part->Print();
231 }
232 continue; // Image partitions are irrelevant to column compatibility.
233 }
234 int y = part->MidY();
235 int left = part->bounding_box().left();
236 int right = part->bounding_box().right();
237 ColPartition* left_col = ColumnContaining(left, y);
238 ColPartition* right_col = ColumnContaining(right, y);
239 if (right_col == NULL || left_col == NULL) {
240 if (debug) {
241 tprintf("CompatibleColumns false due to partition edge outside\n");
242 part->Print();
243 }
244 return false; // A partition edge lies outside of all columns
245 }
246 if (right_col != left_col && cb->Run(right - left)) {
247 if (debug) {
248 tprintf("CompatibleColumns false due to good width in multiple cols\n");
249 part->Print();
250 }
251 return false; // Partition with a good width must be in a single column.
252 }
253
254 ColPartition_IT it2= it;
255 while (!it2.at_last()) {
256 it2.forward();
257 ColPartition* next_part = it2.data();
258 if (!BLOBNBOX::IsTextType(next_part->blob_type()))
259 continue; // Non-text partitions are irrelevant.
260 int next_left = next_part->bounding_box().left();
261 if (next_left == right) {
262 break; // They share the same edge, so one must be a pull-out.
263 }
264 // Search to see if right and next_left fall within a single column.
265 ColPartition* next_left_col = ColumnContaining(next_left, y);
266 if (right_col == next_left_col) {
267 // There is a column break in this column.
268 // This can be due to a figure caption within a column, a pull-out
269 // block, or a simple broken textline that remains to be merged:
270 // all allowed, or a change in column layout: not allowed.
271 // If both partitions are of good width, then it is likely
272 // a change in column layout, otherwise probably an allowed situation.
273 if (part->good_width() && next_part->good_width()) {
274 if (debug) {
275 int next_right = next_part->bounding_box().right();
276 tprintf("CompatibleColumns false due to 2 parts of good width\n");
277 tprintf("part1 %d-%d, part2 %d-%d\n",
278 left, right, next_left, next_right);
279 right_col->Print();
280 }
281 return false;
282 }
283 }
284 break;
285 }
286 }
287 if (debug)
288 tprintf("CompatibleColumns true!\n");
289 return true;
290 }
291
292 // Returns the total width of all blobs in the part_set that do not lie
293 // within an approved column. Used as a cost measure for using this
294 // column set over another that might be compatible.
295 int ColPartitionSet::UnmatchedWidth(ColPartitionSet* part_set) {
296 int total_width = 0;
297 ColPartition_IT it(&part_set->parts_);
298 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
299 ColPartition* part = it.data();
300 if (!BLOBNBOX::IsTextType(part->blob_type())) {
301 continue; // Non-text partitions are irrelevant to column compatibility.
302 }
303 int y = part->MidY();
304 BLOBNBOX_C_IT box_it(part->boxes());
305 for (box_it.mark_cycle_pt(); !box_it.cycled_list(); box_it.forward()) {
306 const TBOX& box = it.data()->bounding_box();
307 // Assume that the whole blob is outside any column iff its x-middle
308 // is outside.
309 int x = (box.left() + box.right()) / 2;
310 ColPartition* col = ColumnContaining(x, y);
311 if (col == NULL)
312 total_width += box.width();
313 }
314 }
315 return total_width;
316 }
317
318 // Return true if this ColPartitionSet makes a legal column candidate by
319 // having legal individual partitions and non-overlapping adjacent pairs.
320 bool ColPartitionSet::LegalColumnCandidate() {
321 ColPartition_IT it(&parts_);
322 if (it.empty())
323 return false;
324 bool any_text_parts = false;
325 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
326 ColPartition* part = it.data();
327 if (BLOBNBOX::IsTextType(part->blob_type())) {
328 if (!part->IsLegal())
329 return false; // Individual partition is illegal.
330 any_text_parts = true;
331 }
332 if (!it.at_last()) {
333 ColPartition* next_part = it.data_relative(1);
334 if (next_part->left_key() < part->right_key()) {
335 return false;
336 }
337 }
338 }
339 return any_text_parts;
340 }
341
342 // Return a copy of this. If good_only will only copy the Good ColPartitions.
343 ColPartitionSet* ColPartitionSet::Copy(bool good_only) {
344 ColPartition_LIST copy_parts;
345 ColPartition_IT src_it(&parts_);
346 ColPartition_IT dest_it(&copy_parts);
347 for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
348 ColPartition* part = src_it.data();
349 if (BLOBNBOX::IsTextType(part->blob_type()) &&
350 (!good_only || part->good_width() || part->good_column()))
351 dest_it.add_after_then_move(part->ShallowCopy());
352 }
353 if (dest_it.empty())
354 return NULL;
355 return new ColPartitionSet(&copy_parts);
356 }
357
358 // Return the bounding boxes of columns at the given y-range
359 void ColPartitionSet::GetColumnBoxes(int y_bottom, int y_top,
360 ColSegment_LIST *segments) {
361 ColPartition_IT it(&parts_);
362 ColSegment_IT col_it(segments);
363 col_it.move_to_last();
364 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
365 ColPartition* part = it.data();
366 ICOORD bot_left(part->LeftAtY(y_top), y_bottom);
367 ICOORD top_right(part->RightAtY(y_bottom), y_top);
368 ColSegment *col_seg = new ColSegment();
369 col_seg->InsertBox(TBOX(bot_left, top_right));
370 col_it.add_after_then_move(col_seg);
371 }
372 }
373
374 // Display the edges of the columns at the given y coords.
375 void ColPartitionSet::DisplayColumnEdges(int y_bottom, int y_top,
376 ScrollView* win) {
377 #ifndef GRAPHICS_DISABLED
378 ColPartition_IT it(&parts_);
379 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
380 ColPartition* part = it.data();
381 win->Line(part->LeftAtY(y_top), y_top, part->LeftAtY(y_bottom), y_bottom);
382 win->Line(part->RightAtY(y_top), y_top, part->RightAtY(y_bottom), y_bottom);
383 }
384 #endif // GRAPHICS_DISABLED
385 }
386
387 // Return the ColumnSpanningType that best explains the columns overlapped
388 // by the given coords(left,right,y), with the given margins.
389 // Also return the first and last column index touched by the coords and
390 // the leftmost spanned column.
391 // Column indices are 2n + 1 for real columns (0 based) and even values
392 // represent the gaps in between columns, with 0 being left of the leftmost.
393 // resolution refers to the ppi resolution of the image.
394 ColumnSpanningType ColPartitionSet::SpanningType(int resolution,
395 int left, int right, int y,
396 int left_margin,
397 int right_margin,
398 int* first_col,
399 int* last_col,
400 int* first_spanned_col) {
401 *first_col = -1;
402 *last_col = -1;
403 *first_spanned_col = -1;
404 int margin_columns = 0;
405 ColPartition_IT it(&parts_);
406 int col_index = 1;
407 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward(), col_index += 2) {
408 ColPartition* part = it.data();
409 if (part->ColumnContains(left, y)) {
410 // In the default case, first_col is set, but columns_spanned remains
411 // zero, so first_col will get reset in the first column genuinely
412 // spanned, but we can tell the difference from a noise partition
413 // that touches no column.
414 *first_col = col_index;
415 if (part->ColumnContains(right, y)) {
416 // Both within a single column.
417 *last_col = col_index;
418 return CST_FLOWING;
419 }
420 if (left_margin <= part->LeftAtY(y)) {
421 // It completely spans this column.
422 *first_spanned_col = col_index;
423 margin_columns = 1;
424 }
425 } else if (part->ColumnContains(right, y)) {
426 if (*first_col < 0) {
427 // It started in-between.
428 *first_col = col_index - 1;
429 }
430 if (right_margin >= part->RightAtY(y)) {
431 // It completely spans this column.
432 if (margin_columns == 0)
433 *first_spanned_col = col_index;
434 ++margin_columns;
435 }
436 *last_col = col_index;
437 break;
438 } else if (left < part->LeftAtY(y) && right > part->RightAtY(y)) {
439 // Neither left nor right are contained within, so it spans this
440 // column.
441 if (*first_col < 0) {
442 // It started in between the previous column and the current column.
443 *first_col = col_index - 1;
444 }
445 if (margin_columns == 0)
446 *first_spanned_col = col_index;
447 *last_col = col_index;
448 } else if (right < part->LeftAtY(y)) {
449 // We have gone past the end.
450 *last_col = col_index - 1;
451 if (*first_col < 0) {
452 // It must lie completely between columns =>noise.
453 *first_col = col_index - 1;
454 }
455 break;
456 }
457 }
458 if (*first_col < 0)
459 *first_col = col_index - 1; // The last in-between.
460 if (*last_col < 0)
461 *last_col = col_index - 1; // The last in-between.
462 ASSERT_HOST(*first_col >= 0 && *last_col >= 0);
463 ASSERT_HOST(*first_col <= *last_col);
464 if (*first_col == *last_col && right - left < kMinColumnWidth * resolution) {
465 // Neither end was in a column, and it didn't span any, so it lies
466 // entirely between columns, therefore noise.
467 return CST_NOISE;
468 } else if (margin_columns <= 1) {
469 // An exception for headings that stick outside of single-column text.
470 if (margin_columns == 1 && parts_.singleton()) {
471 return CST_HEADING;
472 }
473 // It is a pullout, as left and right were not in the same column, but
474 // it doesn't go to the edge of its start and end.
475 return CST_PULLOUT;
476 }
477 // Its margins went to the edges of first and last columns => heading.
478 return CST_HEADING;
479 }
480
481 // The column_set has changed. Close down all in-progress WorkingPartSets in
482 // columns that do not match and start new ones for the new columns in this.
483 // As ColPartitions are turned into BLOCKs, the used ones are put in
484 // used_parts, as they still need to be referenced in the grid.
485 void ColPartitionSet::ChangeWorkColumns(const ICOORD& bleft,
486 const ICOORD& tright,
487 int resolution,
488 ColPartition_LIST* used_parts,
489 WorkingPartSet_LIST* working_set_list) {
490 // Move the input list to a temporary location so we can delete its elements
491 // as we add them to the output working_set.
492 WorkingPartSet_LIST work_src;
493 WorkingPartSet_IT src_it(&work_src);
494 src_it.add_list_after(working_set_list);
495 src_it.move_to_first();
496 WorkingPartSet_IT dest_it(working_set_list);
497 // Completed blocks and to_blocks are accumulated and given to the first new
498 // one whenever we keep a column, or at the end.
499 BLOCK_LIST completed_blocks;
500 TO_BLOCK_LIST to_blocks;
501 WorkingPartSet* first_new_set = NULL;
502 WorkingPartSet* working_set = NULL;
503 ColPartition_IT col_it(&parts_);
504 for (col_it.mark_cycle_pt(); !col_it.cycled_list(); col_it.forward()) {
505 ColPartition* column = col_it.data();
506 // Any existing column to the left of column is completed.
507 while (!src_it.empty() &&
508 ((working_set = src_it.data())->column() == NULL ||
509 working_set->column()->right_key() <= column->left_key())) {
510 src_it.extract();
511 working_set->ExtractCompletedBlocks(bleft, tright, resolution,
512 used_parts, &completed_blocks,
513 &to_blocks);
514 delete working_set;
515 src_it.forward();
516 }
517 // Make a new between-column WorkingSet for before the current column.
518 working_set = new WorkingPartSet(NULL);
519 dest_it.add_after_then_move(working_set);
520 if (first_new_set == NULL)
521 first_new_set = working_set;
522 // A matching column gets to stay, and first_new_set gets all the
523 // completed_sets.
524 working_set = src_it.empty() ? NULL : src_it.data();
525 if (working_set != NULL &&
526 working_set->column()->MatchingColumns(*column)) {
527 working_set->set_column(column);
528 dest_it.add_after_then_move(src_it.extract());
529 src_it.forward();
530 first_new_set->InsertCompletedBlocks(&completed_blocks, &to_blocks);
531 first_new_set = NULL;
532 } else {
533 // Just make a new working set for the current column.
534 working_set = new WorkingPartSet(column);
535 dest_it.add_after_then_move(working_set);
536 }
537 }
538 // Complete any remaining src working sets.
539 while (!src_it.empty()) {
540 working_set = src_it.extract();
541 working_set->ExtractCompletedBlocks(bleft, tright, resolution,
542 used_parts, &completed_blocks,
543 &to_blocks);
544 delete working_set;
545 src_it.forward();
546 }
547 // Make a new between-column WorkingSet for after the last column.
548 working_set = new WorkingPartSet(NULL);
549 dest_it.add_after_then_move(working_set);
550 if (first_new_set == NULL)
551 first_new_set = working_set;
552 // The first_new_set now gets any accumulated completed_parts/blocks.
553 first_new_set->InsertCompletedBlocks(&completed_blocks, &to_blocks);
554 }
555
556 // Accumulate the widths and gaps into the given variables.
557 void ColPartitionSet::AccumulateColumnWidthsAndGaps(int* total_width,
558 int* width_samples,
559 int* total_gap,
560 int* gap_samples) {
561 ColPartition_IT it(&parts_);
562 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
563 ColPartition* part = it.data();
564 *total_width += part->ColumnWidth();
565 ++*width_samples;
566 if (!it.at_last()) {
567 ColPartition* next_part = it.data_relative(1);
568 int gap = part->KeyWidth(part->right_key(), next_part->left_key());
569 *total_gap += gap;
570 ++*gap_samples;
571 }
572 }
573 }
574
575 // Provide debug output for this ColPartitionSet and all the ColPartitions.
576 void ColPartitionSet::Print() {
577 ColPartition_IT it(&parts_);
578 tprintf("Partition set of %d parts, %d good, coverage=%d+%d"
579 " (%d,%d)->(%d,%d)\n",
580 it.length(), good_column_count_, good_coverage_, bad_coverage_,
581 bounding_box_.left(), bounding_box_.bottom(),
582 bounding_box_.right(), bounding_box_.top());
583 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
584 ColPartition* part = it.data();
585 part->Print();
586 }
587 }
588
589 // PRIVATE CODE.
590
591 // Add the given partition to the list in the appropriate place.
592 void ColPartitionSet::AddPartition(ColPartition* new_part,
593 ColPartition_IT* it) {
594 AddPartitionCoverageAndBox(*new_part);
595 int new_right = new_part->right_key();
596 if (it->data()->left_key() >= new_right)
597 it->add_before_stay_put(new_part);
598 else
599 it->add_after_stay_put(new_part);
600 }
601
602 // Compute the coverage and good column count. Coverage is the amount of the
603 // width of the page (in pixels) that is covered by ColPartitions, which are
604 // used to provide candidate column layouts.
605 // Coverage is split into good and bad. Good coverage is provided by
606 // ColPartitions of a frequent width (according to the callback function
607 // provided by TabFinder::WidthCB, which accesses stored statistics on the
608 // widths of ColParititions) and bad coverage is provided by all other
609 // ColPartitions, even if they have tab vectors at both sides. Thus:
610 // |-----------------------------------------------------------------|
611 // | Double width heading |
612 // |-----------------------------------------------------------------|
613 // |-------------------------------| |-------------------------------|
614 // | Common width ColParition | | Common width ColPartition |
615 // |-------------------------------| |-------------------------------|
616 // the layout with two common-width columns has better coverage than the
617 // double width heading, because the coverage is "good," even though less in
618 // total coverage than the heading, because the heading coverage is "bad."
619 void ColPartitionSet::ComputeCoverage() {
620 // Count the number of good columns and sum their width.
621 ColPartition_IT it(&parts_);
622 good_column_count_ = 0;
623 good_coverage_ = 0;
624 bad_coverage_ = 0;
625 bounding_box_ = TBOX();
626 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
627 ColPartition* part = it.data();
628 AddPartitionCoverageAndBox(*part);
629 }
630 }
631
632 // Adds the coverage, column count and box for a single partition,
633 // without adding it to the list. (Helper factored from ComputeCoverage.)
634 void ColPartitionSet::AddPartitionCoverageAndBox(const ColPartition& part) {
635 bounding_box_ += part.bounding_box();
636 int coverage = part.ColumnWidth();
637 if (part.good_width()) {
638 good_coverage_ += coverage;
639 good_column_count_ += 2;
640 } else {
641 if (part.blob_type() < BRT_UNKNOWN)
642 coverage /= 2;
643 if (part.good_column())
644 ++good_column_count_;
645 bad_coverage_ += coverage;
646 }
647 }
648
649 } // namespace tesseract.