tesseract v5.3.3.20231005
alignedblob.cpp
Go to the documentation of this file.
1
2// File: alignedblob.cpp
3// Description: Subclass of BBGrid to find vertically aligned blobs.
4// Author: Ray Smith
5//
6// (C) Copyright 2008, Google Inc.
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10// http://www.apache.org/licenses/LICENSE-2.0
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
18
19#ifdef HAVE_CONFIG_H
20# include "config_auto.h"
21#endif
22
23#include "alignedblob.h"
24
25#include <algorithm>
26
27namespace tesseract {
28
29INT_VAR(textord_debug_tabfind, 0, "Debug tab finding");
30INT_VAR(textord_debug_bugs, 0, "Turn on output related to bugs in tab finding");
31static INT_VAR(textord_testregion_left, -1,
32 "Left edge of debug reporting rectangle in Leptonica coords "
33 "(bottom=0/top=height), with horizontal lines x/y-flipped");
34static INT_VAR(textord_testregion_top, INT32_MAX,
35 "Top edge of debug reporting rectangle in Leptonica coords "
36 "(bottom=0/top=height), with horizontal lines x/y-flipped");
37static INT_VAR(textord_testregion_right, INT32_MAX,
38 "Right edge of debug rectangle in Leptonica coords "
39 "(bottom=0/top=height), with horizontal lines x/y-flipped");
40static INT_VAR(textord_testregion_bottom, -1,
41 "Bottom edge of debug rectangle in Leptonica coords "
42 "(bottom=0/top=height), with horizontal lines x/y-flipped");
43BOOL_VAR(textord_debug_printable, false, "Make debug windows printable");
44
45// Fraction of resolution used as alignment tolerance for aligned tabs.
46const double kAlignedFraction = 0.03125;
47// Fraction of resolution used as alignment tolerance for ragged tabs.
48const double kRaggedFraction = 2.5;
49// Fraction of height used as a minimum gutter gap for aligned blobs.
50const double kAlignedGapFraction = 0.75;
51// Fraction of height used as a minimum gutter gap for ragged tabs.
52const double kRaggedGapFraction = 1.0;
53// Constant number of pixels used as alignment tolerance for line finding.
54const int kVLineAlignment = 3;
55// Constant number of pixels used as gutter gap tolerance for line finding.
56const int kVLineGutter = 1;
57// Constant number of pixels used as the search size for line finding.
58const int kVLineSearchSize = 150;
59// Min number of points to accept for a ragged tab stop.
60const int kMinRaggedTabs = 5;
61// Min number of points to accept for an aligned tab stop.
62const int kMinAlignedTabs = 4;
63// Constant number of pixels minimum height of a vertical line.
64const int kVLineMinLength = 300;
65// Minimum gradient for a vertical tab vector. Used to prune away junk
66// tab vectors with what would be a ridiculously large skew angle.
67// Value corresponds to tan(90 - max allowed skew angle)
68const double kMinTabGradient = 4.0;
69// Tolerance to skew on top of current estimate of skew. Divide x or y length
70// by kMaxSkewFactor to get the y or x skew distance.
71// If the angle is small, the angle in degrees is roughly 60/kMaxSkewFactor.
72const int kMaxSkewFactor = 15;
73
74// Constructor to set the parameters for finding aligned and ragged tabs.
75// Vertical_x and vertical_y are the current estimates of the true vertical
76// direction (up) in the image. Height is the height of the starter blob.
77// v_gap_multiple is the multiple of height that will be used as a limit
78// on vertical gap before giving up and calling the line ended.
79// resolution is the original image resolution, and align0 indicates the
80// type of tab stop to be found.
81AlignedBlobParams::AlignedBlobParams(int vertical_x, int vertical_y, int height, int v_gap_multiple,
82 int min_gutter_width, int resolution, TabAlignment align0)
83 : right_tab(align0 == TA_RIGHT_RAGGED || align0 == TA_RIGHT_ALIGNED)
84 , ragged(align0 == TA_LEFT_RAGGED || align0 == TA_RIGHT_RAGGED)
85 , alignment(align0)
86 , confirmed_type(TT_CONFIRMED)
87 , min_length(0) {
88 // Set the tolerances according to the type of line sought.
89 // For tab search, these are based on the image resolution for most, or
90 // the height of the starting blob for the maximum vertical gap.
91 max_v_gap = height * v_gap_multiple;
92 if (ragged) {
93 // In the case of a ragged edge, we are much more generous with the
94 // inside alignment fraction, but also require a much bigger gutter.
97 l_align_tolerance = static_cast<int>(resolution * kRaggedFraction + 0.5);
98 r_align_tolerance = static_cast<int>(resolution * kAlignedFraction + 0.5);
99 } else {
100 l_align_tolerance = static_cast<int>(resolution * kAlignedFraction + 0.5);
101 r_align_tolerance = static_cast<int>(resolution * kRaggedFraction + 0.5);
102 }
104 } else {
106 l_align_tolerance = static_cast<int>(resolution * kAlignedFraction + 0.5);
107 r_align_tolerance = static_cast<int>(resolution * kAlignedFraction + 0.5);
109 }
110 min_gutter = static_cast<int>(height * gutter_fraction + 0.5);
111 if (min_gutter < min_gutter_width) {
112 min_gutter = min_gutter_width;
113 }
114 // Fit the vertical vector into an ICOORD, which is 16 bit.
115 set_vertical(vertical_x, vertical_y);
116}
117
118// Constructor to set the parameters for finding vertical lines.
119// Vertical_x and vertical_y are the current estimates of the true vertical
120// direction (up) in the image. Width is the width of the starter blob.
121AlignedBlobParams::AlignedBlobParams(int vertical_x, int vertical_y, int width)
122 : gutter_fraction(0.0)
123 , right_tab(false)
124 , ragged(false)
125 , alignment(TA_SEPARATOR)
126 , confirmed_type(TT_VLINE)
127 , max_v_gap(kVLineSearchSize)
128 , min_gutter(kVLineGutter)
129 , min_points(1)
130 , min_length(kVLineMinLength) {
131 // Compute threshold for left and right alignment.
132 l_align_tolerance = std::max(kVLineAlignment, width);
133 r_align_tolerance = std::max(kVLineAlignment, width);
134
135 // Fit the vertical vector into an ICOORD, which is 16 bit.
136 set_vertical(vertical_x, vertical_y);
137}
138
139// Fit the vertical vector into an ICOORD, which is 16 bit.
140void AlignedBlobParams::set_vertical(int vertical_x, int vertical_y) {
141 int factor = 1;
142 if (vertical_y > INT16_MAX) {
143 factor = vertical_y / INT16_MAX + 1;
144 }
145 vertical.set_x(vertical_x / factor);
146 vertical.set_y(vertical_y / factor);
147}
148
149AlignedBlob::AlignedBlob(int gridsize, const ICOORD &bleft, const ICOORD &tright)
150 : BlobGrid(gridsize, bleft, tright) {}
151
152// Return true if the given coordinates are within the test rectangle
153// and the debug level is at least the given detail level.
154bool AlignedBlob::WithinTestRegion(int detail_level, int x, int y) {
155 if (textord_debug_tabfind < detail_level) {
156 return false;
157 }
158 return x >= textord_testregion_left && x <= textord_testregion_right &&
159 y <= textord_testregion_top && y >= textord_testregion_bottom;
160}
161
162#ifndef GRAPHICS_DISABLED
163
164// Display the tab codes of the BLOBNBOXes in this grid.
165ScrollView *AlignedBlob::DisplayTabs(const char *window_name, ScrollView *tab_win) {
166 if (tab_win == nullptr) {
167 tab_win = MakeWindow(0, 50, window_name);
168 }
169 // For every tab in the grid, display it.
171 gsearch.StartFullSearch();
172 BLOBNBOX *bbox;
173 while ((bbox = gsearch.NextFullSearch()) != nullptr) {
174 const TBOX &box = bbox->bounding_box();
175 int left_x = box.left();
176 int right_x = box.right();
177 int top_y = box.top();
178 int bottom_y = box.bottom();
179 TabType tabtype = bbox->left_tab_type();
180 if (tabtype != TT_NONE) {
181 if (tabtype == TT_MAYBE_ALIGNED) {
182 tab_win->Pen(ScrollView::BLUE);
183 } else if (tabtype == TT_MAYBE_RAGGED) {
184 tab_win->Pen(ScrollView::YELLOW);
185 } else if (tabtype == TT_CONFIRMED) {
186 tab_win->Pen(ScrollView::GREEN);
187 } else {
188 tab_win->Pen(ScrollView::GREY);
189 }
190 tab_win->Line(left_x, top_y, left_x, bottom_y);
191 }
192 tabtype = bbox->right_tab_type();
193 if (tabtype != TT_NONE) {
194 if (tabtype == TT_MAYBE_ALIGNED) {
195 tab_win->Pen(ScrollView::MAGENTA);
196 } else if (tabtype == TT_MAYBE_RAGGED) {
197 tab_win->Pen(ScrollView::ORANGE);
198 } else if (tabtype == TT_CONFIRMED) {
199 tab_win->Pen(ScrollView::RED);
200 } else {
201 tab_win->Pen(ScrollView::GREY);
202 }
203 tab_win->Line(right_x, top_y, right_x, bottom_y);
204 }
205 }
206 tab_win->Update();
207 return tab_win;
208}
209
210#endif // !GRAPHICS_DISABLED
211
212// Helper returns true if the total number of line_crossings of all the blobs
213// in the list is at least 2.
214static bool AtLeast2LineCrossings(BLOBNBOX_CLIST *blobs) {
215 BLOBNBOX_C_IT it(blobs);
216 int total_crossings = 0;
217 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
218 total_crossings += it.data()->line_crossings();
219 }
220 return total_crossings >= 2;
221}
222
223// Destructor.
224// It is defined here, so the compiler can create a single vtable
225// instead of weak vtables in every compilation unit.
226AlignedBlob::~AlignedBlob() = default;
227
228// Finds a vector corresponding to a set of vertically aligned blob edges
229// running through the given box. The type of vector returned and the
230// search parameters are determined by the AlignedBlobParams.
231// vertical_x and y are updated with an estimate of the real
232// vertical direction. (skew finding.)
233// Returns nullptr if no decent vector can be found.
235 int *vertical_x, int *vertical_y) {
236 int ext_start_y, ext_end_y;
237 BLOBNBOX_CLIST good_points;
238 // Search up and then down from the starting bbox.
239 TBOX box = bbox->bounding_box();
240 bool debug = WithinTestRegion(2, box.left(), box.bottom());
241 int pt_count = AlignTabs(align_params, false, bbox, &good_points, &ext_end_y);
242 pt_count += AlignTabs(align_params, true, bbox, &good_points, &ext_start_y);
243 BLOBNBOX_C_IT it(&good_points);
244 it.move_to_last();
245 box = it.data()->bounding_box();
246 int end_y = box.top();
247 int end_x = align_params.right_tab ? box.right() : box.left();
248 it.move_to_first();
249 box = it.data()->bounding_box();
250 int start_x = align_params.right_tab ? box.right() : box.left();
251 int start_y = box.bottom();
252 // Acceptable tab vectors must have a minimum number of points,
253 // have a minimum acceptable length, and have a minimum gradient.
254 // The gradient corresponds to the skew angle.
255 // Ragged tabs don't need to satisfy the gradient condition, as they
256 // will always end up parallel to the vertical direction.
257 bool at_least_2_crossings = AtLeast2LineCrossings(&good_points);
258 if ((pt_count >= align_params.min_points && end_y - start_y >= align_params.min_length &&
259 (align_params.ragged || end_y - start_y >= abs(end_x - start_x) * kMinTabGradient)) ||
260 at_least_2_crossings) {
261 int confirmed_points = 0;
262 // Count existing confirmed points to see if vector is acceptable.
263 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
264 bbox = it.data();
265 if (align_params.right_tab) {
266 if (bbox->right_tab_type() == align_params.confirmed_type) {
267 ++confirmed_points;
268 }
269 } else {
270 if (bbox->left_tab_type() == align_params.confirmed_type) {
271 ++confirmed_points;
272 }
273 }
274 }
275 // Ragged vectors are not allowed to use too many already used points.
276 if (!align_params.ragged || confirmed_points + confirmed_points < pt_count) {
277 const TBOX &box = bbox->bounding_box();
278 if (debug) {
279 tprintf("Confirming tab vector of %d pts starting at %d,%d\n", pt_count, box.left(),
280 box.bottom());
281 }
282 // Flag all the aligned neighbours as confirmed .
283 for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
284 bbox = it.data();
285 if (align_params.right_tab) {
286 bbox->set_right_tab_type(align_params.confirmed_type);
287 } else {
288 bbox->set_left_tab_type(align_params.confirmed_type);
289 }
290 if (debug) {
291 bbox->bounding_box().print();
292 }
293 }
294 // Now make the vector and return it.
295 TabVector *result =
296 TabVector::FitVector(align_params.alignment, align_params.vertical, ext_start_y,
297 ext_end_y, &good_points, vertical_x, vertical_y);
298 result->set_intersects_other_lines(at_least_2_crossings);
299 if (debug) {
300 tprintf("Box was %d, %d\n", box.left(), box.bottom());
301 result->Print("After fitting");
302 }
303 return result;
304 } else if (debug) {
305 tprintf("Ragged tab used too many used points: %d out of %d\n", confirmed_points, pt_count);
306 }
307 } else if (debug) {
308 tprintf(
309 "Tab vector failed basic tests: pt count %d vs min %d, "
310 "length %d vs min %d, min grad %g\n",
311 pt_count, align_params.min_points, end_y - start_y, align_params.min_length,
312 abs(end_x - start_x) * kMinTabGradient);
313 }
314 return nullptr;
315}
316
317// Find a set of blobs that are aligned in the given vertical
318// direction with the given blob. Returns a list of aligned
319// blobs and the number in the list.
320// For other parameters see FindAlignedBlob below.
321int AlignedBlob::AlignTabs(const AlignedBlobParams &params, bool top_to_bottom, BLOBNBOX *bbox,
322 BLOBNBOX_CLIST *good_points, int *end_y) {
323 int ptcount = 0;
324 BLOBNBOX_C_IT it(good_points);
325
326 TBOX box = bbox->bounding_box();
327 bool debug = WithinTestRegion(2, box.left(), box.bottom());
328 if (debug) {
329 tprintf("Starting alignment run at blob:");
330 box.print();
331 }
332 int x_start = params.right_tab ? box.right() : box.left();
333 while (bbox != nullptr) {
334 // Add the blob to the list if the appropriate side is a tab candidate,
335 // or if we are working on a ragged tab.
336 TabType type = params.right_tab ? bbox->right_tab_type() : bbox->left_tab_type();
337 if (((type != TT_NONE && type != TT_MAYBE_RAGGED) || params.ragged) &&
338 (it.empty() || it.data() != bbox)) {
339 if (top_to_bottom) {
340 it.add_before_then_move(bbox);
341 } else {
342 it.add_after_then_move(bbox);
343 }
344 ++ptcount;
345 }
346 // Find the next blob that is aligned with the current one.
347 // FindAlignedBlob guarantees that forward progress will be made in the
348 // top_to_bottom direction, and therefore eventually it will return nullptr,
349 // making this while (bbox != nullptr) loop safe.
350 bbox = FindAlignedBlob(params, top_to_bottom, bbox, x_start, end_y);
351 if (bbox != nullptr) {
352 box = bbox->bounding_box();
353 if (!params.ragged) {
354 x_start = params.right_tab ? box.right() : box.left();
355 }
356 }
357 }
358 if (debug) {
359 tprintf("Alignment run ended with %d pts at blob:", ptcount);
360 box.print();
361 }
362 return ptcount;
363}
364
365// Search vertically for a blob that is aligned with the input bbox.
366// The search parameters are determined by AlignedBlobParams.
367// top_to_bottom tells whether to search down or up.
368// The return value is nullptr if nothing was found in the search box
369// or if a blob was found in the gutter. On a nullptr return, end_y
370// is set to the edge of the search box or the leading edge of the
371// gutter blob if one was found.
372BLOBNBOX *AlignedBlob::FindAlignedBlob(const AlignedBlobParams &p, bool top_to_bottom,
373 BLOBNBOX *bbox, int x_start, int *end_y) {
374 TBOX box = bbox->bounding_box();
375 // If there are separator lines, get the column edges.
376 int left_column_edge = bbox->left_rule();
377 int right_column_edge = bbox->right_rule();
378 // start_y is used to guarantee that forward progress is made and the
379 // search does not go into an infinite loop. New blobs must extend the
380 // line beyond start_y.
381 int start_y = top_to_bottom ? box.bottom() : box.top();
382 if (WithinTestRegion(2, x_start, start_y)) {
383 tprintf("Column edges for blob at (%d,%d)->(%d,%d) are [%d, %d]\n", box.left(), box.top(),
384 box.right(), box.bottom(), left_column_edge, right_column_edge);
385 }
386 // Compute skew tolerance.
387 int skew_tolerance = p.max_v_gap / kMaxSkewFactor;
388 // Calculate xmin and xmax of the search box so that it contains
389 // all possibly relevant boxes up to p.max_v_gap above or below according
390 // to top_to_bottom.
391 // Start with a notion of vertical with the current estimate.
392 int x2 = (p.max_v_gap * p.vertical.x() + p.vertical.y() / 2) / p.vertical.y();
393 if (top_to_bottom) {
394 x2 = x_start - x2;
395 *end_y = start_y - p.max_v_gap;
396 } else {
397 x2 = x_start + x2;
398 *end_y = start_y + p.max_v_gap;
399 }
400 // Expand the box by an additional skew tolerance
401 int xmin = std::min(x_start, x2) - skew_tolerance;
402 int xmax = std::max(x_start, x2) + skew_tolerance;
403 // Now add direction-specific tolerances.
404 if (p.right_tab) {
405 xmax += p.min_gutter;
406 xmin -= p.l_align_tolerance;
407 } else {
408 xmax += p.r_align_tolerance;
409 xmin -= p.min_gutter;
410 }
411 // Setup a vertical search for an aligned blob.
412 GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> vsearch(this);
413 if (WithinTestRegion(2, x_start, start_y)) {
414 tprintf("Starting %s %s search at %d-%d,%d, search_size=%d, gutter=%d\n",
415 p.ragged ? "Ragged" : "Aligned", p.right_tab ? "Right" : "Left", xmin, xmax, start_y,
416 p.max_v_gap, p.min_gutter);
417 }
418 vsearch.StartVerticalSearch(xmin, xmax, start_y);
419 // result stores the best real return value.
420 BLOBNBOX *result = nullptr;
421 // The backup_result is not a tab candidate and can be used if no
422 // real tab candidate result is found.
423 BLOBNBOX *backup_result = nullptr;
424 // neighbour is the blob that is currently being investigated.
425 BLOBNBOX *neighbour = nullptr;
426 while ((neighbour = vsearch.NextVerticalSearch(top_to_bottom)) != nullptr) {
427 if (neighbour == bbox) {
428 continue;
429 }
430 TBOX nbox = neighbour->bounding_box();
431 int n_y = (nbox.top() + nbox.bottom()) / 2;
432 if ((!top_to_bottom && n_y > start_y + p.max_v_gap) ||
433 (top_to_bottom && n_y < start_y - p.max_v_gap)) {
434 if (WithinTestRegion(2, x_start, start_y)) {
435 tprintf("Neighbour too far at (%d,%d)->(%d,%d)\n", nbox.left(), nbox.bottom(), nbox.right(),
436 nbox.top());
437 }
438 break; // Gone far enough.
439 }
440 // It is CRITICAL to ensure that forward progress is made, (strictly
441 // in/decreasing n_y) or the caller could loop infinitely, while
442 // waiting for a sequence of blobs in a line to end.
443 // NextVerticalSearch alone does not guarantee this, as there may be
444 // more than one blob in a grid cell. See comment in AlignTabs.
445 if ((n_y < start_y) != top_to_bottom || nbox.y_overlap(box)) {
446 continue; // Only look in the required direction.
447 }
448 if (result != nullptr && result->bounding_box().y_gap(nbox) > gridsize()) {
449 return result; // This result is clear.
450 }
451 if (backup_result != nullptr && p.ragged && result == nullptr &&
452 backup_result->bounding_box().y_gap(nbox) > gridsize()) {
453 return backup_result; // This result is clear.
454 }
455
456 // If the neighbouring blob is the wrong side of a separator line, then it
457 // "doesn't exist" as far as we are concerned.
458 int x_at_n_y = x_start + (n_y - start_y) * p.vertical.x() / p.vertical.y();
459 if (x_at_n_y < neighbour->left_crossing_rule() || x_at_n_y > neighbour->right_crossing_rule()) {
460 continue; // Separator line in the way.
461 }
462 int n_left = nbox.left();
463 int n_right = nbox.right();
464 int n_x = p.right_tab ? n_right : n_left;
465 if (WithinTestRegion(2, x_start, start_y)) {
466 tprintf("neighbour at (%d,%d)->(%d,%d), n_x=%d, n_y=%d, xatn=%d\n", nbox.left(),
467 nbox.bottom(), nbox.right(), nbox.top(), n_x, n_y, x_at_n_y);
468 }
469 if (p.right_tab && n_left < x_at_n_y + p.min_gutter &&
470 n_right > x_at_n_y + p.r_align_tolerance &&
471 (p.ragged || n_left < x_at_n_y + p.gutter_fraction * nbox.height())) {
472 // In the gutter so end of line.
473 if (bbox->right_tab_type() >= TT_MAYBE_ALIGNED) {
474 bbox->set_right_tab_type(TT_DELETED);
475 }
476 *end_y = top_to_bottom ? nbox.top() : nbox.bottom();
477 if (WithinTestRegion(2, x_start, start_y)) {
478 tprintf("gutter\n");
479 }
480 return nullptr;
481 }
482 if (!p.right_tab && n_left < x_at_n_y - p.l_align_tolerance &&
483 n_right > x_at_n_y - p.min_gutter &&
484 (p.ragged || n_right > x_at_n_y - p.gutter_fraction * nbox.height())) {
485 // In the gutter so end of line.
486 if (bbox->left_tab_type() >= TT_MAYBE_ALIGNED) {
487 bbox->set_left_tab_type(TT_DELETED);
488 }
489 *end_y = top_to_bottom ? nbox.top() : nbox.bottom();
490 if (WithinTestRegion(2, x_start, start_y)) {
491 tprintf("gutter\n");
492 }
493 return nullptr;
494 }
495 if ((p.right_tab && neighbour->leader_on_right()) ||
496 (!p.right_tab && neighbour->leader_on_left())) {
497 continue; // Neighbours of leaders are not allowed to be used.
498 }
499 if (n_x <= x_at_n_y + p.r_align_tolerance && n_x >= x_at_n_y - p.l_align_tolerance) {
500 // Aligned so keep it. If it is a marked tab save it as result,
501 // otherwise keep it as backup_result to return in case of later failure.
502 if (WithinTestRegion(2, x_start, start_y)) {
503 tprintf("aligned, seeking%d, l=%d, r=%d\n", p.right_tab, neighbour->left_tab_type(),
504 neighbour->right_tab_type());
505 }
506 TabType n_type = p.right_tab ? neighbour->right_tab_type() : neighbour->left_tab_type();
507 if (n_type != TT_NONE && (p.ragged || n_type != TT_MAYBE_RAGGED)) {
508 if (result == nullptr) {
509 result = neighbour;
510 } else {
511 // Keep the closest neighbour by Euclidean distance.
512 // This prevents it from picking a tab blob in another column.
513 const TBOX &old_box = result->bounding_box();
514 int x_diff = p.right_tab ? old_box.right() : old_box.left();
515 x_diff -= x_at_n_y;
516 int y_diff = (old_box.top() + old_box.bottom()) / 2 - start_y;
517 int old_dist = x_diff * x_diff + y_diff * y_diff;
518 x_diff = n_x - x_at_n_y;
519 y_diff = n_y - start_y;
520 int new_dist = x_diff * x_diff + y_diff * y_diff;
521 if (new_dist < old_dist) {
522 result = neighbour;
523 }
524 }
525 } else if (backup_result == nullptr) {
526 if (WithinTestRegion(2, x_start, start_y)) {
527 tprintf("Backup\n");
528 }
529 backup_result = neighbour;
530 } else {
531 TBOX backup_box = backup_result->bounding_box();
532 if ((p.right_tab && backup_box.right() < nbox.right()) ||
533 (!p.right_tab && backup_box.left() > nbox.left())) {
534 if (WithinTestRegion(2, x_start, start_y)) {
535 tprintf("Better backup\n");
536 }
537 backup_result = neighbour;
538 }
539 }
540 }
541 }
542 return result != nullptr ? result : backup_result;
543}
544
545} // namespace tesseract.
#define BOOL_VAR(name, val, comment)
Definition: params.h:360
#define INT_VAR(name, val, comment)
Definition: params.h:357
@ TBOX
const double y
const char * p
const int kVLineMinLength
Definition: alignedblob.cpp:64
const int kMinAlignedTabs
Definition: alignedblob.cpp:62
const int kVLineSearchSize
Definition: alignedblob.cpp:58
void tprintf(const char *format,...)
Definition: tprintf.cpp:41
const int kVLineGutter
Definition: alignedblob.cpp:56
const double kAlignedGapFraction
Definition: alignedblob.cpp:50
bool textord_debug_printable
Definition: alignedblob.cpp:43
int textord_debug_tabfind
Definition: alignedblob.cpp:29
const double kRaggedGapFraction
Definition: alignedblob.cpp:52
@ TA_RIGHT_ALIGNED
Definition: tabvector.h:45
@ TA_RIGHT_RAGGED
Definition: tabvector.h:46
@ TA_SEPARATOR
Definition: tabvector.h:47
@ TA_LEFT_RAGGED
Definition: tabvector.h:43
const int kVLineAlignment
Definition: alignedblob.cpp:54
const double kAlignedFraction
Definition: alignedblob.cpp:46
const int kMaxSkewFactor
Definition: alignedblob.cpp:72
int textord_debug_bugs
Definition: alignedblob.cpp:30
const double kRaggedFraction
Definition: alignedblob.cpp:48
const int kMinRaggedTabs
Definition: alignedblob.cpp:60
const double kMinTabGradient
Definition: alignedblob.cpp:68
@ TT_MAYBE_RAGGED
Definition: blobbox.h:64
@ TT_VLINE
Definition: blobbox.h:67
@ TT_MAYBE_ALIGNED
Definition: blobbox.h:65
@ TT_CONFIRMED
Definition: blobbox.h:66
@ TT_DELETED
Definition: blobbox.h:63
@ TT_NONE
Definition: blobbox.h:62
type
Definition: upload.py:458
const TBOX & bounding_box() const
Definition: blobbox.h:239
void set_left_tab_type(TabType new_type)
Definition: blobbox.h:289
TabType left_tab_type() const
Definition: blobbox.h:286
void set_right_tab_type(TabType new_type)
Definition: blobbox.h:295
TabType right_tab_type() const
Definition: blobbox.h:292
integer coordinate
Definition: points.h:36
void set_x(TDimension xin)
rewrite function
Definition: points.h:67
void set_y(TDimension yin)
rewrite function
Definition: points.h:71
TDimension left() const
Definition: rect.h:82
TDimension top() const
Definition: rect.h:68
void print() const
Definition: rect.h:289
TDimension right() const
Definition: rect.h:89
TDimension bottom() const
Definition: rect.h:75
void set_vertical(int vertical_x, int vertical_y)
AlignedBlobParams(int vertical_x, int vertical_y, int height, int v_gap_multiple, int min_gutter_width, int resolution, TabAlignment alignment0)
Definition: alignedblob.cpp:81
AlignedBlob(int gridsize, const ICOORD &bleft, const ICOORD &tright)
static bool WithinTestRegion(int detail_level, int x, int y)
TabVector * FindVerticalAlignment(AlignedBlobParams align_params, BLOBNBOX *bbox, int *vertical_x, int *vertical_y)
ScrollView * DisplayTabs(const char *window_name, ScrollView *tab_win)
void StartFullSearch()
Definition: bbgrid.h:701
BBC * NextFullSearch()
Definition: bbgrid.h:711
int gridsize() const
Definition: bbgrid.h:63
ScrollView * MakeWindow(int x, int y, const char *window_name)
Definition: bbgrid.h:633
void set_intersects_other_lines(bool value)
Definition: tabvector.h:174
static TabVector * FitVector(TabAlignment alignment, ICOORD vertical, int extended_start_y, int extended_end_y, BLOBNBOX_CLIST *good_points, int *vertical_x, int *vertical_y)
Definition: tabvector.cpp:176
void Print(const char *prefix)
Definition: tabvector.cpp:518
void Line(int x1, int y1, int x2, int y2)
Definition: scrollview.cpp:498
void Pen(Color color)
Definition: scrollview.cpp:710
static void Update()
Definition: scrollview.cpp:700