Coverage Report

Created: 2026-09-14 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccmain/reject.cpp
Line
Count
Source
1
/**********************************************************************
2
 * File:        reject.cpp  (Formerly reject.c)
3
 * Description: Rejection functions used in tessedit
4
 * Author:      Phil Cheatle
5
 *
6
 * (C) Copyright 1992, 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 automatically generated configuration file if running autoconf.
20
#ifdef HAVE_CONFIG_H
21
#  include "config_auto.h"
22
#endif
23
24
#ifdef DISABLED_LEGACY_ENGINE
25
26
#  include "tesseractclass.h"
27
28
namespace tesseract {
29
30
int16_t Tesseract::safe_dict_word(const WERD_RES *werd_res) {
31
  const WERD_CHOICE &word = *werd_res->best_choice;
32
  int dict_word_type = werd_res->tesseract->dict_word(word);
33
  return dict_word_type == DOC_DAWG_PERM ? 0 : dict_word_type;
34
}
35
} // namespace tesseract
36
37
#else
38
39
#  include "reject.h"
40
41
#  include "control.h"
42
#  include "docqual.h"
43
#  include "tesseractclass.h"
44
#  include "tessvars.h"
45
46
#  include "helpers.h"
47
48
#  include <algorithm> // for std::sort
49
#  include <cctype>
50
#  include <cerrno>
51
#  include <cstring>
52
#  include <vector> // for std::vector
53
54
namespace tesseract {
55
56
/*************************************************************************
57
 * set_done()
58
 *
59
 * Set the done flag based on the word acceptability criteria
60
 *************************************************************************/
61
62
104k
void Tesseract::set_done(WERD_RES *word, int16_t pass) {
63
104k
  word->done =
64
104k
      word->tess_accepted && (strchr(word->best_choice->unichar_string().c_str(), ' ') == nullptr);
65
104k
  bool word_is_ambig = word->best_choice->dangerous_ambig_found();
66
104k
  bool word_from_dict = word->best_choice->permuter() == SYSTEM_DAWG_PERM ||
67
73.9k
                        word->best_choice->permuter() == FREQ_DAWG_PERM ||
68
64.0k
                        word->best_choice->permuter() == USER_DAWG_PERM;
69
104k
  if (word->done && (pass == 1) && (!word_from_dict || word_is_ambig) &&
70
286
      one_ell_conflict(word, false)) {
71
3
    if (tessedit_rejection_debug) {
72
0
      tprintf("one_ell_conflict detected\n");
73
0
    }
74
3
    word->done = false;
75
3
  }
76
104k
  if (word->done &&
77
7.61k
      ((!word_from_dict && word->best_choice->permuter() != NUMBER_PERM) || word_is_ambig)) {
78
752
    if (tessedit_rejection_debug) {
79
0
      tprintf("non-dict or ambig word detected\n");
80
0
    }
81
752
    word->done = false;
82
752
  }
83
104k
  if (tessedit_rejection_debug) {
84
0
    tprintf("set_done(): done=%d\n", word->done);
85
0
    word->best_choice->print("");
86
0
  }
87
104k
}
88
89
/*************************************************************************
90
 * make_reject_map()
91
 *
92
 * Sets the done flag to indicate whether the resylt is acceptable.
93
 *
94
 * Sets a reject map for the word.
95
 *************************************************************************/
