Coverage Report

Created: 2026-10-28 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccmain/pageiterator.cpp
Line
Count
Source
1
///////////////////////////////////////////////////////////////////////
2
// File:        pageiterator.cpp
3
// Description: Iterator for tesseract page structure that avoids using
4
//              tesseract internal data structures.
5
// Author:      Ray Smith
6
//
7
// (C) Copyright 2010, Google Inc.
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 <tesseract/pageiterator.h>
21
#include "helpers.h"
22
#include "image.h"          // for Image
23
#include "pageres.h"
24
#include "tesseractclass.h"
25
26
#include <algorithm>
27
28
namespace tesseract {
29
30
PageIterator::PageIterator(PAGE_RES *page_res, Tesseract *tesseract, int scale,
31
                           int scaled_yres, int rect_left, int rect_top,
32
                           int rect_width, int rect_height)
33
30.0k
    : page_res_(page_res),
34
30.0k
      tesseract_(tesseract),
35
30.0k
      word_(nullptr),
36
30.0k
      word_length_(0),
37
30.0k
      blob_index_(0),
38
30.0k
      cblob_it_(nullptr),
39
30.0k
      include_upper_dots_(false),
40
30.0k
      include_lower_dots_(false),
41
30.0k
      scale_(scale),
42
30.0k
      scaled_yres_(scaled_yres),
43
30.0k
      rect_left_(rect_left),
44
30.0k
      rect_top_(rect_top),
45
30.0k
      rect_width_(rect_width),
46
30.0k
      rect_height_(rect_height) {
47
30.0k
  it_ = new PAGE_RES_IT(page_res);
48
30.0k
  PageIterator::Begin();
49
30.0k
}
50
51
4.46M
PageIterator::~PageIterator() {
52
4.46M
  delete it_;
53
4.46M
  delete cblob_it_;
54
4.46M
}
55
56
/**
57
 * PageIterators may be copied! This makes it possible to iterate over
58
 * all the objects at a lower level, while maintaining an iterator to
59
 * objects at a higher level.
60
 */
61
PageIterator::PageIterator(const PageIterator &src)
62
4.43M
    : page_res_(src.page_res_),
63
4.43M
      tesseract_(src.tesseract_),
64
4.43M
      word_(nullptr),
65
4.43M
      word_length_(src.word_length_),
66
4.43M
      blob_index_(src.blob_index_),
67
4.43M
      cblob_it_(nullptr),
68
4.43M
      include_upper_dots_(src.include_upper_dots_),
69
4.43M
      include_lower_dots_(src.include_lower_dots_),
70
4.43M
      scale_(src.scale_),
71
4.43M
      scaled_yres_(src.scaled_yres_),
72
4.43M
      rect_left_(src.rect_left_),
73
4.43M
      rect_top_(src.rect_top_),
74
4.43M
      rect_width_(src.rect_width_),
75
4.43M
      rect_height_(src.rect_height_) {
76
4.43M
  it_ = new PAGE_RES_IT(*src.it_);
77
4.43M
  BeginWord(src.blob_index_);
78
4.43M
}
79
80
14.2k
const PageIterator &PageIterator::operator=(const PageIterator &src) {
81
14.2k
  page_res_ = src.page_res_;
82
14.2k
  tesseract_ = src.tesseract_;
83
14.2k
  include_upper_dots_ = src.include_upper_dots_;
84
14.2k
  include_lower_dots_ = src.include_lower_dots_;
85
14.2k
  scale_ = src.scale_;
86
14.2k
  scaled_yres_ = src.scaled_yres_;
87
14.2k
  rect_left_ = src.rect_left_;
88
14.2k
  rect_top_ = src.rect_top_;
89
14.2k
  rect_width_ = src.rect_width_;
90
14.2k
  rect_height_ = src.rect_height_;
91
14.2k
  delete it_;
92
14.2k
  it_ = new PAGE_RES_IT(*src.it_);
93
14.2k
  BeginWord(src.blob_index_);
94
14.2k
  return *this;
95
14.2k
}
96
97
1.07M
bool PageIterator::PositionedAtSameWord(const PAGE_RES_IT *other) const {
98
1.07M
  return (it_ == nullptr && it_ == other) ||
99
1.07M
         ((other != nullptr) && (it_ != nullptr) && (*it_ == *other));
100
1.07M
}
101
102
// ============= Moving around within the page ============.
103
104
/** Resets the iterator to point to the start of the page. */
105
30.0k
void PageIterator::Begin() {
106
30.0k
  it_->restart_page_with_empties();
107
30.0k
  BeginWord(0);
108
30.0k
}
109
110
21.1k
void PageIterator::RestartParagraph() {
111
21.1k
  if (it_->block() == nullptr) {
112
0
    return; // At end of the document.
113
0
  }
114
21.1k
  const BLOCK_RES *target_block = it_->block();
115
21.1k
  const PARA *target_para = it_->row() == nullptr ? nullptr : it_->row()->row->para();
116
21.1k
  PAGE_RES_IT para(page_res_);
117
  // Paragraph and block identities are stable within PAGE_RES. Avoid cmp(),
118
  // which searches the page and makes this forward scan quadratic.
119
52.5k
  while (para.block() != nullptr &&
120
52.5k
         (para.block() != target_block ||
121
52.5k
          (para.row() != nullptr && para.row()->row->para() != target_para))) {
122
31.3k
    para.forward_paragraph();
123
31.3k
  }
124
21.1k
  if (para.block() != nullptr) {
125
21.1k
    *it_ = para;
126
21.1k
    BeginWord(0);
127
21.1k
  }
128
21.1k
}
129
130
0
bool PageIterator::IsWithinFirstTextlineOfParagraph() const {
131
0
  PageIterator p_start(*this);
132
0
  p_start.RestartParagraph();
133
0
  return p_start.it_->row() == it_->row();
134
0
}
135
136
1.49M
void PageIterator::RestartRow() {
137
1.49M
  it_->restart_row();
138
1.49M
  BeginWord(0);
139
1.49M
}
140
141
/**
142
 * Moves to the start of the next object at the given level in the
143
 * page hierarchy, and returns false if the end of the page was reached.
144
 * NOTE (CHANGED!) that ALL PageIteratorLevel level values will visit each
145
 * non-text block at least once.
146
 * Think of non text blocks as containing a single para, with at least one
147
 * line, with a single imaginary word, containing a single symbol.
148
 * The bounding boxes mark out any polygonal nature of the block, and
149
 * PTIsTextType(BLockType()) is false for non-text blocks.
150
 * Calls to Next with different levels may be freely intermixed.
151
 * This function iterates words in right-to-left scripts correctly, if
152
 * the appropriate language has been loaded into Tesseract.
153
 */
154
5.92M
bool PageIterator::Next(PageIteratorLevel level) {
155
5.92M
  if (it_->block() == nullptr) {
156
0
    return false; // Already at the end!
157
0
  }
158
5.92M
  if (it_->word() == nullptr) {
159
0
    level = RIL_BLOCK;
160
0
  }
161
162
5.92M
  switch (level) {
163
14.2k
    case RIL_BLOCK:
164
14.2k
      it_->forward_block();
165
14.2k
      break;
166
21.1k
    case RIL_PARA:
167
21.1k
      it_->forward_paragraph();
168
21.1k
      break;
169
692k
    case RIL_TEXTLINE:
170
795k
      for (it_->forward_with_empties(); it_->row() == it_->prev_row();
171
692k
           it_->forward_with_empties()) {
172
103k
        ;
173
103k
      }
174
692k
      break;
175
5.20M
    case RIL_WORD:
176
5.20M
      it_->forward_with_empties();
177
5.20M
      break;
178
0
    case RIL_SYMBOL:
179
0
      if (cblob_it_ != nullptr) {
180
0
        cblob_it_->forward();
181
0
      }
182
0
      ++blob_index_;
183
0
      if (blob_index_ >= word_length_) {
184
0
        it_->forward_with_empties();
185
0
      } else {
186
0
        return true;
187
0
      }
188
0
      break;
189
5.92M
  }
190
5.92M
  BeginWord(0);
191
5.92M
  return it_->block() != nullptr;
192
5.92M
}
193
194
/**
195
 * Returns true if the iterator is at the start of an object at the given
196
 * level. Possible uses include determining if a call to Next(RIL_WORD)
197
 * moved to the start of a RIL_PARA.
198
 */
199
9.26M
bool PageIterator::IsAtBeginningOf(PageIteratorLevel level) const {
200
9.26M
  if (it_->block() == nullptr) {
201
3.28k
    return false; // Already at the end!
202
3.28k
  }
203
9.26M
  if (it_->word() == nullptr) {
204
0
    return true; // In an image block.
205
0
  }
206
9.26M
  switch (level) {
207
0
    case RIL_BLOCK:
208
0
      return blob_index_ == 0 && it_->block() != it_->prev_block();
209
1.46M
    case RIL_PARA:
210
1.46M
      return blob_index_ == 0 &&
211
1.46M
             (it_->block() != it_->prev_block() ||
212
1.43M
              it_->row()->row->para() != it_->prev_row()->row->para());
213
7.79M
    case RIL_TEXTLINE:
214
7.79M
      return blob_index_ == 0 && it_->row() != it_->prev_row();
215
0
    case RIL_WORD:
216
0
      return blob_index_ == 0;
217
0
    case RIL_SYMBOL:
218
0
      return true;
219
9.26M
  }
220
0
  return false;
221
9.26M
}
222
223
/**
224
 * Returns whether the iterator is positioned at the last element in a
225
 * given level. (e.g. the last word in a line, the last line in a block)
226
 */
227
bool PageIterator::IsAtFinalElement(PageIteratorLevel level,
228
0
                                    PageIteratorLevel element) const {
229
0
  if (Empty(element)) {
230
0
    return true; // Already at the end!
231
0
  }
232
  // The result is true if we step forward by element and find we are
233
  // at the end of the page or at beginning of *all* levels in:
234
  // [level, element).
235
  // When there is more than one level difference between element and level,
236
  // we could for instance move forward one symbol and still be at the first
237
  // word on a line, so we also have to be at the first symbol in a word.
238
0
  PageIterator next(*this);
239
0
  next.Next(element);
240
0
  if (next.Empty(element)) {
241
0
    return true; // Reached the end of the page.
242
0
  }
243
0
  while (element > level) {
244
0
    element = static_cast<PageIteratorLevel>(element - 1);
245
0
    if (!next.IsAtBeginningOf(element)) {
246
0
      return false;
247
0
    }
248
0
  }
249
0
  return true;
250
0
}
251
252
/**
253
 * Returns whether this iterator is positioned
254
 *   before other:   -1
255
 *   equal to other:  0
256
 *   after other:     1
257
 */
258
0
int PageIterator::Cmp(const PageIterator &other) const {
259
0
  int word_cmp = it_->cmp(*other.it_);
260
0
  if (word_cmp != 0) {
261
0
    return word_cmp;
262
0
  }
263
0
  if (blob_index_ < other.blob_index_) {
264
0
    return -1;
265
0
  }
266
0
  if (blob_index_ == other.blob_index_) {
267
0
    return 0;
268
0
  }
269
0
  return 1;
270
0
}
271
272
// ============= Accessing data ==============.
273
// Coordinate system:
274
// Integer coordinates are at the cracks between the pixels.
275
// The top-left corner of the top-left pixel in the image is at (0,0).
276
// The bottom-right corner of the bottom-right pixel in the image is at
277
// (width, height).
278
// Every bounding box goes from the top-left of the top-left contained
279
// pixel to the bottom-right of the bottom-right contained pixel, so
280
// the bounding box of the single top-left pixel in the image is:
281
// (0,0)->(1,1).
282
// If an image rectangle has been set in the API, then returned coordinates
283
// relate to the original (full) image, rather than the rectangle.
284
285
/**
286
 * Returns the bounding rectangle of the current object at the given level in
287
 * the coordinates of the working image that is pix_binary().
288
 * See comment on coordinate system above.
289
 * Returns false if there is no such object at the current position.
290
 */
291
bool PageIterator::BoundingBoxInternal(PageIteratorLevel level, int *left,
292
                                       int *top, int *right,
293
0
                                       int *bottom) const {
294
0
  if (Empty(level)) {
295
0
    return false;
296
0
  }
297
0
  TBOX box;
298
0
  PARA *para = nullptr;
299
0
  switch (level) {
300
0
    case RIL_BLOCK:
301
0
      box = it_->block()->block->restricted_bounding_box(include_upper_dots_,
302
0
                                                         include_lower_dots_);
303
0
      break;
304
0
    case RIL_PARA:
305
0
      para = it_->row()->row->para();
306
      // For a paragraph the box is the union of the full bounding boxes of
307
      // all its rows. Do not seed with the current row's restricted box: the
308
      // restricted box excludes upper/lower dots, so the result would depend
309
      // on which row the iterator currently sits on.
310
0
      break;
311
0
    case RIL_TEXTLINE:
312
0
      box = it_->row()->row->restricted_bounding_box(include_upper_dots_,
313
0
                                                     include_lower_dots_);
314
0
      break;
315
0
    case RIL_WORD:
316
0
      box = it_->word()->word->restricted_bounding_box(include_upper_dots_,
317
0
                                                       include_lower_dots_);
318
0
      break;
319
0
    case RIL_SYMBOL:
320
0
      if (cblob_it_ == nullptr) {
321
0
        box = it_->word()->box_word->BlobBox(blob_index_);
322
0
      } else {
323
0
        box = cblob_it_->data()->bounding_box();
324
0
      }
325
0
  }
326
0
  if (level == RIL_PARA) {
327
0
    PageIterator other = *this;
328
0
    other.Begin();
329
0
    do {
330
0
      if (other.it_->block() &&
331
0
          other.it_->block()->block == it_->block()->block &&
332
0
          other.it_->row() && other.it_->row()->row &&
333
0
          other.it_->row()->row->para() == para) {
334
0
        box = box.bounding_union(other.it_->row()->row->bounding_box());
335
0
      }
336
0
    } while (other.Next(RIL_TEXTLINE));
337
0
  }
338
0
  if (level != RIL_SYMBOL || cblob_it_ != nullptr) {
339
0
    box.rotate(it_->block()->block->re_rotation());
340
0
  }
341
  // Now we have a box in tesseract coordinates relative to the image rectangle,
342
  // we have to convert the coords to a top-down system.
343
0
  const int pix_height = pixGetHeight(tesseract_->pix_binary());
344
0
  const int pix_width = pixGetWidth(tesseract_->pix_binary());
345
0
  *left = ClipToRange(static_cast<int>(box.left()), 0, pix_width);
346
0
  *top = ClipToRange(pix_height - box.top(), 0, pix_height);
347
0
  *right = ClipToRange(static_cast<int>(box.right()), *left, pix_width);
348
0
  *bottom = ClipToRange(pix_height - box.bottom(), *top, pix_height);
349
0
  return true;
350
0
}
351
352
/**
353
 * Returns the bounding rectangle of the current object at the given level in
354
 * coordinates of the original image.
355
 * See comment on coordinate system above.
356
 * Returns false if there is no such object at the current position.
357
 */
358
bool PageIterator::BoundingBox(PageIteratorLevel level, int *left, int *top,
359
0
                               int *right, int *bottom) const {
360
0
  return BoundingBox(level, 0, left, top, right, bottom);
361
0
}
362
363
bool PageIterator::BoundingBox(PageIteratorLevel level, const int padding,
364
                               int *left, int *top, int *right,
365
0
                               int *bottom) const {
366
0
  if (!BoundingBoxInternal(level, left, top, right, bottom)) {
367
0
    return false;
368
0
  }
369
  // Convert to the coordinate system of the original image.
370
0
  *left = ClipToRange(*left / scale_ + rect_left_ - padding, rect_left_,
371
0
                      rect_left_ + rect_width_);
372
0
  *top = ClipToRange(*top / scale_ + rect_top_ - padding, rect_top_,
373
0
                     rect_top_ + rect_height_);
374
0
  *right = ClipToRange((*right + scale_ - 1) / scale_ + rect_left_ + padding,
375
0
                       *left, rect_left_ + rect_width_);
376
0
  *bottom = ClipToRange((*bottom + scale_ - 1) / scale_ + rect_top_ + padding,
377
0
                        *top, rect_top_ + rect_height_);
378
0
  return true;
379
0
}
380
381
/** Return that there is no such object at a given level. */
382
6.21M
bool PageIterator::Empty(PageIteratorLevel level) const {
383
6.21M
  if (it_->block() == nullptr) {
384
42.3k
    return true; // Already at the end!
385
42.3k
  }
386
6.17M
  if (it_->word() == nullptr && level != RIL_BLOCK) {
387
0
    return true; // image block
388
0
  }
389
6.17M
  if (level == RIL_SYMBOL && blob_index_ >= word_length_) {
390
0
    return true; // Zero length word, or already at the end of it.
391
0
  }
392
6.17M
  return false;
393
6.17M
}
394
395
/** Returns the type of the current block.
396
 *  See tesseract/publictypes.h for PolyBlockType. */
397
21.1k
PolyBlockType PageIterator::BlockType() const {
398
21.1k
  if (it_->block() == nullptr || it_->block()->block == nullptr) {
399
0
    return PT_UNKNOWN; // Already at the end!
400
0
  }
401
21.1k
  if (it_->block()->block->pdblk.poly_block() == nullptr) {
402
21.1k
    return PT_FLOWING_TEXT; // No layout analysis used - assume text.
403
21.1k
  }
404
0
  return it_->block()->block->pdblk.poly_block()->isA();
405
21.1k
}
406
407
/** Returns the polygon outline of the current block. The returned Pta must
408
 *  be ptaDestroy-ed after use. */
409
0
Pta *PageIterator::BlockPolygon() const {
410
0
  if (it_->block() == nullptr || it_->block()->block == nullptr) {
411
0
    return nullptr; // Already at the end!
412
0
  }
413
0
  if (it_->block()->block->pdblk.poly_block() == nullptr) {
414
0
    return nullptr; // No layout analysis used - no polygon.
415
0
  }
416
  // Copy polygon, so we can unrotate it to image coordinates.
417
0
  POLY_BLOCK *internal_poly = it_->block()->block->pdblk.poly_block();
418
0
  ICOORDELT_LIST vertices;
419
0
  vertices.deep_copy(internal_poly->points(), ICOORDELT::deep_copy);
420
0
  POLY_BLOCK poly(&vertices, internal_poly->isA());
421
0
  poly.rotate(it_->block()->block->re_rotation());
422
0
  ICOORDELT_IT it(poly.points());
423
0
  Pta *pta = ptaCreate(it.length());
424
0
  int num_pts = 0;
425
0
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward(), ++num_pts) {
426
0
    ICOORD *pt = it.data();
427
    // Convert to top-down coords within the input image.
428
0
    int x = static_cast<float>(pt->x()) / scale_ + rect_left_;
429
0
    int y = rect_top_ + rect_height_ - static_cast<float>(pt->y()) / scale_;
430
0
    x = ClipToRange(x, rect_left_, rect_left_ + rect_width_);
431
0
    y = ClipToRange(y, rect_top_, rect_top_ + rect_height_);
432
0
    ptaAddPt(pta, x, y);
433
0
  }
434
0
  return pta;
435
0
}
436
437
/**
438
 * Returns a binary image of the current object at the given level.
439
 * The position and size match the return from BoundingBoxInternal, and so this
440
 * could be upscaled with respect to the original input image.
441
 * Use pixDestroy to delete the image after use.
442
 * The following methods are used to generate the images:
443
 * RIL_BLOCK: mask the page image with the block polygon.
444
 * RIL_TEXTLINE: Clip the rectangle of the line box from the page image.
445
 * TODO(rays) fix this to generate and use a line polygon.
446
 * RIL_WORD: Clip the rectangle of the word box from the page image.
447
 * RIL_SYMBOL: Render the symbol outline to an image for cblobs (prior
448
 * to recognition) or the bounding box otherwise.
449
 * A reconstruction of the original image (using xor to check for double
450
 * representation) should be reasonably accurate,
451
 * apart from removed noise, at the block level. Below the block level, the
452
 * reconstruction will be missing images and line separators.
453
 * At the symbol level, kerned characters will be invade the bounding box
454
 * if rendered after recognition, making an xor reconstruction inaccurate, but
455
 * an or construction better. Before recognition, symbol-level reconstruction
456
 * should be good, even with xor, since the images come from the connected
457
 * components.
458
 */
