Coverage Report

Created: 2025-09-27 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccstruct/polyblk.cpp
Line
Count
Source
1
/**********************************************************************
2
 * File:        polyblk.cpp  (Formerly poly_block.c)
3
 * Description: Polygonal blocks
4
 *
5
 * (C) Copyright 1993, Hewlett-Packard Ltd.
6
 ** Licensed under the Apache License, Version 2.0 (the "License");
7
 ** you may not use this file except in compliance with the License.
8
 ** You may obtain a copy of the License at
9
 ** http://www.apache.org/licenses/LICENSE-2.0
10
 ** Unless required by applicable law or agreed to in writing, software
11
 ** distributed under the License is distributed on an "AS IS" BASIS,
12
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 ** See the License for the specific language governing permissions and
14
 ** limitations under the License.
15
 *
16
 **********************************************************************/
17
18
// Include automatically generated configuration file if running autoconf.
19
#ifdef HAVE_CONFIG_H
20
#  include "config_auto.h"
21
#endif
22
23
#include "polyblk.h"
24
25
#include "elst.h"
26
27
#include <cctype>
28
#include <cinttypes> // PRId32
29
#include <cmath>
30
#include <cstdio>
31
#include <memory> // std::unique_ptr
32
33
namespace tesseract {
34
35
0
#define INTERSECTING INT16_MAX
36
37
0
POLY_BLOCK::POLY_BLOCK(ICOORDELT_LIST *points, PolyBlockType t) {
38
0
  ICOORDELT_IT v = &vertices;
39
40
0
  vertices.clear();
41
0
  v.move_to_first();
42
0
  v.add_list_before(points);
43
0
  compute_bb();
44
0
  type = t;
45
0
}
46
47
// Initialize from box coordinates.
48
12.3k
POLY_BLOCK::POLY_BLOCK(const TBOX &tbox, PolyBlockType t) {
49
12.3k
  vertices.clear();
50
12.3k
  ICOORDELT_IT v = &vertices;
51
12.3k
  v.move_to_first();
52
12.3k
  v.add_to_end(new ICOORDELT(tbox.left(), tbox.top()));
53
12.3k
  v.add_to_end(new ICOORDELT(tbox.left(), tbox.bottom()));
54
12.3k
  v.add_to_end(new ICOORDELT(tbox.right(), tbox.bottom()));
55
12.3k
  v.add_to_end(new ICOORDELT(tbox.right(), tbox.top()));
56
12.3k
  compute_bb();
57
12.3k
  type = t;
58
12.3k
}
59
60
/**
61
 * @name POLY_BLOCK::compute_bb
62
 *
63
 * Compute the bounding box from the outline points.
64
 */
65
66
12.3k
void POLY_BLOCK::compute_bb() { // constructor
67
12.3k
  ICOORD ibl, itr;              // integer bb
68
12.3k
  ICOORD botleft;               // bounding box
69
12.3k
  ICOORD topright;
70
12.3k
  ICOORD pos;                   // current pos;
71
12.3k
  ICOORDELT_IT pts = &vertices; // iterator
72
73
12.3k
  botleft = *pts.data();
74
12.3k
  topright = botleft;
75
49.3k
  do {
76
49.3k
    pos = *pts.data();
77
49.3k
    if (pos.x() < botleft.x()) {
78
      // get bounding box
79
0
      botleft = ICOORD(pos.x(), botleft.y());
80
0
    }
81
49.3k
    if (pos.y() < botleft.y()) {
82
12.3k
      botleft = ICOORD(botleft.x(), pos.y());
83
12.3k
    }
84
49.3k
    if (pos.x() > topright.x()) {
85
12.3k
      topright = ICOORD(pos.x(), topright.y());
86
12.3k
    }
87
49.3k
    if (pos.y() > topright.y()) {
88
0
      topright = ICOORD(topright.x(), pos.y());
89
0
    }
90
49.3k
    pts.forward();
91
49.3k
  } while (!pts.at_first());
92
12.3k
  ibl = ICOORD(botleft.x(), botleft.y());
93
12.3k
  itr = ICOORD(topright.x(), topright.y());
94
12.3k
  box = TBOX(ibl, itr);
95
12.3k
}
96
97
/**
98
 * @name POLY_BLOCK::winding_number
99
 *
100
 * Return the winding number of the outline around the given point.
101
 * @param point point to wind around
102
 */
103
104
0
int16_t POLY_BLOCK::winding_number(const ICOORD &point) {
105
0
  int16_t count;               // winding count
106
0
  ICOORD pt;                   // current point
107
0
  ICOORD vec;                  // point to current point
108
0
  ICOORD vvec;                 // current point to next point
109
0
  int32_t cross;               // cross product
110
0
  ICOORDELT_IT it = &vertices; // iterator
111
112
0
  count = 0;
113
0
  do {
114
0
    pt = *it.data();
115
0
    vec = pt - point;
116
0
    vvec = *it.data_relative(1) - pt;
117
    // crossing the line
118
0
    if (vec.y() <= 0 && vec.y() + vvec.y() > 0) {
119
0
      cross = vec * vvec; // cross product
120
0
      if (cross > 0) {
121
0
        count++; // crossing right half
122
0
      } else if (cross == 0) {
123
0
        return INTERSECTING; // going through point
124
0
      }
125
0
    } else if (vec.y() > 0 && vec.y() + vvec.y() <= 0) {
126
0
      cross = vec * vvec;
127
0
      if (cross < 0) {
128
0
        count--; // crossing back
129
0
      } else if (cross == 0) {
130
0
        return INTERSECTING; // illegal
131
0
      }
132
0
    } else if (vec.y() == 0 && vec.x() == 0) {
133
0
      return INTERSECTING;
134
0
    }
135
0
    it.forward();
136
0
  } while (!it.at_first());
137
0
  return count; // winding number
138
0
}
139
140
/// @return true if other is inside this.
141
0
bool POLY_BLOCK::contains(POLY_BLOCK *other) {
142
0
  int16_t count;               // winding count
143
0
  ICOORDELT_IT it = &vertices; // iterator
144
0
  ICOORD vertex;
145
146
0
  if (!box.overlap(*(other->bounding_box()))) {
147
0
    return false; // can't be contained
148
0
  }
149
150
  /* check that no vertex of this is inside other */
151
152
0
  do {
153
0
    vertex = *it.data();
154
    // get winding number
155
0
    count = other->winding_number(vertex);
156
0
    if (count != INTERSECTING) {
157
0
      if (count != 0) {
158
0
        return false;
159
0
      }
160
0
    }
161
0
    it.forward();
162
0
  } while (!it.at_first());
163
164
  /* check that all vertices of other are inside this */
165
166
  // switch lists
167
0
  it.set_to_list(other->points());
168
0
  do {
169
0
    vertex = *it.data();
170
    // try other way round
171
0
    count = winding_number(vertex);
172
0
    if (count != INTERSECTING) {
173
0
      if (count == 0) {
174
0
        return false;
175
0
      }
176
0
    }
177
0
    it.forward();
178
0
  } while (!it.at_first());
179
0
  return true;
180
0
}
181
182
/**
183
 * @name POLY_BLOCK::rotate
184
 *
185
 * Rotate the POLY_BLOCK.
186
 * @param rotation cos, sin of angle
187
 */
188
189
0
void POLY_BLOCK::rotate(FCOORD rotation) {
190
0
  FCOORD pos;                   // current pos;
191
0
  ICOORDELT *pt;                // current point
192
0
  ICOORDELT_IT pts = &vertices; // iterator
193
194
0
  do {
195
0
    pt = pts.data();
196
0
    pos.set_x(pt->x());
197
0
    pos.set_y(pt->y());
198
0
    pos.rotate(rotation);
199
0
    pt->set_x(static_cast<TDimension>(floor(pos.x() + 0.5)));
200
0
    pt->set_y(static_cast<TDimension>(floor(pos.y() + 0.5)));
201
0
    pts.forward();
202
0
  } while (!pts.at_first());
203
0
  compute_bb();
204
0
}
205
206
/**
207
 * @name POLY_BLOCK::reflect_in_y_axis
208
 *
209
 * Reflect the coords of the polygon in the y-axis. (Flip the sign of x.)
210
 */
211
212
0
void POLY_BLOCK::reflect_in_y_axis() {
213
0
  ICOORDELT *pt;                // current point
214
0
  ICOORDELT_IT pts = &vertices; // Iterator.
215
216
0
  do {
217
0
    pt = pts.data();
218
0
    pt->set_x(-pt->x());
219
0
    pts.forward();
220
0
  } while (!pts.at_first());
221
0
  compute_bb();
222
0
}
223
224
/**
225
 * POLY_BLOCK::move
226
 *
227
 * Move the POLY_BLOCK.
228
 * @param shift x,y translation vector
229
 */
230
231
0
void POLY_BLOCK::move(ICOORD shift) {
232
0
  ICOORDELT *pt;                // current point
233
0
  ICOORDELT_IT pts = &vertices; // iterator
234
235
0
  do {
236
0
    pt = pts.data();
237
0
    *pt += shift;
238
0
    pts.forward();
239
0
  } while (!pts.at_first());
240
0
  compute_bb();
241
0
}
242
243
#ifndef GRAPHICS_DISABLED
244
void POLY_BLOCK::plot(ScrollView *window, int32_t num) {
245
  ICOORDELT_IT v = &vertices;
246
247
  window->Pen(ColorForPolyBlockType(type));
248
249
  v.move_to_first();
250
251
  if (num > 0) {
252
    window->TextAttributes("Times", 80, false, false, false);
253
    char temp_buff[34];
254
#  if !defined(_WIN32) || defined(__MINGW32__)
255
    snprintf(temp_buff, sizeof(temp_buff), "%" PRId32, num);
256
#  else
257
    _ltoa(num, temp_buff, 10);
258
#  endif
259
    window->Text(v.data()->x(), v.data()->y(), temp_buff);
260
  }
261
262
  window->SetCursor(v.data()->x(), v.data()->y());
263
  for (v.mark_cycle_pt(); !v.cycled_list(); v.forward()) {
264
    window->DrawTo(v.data()->x(), v.data()->y());
265
  }
266
  v.move_to_first();
267
  window->DrawTo(v.data()->x(), v.data()->y());
268
}
269
270
void POLY_BLOCK::fill(ScrollView *window, ScrollView::Color colour) {
271
  ICOORDELT_IT s_it;
272
273
  std::unique_ptr<PB_LINE_IT> lines(new PB_LINE_IT(this));
274
  window->Pen(colour);
275
276
  for (auto y = this->bounding_box()->bottom(); y <= this->bounding_box()->top(); y++) {
277
    const std::unique_ptr</*non-const*/ ICOORDELT_LIST> segments(lines->get_line(y));
278
    if (!segments->empty()) {
279
      s_it.set_to_list(segments.get());
280
      for (s_it.mark_cycle_pt(); !s_it.cycled_list(); s_it.forward()) {
281
        // Note different use of ICOORDELT, x coord is x coord of pixel
282
        // at the start of line segment, y coord is length of line segment
283
        // Last pixel is start pixel + length.
284
        auto width = s_it.data()->y();
285
        window->SetCursor(s_it.data()->x(), y);
286
        window->DrawTo(s_it.data()->x() + static_cast<float>(width), y);
287
      }
288
    }
289
  }
290
}
291
#endif
292
293
/// @return true if the polygons of other and this overlap.
294
0
bool POLY_BLOCK::overlap(POLY_BLOCK *other) {
295
0
  int16_t count;               // winding count
296
0
  ICOORDELT_IT it = &vertices; // iterator
297
0
  ICOORD vertex;
298
299
0
  if (!box.overlap(*(other->bounding_box()))) {
300
0
    return false; // can't be any overlap.
301
0
  }
302
303
  /* see if a vertex of this is inside other */
304
305
0
  do {
306
0
    vertex = *it.data();
307
    // get winding number
308
0
    count = other->winding_number(vertex);
309
0
    if (count != INTERSECTING) {
310
0
      if (count != 0) {
311
0
        return true;
312
0
      }
313
0
    }
314
0
    it.forward();
315
0
  } while (!it.at_first());
316
317
  /* see if a vertex of other is inside this */
318
319
  // switch lists
320
0
  it.set_to_list(other->points());
321
0
  do {
322
0
    vertex = *it.data();
323
    // try other way round
324
0
    count = winding_number(vertex);
325
0
    if (count != INTERSECTING) {
326
0
      if (count != 0) {
327
0
        return true;
328
0
      }
329
0
    }
330
0
    it.forward();
331
0
  } while (!it.at_first());
332
0
  return false;
333
0
}
334
335
276k
ICOORDELT_LIST *PB_LINE_IT::get_line(TDimension y) {
336
276k
  ICOORDELT_IT v, r;
337
276k
  ICOORDELT_LIST *result;
338
276k
  ICOORDELT *x, *current, *previous;
339
276k
  float fy = y + 0.5f;
340
276k
  result = new ICOORDELT_LIST();
341
276k
  r.set_to_list(result);
342
276k
  v.set_to_list(block->points());
343
344
1.38M
  for (v.mark_cycle_pt(); !v.cycled_list(); v.forward()) {
345
1.10M
    if (((v.data_relative(-1)->y() > y) && (v.data()->y() <= y)) ||
346
845k
        ((v.data_relative(-1)->y() <= y) && (v.data()->y() > y))) {
347
520k
      previous = v.data_relative(-1);
348
520k
      current = v.data();
349
520k
      float fx =
350
520k
          0.5f + previous->x() +
351
520k
          (current->x() - previous->x()) * (fy - previous->y()) / (current->y() - previous->y());
352
520k
      x = new ICOORDELT(static_cast<TDimension>(fx), 0);
353
520k
      r.add_to_end(x);
354
520k
    }
355
1.10M
  }
356
357
276k
  if (!r.empty()) {
358
260k
    r.sort([](const ICOORDELT *p1, const ICOORDELT *p2) {
359
260k
      if (p1->x() < p2->x()) {
360
0
        return (-1);
361
260k
      } else if (p1->x() > p2->x()) {
362
260k
        return (1);
363
260k
      } else {
364
0
        return (0);
365
0
      }
366
260k
      });
367
780k
    for (r.mark_cycle_pt(); !r.cycled_list(); r.forward()) {
368
520k
      x = r.data();
369
520k
    }
370
520k
    for (r.mark_cycle_pt(); !r.cycled_list(); r.forward()) {
371
260k
      r.data()->set_y(r.data_relative(1)->x() - r.data()->x());
372
260k
      r.forward();
373
260k
      delete (r.extract());
374
260k
    }
375
260k
  }
376
377
276k
  return result;
378
276k
}
379
380
#ifndef GRAPHICS_DISABLED
381
/// Returns a color to draw the given type.
382
ScrollView::Color POLY_BLOCK::ColorForPolyBlockType(PolyBlockType type) {
383
  // Keep kPBColors in sync with PolyBlockType.
384
  const ScrollView::Color kPBColors[PT_COUNT] = {
385
      ScrollView::WHITE,       // Type is not yet known. Keep as the 1st element.
386
      ScrollView::BLUE,        // Text that lives inside a column.
387
      ScrollView::CYAN,        // Text that spans more than one column.
388
      ScrollView::MEDIUM_BLUE, // Text that is in a cross-column pull-out
389
                               // region.
390
      ScrollView::AQUAMARINE,  // Partition belonging to an equation region.
391
      ScrollView::SKY_BLUE,    // Partition belonging to an inline equation
392
                               // region.
393
      ScrollView::MAGENTA,     // Partition belonging to a table region.
394
      ScrollView::GREEN,       // Text-line runs vertically.
395
      ScrollView::LIGHT_BLUE,  // Text that belongs to an image.
396
      ScrollView::RED,         // Image that lives inside a column.
397
      ScrollView::YELLOW,      // Image that spans more than one column.
398
      ScrollView::ORANGE,      // Image in a cross-column pull-out region.
399
      ScrollView::BROWN,       // Horizontal Line.
400
      ScrollView::DARK_GREEN,  // Vertical Line.
401
      ScrollView::GREY         // Lies outside of any column.
402
  };
403
  if (type < PT_COUNT) {
404
    return kPBColors[type];
405
  }
406
  return ScrollView::WHITE;
407
}
408
#endif // !GRAPHICS_DISABLED
409
410
} // namespace tesseract