96
104k
void Tesseract::make_reject_map(WERD_RES *word, int16_t pass) {
97
104k
  flip_0O(word);
98
104k
  check_debug_pt(word, -1); // For trap only
99
104k
  set_done(word, pass);     // Set acceptance
100
104k
  word->reject_map.initialise(word->best_choice->unichar_lengths().length());
101
104k
  reject_blanks(word);
102
  /*
103
0: Rays original heuristic - the baseline
104
*/
105
104k
  if (tessedit_reject_mode == 0) {
106
104k
    if (!word->done) {
107
97.9k
      reject_poor_matches(word);
108
97.9k
    }
109
104k
  } else if (tessedit_reject_mode == 5) {
110
    /*
111
5: Reject I/1/l from words where there is no strong contextual confirmation;
112
  the whole of any unacceptable words (incl PERM rej of dubious 1/I/ls);
113
  and the whole of any words which are very small
114
*/
115
0
    if (kBlnXHeight / word->denorm.y_scale() <= min_sane_x_ht_pixels) {
116
0
      word->reject_map.rej_word_small_xht();
117
0
    } else {
118
0
      one_ell_conflict(word, true);
119
      /*
120
  Originally the code here just used the done flag. Now I have duplicated
121
  and unpacked the conditions for setting the done flag so that each
122
  mechanism can be turned on or off independently. This works WITHOUT
123
  affecting the done flag setting.
124
*/
125
0
      if (rej_use_tess_accepted && !word->tess_accepted) {
126
0
        word->reject_map.rej_word_not_tess_accepted();
127
0
      }
128
129
0
      if (rej_use_tess_blanks &&
130
0
          (strchr(word->best_choice->unichar_string().c_str(), ' ') != nullptr)) {
131
0
        word->reject_map.rej_word_contains_blanks();
132
0
      }
133
134
0
      WERD_CHOICE *best_choice = word->best_choice;
135
0
      if (rej_use_good_perm) {
136
0
        if ((best_choice->permuter() == SYSTEM_DAWG_PERM ||
137
0
             best_choice->permuter() == FREQ_DAWG_PERM ||
138
0
             best_choice->permuter() == USER_DAWG_PERM) &&
139
0
            (!rej_use_sensible_wd ||
140
0
             acceptable_word_string(*word->uch_set, best_choice->unichar_string().c_str(),
141
0
                                    best_choice->unichar_lengths().c_str()) != AC_UNACCEPTABLE)) {
142
          // PASSED TEST
143
0
        } else if (best_choice->permuter() == NUMBER_PERM) {
144
0
          if (rej_alphas_in_number_perm) {
145
0
            for (int i = 0, offset = 0; best_choice->unichar_string()[offset] != '\0';
146
0
                 offset += best_choice->unichar_lengths()[i++]) {
147
0
              if (word->reject_map[i].accepted() &&
148
0
                  word->uch_set->get_isalpha(best_choice->unichar_string().c_str() + offset,
149
0
                                             best_choice->unichar_lengths()[i])) {
150
0
                word->reject_map[i].setrej_bad_permuter();
151
0
              }
152
              // rej alpha
153
0
            }
154
0
          }
155
0
        } else {
156
0
          word->reject_map.rej_word_bad_permuter();
157
0
        }
158
0
      }
159
      /* Ambig word rejection was here once !!*/
160
0
    }
161
0
  } else {
162
0
    tprintf("BAD tessedit_reject_mode\n");
163
0
    ASSERT_HOST("Fatal error encountered!" == nullptr);
164
0
  }
165
166
104k
  if (tessedit_image_border > -1) {
167
104k
    reject_edge_blobs(word);
168
104k
  }
169
170
104k
  check_debug_pt(word, 10);
171
104k
  if (tessedit_rejection_debug) {
172
0
    tprintf("Permuter Type = %d\n", word->best_choice->permuter());
173
0
    tprintf("Certainty: %f     Rating: %f\n", word->best_choice->certainty(),
174
0
            word->best_choice->rating());
175
0
    tprintf("Dict word: %d\n", dict_word(*(word->best_choice)));
176
0
  }
177
178
104k
  flip_hyphens(word);
179
104k
  check_debug_pt(word, 20);
180
104k
}
181
182
104k
void reject_blanks(WERD_RES *word) {
183
104k
  int16_t i;
184
104k
  int16_t offset;
185
186
519k
  for (i = 0, offset = 0; word->best_choice->unichar_string()[offset] != '\0';
187
414k
       offset += word->best_choice->unichar_lengths()[i], i += 1) {
188
414k
    if (word->best_choice->unichar_string()[offset] == ' ') {
189
      // rej unrecognised blobs
190
27
      word->reject_map[i].setrej_tess_failure();
191
27
    }
192
414k
  }
193
104k
}
194
195
0
void Tesseract::reject_I_1_L(WERD_RES *word) {
196
0
  int16_t i;
197
0
  int16_t offset;
198
199
0
  for (i = 0, offset = 0; word->best_choice->unichar_string()[offset] != '\0';
200
0
       offset += word->best_choice->unichar_lengths()[i], i += 1) {
201
0
    if (conflict_set_I_l_1.contains(word->best_choice->unichar_string()[offset])) {
202
      // rej 1Il conflict
203
0
      word->reject_map[i].setrej_1Il_conflict();
204
0
    }
205
0
  }
206
0
}
207
208
97.9k
void reject_poor_matches(WERD_RES *word) {
209
97.9k
  float threshold = compute_reject_threshold(word->best_choice);
210
505k
  for (unsigned i = 0; i < word->best_choice->length(); ++i) {
211
407k
    if (word->best_choice->unichar_id(i) == UNICHAR_SPACE) {
212
27
      word->reject_map[i].setrej_tess_failure();
213
407k
    } else if (word->best_choice->certainty(i) < threshold) {
214
179k
      word->reject_map[i].setrej_poor_match();
215
179k
    }
216
407k
  }
217
97.9k
}
218
219
/**********************************************************************
220
 * compute_reject_threshold
221
 *
222
 * Set a rejection threshold for this word.
223
 * Initially this is a trivial function which looks for the largest
224
 * gap in the certainty value.
225
 **********************************************************************/