459
0
Pix *PageIterator::GetBinaryImage(PageIteratorLevel level) const {
460
0
  int left, top, right, bottom;
461
0
  if (!BoundingBoxInternal(level, &left, &top, &right, &bottom)) {
462
0
    return nullptr;
463
0
  }
464
0
  if (level == RIL_SYMBOL && cblob_it_ != nullptr &&
465
0
      cblob_it_->data()->area() != 0) {
466
0
    return cblob_it_->data()->render();
467
0
  }
468
0
  Box *box = boxCreate(left, top, right - left, bottom - top);
469
0
  Image pix = pixClipRectangle(tesseract_->pix_binary(), box, nullptr);
470
0
  boxDestroy(&box);
471
0
  if (level == RIL_BLOCK || level == RIL_PARA) {
472
    // Clip to the block polygon as well.
473
0
    TBOX mask_box;
474
0
    Image mask = it_->block()->block->render_mask(&mask_box);
475
0
    int mask_x = left - mask_box.left();
476
0
    int mask_y = top - (tesseract_->ImageHeight() - mask_box.top());
477
    // AND the mask and pix, putting the result in pix.
478
0
    pixRasterop(pix, std::max(0, -mask_x), std::max(0, -mask_y),
479
0
                pixGetWidth(pix), pixGetHeight(pix), PIX_SRC & PIX_DST, mask,
480
0
                std::max(0, mask_x), std::max(0, mask_y));
481
0
    mask.destroy();
482
0
  }
483
0
  return pix;
484
0
}
485
486
/**
487
 * Returns an image of the current object at the given level in greyscale
488
 * if available in the input. To guarantee a binary image use BinaryImage.
489
 * NOTE that in order to give the best possible image, the bounds are
490
 * expanded slightly over the binary connected component, by the supplied
491
 * padding, so the top-left position of the returned image is returned
492
 * in (left,top). These will most likely not match the coordinates
493
 * returned by BoundingBox.
494
 * If you do not supply an original image, you will get a binary one.
495
 * Use pixDestroy to delete the image after use.
496
 */
