tesseract  4.0.0-1-g2a2b
superscript.cpp
Go to the documentation of this file.
1 /******************************************************************
2  * File: superscript.cpp
3  * Description: Correction pass to fix superscripts and subscripts.
4  * Author: David Eger
5  * Created: Mon Mar 12 14:05:00 PDT 2012
6  *
7  * (C) Copyright 2012, 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 "normalis.h"
21 #include "tesseractclass.h"
22 
23 static int LeadingUnicharsToChopped(WERD_RES *word, int num_unichars) {
24  int num_chopped = 0;
25  for (int i = 0; i < num_unichars; i++)
26  num_chopped += word->best_state[i];
27  return num_chopped;
28 }
29 
30 static int TrailingUnicharsToChopped(WERD_RES *word, int num_unichars) {
31  int num_chopped = 0;
32  for (int i = 0; i < num_unichars; i++)
33  num_chopped += word->best_state[word->best_state.size() - 1 - i];
34  return num_chopped;
35 }
36 
37 
38 namespace tesseract {
39 
46 static void YOutlierPieces(WERD_RES *word, int rebuilt_blob_index,
47  int super_y_bottom, int sub_y_top,
48  ScriptPos *leading_pos, int *num_leading_outliers,
49  ScriptPos *trailing_pos,
50  int *num_trailing_outliers) {
51  ScriptPos sp_unused1, sp_unused2;
52  int unused1, unused2;
53  if (!leading_pos) leading_pos = &sp_unused1;
54  if (!num_leading_outliers) num_leading_outliers = &unused1;
55  if (!trailing_pos) trailing_pos = &sp_unused2;
56  if (!num_trailing_outliers) num_trailing_outliers = &unused2;
57 
58  *num_leading_outliers = *num_trailing_outliers = 0;
59  *leading_pos = *trailing_pos = SP_NORMAL;
60 
61  int chopped_start = LeadingUnicharsToChopped(word, rebuilt_blob_index);
62  int num_chopped_pieces = word->best_state[rebuilt_blob_index];
63  ScriptPos last_pos = SP_NORMAL;
64  int trailing_outliers = 0;
65  for (int i = 0; i < num_chopped_pieces; i++) {
66  TBOX box = word->chopped_word->blobs[chopped_start + i]->bounding_box();
67  ScriptPos pos = SP_NORMAL;
68  if (box.bottom() >= super_y_bottom) {
69  pos = SP_SUPERSCRIPT;
70  } else if (box.top() <= sub_y_top) {
71  pos = SP_SUBSCRIPT;
72  }
73  if (pos == SP_NORMAL) {
74  if (trailing_outliers == i) {
75  *num_leading_outliers = trailing_outliers;
76  *leading_pos = last_pos;
77  }
78  trailing_outliers = 0;
79  } else {
80  if (pos == last_pos) {
81  trailing_outliers++;
82  } else {
83  trailing_outliers = 1;
84  }
85  }
86  last_pos = pos;
87  }
88  *num_trailing_outliers = trailing_outliers;
89  *trailing_pos = last_pos;
90 }
91 
103  if (word->tess_failed || word->word->flag(W_REP_CHAR) ||
104  !word->best_choice) {
105  return false;
106  }
107  int num_leading, num_trailing;
108  ScriptPos sp_leading, sp_trailing;
109  float leading_certainty, trailing_certainty;
110  float avg_certainty, unlikely_threshold;
111 
112  // Calculate the number of whole suspicious characters at the edges.
114  word, &num_leading, &sp_leading, &leading_certainty,
115  &num_trailing, &sp_trailing, &trailing_certainty,
116  &avg_certainty, &unlikely_threshold);
117 
118  const char *leading_pos = sp_leading == SP_SUBSCRIPT ? "sub" : "super";
119  const char *trailing_pos = sp_trailing == SP_SUBSCRIPT ? "sub" : "super";
120 
121  int num_blobs = word->best_choice->length();
122 
123  // Calculate the remainder (partial characters) at the edges.
124  // This accounts for us having classified the best version of
125  // a word as [speaker?'] when it was instead [speaker.^{21}]
126  // (that is we accidentally thought the 2 was attached to the period).
127  int num_remainder_leading = 0, num_remainder_trailing = 0;
128  if (num_leading + num_trailing < num_blobs && unlikely_threshold < 0.0) {
129  int super_y_bottom =
131  int sub_y_top =
133  int last_word_char = num_blobs - 1 - num_trailing;
134  float last_char_certainty = word->best_choice->certainty(last_word_char);
135  if (word->best_choice->unichar_id(last_word_char) != 0 &&
136  last_char_certainty <= unlikely_threshold) {
137  ScriptPos rpos;
138  YOutlierPieces(word, last_word_char, super_y_bottom, sub_y_top,
139  nullptr, nullptr, &rpos, &num_remainder_trailing);
140  if (num_trailing > 0 && rpos != sp_trailing) num_remainder_trailing = 0;
141  if (num_remainder_trailing > 0 &&
142  last_char_certainty < trailing_certainty) {
143  trailing_certainty = last_char_certainty;
144  }
145  }
146  bool another_blob_available = (num_remainder_trailing == 0) ||
147  num_leading + num_trailing + 1 < num_blobs;
148  int first_char_certainty = word->best_choice->certainty(num_leading);
149  if (another_blob_available &&
150  word->best_choice->unichar_id(num_leading) != 0 &&
151  first_char_certainty <= unlikely_threshold) {
152  ScriptPos lpos;
153  YOutlierPieces(word, num_leading, super_y_bottom, sub_y_top,
154  &lpos, &num_remainder_leading, nullptr, nullptr);
155  if (num_leading > 0 && lpos != sp_leading) num_remainder_leading = 0;
156  if (num_remainder_leading > 0 &&
157  first_char_certainty < leading_certainty) {
158  leading_certainty = first_char_certainty;
159  }
160  }
161  }
162 
163  // If nothing to do, bail now.
164  if (num_leading + num_trailing +
165  num_remainder_leading + num_remainder_trailing == 0) {
166  return false;
167  }
168 
169  if (superscript_debug >= 1) {
170  tprintf("Candidate for superscript detection: %s (",
171  word->best_choice->unichar_string().string());
172  if (num_leading || num_remainder_leading) {
173  tprintf("%d.%d %s-leading ", num_leading, num_remainder_leading,
174  leading_pos);
175  }
176  if (num_trailing || num_remainder_trailing) {
177  tprintf("%d.%d %s-trailing ", num_trailing, num_remainder_trailing,
178  trailing_pos);
179  }
180  tprintf(")\n");
181  }
182  if (superscript_debug >= 3) {
183  word->best_choice->print();
184  }
185  if (superscript_debug >= 2) {
186  tprintf(" Certainties -- Average: %.2f Unlikely thresh: %.2f ",
187  avg_certainty, unlikely_threshold);
188  if (num_leading)
189  tprintf("Orig. leading (min): %.2f ", leading_certainty);
190  if (num_trailing)
191  tprintf("Orig. trailing (min): %.2f ", trailing_certainty);
192  tprintf("\n");
193  }
194 
195  // We've now calculated the number of rebuilt blobs we want to carve off.
196  // However, split_word() works from TBLOBs in chopped_word, so we need to
197  // convert to those.
198  int num_chopped_leading =
199  LeadingUnicharsToChopped(word, num_leading) + num_remainder_leading;
200  int num_chopped_trailing =
201  TrailingUnicharsToChopped(word, num_trailing) + num_remainder_trailing;
202 
203  int retry_leading = 0;
204  int retry_trailing = 0;
205  bool is_good = false;
206  WERD_RES *revised = TrySuperscriptSplits(
207  num_chopped_leading, leading_certainty, sp_leading,
208  num_chopped_trailing, trailing_certainty, sp_trailing,
209  word, &is_good, &retry_leading, &retry_trailing);
210  if (is_good) {
211  word->ConsumeWordResults(revised);
212  } else if (retry_leading || retry_trailing) {
213  int retry_chopped_leading =
214  LeadingUnicharsToChopped(revised, retry_leading);
215  int retry_chopped_trailing =
216  TrailingUnicharsToChopped(revised, retry_trailing);
217  WERD_RES *revised2 = TrySuperscriptSplits(
218  retry_chopped_leading, leading_certainty, sp_leading,
219  retry_chopped_trailing, trailing_certainty, sp_trailing,
220  revised, &is_good, &retry_leading, &retry_trailing);
221  if (is_good) {
222  word->ConsumeWordResults(revised2);
223  }
224  delete revised2;
225  }
226  delete revised;
227  return is_good;
228 }
229 
255  int *num_rebuilt_leading,
256  ScriptPos *leading_pos,
257  float *leading_certainty,
258  int *num_rebuilt_trailing,
259  ScriptPos *trailing_pos,
260  float *trailing_certainty,
261  float *avg_certainty,
262  float *unlikely_threshold) {
263  *avg_certainty = *unlikely_threshold = 0.0f;
264  *num_rebuilt_leading = *num_rebuilt_trailing = 0;
265  *leading_certainty = *trailing_certainty = 0.0f;
266 
267  int super_y_bottom =
269  int sub_y_top =
271 
272  // Step one: Get an average certainty for "normally placed" characters.
273 
274  // Counts here are of blobs in the rebuild_word / unichars in best_choice.
275  *leading_pos = *trailing_pos = SP_NORMAL;
276  int leading_outliers = 0;
277  int trailing_outliers = 0;
278  int num_normal = 0;
279  float normal_certainty_total = 0.0f;
280  float worst_normal_certainty = 0.0f;
281  ScriptPos last_pos = SP_NORMAL;
282  int num_blobs = word->rebuild_word->NumBlobs();
283  for (int b = 0; b < num_blobs; ++b) {
284  TBOX box = word->rebuild_word->blobs[b]->bounding_box();
285  ScriptPos pos = SP_NORMAL;
286  if (box.bottom() >= super_y_bottom) {
287  pos = SP_SUPERSCRIPT;
288  } else if (box.top() <= sub_y_top) {
289  pos = SP_SUBSCRIPT;
290  }
291  if (pos == SP_NORMAL) {
292  if (word->best_choice->unichar_id(b) != 0) {
293  float char_certainty = word->best_choice->certainty(b);
294  if (char_certainty < worst_normal_certainty) {
295  worst_normal_certainty = char_certainty;
296  }
297  num_normal++;
298  normal_certainty_total += char_certainty;
299  }
300  if (trailing_outliers == b) {
301  leading_outliers = trailing_outliers;
302  *leading_pos = last_pos;
303  }
304  trailing_outliers = 0;
305  } else {
306  if (last_pos == pos) {
307  trailing_outliers++;
308  } else {
309  trailing_outliers = 1;
310  }
311  }
312  last_pos = pos;
313  }
314  *trailing_pos = last_pos;
315  if (num_normal >= 3) { // throw out the worst as an outlier.
316  num_normal--;
317  normal_certainty_total -= worst_normal_certainty;
318  }
319  if (num_normal > 0) {
320  *avg_certainty = normal_certainty_total / num_normal;
321  *unlikely_threshold = superscript_worse_certainty * (*avg_certainty);
322  }
323  if (num_normal == 0 ||
324  (leading_outliers == 0 && trailing_outliers == 0)) {
325  return;
326  }
327 
328  // Step two: Try to split off bits of the word that are both outliers
329  // and have much lower certainty than average
330  // Calculate num_leading and leading_certainty.
331  for (*leading_certainty = 0.0f, *num_rebuilt_leading = 0;
332  *num_rebuilt_leading < leading_outliers;
333  (*num_rebuilt_leading)++) {
334  float char_certainty = word->best_choice->certainty(*num_rebuilt_leading);
335  if (char_certainty > *unlikely_threshold) {
336  break;
337  }
338  if (char_certainty < *leading_certainty) {
339  *leading_certainty = char_certainty;
340  }
341  }
342 
343  // Calculate num_trailing and trailing_certainty.
344  for (*trailing_certainty = 0.0f, *num_rebuilt_trailing = 0;
345  *num_rebuilt_trailing < trailing_outliers;
346  (*num_rebuilt_trailing)++) {
347  int blob_idx = num_blobs - 1 - *num_rebuilt_trailing;
348  float char_certainty = word->best_choice->certainty(blob_idx);
349  if (char_certainty > *unlikely_threshold) {
350  break;
351  }
352  if (char_certainty < *trailing_certainty) {
353  *trailing_certainty = char_certainty;
354  }
355  }
356 }
357 
358 
384  int num_chopped_leading, float leading_certainty, ScriptPos leading_pos,
385  int num_chopped_trailing, float trailing_certainty,
386  ScriptPos trailing_pos,
387  WERD_RES *word,
388  bool *is_good,
389  int *retry_rebuild_leading, int *retry_rebuild_trailing) {
390  int num_chopped = word->chopped_word->NumBlobs();
391 
392  *retry_rebuild_leading = *retry_rebuild_trailing = 0;
393 
394  // Chop apart the word into up to three pieces.
395 
396  BlamerBundle *bb0 = nullptr;
397  BlamerBundle *bb1 = nullptr;
398  WERD_RES *prefix = nullptr;
399  WERD_RES *core = nullptr;
400  WERD_RES *suffix = nullptr;
401  if (num_chopped_leading > 0) {
402  prefix = new WERD_RES(*word);
403  split_word(prefix, num_chopped_leading, &core, &bb0);
404  } else {
405  core = new WERD_RES(*word);
406  }
407 
408  if (num_chopped_trailing > 0) {
409  int split_pt = num_chopped - num_chopped_trailing - num_chopped_leading;
410  split_word(core, split_pt, &suffix, &bb1);
411  }
412 
413  // Recognize the pieces in turn.
414  int saved_cp_multiplier = classify_class_pruner_multiplier;
415  int saved_im_multiplier = classify_integer_matcher_multiplier;
416  if (prefix) {
417  // Turn off Tesseract's y-position penalties for the leading superscript.
420 
421  // Adjust our expectations about the baseline for this prefix.
422  if (superscript_debug >= 3) {
423  tprintf(" recognizing first %d chopped blobs\n", num_chopped_leading);
424  }
425  recog_word_recursive(prefix);
426  if (superscript_debug >= 2) {
427  tprintf(" The leading bits look like %s %s\n",
428  ScriptPosToString(leading_pos),
429  prefix->best_choice->unichar_string().string());
430  }
431 
432  // Restore the normal y-position penalties.
433  classify_class_pruner_multiplier.set_value(saved_cp_multiplier);
434  classify_integer_matcher_multiplier.set_value(saved_im_multiplier);
435  }
436 
437  if (superscript_debug >= 3) {
438  tprintf(" recognizing middle %d chopped blobs\n",
439  num_chopped - num_chopped_leading - num_chopped_trailing);
440  }
441 
442  if (suffix) {
443  // Turn off Tesseract's y-position penalties for the trailing superscript.
446 
447  if (superscript_debug >= 3) {
448  tprintf(" recognizing last %d chopped blobs\n", num_chopped_trailing);
449  }
450  recog_word_recursive(suffix);
451  if (superscript_debug >= 2) {
452  tprintf(" The trailing bits look like %s %s\n",
453  ScriptPosToString(trailing_pos),
454  suffix->best_choice->unichar_string().string());
455  }
456 
457  // Restore the normal y-position penalties.
458  classify_class_pruner_multiplier.set_value(saved_cp_multiplier);
459  classify_integer_matcher_multiplier.set_value(saved_im_multiplier);
460  }
461 
462  // Evaluate whether we think the results are believably better
463  // than what we already had.
464  bool good_prefix = !prefix || BelievableSuperscript(
465  superscript_debug >= 1, *prefix,
466  superscript_bettered_certainty * leading_certainty,
467  retry_rebuild_leading, nullptr);
468  bool good_suffix = !suffix || BelievableSuperscript(
469  superscript_debug >= 1, *suffix,
470  superscript_bettered_certainty * trailing_certainty,
471  nullptr, retry_rebuild_trailing);
472 
473  *is_good = good_prefix && good_suffix;
474  if (!*is_good && !*retry_rebuild_leading && !*retry_rebuild_trailing) {
475  // None of it is any good. Quit now.
476  delete core;
477  delete prefix;
478  delete suffix;
479  delete bb1;
480  return nullptr;
481  }
482  recog_word_recursive(core);
483 
484  // Now paste the results together into core.
485  if (suffix) {
486  suffix->SetAllScriptPositions(trailing_pos);
487  join_words(core, suffix, bb1);
488  }
489  if (prefix) {
490  prefix->SetAllScriptPositions(leading_pos);
491  join_words(prefix, core, bb0);
492  core = prefix;
493  prefix = nullptr;
494  }
495 
496  if (superscript_debug >= 1) {
497  tprintf("%s superscript fix: %s\n", *is_good ? "ACCEPT" : "REJECT",
498  core->best_choice->unichar_string().string());
499  }
500  return core;
501 }
502 
503 
523  const WERD_RES &word,
524  float certainty_threshold,
525  int *left_ok,
526  int *right_ok) const {
527  int initial_ok_run_count = 0;
528  int ok_run_count = 0;
529  float worst_certainty = 0.0f;
530  const WERD_CHOICE &wc = *word.best_choice;
531 
532  const UnicityTable<FontInfo>& fontinfo_table = get_fontinfo_table();
533  for (int i = 0; i < wc.length(); i++) {
534  TBLOB *blob = word.rebuild_word->blobs[i];
535  UNICHAR_ID unichar_id = wc.unichar_id(i);
536  float char_certainty = wc.certainty(i);
537  bool bad_certainty = char_certainty < certainty_threshold;
538  bool is_punc = wc.unicharset()->get_ispunctuation(unichar_id);
539  bool is_italic = word.fontinfo && word.fontinfo->is_italic();
540  BLOB_CHOICE *choice = word.GetBlobChoice(i);
541  if (choice && fontinfo_table.size() > 0) {
542  // Get better information from the specific choice, if available.
543  int font_id1 = choice->fontinfo_id();
544  bool font1_is_italic = font_id1 >= 0
545  ? fontinfo_table.get(font_id1).is_italic() : false;
546  int font_id2 = choice->fontinfo_id2();
547  is_italic = font1_is_italic &&
548  (font_id2 < 0 || fontinfo_table.get(font_id2).is_italic());
549  }
550 
551  float height_fraction = 1.0f;
552  float char_height = blob->bounding_box().height();
553  float normal_height = char_height;
554  if (wc.unicharset()->top_bottom_useful()) {
555  int min_bot, max_bot, min_top, max_top;
556  wc.unicharset()->get_top_bottom(unichar_id,
557  &min_bot, &max_bot,
558  &min_top, &max_top);
559  float hi_height = max_top - max_bot;
560  float lo_height = min_top - min_bot;
561  normal_height = (hi_height + lo_height) / 2;
562  if (normal_height >= kBlnXHeight) {
563  // Only ding characters that we have decent information for because
564  // they're supposed to be normal sized, not tiny specks or dashes.
565  height_fraction = char_height / normal_height;
566  }
567  }
568  bool bad_height = height_fraction < superscript_scaledown_ratio;
569 
570  if (debug) {
571  if (is_italic) {
572  tprintf(" Rejecting: superscript is italic.\n");
573  }
574  if (is_punc) {
575  tprintf(" Rejecting: punctuation present.\n");
576  }
577  const char *char_str = wc.unicharset()->id_to_unichar(unichar_id);
578  if (bad_certainty) {
579  tprintf(" Rejecting: don't believe character %s with certainty %.2f "
580  "which is less than threshold %.2f\n", char_str,
581  char_certainty, certainty_threshold);
582  }
583  if (bad_height) {
584  tprintf(" Rejecting: character %s seems too small @ %.2f versus "
585  "expected %.2f\n", char_str, char_height, normal_height);
586  }
587  }
588  if (bad_certainty || bad_height || is_punc || is_italic) {
589  if (ok_run_count == i) {
590  initial_ok_run_count = ok_run_count;
591  }
592  ok_run_count = 0;
593  } else {
594  ok_run_count++;
595  }
596  if (char_certainty < worst_certainty) {
597  worst_certainty = char_certainty;
598  }
599  }
600  bool all_ok = ok_run_count == wc.length();
601  if (all_ok && debug) {
602  tprintf(" Accept: worst revised certainty is %.2f\n", worst_certainty);
603  }
604  if (!all_ok) {
605  if (left_ok) *left_ok = initial_ok_run_count;
606  if (right_ok) *right_ok = ok_run_count;
607  }
608  return all_ok;
609 }
610 
611 
612 } // namespace tesseract
double superscript_bettered_certainty
void ConsumeWordResults(WERD_RES *word)
Definition: pageres.cpp:771
bool tess_failed
Definition: pageres.h:288
TWERD * rebuild_word
Definition: pageres.h:260
int UNICHAR_ID
Definition: unichar.h:35
int size() const
Definition: genericvector.h:71
void join_words(WERD_RES *word, WERD_RES *word2, BlamerBundle *orig_bb) const
Definition: tfacepp.cpp:234
bool get_ispunctuation(UNICHAR_ID unichar_id) const
Definition: unicharset.h:514
UnicityTable< FontInfo > & get_fontinfo_table()
Definition: classify.h:386
const char * string() const
Definition: strngs.cpp:196
void print() const
Definition: ratngs.h:580
const UNICHARSET * unicharset() const
Definition: ratngs.h:300
Definition: rect.h:34
int NumBlobs() const
Definition: blobs.h:432
const int kBlnXHeight
Definition: normalis.h:24
const FontInfo * fontinfo
Definition: pageres.h:304
float certainty() const
Definition: ratngs.h:330
const int kBlnBaselineOffset
Definition: normalis.h:25
bool SubAndSuperscriptFix(WERD_RES *word_res)
int16_t fontinfo_id() const
Definition: ratngs.h:86
int classify_class_pruner_multiplier
Definition: classify.h:506
bool flag(WERD_FLAGS mask) const
Definition: werd.h:126
int16_t top() const
Definition: rect.h:58
void get_top_bottom(UNICHAR_ID unichar_id, int *min_bottom, int *max_bottom, int *min_top, int *max_top) const
Definition: unicharset.h:563
int16_t fontinfo_id2() const
Definition: ratngs.h:89
bool is_italic() const
Definition: fontinfo.h:111
int size() const
Return the size used.
GenericVector< int > best_state
Definition: pageres.h:271
bool BelievableSuperscript(bool debug, const WERD_RES &word, float certainty_threshold, int *left_ok, int *right_ok) const
UNICHAR_ID unichar_id(int index) const
Definition: ratngs.h:315
DLLSYM void tprintf(const char *format,...)
Definition: tprintf.cpp:37
TBOX bounding_box() const
Definition: blobs.cpp:478
int length() const
Definition: ratngs.h:303
GenericVector< TBLOB * > blobs
Definition: blobs.h:443
int classify_integer_matcher_multiplier
Definition: classify.h:510
const T & get(int id) const
Return the object from an id.
const char * id_to_unichar(UNICHAR_ID id) const
Definition: unicharset.cpp:290
const char * ScriptPosToString(enum ScriptPos script_pos)
Definition: ratngs.cpp:200
void recog_word_recursive(WERD_RES *word)
Definition: tfacepp.cpp:104
const STRING & unichar_string() const
Definition: ratngs.h:541
void GetSubAndSuperscriptCandidates(const WERD_RES *word, int *num_rebuilt_leading, ScriptPos *leading_pos, float *leading_certainty, int *num_rebuilt_trailing, ScriptPos *trailing_pos, float *trailing_certainty, float *avg_certainty, float *unlikely_threshold)
WERD_RES * TrySuperscriptSplits(int num_chopped_leading, float leading_certainty, ScriptPos leading_pos, int num_chopped_trailing, float trailing_certainty, ScriptPos trailing_pos, WERD_RES *word, bool *is_good, int *retry_leading, int *retry_trailing)
void split_word(WERD_RES *word, int split_pt, WERD_RES **right_piece, BlamerBundle **orig_blamer_bundle) const
Definition: tfacepp.cpp:176
Definition: blobs.h:268
void SetAllScriptPositions(tesseract::ScriptPos position)
Definition: pageres.cpp:871
TWERD * chopped_word
Definition: pageres.h:215
int16_t bottom() const
Definition: rect.h:65
bool top_bottom_useful() const
Definition: unicharset.h:532
WERD_CHOICE * best_choice
Definition: pageres.h:235
int16_t height() const
Definition: rect.h:108
BLOB_CHOICE * GetBlobChoice(int index) const
Definition: pageres.cpp:756
WERD * word
Definition: pageres.h:189