226
227
97.9k
float compute_reject_threshold(WERD_CHOICE *word) {
228
97.9k
  float threshold;      // rejection threshold
229
97.9k
  float bestgap = 0.0f; // biggest gap
230
97.9k
  float gapstart;       // bottom of gap
231
232
97.9k
  auto blob_count = word->length();
233
97.9k
  std::vector<float> ratings;
234
97.9k
  ratings.reserve(blob_count);
235
505k
  for (unsigned i = 0; i < blob_count; ++i) {
236
407k
    ratings.push_back(word->certainty(i));
237
407k
  }
238
97.9k
  std::sort(ratings.begin(), ratings.end());
239
97.9k
  gapstart = ratings[0] - 1; // all reject if none better
240
97.9k
  if (blob_count >= 3) {
241
342k
    for (unsigned index = 0; index < blob_count - 1; index++) {
242
300k
      if (ratings[index + 1] - ratings[index] > bestgap) {
243
96.1k
        bestgap = ratings[index + 1] - ratings[index];
244
        // find biggest
245
96.1k
        gapstart = ratings[index];
246
96.1k
      }
247
300k
    }
248
42.2k
  }
249
97.9k
  threshold = gapstart + bestgap / 2;
250
251
97.9k
  return threshold;
252
97.9k
}
253
254
/*************************************************************************
255
 * reject_edge_blobs()
256
 *
257
 * If the word is perilously close to the edge of the image, reject those blobs
258
 * in the word which are too close to the edge as they could be clipped.
259
 *************************************************************************/
260
104k
void Tesseract::reject_edge_blobs(WERD_RES *word) {
261
104k
  TBOX word_box = word->word->bounding_box();
262
  // Use the box_word as it is already denormed back to image coordinates.
263
104k
  int blobcount = word->box_word->length();
264
265
104k
  if (word_box.left() < tessedit_image_border || word_box.bottom() < tessedit_image_border ||
266
27.8k
      word_box.right() + tessedit_image_border > ImageWidth() - 1 ||
267
81.7k
      word_box.top() + tessedit_image_border > ImageHeight() - 1) {
268
81.7k
    ASSERT_HOST(word->reject_map.length() == blobcount);
269
377k
    for (int blobindex = 0; blobindex < blobcount; blobindex++) {
270
296k
      TBOX blob_box = word->box_word->BlobBox(blobindex);
271
296k
      if (blob_box.left() < tessedit_image_border || blob_box.bottom() < tessedit_image_border ||
272
203k
          blob_box.right() + tessedit_image_border > ImageWidth() - 1 ||
273
202k
          blob_box.top() + tessedit_image_border > ImageHeight() - 1) {
274
106k
        word->reject_map[blobindex].setrej_edge_char();
275
        // Close to edge
276
106k
      }
277
296k
    }
278
81.7k
  }
279
104k
}
280
281
/**********************************************************************
282
 * one_ell_conflict()
283
 *
284
 * Identify words where there is a potential I/l/1 error.
285
 * - A bundle of contextual heuristics!
286
 **********************************************************************/
