tesseract  4.1.0
werd.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: werd.cpp (Formerly word.c)
3  * Description: Code for the WERD class.
4  * Author: Ray Smith
5  *
6  * (C) Copyright 1991, Hewlett-Packard Ltd.
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  *
17  **********************************************************************/
18 
19 #include "werd.h"
20 #include "helpers.h"
21 #include "linlsq.h"
22 
23 // Include automatically generated configuration file if running autoconf.
24 #ifdef HAVE_CONFIG_H
25 # include "config_auto.h"
26 #endif
27 
28 #define FIRST_COLOUR ScrollView::RED
29 #define LAST_COLOUR ScrollView::AQUAMARINE
30 #define CHILD_COLOUR ScrollView::BROWN
31 
33 
34 
43 WERD::WERD(C_BLOB_LIST* blob_list, uint8_t blank_count, const char* text)
44  : blanks(blank_count), flags(0), script_id_(0), correct(text) {
45  C_BLOB_IT start_it = &cblobs;
46  C_BLOB_IT rej_cblob_it = &rej_cblobs;
47  C_OUTLINE_IT c_outline_it;
48  int16_t inverted_vote = 0;
49  int16_t non_inverted_vote = 0;
50 
51  // Move blob_list's elements into cblobs.
52  start_it.add_list_after(blob_list);
53 
54  /*
55  Set white on black flag for the WERD, moving any duff blobs onto the
56  rej_cblobs list.
57  First, walk the cblobs checking the inverse flag for each outline of each
58  cblob. If a cblob has inconsistent flag settings for its different
59  outlines, move the blob to the reject list. Otherwise, increment the
60  appropriate w-on-b or b-on-w vote for the word.
61 
62  Now set the inversion flag for the WERD by maximum vote.
63 
64  Walk the blobs again, moving any blob whose inversion flag does not agree
65  with the concencus onto the reject list.
66  */
67  start_it.set_to_list(&cblobs);
68  if (start_it.empty()) return;
69  for (start_it.mark_cycle_pt(); !start_it.cycled_list(); start_it.forward()) {
70  bool reject_blob = false;
71  bool blob_inverted;
72 
73  c_outline_it.set_to_list(start_it.data()->out_list());
74  blob_inverted = c_outline_it.data()->flag(COUT_INVERSE);
75  for (c_outline_it.mark_cycle_pt();
76  !c_outline_it.cycled_list() && !reject_blob; c_outline_it.forward()) {
77  reject_blob = c_outline_it.data()->flag(COUT_INVERSE) != blob_inverted;
78  }
79  if (reject_blob) {
80  rej_cblob_it.add_after_then_move(start_it.extract());
81  } else {
82  if (blob_inverted)
83  inverted_vote++;
84  else
85  non_inverted_vote++;
86  }
87  }
88 
89  flags.set_bit(W_INVERSE, (inverted_vote > non_inverted_vote));
90 
91  start_it.set_to_list(&cblobs);
92  if (start_it.empty()) return;
93  for (start_it.mark_cycle_pt(); !start_it.cycled_list(); start_it.forward()) {
94  c_outline_it.set_to_list(start_it.data()->out_list());
95  if (c_outline_it.data()->flag(COUT_INVERSE) != flags.bit(W_INVERSE))
96  rej_cblob_it.add_after_then_move(start_it.extract());
97  }
98 }
99 
107 WERD::WERD(C_BLOB_LIST* blob_list,
108  WERD* clone)
109  : flags(clone->flags),
110  script_id_(clone->script_id_),
111  correct(clone->correct) {
112  C_BLOB_IT start_it = blob_list; // iterator
113  C_BLOB_IT end_it = blob_list; // another
114 
115  while (!end_it.at_last()) end_it.forward(); // move to last
116  (reinterpret_cast<C_BLOB_LIST*>(&cblobs))
117  ->assign_to_sublist(&start_it, &end_it);
118  // move to our list
119  blanks = clone->blanks;
120  // fprintf(stderr,"Wrong constructor!!!!\n");
121 }
122 
123 // Construct a WERD from a single_blob and clone the flags from this.
124 // W_BOL and W_EOL flags are set according to the given values.
125 WERD* WERD::ConstructFromSingleBlob(bool bol, bool eol, C_BLOB* blob) {
126  C_BLOB_LIST temp_blobs;
127  C_BLOB_IT temp_it(&temp_blobs);
128  temp_it.add_after_then_move(blob);
129  WERD* blob_word = new WERD(&temp_blobs, this);
130  blob_word->set_flag(W_BOL, bol);
131  blob_word->set_flag(W_EOL, eol);
132  return blob_word;
133 }
134 
148 TBOX WERD::bounding_box() const { return restricted_bounding_box(true, true); }
149 
150 // Returns the bounding box including the desired combination of upper and
151 // lower noise/diacritic elements.
152 TBOX WERD::restricted_bounding_box(bool upper_dots, bool lower_dots) const {
153  TBOX box = true_bounding_box();
154  int bottom = box.bottom();
155  int top = box.top();
156  // This is a read-only iteration of the rejected blobs.
157  C_BLOB_IT it(const_cast<C_BLOB_LIST*>(&rej_cblobs));
158  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
159  TBOX dot_box = it.data()->bounding_box();
160  if ((upper_dots || dot_box.bottom() <= top) &&
161  (lower_dots || dot_box.top() >= bottom)) {
162  box += dot_box;
163  }
164  }
165  return box;
166 }
167 
168 // Returns the bounding box of only the good blobs.
170  TBOX box; // box being built
171  // This is a read-only iteration of the good blobs.
172  C_BLOB_IT it(const_cast<C_BLOB_LIST*>(&cblobs));
173  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
174  box += it.data()->bounding_box();
175  }
176  return box;
177 }
178 
186 void WERD::move(const ICOORD vec) {
187  C_BLOB_IT cblob_it(&cblobs); // cblob iterator
188 
189  for (cblob_it.mark_cycle_pt(); !cblob_it.cycled_list(); cblob_it.forward())
190  cblob_it.data()->move(vec);
191 }
192 
199 void WERD::join_on(WERD* other) {
200  C_BLOB_IT blob_it(&cblobs);
201  C_BLOB_IT src_it(&other->cblobs);
202  C_BLOB_IT rej_cblob_it(&rej_cblobs);
203  C_BLOB_IT src_rej_it(&other->rej_cblobs);
204 
205  while (!src_it.empty()) {
206  blob_it.add_to_end(src_it.extract());
207  src_it.forward();
208  }
209  while (!src_rej_it.empty()) {
210  rej_cblob_it.add_to_end(src_rej_it.extract());
211  src_rej_it.forward();
212  }
213 }
214 
221 void WERD::copy_on(WERD* other) {
222  bool reversed = other->bounding_box().left() < bounding_box().left();
223  C_BLOB_IT c_blob_it(&cblobs);
224  C_BLOB_LIST c_blobs;
225 
226  c_blobs.deep_copy(&other->cblobs, &C_BLOB::deep_copy);
227  if (reversed) {
228  c_blob_it.add_list_before(&c_blobs);
229  } else {
230  c_blob_it.move_to_last();
231  c_blob_it.add_list_after(&c_blobs);
232  }
233  if (!other->rej_cblobs.empty()) {
234  C_BLOB_IT rej_c_blob_it(&rej_cblobs);
235  C_BLOB_LIST new_rej_c_blobs;
236 
237  new_rej_c_blobs.deep_copy(&other->rej_cblobs, &C_BLOB::deep_copy);
238  if (reversed) {
239  rej_c_blob_it.add_list_before(&new_rej_c_blobs);
240  } else {
241  rej_c_blob_it.move_to_last();
242  rej_c_blob_it.add_list_after(&new_rej_c_blobs);
243  }
244  }
245 }
246 
253 void WERD::print() {
254  tprintf("Blanks= %d\n", blanks);
255  bounding_box().print();
256  tprintf("Flags = %d = 0%o\n", flags.val, flags.val);
257  tprintf(" W_SEGMENTED = %s\n", flags.bit(W_SEGMENTED) ? "TRUE" : "FALSE");
258  tprintf(" W_ITALIC = %s\n", flags.bit(W_ITALIC) ? "TRUE" : "FALSE");
259  tprintf(" W_BOL = %s\n", flags.bit(W_BOL) ? "TRUE" : "FALSE");
260  tprintf(" W_EOL = %s\n", flags.bit(W_EOL) ? "TRUE" : "FALSE");
261  tprintf(" W_NORMALIZED = %s\n",
262  flags.bit(W_NORMALIZED) ? "TRUE" : "FALSE");
263  tprintf(" W_SCRIPT_HAS_XHEIGHT = %s\n",
264  flags.bit(W_SCRIPT_HAS_XHEIGHT) ? "TRUE" : "FALSE");
265  tprintf(" W_SCRIPT_IS_LATIN = %s\n",
266  flags.bit(W_SCRIPT_IS_LATIN) ? "TRUE" : "FALSE");
267  tprintf(" W_DONT_CHOP = %s\n", flags.bit(W_DONT_CHOP) ? "TRUE" : "FALSE");
268  tprintf(" W_REP_CHAR = %s\n", flags.bit(W_REP_CHAR) ? "TRUE" : "FALSE");
269  tprintf(" W_FUZZY_SP = %s\n", flags.bit(W_FUZZY_SP) ? "TRUE" : "FALSE");
270  tprintf(" W_FUZZY_NON = %s\n", flags.bit(W_FUZZY_NON) ? "TRUE" : "FALSE");
271  tprintf("Correct= %s\n", correct.string());
272  tprintf("Rejected cblob count = %d\n", rej_cblobs.length());
273  tprintf("Script = %d\n", script_id_);
274 }
275 
282 #ifndef GRAPHICS_DISABLED
283 void WERD::plot(ScrollView* window, ScrollView::Color colour) {
284  C_BLOB_IT it = &cblobs;
285  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
286  it.data()->plot(window, colour, colour);
287  }
288  plot_rej_blobs(window);
289 }
290 
291 // Get the next color in the (looping) rainbow.
293  auto next = static_cast<ScrollView::Color>(colour + 1);
295  return next;
296 }
297 
304 void WERD::plot(ScrollView* window) {
306  C_BLOB_IT it = &cblobs;
307  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
308  it.data()->plot(window, colour, CHILD_COLOUR);
309  colour = NextColor(colour);
310  }
311  plot_rej_blobs(window);
312 }
313 
321  C_BLOB_IT it = &rej_cblobs;
322  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
323  it.data()->plot(window, ScrollView::GREY, ScrollView::GREY);
324  }
325 }
326 #endif // GRAPHICS_DISABLED
327 
335  WERD* new_word = new WERD;
336 
337  new_word->blanks = blanks;
338  new_word->flags = flags;
339  new_word->dummy = dummy;
340  new_word->correct = correct;
341  return new_word;
342 }
343 
350 WERD& WERD::operator=(const WERD& source) {
351  this->ELIST2_LINK::operator=(source);
352  blanks = source.blanks;
353  flags = source.flags;
354  script_id_ = source.script_id_;
355  dummy = source.dummy;
356  correct = source.correct;
357  if (!cblobs.empty()) cblobs.clear();
358  cblobs.deep_copy(&source.cblobs, &C_BLOB::deep_copy);
359 
360  if (!rej_cblobs.empty()) rej_cblobs.clear();
361  rej_cblobs.deep_copy(&source.rej_cblobs, &C_BLOB::deep_copy);
362  return *this;
363 }
364 
372 int word_comparator(const void* word1p, const void* word2p) {
373  const WERD* word1 = *reinterpret_cast<const WERD* const*>(word1p);
374  const WERD* word2 = *reinterpret_cast<const WERD* const*>(word2p);
375  return word1->bounding_box().left() - word2->bounding_box().left();
376 }
377 
390 WERD* WERD::ConstructWerdWithNewBlobs(C_BLOB_LIST* all_blobs,
391  C_BLOB_LIST* orphan_blobs) {
392  C_BLOB_LIST current_blob_list;
393  C_BLOB_IT werd_blobs_it(&current_blob_list);
394  // Add the word's c_blobs.
395  werd_blobs_it.add_list_after(cblob_list());
396 
397  // New blob list. These contain the blobs which will form the new word.
398  C_BLOB_LIST new_werd_blobs;
399  C_BLOB_IT new_blobs_it(&new_werd_blobs);
400 
401  // not_found_blobs contains the list of current word's blobs for which a
402  // corresponding blob wasn't found in the input all_blobs list.
403  C_BLOB_LIST not_found_blobs;
404  C_BLOB_IT not_found_it(&not_found_blobs);
405  not_found_it.move_to_last();
406 
407  werd_blobs_it.move_to_first();
408  for (werd_blobs_it.mark_cycle_pt(); !werd_blobs_it.cycled_list();
409  werd_blobs_it.forward()) {
410  C_BLOB* werd_blob = werd_blobs_it.extract();
411  TBOX werd_blob_box = werd_blob->bounding_box();
412  bool found = false;
413  // Now find the corresponding blob for this blob in the all_blobs
414  // list. For now, follow the inefficient method of pairwise
415  // comparisons. Ideally, one can pre-bucket the blobs by row.
416  C_BLOB_IT all_blobs_it(all_blobs);
417  for (all_blobs_it.mark_cycle_pt(); !all_blobs_it.cycled_list();
418  all_blobs_it.forward()) {
419  C_BLOB* a_blob = all_blobs_it.data();
420  // Compute the overlap of the two blobs. If major, a_blob should
421  // be added to the new blobs list.
422  TBOX a_blob_box = a_blob->bounding_box();
423  if (a_blob_box.null_box()) {
424  tprintf("Bounding box couldn't be ascertained\n");
425  }
426  if (werd_blob_box.contains(a_blob_box) ||
427  werd_blob_box.major_overlap(a_blob_box)) {
428  // Old blobs are from minimal splits, therefore are expected to be
429  // bigger. The new small blobs should cover a significant portion.
430  // This is it.
431  all_blobs_it.extract();
432  new_blobs_it.add_after_then_move(a_blob);
433  found = true;
434  }
435  }
436  if (!found) {
437  not_found_it.add_after_then_move(werd_blob);
438  } else {
439  delete werd_blob;
440  }
441  }
442  // Iterate over all not found blobs. Some of them may be due to
443  // under-segmentation (which is OK, since the corresponding blob is already
444  // in the list in that case.
445  not_found_it.move_to_first();
446  for (not_found_it.mark_cycle_pt(); !not_found_it.cycled_list();
447  not_found_it.forward()) {
448  C_BLOB* not_found = not_found_it.data();
449  TBOX not_found_box = not_found->bounding_box();
450  C_BLOB_IT existing_blobs_it(new_blobs_it);
451  for (existing_blobs_it.mark_cycle_pt(); !existing_blobs_it.cycled_list();
452  existing_blobs_it.forward()) {
453  C_BLOB* a_blob = existing_blobs_it.data();
454  TBOX a_blob_box = a_blob->bounding_box();
455  if ((not_found_box.major_overlap(a_blob_box) ||
456  a_blob_box.major_overlap(not_found_box)) &&
457  not_found_box.y_overlap_fraction(a_blob_box) > 0.8) {
458  // Already taken care of.
459  delete not_found_it.extract();
460  break;
461  }
462  }
463  }
464  if (orphan_blobs) {
465  C_BLOB_IT orphan_blobs_it(orphan_blobs);
466  orphan_blobs_it.move_to_last();
467  orphan_blobs_it.add_list_after(&not_found_blobs);
468  }
469 
470  // New blobs are ready. Create a new werd object with these.
471  WERD* new_werd = nullptr;
472  if (!new_werd_blobs.empty()) {
473  new_werd = new WERD(&new_werd_blobs, this);
474  } else {
475  // Add the blobs back to this word so that it can be reused.
476  C_BLOB_IT this_list_it(cblob_list());
477  this_list_it.add_list_after(&not_found_blobs);
478  }
479  return new_werd;
480 }
481 
482 // Removes noise from the word by moving small outlines to the rej_cblobs
483 // list, based on the size_threshold.
484 void WERD::CleanNoise(float size_threshold) {
485  C_BLOB_IT blob_it(&cblobs);
486  C_BLOB_IT rej_it(&rej_cblobs);
487  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
488  C_BLOB* blob = blob_it.data();
489  C_OUTLINE_IT ol_it(blob->out_list());
490  for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) {
491  C_OUTLINE* outline = ol_it.data();
492  TBOX ol_box = outline->bounding_box();
493  int ol_size =
494  ol_box.width() > ol_box.height() ? ol_box.width() : ol_box.height();
495  if (ol_size < size_threshold) {
496  // This outline is too small. Move it to a separate blob in the
497  // reject blobs list.
498  auto* rej_blob = new C_BLOB(ol_it.extract());
499  rej_it.add_after_then_move(rej_blob);
500  }
501  }
502  if (blob->out_list()->empty()) delete blob_it.extract();
503  }
504 }
505 
506 // Extracts all the noise outlines and stuffs the pointers into the given
507 // vector of outlines. Afterwards, the outlines vector owns the pointers.
509  C_BLOB_IT rej_it(&rej_cblobs);
510  for (rej_it.mark_cycle_pt(); !rej_it.empty(); rej_it.forward()) {
511  C_BLOB* blob = rej_it.extract();
512  C_OUTLINE_IT ol_it(blob->out_list());
513  outlines->push_back(ol_it.extract());
514  delete blob;
515  }
516 }
517 
518 // Adds the selected outlines to the indcated real blobs, and puts the rest
519 // back in rej_cblobs where they came from. Where the target_blobs entry is
520 // nullptr, a run of wanted outlines is put into a single new blob.
521 // Ownership of the outlines is transferred back to the word. (Hence
522 // GenericVector and not PointerVector.)
523 // Returns true if any new blob was added to the start of the word, which
524 // suggests that it might need joining to the word before it, and likewise
525 // sets make_next_word_fuzzy true if any new blob was added to the end.
527  const GenericVector<C_BLOB*>& target_blobs,
528  const GenericVector<C_OUTLINE*>& outlines,
529  bool* make_next_word_fuzzy) {
530  bool outline_added_to_start = false;
531  if (make_next_word_fuzzy != nullptr) *make_next_word_fuzzy = false;
532  C_BLOB_IT rej_it(&rej_cblobs);
533  for (int i = 0; i < outlines.size(); ++i) {
534  C_OUTLINE* outline = outlines[i];
535  if (outline == nullptr) continue; // Already used it.
536  if (wanted[i]) {
537  C_BLOB* target_blob = target_blobs[i];
538  TBOX noise_box = outline->bounding_box();
539  if (target_blob == nullptr) {
540  target_blob = new C_BLOB(outline);
541  // Need to find the insertion point.
542  C_BLOB_IT blob_it(&cblobs);
543  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list();
544  blob_it.forward()) {
545  C_BLOB* blob = blob_it.data();
546  TBOX blob_box = blob->bounding_box();
547  if (blob_box.left() > noise_box.left()) {
548  if (blob_it.at_first() && !flag(W_FUZZY_SP) && !flag(W_FUZZY_NON)) {
549  // We might want to join this word to its predecessor.
550  outline_added_to_start = true;
551  }
552  blob_it.add_before_stay_put(target_blob);
553  break;
554  }
555  }
556  if (blob_it.cycled_list()) {
557  blob_it.add_to_end(target_blob);
558  if (make_next_word_fuzzy != nullptr) *make_next_word_fuzzy = true;
559  }
560  // Add all consecutive wanted, but null-blob outlines to same blob.
561  C_OUTLINE_IT ol_it(target_blob->out_list());
562  while (i + 1 < outlines.size() && wanted[i + 1] &&
563  target_blobs[i + 1] == nullptr) {
564  ++i;
565  ol_it.add_to_end(outlines[i]);
566  }
567  } else {
568  // Insert outline into this blob.
569  C_OUTLINE_IT ol_it(target_blob->out_list());
570  ol_it.add_to_end(outline);
571  }
572  } else {
573  // Put back on noise list.
574  rej_it.add_to_end(new C_BLOB(outline));
575  }
576  }
577  return outline_added_to_start;
578 }
Definition: werd.h:56
void GetNoiseOutlines(GenericVector< C_OUTLINE * > *outlines)
Definition: werd.cpp:508
int16_t top() const
Definition: rect.h:58
void CleanNoise(float size_threshold)
Definition: werd.cpp:484
C_OUTLINE_LIST * out_list()
Definition: stepblob.h:70
Special case latin for y. splitting.
Definition: werd.h:36
Definition: rect.h:34
char flags[EDGEPTFLAGS]
Definition: blobs.h:170
bool major_overlap(const TBOX &box) const
Definition: rect.h:368
#define FIRST_COLOUR
first rainbow colour
Definition: werd.cpp:28
fuzzy nonspace
Definition: werd.h:40
void print() const
Definition: rect.h:278
TBOX restricted_bounding_box(bool upper_dots, bool lower_dots) const
Definition: werd.cpp:152
EDGEPT * next
Definition: blobs.h:171
WERD * shallow_copy()
Definition: werd.cpp:334
const TBOX & bounding_box() const
Definition: coutln.h:113
#define CHILD_COLOUR
colour of children
Definition: werd.cpp:30
start of line
Definition: werd.h:32
static ScrollView::Color NextColor(ScrollView::Color colour)
Definition: werd.cpp:292
integer coordinate
Definition: points.h:31
italic text
Definition: werd.h:30
bool contains(const FCOORD pt) const
Definition: rect.h:333
bool AddSelectedOutlines(const GenericVector< bool > &wanted, const GenericVector< C_BLOB * > &target_blobs, const GenericVector< C_OUTLINE * > &outlines, bool *make_next_word_fuzzy)
Definition: werd.cpp:526
TBOX bounding_box() const
Definition: stepblob.cpp:253
void copy_on(WERD *other)
Definition: werd.cpp:221
repeated character
Definition: werd.h:38
fuzzy space
Definition: werd.h:39
void plot_rej_blobs(ScrollView *window)
Definition: werd.cpp:320
fixed pitch chopped
Definition: werd.h:37
end of line
Definition: werd.h:33
int16_t height() const
Definition: rect.h:108
void print()
Definition: werd.cpp:253
flags
Definition: werd.h:34
double y_overlap_fraction(const TBOX &box) const
Definition: rect.h:479
const char * string() const
Definition: strngs.cpp:194
white on black
Definition: werd.h:41
DLLSYM void tprintf(const char *format,...)
Definition: tprintf.cpp:36
#define LAST_COLOUR
last rainbow colour
Definition: werd.cpp:29
int push_back(T object)
int16_t width() const
Definition: rect.h:115
void move(const ICOORD vec)
Definition: werd.cpp:186
int16_t bottom() const
Definition: rect.h:65
WERD()=default
TBOX bounding_box() const
Definition: werd.cpp:148
void plot(ScrollView *window, ScrollView::Color colour)
Definition: werd.cpp:283
WERD * ConstructFromSingleBlob(bool bol, bool eol, C_BLOB *blob)
Definition: werd.cpp:125
int word_comparator(const void *word1p, const void *word2p)
Definition: werd.cpp:372
TBOX true_bounding_box() const
Definition: werd.cpp:169
int16_t left() const
Definition: rect.h:72
WERD * ConstructWerdWithNewBlobs(C_BLOB_LIST *all_blobs, C_BLOB_LIST *orphan_blobs)
Definition: werd.cpp:390
bool flag(WERD_FLAGS mask) const
Definition: werd.h:117
void operator=(const ELIST2_LINK &)
Definition: elst2.h:74
WERD & operator=(const WERD &source)
Definition: werd.cpp:350
static C_BLOB * deep_copy(const C_BLOB *src)
Definition: stepblob.h:119
correctly segmented
Definition: werd.h:29
#define ELIST2IZE(CLASSNAME)
Definition: elst2.h:959
int size() const
Definition: genericvector.h:70
x-height concept makes sense.
Definition: werd.h:35
void join_on(WERD *other)
Definition: werd.cpp:199
bool null_box() const
Definition: rect.h:50
void set_flag(WERD_FLAGS mask, bool value)
Definition: werd.h:118
VECTOR vec
Definition: blobs.h:166
C_BLOB_LIST * cblob_list()
Definition: werd.h:95