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
ocrblock.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 * File: ocrblock.cpp (Formerly block.c)
3 * Description: BLOCK member functions and iterator functions.
4 * Author: Ray Smith
5 * Created: Fri Mar 15 09:41:28 GMT 1991
6 *
7 * (C) Copyright 1991, Hewlett-Packard Ltd.
8 ** Licensed under the Apache License, Version 2.0 (the "License");
9 ** you may not use this file except in compliance with the License.
10 ** You may obtain a copy of the License at
11 ** http://www.apache.org/licenses/LICENSE-2.0
12 ** Unless required by applicable law or agreed to in writing, software
13 ** distributed under the License is distributed on an "AS IS" BASIS,
14 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 ** See the License for the specific language governing permissions and
16 ** limitations under the License.
17 *
18 **********************************************************************/
19
20 #include "mfcpch.h"
21 #include <stdlib.h>
22 #include "blckerr.h"
23 #include "ocrblock.h"
24 #include "stepblob.h"
25 #include "tprintf.h"
26
27 #define BLOCK_LABEL_HEIGHT 150 //char height of block id
28
29 ELISTIZE (BLOCK)
35 BLOCK::BLOCK(const char *name, //< filename
36 BOOL8 prop, //< proportional
37 inT16 kern, //< kerning
38 inT16 space, //< spacing
39 inT16 xmin, //< bottom left
40 inT16 ymin, inT16 xmax, //< top right
41 inT16 ymax)
42 : PDBLK (xmin, ymin, xmax, ymax),
43 filename(name),
44 re_rotation_(1.0f, 0.0f),
45 classify_rotation_(1.0f, 0.0f),
46 skew_(1.0f, 0.0f) {
47 ICOORDELT_IT left_it = &leftside;
48 ICOORDELT_IT right_it = &rightside;
49
50 proportional = prop;
51 right_to_left_ = false;
52 kerning = kern;
53 spacing = space;
54 font_class = -1; //not assigned
55 cell_over_xheight_ = 2.0f;
56 hand_poly = NULL;
57 left_it.set_to_list (&leftside);
58 right_it.set_to_list (&rightside);
59 //make default box
60 left_it.add_to_end (new ICOORDELT (xmin, ymin));
61 left_it.add_to_end (new ICOORDELT (xmin, ymax));
62 right_it.add_to_end (new ICOORDELT (xmax, ymin));
63 right_it.add_to_end (new ICOORDELT (xmax, ymax));
64 }
65
72 int decreasing_top_order( //
73 const void *row1,
74 const void *row2) {
75 return (*(ROW **) row2)->bounding_box ().top () -
76 (*(ROW **) row1)->bounding_box ().top ();
77 }
78
79
85 void BLOCK::rotate(const FCOORD& rotation) {
86 poly_block()->rotate(rotation);
87 box = *poly_block()->bounding_box();
88 }
89
96 void BLOCK::reflect_polygon_in_y_axis() {
97 poly_block()->reflect_in_y_axis();
98 box = *poly_block()->bounding_box();
99 }
100
107 void BLOCK::sort_rows() { // order on "top"
108 ROW_IT row_it(&rows);
109
110 row_it.sort (decreasing_top_order);
111 }
112
113
121 void BLOCK::compress() { // squash it up
122 #define ROW_SPACING 5
123
124 ROW_IT row_it(&rows);
125 ROW *row;
126 ICOORD row_spacing (0, ROW_SPACING);
127
128 ICOORDELT_IT icoordelt_it;
129
130 sort_rows();
131
132 box = TBOX (box.topleft (), box.topleft ());
133 box.move_bottom_edge (ROW_SPACING);
134 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
135 row = row_it.data ();
136 row->move (box.botleft () - row_spacing -
137 row->bounding_box ().topleft ());
138 box += row->bounding_box ();
139 }
140
141 leftside.clear ();
142 icoordelt_it.set_to_list (&leftside);
143 icoordelt_it.add_to_end (new ICOORDELT (box.left (), box.bottom ()));
144 icoordelt_it.add_to_end (new ICOORDELT (box.left (), box.top ()));
145 rightside.clear ();
146 icoordelt_it.set_to_list (&rightside);
147 icoordelt_it.add_to_end (new ICOORDELT (box.right (), box.bottom ()));
148 icoordelt_it.add_to_end (new ICOORDELT (box.right (), box.top ()));
149 }
150
151
159 void BLOCK::check_pitch() { // check prop
160 // tprintf("Missing FFT fixed pitch stuff!\n");
161 pitch = -1;
162 }
163
164
171 void BLOCK::compress( // squash it up
172 const ICOORD vec // and move
173 ) {
174 box.move (vec);
175 compress();
176 }
177
178
185 void BLOCK::print( //print list of sides
186 FILE *, //< file to print on
187 BOOL8 dump //< print full detail
188 ) {
189 ICOORDELT_IT it = &leftside; //iterator
190
191 box.print ();
192 tprintf ("Proportional= %s\n", proportional ? "TRUE" : "FALSE");
193 tprintf ("Kerning= %d\n", kerning);
194 tprintf ("Spacing= %d\n", spacing);
195 tprintf ("Fixed_pitch=%d\n", pitch);
196 tprintf ("Filename= %s\n", filename.string ());
197
198 if (dump) {
199 tprintf ("Left side coords are:\n");
200 for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
201 tprintf ("(%d,%d) ", it.data ()->x (), it.data ()->y ());
202 tprintf ("\n");
203 tprintf ("Right side coords are:\n");
204 it.set_to_list (&rightside);
205 for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ())
206 tprintf ("(%d,%d) ", it.data ()->x (), it.data ()->y ());
207 tprintf ("\n");
208 }
209 }
210
217 BLOCK & BLOCK::operator= ( //assignment
218 const BLOCK & source //from this
219 ) {
220 this->ELIST_LINK::operator= (source);
221 this->PDBLK::operator= (source);
222 proportional = source.proportional;
223 kerning = source.kerning;
224 spacing = source.spacing;
225 filename = source.filename; //STRINGs assign ok
226 if (!rows.empty ())
227 rows.clear ();
228 re_rotation_ = source.re_rotation_;
229 classify_rotation_ = source.classify_rotation_;
230 skew_ = source.skew_;
231 return *this;
232 }
233
234 // This function is for finding the approximate (horizontal) distance from
235 // the x-coordinate of the left edge of a symbol to the left edge of the
236 // text block which contains it. We are passed:
237 // segments - output of PB_LINE_IT::get_line() which contains x-coordinate
238 // intervals for the scan line going through the symbol's y-coordinate.
239 // Each element of segments is of the form (x()=start_x, y()=length).
240 // x - the x coordinate of the symbol we're interested in.
241 // margin - return value, the distance from x,y to the left margin of the
242 // block containing it.
243 // If all segments were to the right of x, we return false and 0.
244 bool LeftMargin(ICOORDELT_LIST *segments, int x, int *margin) {
245 bool found = false;
246 *margin = 0;
247 if (segments->empty())
248 return found;
249 ICOORDELT_IT seg_it(segments);
250 for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
251 int cur_margin = x - seg_it.data()->x();
252 if (cur_margin >= 0) {
253 if (!found) {
254 *margin = cur_margin;
255 } else if (cur_margin < *margin) {
256 *margin = cur_margin;
257 }
258 found = true;
259 }
260 }
261 return found;
262 }
263
264 // This function is for finding the approximate (horizontal) distance from
265 // the x-coordinate of the right edge of a symbol to the right edge of the
266 // text block which contains it. We are passed:
267 // segments - output of PB_LINE_IT::get_line() which contains x-coordinate
268 // intervals for the scan line going through the symbol's y-coordinate.
269 // Each element of segments is of the form (x()=start_x, y()=length).
270 // x - the x coordinate of the symbol we're interested in.
271 // margin - return value, the distance from x,y to the right margin of the
272 // block containing it.
273 // If all segments were to the left of x, we return false and 0.
274 bool RightMargin(ICOORDELT_LIST *segments, int x, int *margin) {
275 bool found = false;
276 *margin = 0;
277 if (segments->empty())
278 return found;
279 ICOORDELT_IT seg_it(segments);
280 for (seg_it.mark_cycle_pt(); !seg_it.cycled_list(); seg_it.forward()) {
281 int cur_margin = seg_it.data()->x() + seg_it.data()->y() - x;
282 if (cur_margin >= 0) {
283 if (!found) {
284 *margin = cur_margin;
285 } else if (cur_margin < *margin) {
286 *margin = cur_margin;
287 }
288 found = true;
289 }
290 }
291 return found;
292 }
293
294 // Compute the distance from the left and right ends of each row to the
295 // left and right edges of the block's polyblock. Illustration:
296 // ____________________________ _______________________
297 // | Howdy neighbor! | |rectangular blocks look|
298 // | This text is written to| |more like stacked pizza|
299 // |illustrate how useful poly- |boxes. |
300 // |blobs are in ----------- ------ The polyblob|
301 // |dealing with| _________ |for a BLOCK rec-|
302 // |harder layout| /===========\ |ords the possibly|
303 // |issues. | | _ _ | |skewed pseudo-|
304 // | You see this| | |_| \|_| | |rectangular |
305 // |text is flowed| | } | |boundary that|
306 // |around a mid-| \ ____ | |forms the ideal-|
307 // |cloumn portrait._____ \ / __|ized text margin|
308 // | Polyblobs exist| \ / |from which we should|
309 // |to account for insets| | | |measure paragraph|
310 // |which make otherwise| ----- |indentation. |
311 // ----------------------- ----------------------
312 //
313 // If we identify a drop-cap, we measure the left margin for the lines
314 // below the first line relative to one space past the drop cap. The
315 // first line's margin and those past the drop cap area are measured
316 // relative to the enclosing polyblock.
317 //
318 // TODO(rays): Before this will work well, we'll need to adjust the
319 // polyblob tighter around the text near images, as in:
320 // UNLV_AUTO:mag.3G0 page 2
321 // UNLV_AUTO:mag.3G4 page 16
322 void BLOCK::compute_row_margins() {
323 if (row_list()->empty() || row_list()->singleton()) {
324 return;
325 }
326
327 // If Layout analysis was not called, default to this.
328 POLY_BLOCK rect_block(bounding_box(), PT_FLOWING_TEXT);
329 POLY_BLOCK *pblock = &rect_block;
330 if (poly_block() != NULL) {
331 pblock = poly_block();
332 }
333
334 // Step One: Determine if there is a drop-cap.
335 // TODO(eger): Fix up drop cap code for RTL languages.
336 ROW_IT r_it(row_list());
337 ROW *first_row = r_it.data();
338 ROW *second_row = r_it.data_relative(1);
339
340 // initialize the bottom of a fictitious drop cap far above the first line.
341 int drop_cap_bottom = first_row->bounding_box().top() +
342 first_row->bounding_box().height();
343 int drop_cap_right = first_row->bounding_box().left();
344 int mid_second_line = second_row->bounding_box().top() -
345 second_row->bounding_box().height() / 2;
346 WERD_IT werd_it(r_it.data()->word_list()); // words of line one
347 if (!werd_it.empty()) {
348 C_BLOB_IT cblob_it(werd_it.data()->cblob_list());
349 for (cblob_it.mark_cycle_pt(); !cblob_it.cycled_list();
350 cblob_it.forward()) {
351 TBOX bbox = cblob_it.data()->bounding_box();
352 if (bbox.bottom() <= mid_second_line) {
353 // we found a real drop cap
354 first_row->set_has_drop_cap(true);
355 if (drop_cap_bottom > bbox.bottom())
356 drop_cap_bottom = bbox.bottom();
357 if (drop_cap_right < bbox.right())
358 drop_cap_right = bbox.right();
359 }
360 }
361 }
362
363 // Step Two: Calculate the margin from the text of each row to the block
364 // (or drop-cap) boundaries.
365 PB_LINE_IT lines(pblock);
366 r_it.set_to_list(row_list());
367 for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward()) {
368 ROW *row = r_it.data();
369 TBOX row_box = row->bounding_box();
370 int left_y = row->base_line(row_box.left()) + row->x_height();
371 int left_margin;
372 ICOORDELT_LIST *segments = lines.get_line(left_y);
373 LeftMargin(segments, row_box.left(), &left_margin);
374 delete segments;
375
376 if (row_box.top() >= drop_cap_bottom) {
377 int drop_cap_distance = row_box.left() - row->space() - drop_cap_right;
378 if (drop_cap_distance < 0)
379 drop_cap_distance = 0;
380 if (drop_cap_distance < left_margin)
381 left_margin = drop_cap_distance;
382 }
383
384 int right_y = row->base_line(row_box.right()) + row->x_height();
385 int right_margin;
386 segments = lines.get_line(right_y);
387 RightMargin(segments, row_box.right(), &right_margin);
388 delete segments;
389 row->set_lmargin(left_margin);
390 row->set_rmargin(right_margin);
391 }
392 }
393
394 /**********************************************************************
395 * PrintSegmentationStats
396 *
397 * Prints segmentation stats for the given block list.
398 **********************************************************************/
399
400 void PrintSegmentationStats(BLOCK_LIST* block_list) {
401 int num_blocks = 0;
402 int num_rows = 0;
403 int num_words = 0;
404 int num_blobs = 0;
405 BLOCK_IT block_it(block_list);
406 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
407 BLOCK* block = block_it.data();
408 ++num_blocks;
409 ROW_IT row_it(block->row_list());
410 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
411 ++num_rows;
412 ROW* row = row_it.data();
413 // Iterate over all werds in the row.
414 WERD_IT werd_it(row->word_list());
415 for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
416 WERD* werd = werd_it.data();
417 ++num_words;
418 num_blobs += werd->cblob_list()->length();
419 }
420 }
421 }
422 tprintf("Block list stats:\nBlocks = %d\nRows = %d\nWords = %d\nBlobs = %d\n",
423 num_blocks, num_rows, num_words, num_blobs);
424 }
425
426 /**********************************************************************
427 * ExtractBlobsFromSegmentation
428 *
429 * Extracts blobs from the given block list and adds them to the output list.
430 * The block list must have been created by performing a page segmentation.
431 **********************************************************************/
432
433 void ExtractBlobsFromSegmentation(BLOCK_LIST* blocks,
434 C_BLOB_LIST* output_blob_list) {
435 C_BLOB_IT return_list_it(output_blob_list);
436 BLOCK_IT block_it(blocks);
437 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
438 BLOCK* block = block_it.data();
439 ROW_IT row_it(block->row_list());
440 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
441 ROW* row = row_it.data();
442 // Iterate over all werds in the row.
443 WERD_IT werd_it(row->word_list());
444 for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
445 WERD* werd = werd_it.data();
446 return_list_it.move_to_last();
447 return_list_it.add_list_after(werd->cblob_list());
448 return_list_it.move_to_last();
449 return_list_it.add_list_after(werd->rej_cblob_list());
450 }
451 }
452 }
453 }
454
455 /**********************************************************************
456 * RefreshWordBlobsFromNewBlobs()
457 *
458 * Refreshes the words in the block_list by using blobs in the
459 * new_blobs list.
460 * Block list must have word segmentation in it.
461 * It consumes the blobs provided in the new_blobs list. The blobs leftover in
462 * the new_blobs list after the call weren't matched to any blobs of the words
463 * in block list.
464 * The output not_found_blobs is a list of blobs from the original segmentation
465 * in the block_list for which no corresponding new blobs were found.
466 **********************************************************************/
467
468 void RefreshWordBlobsFromNewBlobs(BLOCK_LIST* block_list,
469 C_BLOB_LIST* new_blobs,
470 C_BLOB_LIST* not_found_blobs) {
471 // Now iterate over all the blobs in the segmentation_block_list_, and just
472 // replace the corresponding c-blobs inside the werds.
473 BLOCK_IT block_it(block_list);
474 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
475 BLOCK* block = block_it.data();
476 // Iterate over all rows in the block.
477 ROW_IT row_it(block->row_list());
478 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
479 ROW* row = row_it.data();
480 // Iterate over all werds in the row.
481 WERD_IT werd_it(row->word_list());
482 WERD_LIST new_words;
483 WERD_IT new_words_it(&new_words);
484 for (werd_it.mark_cycle_pt(); !werd_it.cycled_list(); werd_it.forward()) {
485 WERD* werd = werd_it.extract();
486 WERD* new_werd = werd->ConstructWerdWithNewBlobs(new_blobs,
487 not_found_blobs);
488 if (new_werd) {
489 // Insert this new werd into the actual row's werd-list. Remove the
490 // existing one.
491 new_words_it.add_after_then_move(new_werd);
492 delete werd;
493 } else {
494 // Reinsert the older word back, for lack of better options.
495 // This is critical since dropping the words messes up segmentation:
496 // eg. 1st word in the row might otherwise have W_FUZZY_NON turned on.
497 new_words_it.add_after_then_move(werd);
498 }
499 }
500 // Get rid of the old word list & replace it with the new one.
501 row->word_list()->clear();
502 werd_it.move_to_first();
503 werd_it.add_list_after(&new_words);
504 }
505 }
506 }