497
Pix *PageIterator::GetImage(PageIteratorLevel level, int padding,
498
0
                            Pix *original_img, int *left, int *top) const {
499
0
  int right, bottom;
500
0
  if (!BoundingBox(level, left, top, &right, &bottom)) {
501
0
    return nullptr;
502
0
  }
503
0
  if (original_img == nullptr) {
504
0
    return GetBinaryImage(level);
505
0
  }
506
507
  // Expand the box.
508
0
  *left = std::max(*left - padding, 0);
509
0
  *top = std::max(*top - padding, 0);
510
0
  right = std::min(right + padding, rect_width_);
511
0
  bottom = std::min(bottom + padding, rect_height_);
512
0
  Box *box = boxCreate(*left, *top, right - *left, bottom - *top);
513
0
  Image grey_pix = pixClipRectangle(original_img, box, nullptr);
514
0
  boxDestroy(&box);
515
0
  if (level == RIL_BLOCK || level == RIL_PARA) {
516
    // Clip to the block polygon as well.
517
0
    TBOX mask_box;
518
0
    Image mask = it_->block()->block->render_mask(&mask_box);
519
    // Copy the mask registered correctly into an image the size of grey_pix.
520
0
    int mask_x = *left - mask_box.left();
521
0
    int mask_y = *top - (pixGetHeight(original_img) - mask_box.top());
522
0
    int width = pixGetWidth(grey_pix);
523
0
    int height = pixGetHeight(grey_pix);
524
0
    Image resized_mask = pixCreate(width, height, 1);
525
0
    pixRasterop(resized_mask, std::max(0, -mask_x), std::max(0, -mask_y), width,
526
0
                height, PIX_SRC, mask, std::max(0, mask_x),
527
0
                std::max(0, mask_y));
528
0
    mask.destroy();
529
0
    pixDilateBrick(resized_mask, resized_mask, 2 * padding + 1,
530
0
                   2 * padding + 1);
531
0
    pixInvert(resized_mask, resized_mask);
532
0
    pixSetMasked(grey_pix, resized_mask, UINT32_MAX);
533
0
    resized_mask.destroy();
534
0
  }
535
0
  return grey_pix;
536
0
}
537
538
/**
539
 * Returns the baseline of the current object at the given level.
540
 * The baseline is the line that passes through (x1, y1) and (x2, y2).
541
 * WARNING: with vertical text, baselines may be vertical!
542
 */