287
286
bool Tesseract::one_ell_conflict(WERD_RES *word_res, bool update_map) {
288
286
  int16_t word_len; // its length
289
286
  int16_t first_alphanum_index_;
290
286
  int16_t first_alphanum_offset_;
291
286
  int16_t i;
292
286
  int16_t offset;
293
286
  bool non_conflict_set_char; // non conf set a/n?
294
286
  ACCEPTABLE_WERD_TYPE word_type;
295
286
  bool dict_perm_type;
296
286
  bool dict_word_ok;
297
286
  int dict_word_type;
298
299
  // unichar_string() and unichar_lengths() rebuild their internal strings,
300
  // so copy them here to keep stable pointers for the rest of the function.
301
286
  std::string word_str = word_res->best_choice->unichar_string();
302
286
  std::string lengths_str = word_res->best_choice->unichar_lengths();
303
286
  const char *word = word_str.c_str();
304
286
  const char *lengths = lengths_str.c_str();
305
286
  word_len = strlen(lengths);
306
  /*
307
  If there are no occurrences of the conflict set characters then the word
308
  is OK.
309
*/
310
286
  if (strpbrk(word, conflict_set_I_l_1.c_str()) == nullptr) {
311
283
    return false;
312
283
  }
313
314
  /*
315
  There is a conflict if there are NO other (confirmed) alphanumerics apart
316
  from those in the conflict set.
317
*/
318
319
21
  for (i = 0, offset = 0, non_conflict_set_char = false; (i < word_len) && !non_conflict_set_char;
320
18
       offset += lengths[i++]) {
321
18
    non_conflict_set_char = (word_res->uch_set->get_isalpha(word + offset, lengths[i]) ||
322
16
                             word_res->uch_set->get_isdigit(word + offset, lengths[i])) &&
323
3
                            !conflict_set_I_l_1.contains(word[offset]);
324
18
  }
325
3
  if (!non_conflict_set_char) {
326
3
    if (update_map) {
327
0
      reject_I_1_L(word_res);
328
0
    }
329
3
    return true;
330
3
  }
331
332
  /*
333
  If the word is accepted by a dawg permuter, and the first alpha character
334
  is "I" or "l", check to see if the alternative is also a dawg word. If it
335
  is, then there is a potential error otherwise the word is ok.
336
*/
337
338
0
  dict_perm_type = (word_res->best_choice->permuter() == SYSTEM_DAWG_PERM) ||
339
0
                   (word_res->best_choice->permuter() == USER_DAWG_PERM) ||
340
0
                   (rej_trust_doc_dawg && (word_res->best_choice->permuter() == DOC_DAWG_PERM)) ||
341
0
                   (word_res->best_choice->permuter() == FREQ_DAWG_PERM);
342
0
  dict_word_type = dict_word(*(word_res->best_choice));
343
0
  dict_word_ok = (dict_word_type > 0) && (rej_trust_doc_dawg || (dict_word_type != DOC_DAWG_PERM));
344
345
0
  if ((rej_1Il_use_dict_word && dict_word_ok) || (rej_1Il_trust_permuter_type && dict_perm_type) ||
346
0
      (dict_perm_type && dict_word_ok)) {
347
0
    first_alphanum_index_ = first_alphanum_index(word, lengths);
348
0
    first_alphanum_offset_ = first_alphanum_offset(word, lengths);
349
0
    if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'I') {
350
0
      word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l';
351
0
      if (safe_dict_word(word_res) > 0) {
352
0
        word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I';
353
0
        if (update_map) {
354
0
          word_res->reject_map[first_alphanum_index_].setrej_1Il_conflict();
355
0
        }
356
0
        return true;
357
0
      } else {
358
0
        word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I';
359
0
        return false;
360
0
      }
361
0
    }
362
363
0
    if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'l') {
364
0
      word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I';
365
0
      if (safe_dict_word(word_res) > 0) {
366
0
        word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l';
367
0
        if (update_map) {
368
0
          word_res->reject_map[first_alphanum_index_].setrej_1Il_conflict();
369
0
        }
370
0
        return true;
371
0
      } else {
372
0
        word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l';
373
0
        return false;
374
0
      }
375
0
    }
376
0
    return false;
377
0
  }
378
379
  /*
380
  NEW 1Il code. The old code relied on permuter types too much. In fact,
381
  tess will use TOP_CHOICE permute for good things like "palette".
382
  In this code the string is examined independently to see if it looks like
383
  a well formed word.
384
*/
385
386
  /*
387
  REGARDLESS OF PERMUTER, see if flipping a leading I/l generates a
388
  dictionary word.
389
*/
390
0
  first_alphanum_index_ = first_alphanum_index(word, lengths);
391
0
  first_alphanum_offset_ = first_alphanum_offset(word, lengths);
392
0
  if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'l') {
393
0
    word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I';
394
0
    if (safe_dict_word(word_res) > 0) {
395
0
      return false;
396
0
    } else {
397
0
      word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l';
398
0
    }
399
0
  } else if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'I') {
400
0
    word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l';
401
0
    if (safe_dict_word(word_res) > 0) {
402
0
      return false;
403
0
    } else {
404
0
      word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I';
405
0
    }
406
0
  }
