20 #include "config_auto.h"
62 static BOOL_VAR(textord_tabfind_show_initialtabs,
false,
"Show tab candidates");
63 static BOOL_VAR(textord_tabfind_show_finaltabs,
false,
"Show tab vectors");
66 TabVector_LIST* vlines,
int vertical_x,
int vertical_y,
69 resolution_(resolution),
70 image_origin_(0, tright.y() - 1),
73 v_it_.add_list_after(vlines);
74 SetVerticalSkewAndParallelize(vertical_x, vertical_y);
75 using namespace std::placeholders;
94 BLOBNBOX_C_IT>* grid) {
95 BLOBNBOX_IT blob_it(blobs);
98 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
101 if (
InsertBlob(h_spread, v_spread, blob, grid)) {
108 tprintf(
"Inserted %d blobs into grid, %d rejected.\n",
109 b_count, reject_count);
120 BLOBNBOX_C_IT>* grid) {
128 grid->InsertBBox(h_spread, v_spread, blob);
143 BLOBNBOX_IT blob_it(blobs);
144 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
162 bool ignore_unmergeables,
int max_gutter_width,
163 int* required_shift) {
165 int bottom_x = v.
XAtY(bottom_y);
166 int top_x = v.
XAtY(top_y);
167 int start_x = right_to_left ? std::max(top_x, bottom_x) : std::min(top_x, bottom_x);
170 int min_gap = max_gutter_width;
173 while ((blob = sidesearch.
NextSideSearch(right_to_left)) !=
nullptr) {
175 if (box.
bottom() >= top_y || box.
top() <= bottom_y)
184 int mid_y = (box.
bottom() + box.
top()) / 2;
189 int tab_x = v.
XAtY(mid_y);
192 gap = tab_x - box.
right();
193 if (gap < 0 && box.
left() - tab_x < *required_shift)
194 *required_shift = box.
left() - tab_x;
196 gap = box.
left() - tab_x;
197 if (gap < 0 && box.
right() - tab_x > *required_shift)
198 *required_shift = box.
right() - tab_x;
200 if (gap > 0 && gap < min_gap)
204 return min_gap - abs(*required_shift);
209 int max_gutter,
bool left,
211 int* neighbour_gap) {
214 int gutter_x = left ? box.
left() : box.
right();
215 int internal_x = left ? box.
right() : box.
left();
217 int tab_gap = left ? gutter_x - tab_x : tab_x - gutter_x;
218 *gutter_width = max_gutter;
222 *gutter_width += tab_gap;
225 tprintf(
"Looking in gutter\n");
227 BLOBNBOX* gutter_bbox = AdjacentBlob(bbox, left,
230 if (gutter_bbox !=
nullptr) {
232 *gutter_width = left ? tab_x - gutter_box.
right()
233 : gutter_box.
left() - tab_x;
235 if (*gutter_width >= max_gutter) {
237 TBOX gutter_box(box);
239 gutter_box.
set_left(tab_x - max_gutter - 1);
240 gutter_box.
set_right(tab_x - max_gutter);
242 if (tab_gutter < tab_x - 1)
243 *gutter_width = tab_x - tab_gutter;
245 gutter_box.
set_left(tab_x + max_gutter);
246 gutter_box.
set_right(tab_x + max_gutter + 1);
248 if (tab_gutter > tab_x + 1)
249 *gutter_width = tab_gutter - tab_x;
252 if (*gutter_width > max_gutter)
253 *gutter_width = max_gutter;
256 tprintf(
"Looking for neighbour\n");
257 BLOBNBOX* neighbour = AdjacentBlob(bbox, !left,
262 if (neighbour !=
nullptr) {
268 if (left && n_box.
left() < neighbour_edge)
269 neighbour_edge = n_box.
left();
270 else if (!left && n_box.
right() > neighbour_edge)
271 neighbour_edge = n_box.
right();
273 *neighbour_gap = left ? neighbour_edge - internal_x
274 : internal_x - neighbour_edge;
308 int top_y = box.
top();
309 int bottom_y = box.
bottom();
310 int mid_y = (top_y + bottom_y) / 2;
311 int right = crossing ? (box.
left() + box.
right()) / 2 : box.
right();
312 int min_key, max_key;
315 while (!v_it_.at_first() && v_it_.data()->sort_key() >= min_key)
317 while (!v_it_.at_last() && v_it_.data()->sort_key() < min_key)
325 int x = v->
XAtY(mid_y);
327 (v->
VOverlap(top_y, bottom_y) > 0 ||
329 if (best_v ==
nullptr || x < best_x) {
334 key_limit = v->
sort_key() + max_key - min_key;
339 if (v_it_.at_last() ||
340 (best_v !=
nullptr && v->
sort_key() > key_limit))
343 }
while (!v_it_.at_first());
352 int top_y = box.
top();
353 int bottom_y = box.
bottom();
354 int mid_y = (top_y + bottom_y) / 2;
355 int left = crossing ? (box.
left() + box.
right()) / 2 : box.
left();
356 int min_key, max_key;
359 while (!v_it_.at_last() && v_it_.data()->sort_key() <= max_key)
361 while (!v_it_.at_first() && v_it_.data()->sort_key() > max_key) {
370 int x = v->
XAtY(mid_y);
372 (v->
VOverlap(top_y, bottom_y) > 0 ||
374 if (best_v ==
nullptr || x > best_x) {
379 key_limit = v->
sort_key() - (max_key - min_key);
384 if (v_it_.at_first() ||
385 (best_v !=
nullptr && v->
sort_key() < key_limit))
388 }
while (!v_it_.at_last());
396 ICOORDELT_IT it(&column_widths_);
397 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
399 if (w->
x() - 1 <= width && width <= w->
y() + 1)
408 return size1 > size2 * 2 || size2 > size1 * 2;
414 return size1 > size2 * 5 || size2 > size1 * 5;
423 BLOBNBOX_LIST* image_blobs,
TO_BLOCK* block,
424 int min_gutter_width,
425 double tabfind_aligned_gap_fraction,
429 tabfind_aligned_gap_fraction,
431 ComputeColumnWidths(tab_win, part_grid);
435 if (!Deskew(hlines, image_blobs, block, deskew, reskew))
437 part_grid->
Deskew(*deskew);
438 ApplyTabConstraints();
439 #ifndef GRAPHICS_DISABLED
440 if (textord_tabfind_show_finaltabs) {
446 #endif // GRAPHICS_DISABLED
467 BLOBNBOX_IT blob_it = &block->
blobs;
469 for (large_it.mark_cycle_pt(); !large_it.cycled_list(); large_it.forward()) {
470 BLOBNBOX* large_blob = large_it.data();
471 if (large_blob->
owner() !=
nullptr) {
472 blob_it.add_to_end(large_it.extract());
477 tprintf(
"Moved %d large blobs to normal list\n",
479 #ifndef GRAPHICS_DISABLED
484 #endif // GRAPHICS_DISABLED
493 *min_key = std::min(key1, key2);
494 *max_key = std::max(key1, key2);
498 #ifndef GRAPHICS_DISABLED
500 TabVector_IT it(&vectors_);
501 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
515 int min_gutter_width,
516 double tabfind_aligned_gap_fraction,
518 if (textord_tabfind_show_initialtabs) {
523 if (image_blobs !=
nullptr)
526 ScrollView* initial_win = FindTabBoxes(min_gutter_width,
527 tabfind_aligned_gap_fraction);
528 FindAllTabVectors(min_gutter_width);
533 if (textord_tabfind_show_initialtabs && initial_win !=
nullptr)
542 #ifndef GRAPHICS_DISABLED
543 for (
int i = 0; i < boxes.
size(); ++i) {
544 TBOX box = boxes[i]->bounding_box();
545 int left_x = box.
left();
546 int right_x = box.
right();
547 int top_y = box.
top();
548 int bottom_y = box.
bottom();
551 win->
Rectangle(left_x, bottom_y, right_x, top_y);
554 #endif // GRAPHICS_DISABLED
559 ScrollView* TabFind::FindTabBoxes(
int min_gutter_width,
560 double tabfind_aligned_gap_fraction) {
561 left_tab_boxes_.
clear();
562 right_tab_boxes_.
clear();
564 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> gsearch(
this);
565 gsearch.StartFullSearch();
567 while ((bbox = gsearch.NextFullSearch()) !=
nullptr) {
568 if (TestBoxForTabs(bbox, min_gutter_width, tabfind_aligned_gap_fraction)) {
578 left_tab_boxes_.
sort(SortByBoxLeft<BLOBNBOX>);
579 right_tab_boxes_.
sort(SortRightToLeft<BLOBNBOX>);
581 #ifndef GRAPHICS_DISABLED
582 if (textord_tabfind_show_initialtabs) {
587 DisplayBoxVector(left_tab_boxes_, tab_win);
588 DisplayBoxVector(right_tab_boxes_, tab_win);
591 #endif // GRAPHICS_DISABLED
595 bool TabFind::TestBoxForTabs(
BLOBNBOX* bbox,
int min_gutter_width,
596 double tabfind_aligned_gap_fraction) {
597 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> radsearch(
this);
600 int left_column_edge = bbox->
left_rule();
603 int left_x = box.
left();
604 int right_x = box.
right();
605 int top_y = box.
top();
606 int bottom_y = box.
bottom();
607 int height = box.
height();
610 tprintf(
"Column edges for blob at (%d,%d)->(%d,%d) are [%d, %d]\n",
611 left_x, top_y, right_x, bottom_y,
612 left_column_edge, right_column_edge);
616 radsearch.StartRadSearch((left_x + right_x)/2, (top_y + bottom_y)/2, radius);
622 static_cast<int>(height * tabfind_aligned_gap_fraction);
623 if (min_gutter_width > min_spacing)
624 min_spacing = min_gutter_width;
626 if (min_gutter_width > min_ragged_gutter)
627 min_ragged_gutter = min_gutter_width;
628 int target_right = left_x - min_spacing;
629 int target_left = right_x + min_spacing;
645 bool is_left_tab =
true;
646 bool is_right_tab =
true;
647 bool maybe_ragged_left =
true;
648 bool maybe_ragged_right =
true;
649 int maybe_left_tab_up = 0;
650 int maybe_right_tab_up = 0;
651 int maybe_left_tab_down = 0;
652 int maybe_right_tab_down = 0;
655 maybe_ragged_left =
false;
656 maybe_left_tab_up = -INT32_MAX;
657 maybe_left_tab_down = -INT32_MAX;
660 is_right_tab =
false;
661 maybe_ragged_right =
false;
662 maybe_right_tab_up = -INT32_MAX;
663 maybe_right_tab_down = -INT32_MAX;
667 while ((neighbour = radsearch.NextRadSearch()) !=
nullptr) {
668 if (neighbour == bbox)
671 int n_left = nbox.
left();
672 int n_right = nbox.
right();
674 tprintf(
"Neighbour at (%d,%d)->(%d,%d)\n",
675 n_left, nbox.
bottom(), n_right, nbox.
top());
678 if (n_right > right_column_edge || n_left < left_column_edge ||
679 left_x < neighbour->left_rule() || right_x > neighbour->
right_rule())
681 int n_mid_x = (n_left + n_right) / 2;
682 int n_mid_y = (nbox.
top() + nbox.
bottom()) / 2;
683 if (n_mid_x <= left_x && n_right >= target_right) {
688 maybe_left_tab_down = -INT32_MAX;
689 if (n_mid_y > bottom_y)
690 maybe_left_tab_up = -INT32_MAX;
691 }
else if (
NearlyEqual(left_x, n_left, alignment_tolerance)) {
694 if (n_mid_y > top_y && maybe_left_tab_up > -INT32_MAX)
696 if (n_mid_y < bottom_y && maybe_left_tab_down > -INT32_MAX)
697 ++maybe_left_tab_down;
698 }
else if (n_left < left_x && n_right >= left_x) {
701 tprintf(
"Maybe Not a left tab\n");
702 if (n_mid_y > top_y && maybe_left_tab_up > -INT32_MAX)
704 if (n_mid_y < bottom_y && maybe_left_tab_down > -INT32_MAX)
705 --maybe_left_tab_down;
707 if (n_left < left_x && nbox.
y_overlap(box) && n_right >= target_right) {
708 maybe_ragged_left =
false;
710 tprintf(
"Not a ragged left\n");
712 if (n_mid_x >= right_x && n_left <= target_left) {
715 is_right_tab =
false;
717 maybe_right_tab_down = -INT32_MAX;
718 if (n_mid_y > bottom_y)
719 maybe_right_tab_up = -INT32_MAX;
720 }
else if (
NearlyEqual(right_x, n_right, alignment_tolerance)) {
722 tprintf(
"Maybe a right tab\n");
723 if (n_mid_y > top_y && maybe_right_tab_up > -INT32_MAX)
724 ++maybe_right_tab_up;
725 if (n_mid_y < bottom_y && maybe_right_tab_down > -INT32_MAX)
726 ++maybe_right_tab_down;
727 }
else if (n_right > right_x && n_left <= right_x) {
730 tprintf(
"Maybe Not a right tab\n");
731 if (n_mid_y > top_y && maybe_right_tab_up > -INT32_MAX)
732 --maybe_right_tab_up;
733 if (n_mid_y < bottom_y && maybe_right_tab_down > -INT32_MAX)
734 --maybe_right_tab_down;
736 if (n_right > right_x && nbox.
y_overlap(box) && n_left <= target_left) {
737 maybe_ragged_right =
false;
739 tprintf(
"Not a ragged right\n");
741 if (maybe_left_tab_down == -INT32_MAX && maybe_left_tab_up == -INT32_MAX &&
742 maybe_right_tab_down == -INT32_MAX && maybe_right_tab_up == -INT32_MAX)
745 if (is_left_tab || maybe_left_tab_up > 1 || maybe_left_tab_down > 1) {
747 }
else if (maybe_ragged_left && ConfirmRaggedLeft(bbox, min_ragged_gutter)) {
752 if (is_right_tab || maybe_right_tab_up > 1 || maybe_right_tab_down > 1) {
754 }
else if (maybe_ragged_right &&
755 ConfirmRaggedRight(bbox, min_ragged_gutter)) {
761 tprintf(
"Left result = %s, Right result=%s\n",
772 bool TabFind::ConfirmRaggedLeft(
BLOBNBOX* bbox,
int min_gutter) {
775 search_box.set_left(search_box.left() - min_gutter);
776 return NothingYOverlapsInBox(search_box, bbox->
bounding_box());
781 bool TabFind::ConfirmRaggedRight(
BLOBNBOX* bbox,
int min_gutter) {
783 search_box.
set_left(search_box.right());
784 search_box.set_right(search_box.right() + min_gutter);
785 return NothingYOverlapsInBox(search_box, bbox->
bounding_box());
790 bool TabFind::NothingYOverlapsInBox(
const TBOX& search_box,
791 const TBOX& target_box) {
793 rsearch.StartRectSearch(search_box);
795 while ((blob = rsearch.NextRectSearch()) !=
nullptr) {
797 if (box.
y_overlap(target_box) && !(box == target_box))
803 void TabFind::FindAllTabVectors(
int min_gutter_width) {
805 TabVector_LIST dummy_vectors;
816 &vertical_x, &vertical_y);
820 &vertical_x, &vertical_y);
821 if (vector_count > 0)
825 dummy_vectors.clear();
826 for (
int i = 0; i < left_tab_boxes_.
size(); ++i) {
827 BLOBNBOX* bbox = left_tab_boxes_[i];
831 for (
int i = 0; i < right_tab_boxes_.
size(); ++i) {
832 BLOBNBOX* bbox = right_tab_boxes_[i];
837 tprintf(
"Beginning real tab search with vertical = %d,%d...\n",
838 vertical_x, vertical_y);
844 &dummy_vectors, &vertical_x, &vertical_y);
846 &dummy_vectors, &vertical_x, &vertical_y);
848 &dummy_vectors, &vertical_x, &vertical_y);
850 &dummy_vectors, &vertical_x, &vertical_y);
852 TabVector_IT v_it(&vectors_);
853 v_it.add_list_after(&dummy_vectors);
855 SetVerticalSkewAndParallelize(vertical_x, vertical_y);
860 int min_gutter_width, TabVector_LIST*
vectors,
861 int* vertical_x,
int* vertical_y) {
862 TabVector_IT vector_it(
vectors);
863 int vector_count = 0;
868 for (
int i = 0; i < boxes.
size(); ++i) {
872 TabVector* vector = FindTabVector(search_size_multiple, min_gutter_width,
874 bbox, vertical_x, vertical_y);
875 if (vector !=
nullptr) {
877 vector_it.add_to_end(vector);
891 TabVector* TabFind::FindTabVector(
int search_size_multiple,
892 int min_gutter_width,
895 int* vertical_x,
int* vertical_y) {
897 AlignedBlobParams align_params(*vertical_x, *vertical_y,
899 search_size_multiple, min_gutter_width,
907 void TabFind::SetVerticalSkewAndParallelize(
int vertical_x,
int vertical_y) {
911 tprintf(
"Vertical skew vector=(%d,%d)\n",
913 v_it_.set_to_list(&vectors_);
914 for (v_it_.mark_cycle_pt(); !v_it_.cycled_list(); v_it_.forward()) {
915 TabVector* v = v_it_.data();
923 void TabFind::SortVectors() {
925 v_it_.set_to_list(&vectors_);
929 void TabFind::EvaluateTabs() {
930 TabVector_IT rule_it(&vectors_);
931 for (rule_it.mark_cycle_pt(); !rule_it.cycled_list(); rule_it.forward()) {
932 TabVector* tab = rule_it.data();
933 if (!tab->IsSeparator()) {
937 tab->Print(
"Too few boxes");
938 delete rule_it.extract();
939 v_it_.set_to_list(&vectors_);
941 tab->Print(
"Evaluated tab");
950 void TabFind::ComputeColumnWidths(
ScrollView* tab_win,
951 ColPartitionGrid* part_grid) {
952 #ifndef GRAPHICS_DISABLED
953 if (tab_win !=
nullptr)
955 #endif // GRAPHICS_DISABLED
958 STATS col_widths(0, col_widths_size + 1);
959 ApplyPartitionsToColumnWidths(part_grid, &col_widths);
960 #ifndef GRAPHICS_DISABLED
961 if (tab_win !=
nullptr) {
964 #endif // GRAPHICS_DISABLED
968 MakeColumnWidths(col_widths_size, &col_widths);
970 ApplyPartitionsToColumnWidths(part_grid,
nullptr);
979 void TabFind::ApplyPartitionsToColumnWidths(ColPartitionGrid* part_grid,
984 gsearch.StartFullSearch();
986 while ((part = gsearch.NextFullSearch()) !=
nullptr) {
987 BLOBNBOX_C_IT blob_it(part->boxes());
990 BLOBNBOX* left_blob = blob_it.data();
991 blob_it.move_to_last();
992 BLOBNBOX* right_blob = blob_it.data();
995 if (left_vector ==
nullptr || left_vector->IsRightTab())
999 if (right_vector ==
nullptr || right_vector->IsLeftTab())
1005 int width = line_right - line_left;
1006 if (col_widths !=
nullptr) {
1007 AddPartnerVector(left_blob, right_blob, left_vector, right_vector);
1012 ICOORDELT_IT it(&column_widths_);
1013 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1015 if (NearlyEqual<int>(width, w->
y(), 1)) {
1017 if (true_width <= w->y() && true_width > w->
x())
1018 w->
set_x(true_width);
1029 void TabFind::MakeColumnWidths(
int col_widths_size,
STATS* col_widths) {
1030 ICOORDELT_IT w_it(&column_widths_);
1031 int total_col_count = col_widths->
get_total();
1033 int width = col_widths->
mode();
1034 int col_count = col_widths->
pile_count(width);
1035 col_widths->
add(width, -col_count);
1037 for (
int left = width - 1; left > 0 &&
1040 int new_count = col_widths->
pile_count(left);
1041 col_count += new_count;
1042 col_widths->
add(left, -new_count);
1044 for (
int right = width + 1; right < col_widths_size &&
1047 int new_count = col_widths->
pile_count(right);
1048 col_count += new_count;
1049 col_widths->
add(right, -new_count);
1054 w_it.add_after_then_move(w);
1056 tprintf(
"Column of width %d has %d = %.2f%% lines\n",
1058 100.0 * col_count / total_col_count);
1065 void TabFind::MarkVerticalText() {
1067 tprintf(
"Checking for vertical lines\n");
1069 gsearch.StartFullSearch();
1071 while ((blob = gsearch.NextFullSearch()) !=
nullptr) {
1080 int TabFind::FindMedianGutterWidth(TabVector_LIST *lines) {
1081 TabVector_IT it(lines);
1082 int prev_right = -1;
1084 STATS gaps(0, max_gap);
1085 STATS heights(0, max_gap);
1086 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1087 TabVector* v = it.data();
1088 TabVector* partner = v->GetSinglePartner();
1089 if (!v->IsLeftTab() || v->IsSeparator() || !partner)
continue;
1090 heights.add(partner->startpt().x() - v->startpt().x(), 1);
1091 if (prev_right > 0 && v->startpt().x() > prev_right) {
1092 gaps.add(v->startpt().x() - prev_right, 1);
1094 prev_right = partner->startpt().x();
1097 tprintf(
"TabGutter total %d median_gap %.2f median_hgt %.2f\n",
1098 gaps.get_total(), gaps.median(), heights.median());
1100 return static_cast<int>(gaps.median());
1109 bool look_left,
bool ignore_images,
1110 double min_overlap_fraction,
1111 int gap_limit,
int top_y,
int bottom_y) {
1112 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> sidesearch(
this);
1114 int left = box.
left();
1115 int right = box.
right();
1116 int mid_x = (left + right) / 2;
1117 sidesearch.StartSideSearch(mid_x, bottom_y, top_y);
1122 while ((neighbour = sidesearch.NextSideSearch(look_left)) !=
nullptr) {
1124 tprintf(
"Adjacent blob: considering box:");
1127 if (neighbour == bbox ||
1131 int n_top_y = nbox.
top();
1132 int n_bottom_y = nbox.
bottom();
1133 int v_overlap = std::min(n_top_y, top_y) - std::max(n_bottom_y, bottom_y);
1134 int height = top_y - bottom_y;
1135 int n_height = n_top_y - n_bottom_y;
1136 if (v_overlap > min_overlap_fraction * std::min(height, n_height) &&
1137 (min_overlap_fraction == 0.0 || !
DifferentSizes(height, n_height))) {
1138 int n_left = nbox.
left();
1139 int n_right = nbox.
right();
1140 int h_gap = std::max(n_left, left) - std::min(n_right, right);
1141 int n_mid_x = (n_left + n_right) / 2;
1142 if (look_left == (n_mid_x < mid_x) && n_mid_x != mid_x) {
1143 if (h_gap > gap_limit) {
1146 tprintf(
"Giving up due to big gap = %d vs %d\n",
1155 tprintf(
"Collision with like tab of type %d at %d,%d\n",
1163 if (result ==
nullptr || h_gap < best_gap) {
1176 tprintf(
"Insufficient overlap\n");
1180 tprintf(
"Giving up due to end of search\n");
1189 TabVector* left, TabVector* right) {
1192 if (left->IsSeparator()) {
1195 if (v !=
nullptr && v != left && v->IsLeftTab() &&
1196 v->XAtY(left_box.
top()) > left->XAtY(left_box.
top())) {
1198 left->ExtendToBox(left_blob);
1203 v_it_.move_to_first();
1206 if (right->IsSeparator()) {
1209 tprintf(
"Box edge (%d,%d-%d)",
1211 right->Print(
" looking for improvement for");
1214 if (v !=
nullptr && v != right && v->IsRightTab() &&
1215 v->XAtY(right_box.
top()) < right->XAtY(right_box.
top())) {
1217 right->ExtendToBox(right_blob);
1219 right->Print(
"Extended vector");
1226 v_it_.move_to_first();
1228 right->Print(
"Created new vector");
1232 left->AddPartner(right);
1233 right->AddPartner(left);
1238 void TabFind::CleanupTabs() {
1242 TabVector_IT it(&vectors_);
1243 TabVector_IT dead_it(&dead_vectors_);
1244 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1245 TabVector* v = it.data();
1246 if (v->IsSeparator() || v->Partnerless()) {
1247 dead_it.add_after_then_move(it.extract());
1248 v_it_.set_to_list(&vectors_);
1257 BLOBNBOX_IT it(blobs);
1258 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1259 it.data()->rotate_box(rotation);
1265 bool TabFind::Deskew(TabVector_LIST* hlines, BLOBNBOX_LIST* image_blobs,
1267 ComputeDeskewVectors(deskew, reskew);
1277 TabVector_IT h_it(hlines);
1278 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
1282 TabVector_IT d_it(&dead_vectors_);
1283 for (d_it.mark_cycle_pt(); !d_it.cycled_list(); d_it.forward()) {
1284 TabVector* d = d_it.data();
1287 SetVerticalSkewAndParallelize(0, 1);
1290 grid_box.rotate_large(*deskew);
1291 Init(
gridsize(), grid_box.botleft(), grid_box.topright());
1301 TabVector_LIST* horizontal_lines,
1302 int* min_gutter_width) {
1306 TabVector_LIST ex_verticals;
1307 TabVector_IT ex_v_it(&ex_verticals);
1308 TabVector_LIST vlines;
1309 TabVector_IT v_it(&vlines);
1310 while (!v_it_.empty()) {
1314 ex_v_it.add_after_then_move(v);
1316 v_it.add_after_then_move(v);
1323 int median_gutter = FindMedianGutterWidth(&vlines);
1324 if (median_gutter > *min_gutter_width)
1325 *min_gutter_width = median_gutter;
1327 TabVector_IT h_it(horizontal_lines);
1328 for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) {
1332 v_it_.add_list_after(horizontal_lines);
1333 v_it_.move_to_first();
1334 h_it.set_to_list(horizontal_lines);
1335 h_it.add_list_after(&ex_verticals);
1346 v_it_.move_to_first();
1347 for (v_it_.mark_cycle_pt(); !v_it_.cycled_list(); v_it_.forward()) {
1348 if (!v_it_.data()->IsSeparator())
1349 delete v_it_.extract();
1357 TabVector_LIST temp_list;
1358 TabVector_IT temp_it(&temp_list);
1359 v_it_.move_to_first();
1363 while (!v_it_.empty()) {
1367 temp_it.add_before_then_move(v);
1369 v_it_.add_list_after(&temp_list);
1370 v_it_.move_to_first();
1373 int tmp = grid_box.
left();
1380 void TabFind::ComputeDeskewVectors(
FCOORD* deskew,
FCOORD* reskew) {
1382 length = sqrt(length);
1385 reskew->
set_x(deskew->
x());
1386 reskew->
set_y(-deskew->
y());
1391 void TabFind::ApplyTabConstraints() {
1392 TabVector_IT it(&vectors_);
1393 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1394 TabVector* v = it.data();
1395 v->SetupConstraints();
1397 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1398 TabVector* v = it.data();
1402 v->SetupPartnerConstraints();
1407 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1408 TabVector* v = it.data();
1409 if (!v->IsRightTab())
1412 TabVector_IT partner_it(it);
1413 for (partner_it.forward(); !partner_it.at_first(); partner_it.forward()) {
1414 TabVector* partner = partner_it.data();
1415 if (!partner->IsLeftTab() || !v->VOverlap(*partner))
1417 v->SetupPartnerConstraints(partner);
1421 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
1422 TabVector* v = it.data();
1423 if (!v->IsSeparator())
1424 v->ApplyConstraints();