543
bool PageIterator::Baseline(PageIteratorLevel level, int *x1, int *y1, int *x2,
544
0
                            int *y2) const {
545
0
  if (it_->word() == nullptr) {
546
0
    return false; // Already at the end!
547
0
  }
548
0
  ROW *row = it_->row()->row;
549
0
  WERD *word = it_->word()->word;
550
0
  TBOX box = (level == RIL_WORD || level == RIL_SYMBOL) ? word->bounding_box()
551
0
                                                        : row->bounding_box();
552
0
  int left = box.left();
553
0
  ICOORD startpt(left, static_cast<int16_t>(row->base_line(left) + 0.5));
554
0
  int right = box.right();
555
0
  ICOORD endpt(right, static_cast<int16_t>(row->base_line(right) + 0.5));
556
  // Rotate to image coordinates and convert to global image coords.
557
0
  startpt.rotate(it_->block()->block->re_rotation());
558
0
  endpt.rotate(it_->block()->block->re_rotation());
559
0
  *x1 = startpt.x() / scale_ + rect_left_;
560
0
  *y1 = (rect_height_ - startpt.y()) / scale_ + rect_top_;
561
0
  *x2 = endpt.x() / scale_ + rect_left_;
562
0
  *y2 = (rect_height_ - endpt.y()) / scale_ + rect_top_;
563
0
  return true;
564
0
}
565
566
void PageIterator::RowAttributes(float *row_height, float *descenders,
567
0
                                 float *ascenders) const {
568
0
  *row_height = it_->row()->row->x_height() + it_->row()->row->ascenders() -
569
0
                it_->row()->row->descenders();
570
0
  *descenders = it_->row()->row->descenders();
571
0
  *ascenders = it_->row()->row->ascenders();
572
0
}
573
574
void PageIterator::Orientation(tesseract::Orientation *orientation,
575
                               tesseract::WritingDirection *writing_direction,
576
                               tesseract::TextlineOrder *textline_order,
577
0
                               float *deskew_angle) const {
578
0
  auto *block_res = it_->block();
579
0
  if (block_res == nullptr) {
580
    // Nothing can be done, so return default values.
581
0
    *orientation = ORIENTATION_PAGE_UP;
582
0
    *writing_direction = WRITING_DIRECTION_LEFT_TO_RIGHT;
583
0
    *textline_order = TEXTLINE_ORDER_TOP_TO_BOTTOM;
584
0
    return;
585
0
  }
586
0
  auto *block = block_res->block;
587
588
  // Orientation
589
0
  FCOORD up_in_image(0.0, 1.0);
590
0
  up_in_image.unrotate(block->classify_rotation());
591
0
  up_in_image.rotate(block->re_rotation());
592
593
0
  if (up_in_image.x() == 0.0F) {
594
0
    if (up_in_image.y() > 0.0F) {
595
0
      *orientation = ORIENTATION_PAGE_UP;
596
0
    } else {
597
0
      *orientation = ORIENTATION_PAGE_DOWN;
598
0
    }
599
0
  } else if (up_in_image.x() > 0.0F) {
600
0
    *orientation = ORIENTATION_PAGE_RIGHT;
601
0
  } else {
602
0
    *orientation = ORIENTATION_PAGE_LEFT;
603
0
  }
604
605
  // Writing direction
606
0
  bool is_vertical_text = (block->classify_rotation().x() == 0.0);
607
0
  bool right_to_left = block->right_to_left();
608
0
  *writing_direction = is_vertical_text
609
0
                           ? WRITING_DIRECTION_TOP_TO_BOTTOM
610
0
                           : (right_to_left ? WRITING_DIRECTION_RIGHT_TO_LEFT
611
0
                                            : WRITING_DIRECTION_LEFT_TO_RIGHT);
612
613
  // Textline Order
614
0
  const bool is_mongolian = false; // TODO(eger): fix me
615
0
  *textline_order = is_vertical_text
616
0
                        ? (is_mongolian ? TEXTLINE_ORDER_LEFT_TO_RIGHT
617
0
                                        : TEXTLINE_ORDER_RIGHT_TO_LEFT)
618
0
                        : TEXTLINE_ORDER_TOP_TO_BOTTOM;
619
620
  // Deskew angle
621
0
  FCOORD skew = block->skew(); // true horizontal for textlines
622
0
  *deskew_angle = -skew.angle();
623
0
}
624
625
void PageIterator::ParagraphInfo(tesseract::ParagraphJustification *just,
626
                                 bool *is_list_item, bool *is_crown,
627
0
                                 int *first_line_indent) const {
628
0
  *just = tesseract::JUSTIFICATION_UNKNOWN;
629
0
  if (!it_->row() || !it_->row()->row || !it_->row()->row->para() ||
630
0
      !it_->row()->row->para()->model) {
631
0
    return;
632
0
  }
633
634
0
  PARA *para = it_->row()->row->para();
635
0
  *is_list_item = para->is_list_item;
636
0
  *is_crown = para->is_very_first_or_continuation;
637
0
  *first_line_indent = para->model->first_indent() - para->model->body_indent();
638
0
  *just = para->model->justification();
639
0
}
640
641
/**
642
 * Sets up the internal data for iterating the blobs of a new word, then
643
 * moves the iterator to the given offset.
644
 */