407
  /*
408
  For strings containing digits:
409
    If there are no alphas OR the numeric permuter liked the word,
410
      reject any non 1 conflict chs
411
    Else reject all conflict chs
412
*/
413
0
  if (word_contains_non_1_digit(word, lengths)) {
414
0
    bool allow_1s =
415
0
        (alpha_count(word, lengths) == 0) || (word_res->best_choice->permuter() == NUMBER_PERM);
416
417
0
    int16_t offset;
418
0
    bool conflict = false;
419
0
    for (i = 0, offset = 0; word[offset] != '\0';
420
0
         offset += word_res->best_choice->unichar_lengths()[i++]) {
421
0
      if ((!allow_1s || (word[offset] != '1')) &&
422
0
          conflict_set_I_l_1.contains(word[offset])) {
423
0
        if (update_map) {
424
0
          word_res->reject_map[i].setrej_1Il_conflict();
425
0
        }
426
0
        conflict = true;
427
0
      }
428
0
    }
429
0
    return conflict;
430
0
  }
431
  /*
432
  For anything else. See if it conforms to an acceptable word type. If so,
433
  treat accordingly.
434
*/
435
0
  word_type = acceptable_word_string(*word_res->uch_set, word, lengths);
436
0
  if ((word_type == AC_LOWER_CASE) || (word_type == AC_INITIAL_CAP)) {
437
0
    first_alphanum_index_ = first_alphanum_index(word, lengths);
438
0
    first_alphanum_offset_ = first_alphanum_offset(word, lengths);
439
0
    if (conflict_set_I_l_1.contains(word[first_alphanum_offset_])) {
440
0
      if (update_map) {
441
0
        word_res->reject_map[first_alphanum_index_].setrej_1Il_conflict();
442
0
      }
443
0
      return true;
444
0
    } else {
445
0
      return false;
446
0
    }
447
0
  } else if (word_type == AC_UPPER_CASE) {
448
0
    return false;
449
0
  } else {
450
0
    if (update_map) {
451
0
      reject_I_1_L(word_res);
452
0
    }
453
0
    return true;
454
0
  }
455
0
}
456
457
0
int16_t Tesseract::first_alphanum_index(const char *word, const char *word_lengths) {
458
0
  int16_t i;
459
0
  int16_t offset;
460
461
0
  for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) {
462
0
    if (unicharset.get_isalpha(word + offset, word_lengths[i]) ||
463
0
        unicharset.get_isdigit(word + offset, word_lengths[i])) {
464
0
      return i;
465
0
    }
466
0
  }
467
0
  return -1;
468
0
}
469
470
0
int16_t Tesseract::first_alphanum_offset(const char *word, const char *word_lengths) {
471
0
  int16_t i;
472
0
  int16_t offset;
473
474
0
  for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) {
475
0
    if (unicharset.get_isalpha(word + offset, word_lengths[i]) ||
476
0
        unicharset.get_isdigit(word + offset, word_lengths[i])) {
477
0
      return offset;
478
0
    }
479
0
  }
480
0
  return -1;
481
0
}
482
483
40.7k
int16_t Tesseract::alpha_count(const char *word, const char *word_lengths) {
484
40.7k
  int16_t i;
485
40.7k
  int16_t offset;
486
40.7k
  int16_t count = 0;
487
488
86.0k
  for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) {
489
45.3k
    if (unicharset.get_isalpha(word + offset, word_lengths[i])) {
490
42.5k
      count++;
491
42.5k
    }
492
45.3k
  }
493
40.7k
  return count;
494
40.7k
}
495
496
0
bool Tesseract::word_contains_non_1_digit(const char *word, const char *word_lengths) {
497
0
  int16_t i;
498
0
  int16_t offset;
499
500
0
  for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) {
501
0
    if (unicharset.get_isdigit(word + offset, word_lengths[i]) &&
502
0
        (word_lengths[i] != 1 || word[offset] != '1')) {
503
0
      return true;
504
0
    }
505
0
  }
506
0
  return false;
507
0
}
508
509
/*************************************************************************
510
 * dont_allow_1Il()
511
 * Don't unreject LONE accepted 1Il conflict set chars
512
 *************************************************************************/
513
0
void Tesseract::dont_allow_1Il(WERD_RES *word) {
514
0
  int word_len = word->reject_map.length();
515
0
  const char *s = word->best_choice->unichar_string().c_str();
516
0
  const char *lengths = word->best_choice->unichar_lengths().c_str();
517
0
  bool accepted_1Il = false;
518
519
0
  for (int i = 0, offset = 0; i < word_len; offset += word->best_choice->unichar_lengths()[i++]) {
520
0
    if (word->reject_map[i].accepted()) {
521
0
      if (conflict_set_I_l_1.contains(s[offset])) {
522
0
        accepted_1Il = true;
523
0
      } else {
524
0
        if (word->uch_set->get_isalpha(s + offset, lengths[i]) ||
525
0
            word->uch_set->get_isdigit(s + offset, lengths[i])) {
526
0
          return; // >=1 non 1Il ch accepted
527
0
        }
528
0
      }
529
0
    }
530
0
  }
531
0
  if (!accepted_1Il) {
532
0
    return; // Nothing to worry about
533
0
  }
534
535
0
  for (int i = 0, offset = 0; i < word_len; offset += word->best_choice->unichar_lengths()[i++]) {
536
0
    if (conflict_set_I_l_1.contains(s[offset]) && word->reject_map[i].accepted()) {
537
0
      word->reject_map[i].setrej_postNN_1Il();
538
0
    }
539
0
  }
540
0
}
541
542
0
int16_t Tesseract::count_alphanums(WERD_RES *word_res) {
543
0
  int count = 0;
544
0
  const WERD_CHOICE *best_choice = word_res->best_choice;
545
0
  for (unsigned i = 0; i < word_res->reject_map.length(); ++i) {
546
0
    if ((word_res->reject_map[i].accepted()) &&
547
0
        (word_res->uch_set->get_isalpha(best_choice->unichar_id(i)) ||
548
0
         word_res->uch_set->get_isdigit(best_choice->unichar_id(i)))) {
549
0
      count++;
550
0
    }
551
0
  }
552
0
  return count;
553
0
}
554
555
// reject all if most rejected.
556
0
void Tesseract::reject_mostly_rejects(WERD_RES *word) {
557
  /* Reject the whole of the word if the fraction of rejects exceeds a limit */
558
559
0
  if (static_cast<float>(word->reject_map.reject_count()) / word->reject_map.length() >=
560
0
      rej_whole_of_mostly_reject_word_fract) {
561
0
    word->reject_map.rej_word_mostly_rej();
562
0
  }
563
0
}
564
565
0
bool Tesseract::repeated_nonalphanum_wd(WERD_RES *word) {
566
0
  if (word->best_choice->unichar_lengths().length() <= 1) {
567
0
    return false;
568
0
  }
569
570
0
  if (!ok_repeated_ch_non_alphanum_wds.contains(word->best_choice->unichar_string()[0])) {
571
0
    return false;
572
0
  }
573
574
0
  UNICHAR_ID uch_id = word->best_choice->unichar_id(0);
575
0
  for (unsigned i = 1; i < word->best_choice->length(); ++i) {
576
0
    if (word->best_choice->unichar_id(i) != uch_id) {
577
0
      return false;
578
0
    }
579
0
  }
580
581
0
  int16_t char_quality;
582
0
  int16_t accepted_char_quality;
583
0
  word_char_quality(word, &char_quality, &accepted_char_quality);
584
585
0
  if ((word->best_choice->unichar_lengths().length() == static_cast<size_t>(char_quality)) &&
586
0
      (char_quality == accepted_char_quality)) {
587
0
    return true;
588
0
  } else {
589
0
    return false;
590
0
  }
591
0
}
592
593
0
int16_t Tesseract::safe_dict_word(const WERD_RES *werd_res) {
594
0
  const WERD_CHOICE &word = *werd_res->best_choice;
595
0
  int dict_word_type = werd_res->tesseract->dict_word(word);
596
0
  return dict_word_type == DOC_DAWG_PERM ? 0 : dict_word_type;
597
0
}
598
599
// Note: After running this function word_res->ratings
600
// might not contain the right BLOB_CHOICE corresponding to each character
601
// in word_res->best_choice.
602
104k
void Tesseract::flip_hyphens(WERD_RES *word_res) {
603
104k
  WERD_CHOICE *best_choice = word_res->best_choice;
604
104k
  int prev_right = -9999;
605
104k
  int next_left;
606
104k
  TBOX out_box;
607
104k
  float aspect_ratio;
608
609
104k
  if (tessedit_lower_flip_hyphen <= 1) {
610
0
    return;
611
0
  }
612
613
104k
  auto num_blobs = word_res->rebuild_word->NumBlobs();
614
104k
  UNICHAR_ID unichar_dash = word_res->uch_set->unichar_to_id("-");
615
519k
  for (unsigned i = 0; i < best_choice->length() && i < num_blobs; ++i) {
616
414k
    TBLOB *blob = word_res->rebuild_word->blobs[i];
617
414k
    out_box = blob->bounding_box();
618
414k
    if (i + 1 == num_blobs) {
619
104k
      next_left = 9999;
620
309k
    } else {
621
309k
      next_left = word_res->rebuild_word->blobs[i + 1]->bounding_box().left();
622
309k
    }
623
    // Don't touch small or touching blobs - it is too dangerous.
624
414k
    if ((out_box.width() > 8 * word_res->denorm.x_scale()) && (out_box.left() > prev_right) &&
625
38.8k
        (out_box.right() < next_left)) {
626
32.1k
      aspect_ratio = out_box.width() / static_cast<float>(out_box.height());
627
32.1k
      if (word_res->uch_set->eq(best_choice->unichar_id(i), ".")) {
628
330
        if (aspect_ratio >= tessedit_upper_flip_hyphen &&
629
49
            word_res->uch_set->contains_unichar_id(unichar_dash) &&
630
49
            word_res->uch_set->get_enabled(unichar_dash)) {
631
          /* Certain HYPHEN */
632
49
          best_choice->set_unichar_id(unichar_dash, i);
633
49
          if (word_res->reject_map[i].rejected()) {
634
18
            word_res->reject_map[i].setrej_hyphen_accept();
635
18
          }
636
49
        }
637
330
        if ((aspect_ratio > tessedit_lower_flip_hyphen) && word_res->reject_map[i].accepted()) {
638
          // Suspected HYPHEN
639
41
          word_res->reject_map[i].setrej_hyphen();
640
41
        }
641
31.8k
      } else if (best_choice->unichar_id(i) == unichar_dash) {
642
348
        if ((aspect_ratio >= tessedit_upper_flip_hyphen) && (word_res->reject_map[i].rejected())) {
643
60
          word_res->reject_map[i].setrej_hyphen_accept();
644
60
        }
645
        // Certain HYPHEN
646
647
348
        if ((aspect_ratio <= tessedit_lower_flip_hyphen) && (word_res->reject_map[i].accepted())) {
648
          // Suspected HYPHEN
649
84
          word_res->reject_map[i].setrej_hyphen();
650
84
        }
651
348
      }
652
32.1k
    }
653
414k
    prev_right = out_box.right();
654
414k
  }
655
104k
}
656
657
// Note: After running this function word_res->ratings
658
// might not contain the right BLOB_CHOICE corresponding to each character
659
// in word_res->best_choice.
660
104k
void Tesseract::flip_0O(WERD_RES *word_res) {
661
104k
  WERD_CHOICE *best_choice = word_res->best_choice;
662
104k
  TBOX out_box;
663
664
104k
  if (!tessedit_flip_0O) {
665
0
    return;
666
0
  }
667
668
104k
  auto num_blobs = word_res->rebuild_word->NumBlobs();
669
440k
  for (unsigned i = 0; i < best_choice->length() && i < num_blobs; ++i) {
670
351k
    TBLOB *blob = word_res->rebuild_word->blobs[i];
671
351k
    if (word_res->uch_set->get_isupper(best_choice->unichar_id(i)) ||
672
318k
        word_res->uch_set->get_isdigit(best_choice->unichar_id(i))) {
673
40.0k
      out_box = blob->bounding_box();
674
40.0k
      if ((out_box.top() < kBlnBaselineOffset + kBlnXHeight) ||
675
28.7k
          (out_box.bottom() > kBlnBaselineOffset + kBlnXHeight / 4)) {
676
15.7k
        return; // Beware words with sub/superscripts
677
15.7k
      }
678
40.0k
    }
679
351k
  }
680
89.0k
  UNICHAR_ID unichar_0 = word_res->uch_set->unichar_to_id("0");
681
89.0k
  UNICHAR_ID unichar_O = word_res->uch_set->unichar_to_id("O");
682
89.0k
  if (unichar_0 == INVALID_UNICHAR_ID || !word_res->uch_set->get_enabled(unichar_0) ||
683
89.0k
      unichar_O == INVALID_UNICHAR_ID || !word_res->uch_set->get_enabled(unichar_O)) {
684
0
    return; // 0 or O are not present/enabled in unicharset
685
0
  }
686
312k
  for (unsigned i = 1; i < best_choice->length(); ++i) {
687
223k
    if (best_choice->unichar_id(i) == unichar_0 || best_choice->unichar_id(i) == unichar_O) {
688
      /* A0A */
689
462
      if ((i + 1) < best_choice->length() &&
690
431
          non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
691
16
          non_O_upper(*word_res->uch_set, best_choice->unichar_id(i + 1))) {
692
15
        best_choice->set_unichar_id(unichar_O, i);
693
15
      }
694
      /* A00A */
695
462
      if (non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
696
16
          (i + 1) < best_choice->length() &&
697
16
          (best_choice->unichar_id(i + 1) == unichar_0 ||
698
16
           best_choice->unichar_id(i + 1) == unichar_O) &&
699
1
          (i + 2) < best_choice->length() &&
700
1
          non_O_upper(*word_res->uch_set, best_choice->unichar_id(i + 2))) {
701
1
        best_choice->set_unichar_id(unichar_O, i);
702
1
        i++;
703
1
      }
704
      /* AA0<non digit or end of word> */
705
462
      if ((i > 1) && non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 2)) &&
706
20
          non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
707
10
          (((i + 1) < best_choice->length() &&
708
10
            !word_res->uch_set->get_isdigit(best_choice->unichar_id(i + 1)) &&
709
10
            !word_res->uch_set->eq(best_choice->unichar_id(i + 1), "l") &&
710
10
            !word_res->uch_set->eq(best_choice->unichar_id(i + 1), "I")) ||
711
6
           (i == best_choice->length() - 1))) {
712
4
        best_choice->set_unichar_id(unichar_O, i);
713
4
      }
714
      /* 9O9 */
715
462
      if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
716
60
          (i + 1) < best_choice->length() &&
717
56
          non_0_digit(*word_res->uch_set, best_choice->unichar_id(i + 1))) {
718
13
        best_choice->set_unichar_id(unichar_0, i);
719
13
      }