645
11.9M
void PageIterator::BeginWord(int offset) {
646
11.9M
  WERD_RES *word_res = it_->word();
647
11.9M
  if (word_res == nullptr) {
648
    // This is a non-text block, so there is no word.
649
399k
    word_length_ = 0;
650
399k
    blob_index_ = 0;
651
399k
    word_ = nullptr;
652
399k
    return;
653
399k
  }
654
11.5M
  if (word_res->best_choice != nullptr) {
655
    // Recognition has been done, so we are using the box_word, which
656
    // is already baseline denormalized.
657
11.5M
    word_length_ = word_res->best_choice->length();
658
11.5M
    if (word_res->box_word != nullptr) {
659
11.5M
      if (word_res->box_word->length() != static_cast<unsigned>(word_length_)) {
660
0
        tprintf("Corrupted word! best_choice[len=%d] = %s, box_word[len=%d]: ",
661
0
                word_length_, word_res->best_choice->unichar_string().c_str(),
662
0
                word_res->box_word->length());
663
0
        word_res->box_word->bounding_box().print();
664
0
      }
665
11.5M
      ASSERT_HOST(word_res->box_word->length() ==
666
11.5M
                  static_cast<unsigned>(word_length_));
667
11.5M
    }
668
11.5M
    word_ = nullptr;
669
    // We will be iterating the box_word.
670
11.5M
    delete cblob_it_;
671
11.5M
    cblob_it_ = nullptr;
672
11.5M
  } else {
673
    // No recognition yet, so a "symbol" is a cblob.
674
0
    word_ = word_res->word;
675
0
    ASSERT_HOST(word_->cblob_list() != nullptr);
676
0
    word_length_ = word_->cblob_list()->length();
677
0
    if (cblob_it_ == nullptr) {
678
0
      cblob_it_ = new C_BLOB_IT;
679
0
    }
680
0
    cblob_it_->set_to_list(word_->cblob_list());
681
0
  }
682
11.5M
  for (blob_index_ = 0; blob_index_ < offset; ++blob_index_) {
683
0
    if (cblob_it_ != nullptr) {
684
0
      cblob_it_->forward();
685
0
    }
686
0
  }
687
11.5M
}
688
689
0
bool PageIterator::SetWordBlamerBundle(BlamerBundle *blamer_bundle) {
690
0
  if (it_->word() != nullptr) {
691
0
    it_->word()->blamer_bundle = blamer_bundle;
692
0
    return true;
693
0
  } else {
694
0
    return false;
695
0
  }
696
0
}
697
698
} // namespace tesseract.