720
      /* 9OOO */
721
462
      if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
722
60
          (i + 2) < best_choice->length() &&
723
49
          (best_choice->unichar_id(i + 1) == unichar_0 ||
724
24
           best_choice->unichar_id(i + 1) == unichar_O) &&
725
25
          (best_choice->unichar_id(i + 2) == unichar_0 ||
726
21
           best_choice->unichar_id(i + 2) == unichar_O)) {
727
4
        best_choice->set_unichar_id(unichar_0, i);
728
4
        best_choice->set_unichar_id(unichar_0, i + 1);
729
4
        best_choice->set_unichar_id(unichar_0, i + 2);
730
4
        i += 2;
731
4
      }
732
      /* 9OO<non upper> */
733
462
      if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
734
56
          (i + 2) < best_choice->length() &&
735
45
          (best_choice->unichar_id(i + 1) == unichar_0 ||
736
24
           best_choice->unichar_id(i + 1) == unichar_O) &&
737
21
          !word_res->uch_set->get_isupper(best_choice->unichar_id(i + 2))) {
738
21
        best_choice->set_unichar_id(unichar_0, i);
739
21
        best_choice->set_unichar_id(unichar_0, i + 1);
740
21
        i++;
741
21
      }
742
      /* 9O<non upper> */
743
462
      if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) &&
744
35
          (i + 1) < best_choice->length() &&
745
31
          !word_res->uch_set->get_isupper(best_choice->unichar_id(i + 1))) {
746
31
        best_choice->set_unichar_id(unichar_0, i);
747
31
      }
748
      /* 9[.,]OOO.. */
749
462
      if ((i > 1) &&
750
423
          (word_res->uch_set->eq(best_choice->unichar_id(i - 1), ".") ||
751
419
           word_res->uch_set->eq(best_choice->unichar_id(i - 1), ",")) &&
752
7
          (word_res->uch_set->get_isdigit(best_choice->unichar_id(i - 2)) ||
753
5
           best_choice->unichar_id(i - 2) == unichar_O)) {
754
2
        if (best_choice->unichar_id(i - 2) == unichar_O) {
755
0
          best_choice->set_unichar_id(unichar_0, i - 2);
756
0
        }
757
4
        while (i < best_choice->length() && (best_choice->unichar_id(i) == unichar_O ||
758
4
                                             best_choice->unichar_id(i) == unichar_0)) {
759
2
          best_choice->set_unichar_id(unichar_0, i);
760
2
          i++;
761
2
        }
762
2
        i--;
763
2
      }
764
462
    }
765
223k
  }
766
89.0k
}
767
768
1.34k
bool Tesseract::non_O_upper(const UNICHARSET &ch_set, UNICHAR_ID unichar_id) {
769
1.34k
  return ch_set.get_isupper(unichar_id) && !ch_set.eq(unichar_id, "O");
770
1.34k
}
771
772
1.90k
bool Tesseract::non_0_digit(const UNICHARSET &ch_set, UNICHAR_ID unichar_id) {
773
1.90k
  return ch_set.get_isdigit(unichar_id) && !ch_set.eq(unichar_id, "0");
774
1.90k
}
775
} // namespace tesseract
776
777
#endif // def DISABLED_LEGACY_ENGINE