Coverage Report

Created: 2026-09-14 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccmain/superscript.cpp
Line
Count
Source
1
/******************************************************************
2
 * File:        superscript.cpp
3
 * Description: Correction pass to fix superscripts and subscripts.
4
 * Author:      David Eger
5
 *
6
 * (C) Copyright 2012, Google, Inc.
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 "normalis.h"
20
#include "tesseractclass.h"
21
22
namespace tesseract {
23
24
3.09k
static int LeadingUnicharsToChopped(WERD_RES *word, int num_unichars) {
25
3.09k
  int num_chopped = 0;
26
7.12k
  for (int i = 0; i < num_unichars; i++) {
27
4.03k
    num_chopped += word->best_state[i];
28
4.03k
  }
29
3.09k
  return num_chopped;
30
3.09k
}
31
32
2.64k
static int TrailingUnicharsToChopped(WERD_RES *word, int num_unichars) {
33
2.64k
  int num_chopped = 0;
34
5.59k
  for (int i = 0; i < num_unichars; i++) {
35
2.94k
    num_chopped += word->best_state[word->best_state.size() - 1 - i];
36
2.94k
  }
37
2.64k
  return num_chopped;
38
2.64k
}
39
40
/**
41
 * Given a recognized blob, see if a contiguous collection of sub-pieces
42
 * (chopped blobs) starting at its left might qualify as being a subscript
43
 * or superscript letter based only on y position.  Also do this for the
44
 * right side.
45
 */
46
static void YOutlierPieces(WERD_RES *word, int rebuilt_blob_index, int super_y_bottom,
47
                           int sub_y_top, ScriptPos *leading_pos, int *num_leading_outliers,
48
451
                           ScriptPos *trailing_pos, int *num_trailing_outliers) {
49
451
  ScriptPos sp_unused1, sp_unused2;
50
451
  int unused1, unused2;
51
451
  if (!leading_pos) {
52
175
    leading_pos = &sp_unused1;
53
175
  }
54
451
  if (!num_leading_outliers) {
55
175
    num_leading_outliers = &unused1;
56
175
  }
57
451
  if (!trailing_pos) {
58
276
    trailing_pos = &sp_unused2;
59
276
  }
60
451
  if (!num_trailing_outliers) {
61
276
    num_trailing_outliers = &unused2;
62
276
  }
63
64
451
  *num_leading_outliers = *num_trailing_outliers = 0;
65
451
  *leading_pos = *trailing_pos = SP_NORMAL;
66
67
451
  int chopped_start = LeadingUnicharsToChopped(word, rebuilt_blob_index);
68
451
  int num_chopped_pieces = word->best_state[rebuilt_blob_index];
69
451
  ScriptPos last_pos = SP_NORMAL;
70
451
  int trailing_outliers = 0;
71
1.24k
  for (int i = 0; i < num_chopped_pieces; i++) {
72
791
    TBOX box = word->chopped_word->blobs[chopped_start + i]->bounding_box();
73
791
    ScriptPos pos = SP_NORMAL;
74
791
    if (box.bottom() >= super_y_bottom) {
75
125
      pos = SP_SUPERSCRIPT;
76
666
    } else if (box.top() <= sub_y_top) {
77
204
      pos = SP_SUBSCRIPT;
78
204
    }
79
791
    if (pos == SP_NORMAL) {
80
462
      if (trailing_outliers == i) {
81
379
        *num_leading_outliers = trailing_outliers;
82
379
        *leading_pos = last_pos;
83
379
      }
84
462
      trailing_outliers = 0;
85
462
    } else {
86
329
      if (pos == last_pos) {
87
105
        trailing_outliers++;
88
224
      } else {
89
224
        trailing_outliers = 1;
90
224
      }
91
329
    }
92
791
    last_pos = pos;
93
791
  }
94
451
  *num_trailing_outliers = trailing_outliers;
95
451
  *trailing_pos = last_pos;
96
451
}
97
98
/**
99
 * Attempt to split off any high (or low) bits at the ends of the word with poor
100
 * certainty and recognize them separately.  If the certainty gets much better
101
 * and other sanity checks pass, accept.
102
 *
103
 * This superscript fix is meant to be called in the second pass of recognition
104
 * when we have tried once and already have a preliminary answer for word.
105
 *
106
 * @return Whether we modified the given word.
107
 */
108
41.1k
bool Tesseract::SubAndSuperscriptFix(WERD_RES *word) {
109
41.1k
  if (word->tess_failed || word->word->flag(W_REP_CHAR) || !word->best_choice) {
110
10
    return false;
111
10
  }
112
41.0k
  int num_leading, num_trailing;
113
41.0k
  ScriptPos sp_leading, sp_trailing;
114
41.0k
  float leading_certainty, trailing_certainty;
115
41.0k
  float avg_certainty, unlikely_threshold;
116
117
  // Calculate the number of whole suspicious characters at the edges.
118
41.0k
  GetSubAndSuperscriptCandidates(word, &num_leading, &sp_leading, &leading_certainty, &num_trailing,
119
41.0k
                                 &sp_trailing, &trailing_certainty, &avg_certainty,
120
41.0k
                                 &unlikely_threshold);
121
122
41.0k
  const char *leading_pos = sp_leading == SP_SUBSCRIPT ? "sub" : "super";
123
41.0k
  const char *trailing_pos = sp_trailing == SP_SUBSCRIPT ? "sub" : "super";
124
125
41.0k
  int num_blobs = word->best_choice->length();
126
127
  // Calculate the remainder (partial characters) at the edges.
128
  // This accounts for us having classified the best version of
129
  // a word as [speaker?'] when it was instead [speaker.^{21}]
130
  // (that is we accidentally thought the 2 was attached to the period).
131
41.0k
  int num_remainder_leading = 0, num_remainder_trailing = 0;
132
41.0k
  if (num_leading + num_trailing < num_blobs && unlikely_threshold < 0.0) {
133
32.6k
    int super_y_bottom = kBlnBaselineOffset + kBlnXHeight * superscript_min_y_bottom;
134
32.6k
    int sub_y_top = kBlnBaselineOffset + kBlnXHeight * subscript_max_y_top;
135
32.6k
    int last_word_char = num_blobs - 1 - num_trailing;
136
32.6k
    float last_char_certainty = word->best_choice->certainty(last_word_char);
137
32.6k
    if (word->best_choice->unichar_id(last_word_char) != 0 &&
138
32.6k
        last_char_certainty <= unlikely_threshold) {
139
175
      ScriptPos rpos;
140
175
      YOutlierPieces(word, last_word_char, super_y_bottom, sub_y_top, nullptr, nullptr, &rpos,
141
175
                     &num_remainder_trailing);
142
175
      if (num_trailing > 0 && rpos != sp_trailing) {
143
49
        num_remainder_trailing = 0;
144
49
      }
145
175
      if (num_remainder_trailing > 0 && last_char_certainty < trailing_certainty) {
146
26
        trailing_certainty = last_char_certainty;
147
26
      }
148
175
    }
149
32.6k
    bool another_blob_available =
150
32.6k
        (num_remainder_trailing == 0) || num_leading + num_trailing + 1 < num_blobs;
151
32.6k
    int first_char_certainty = word->best_choice->certainty(num_leading);
152
32.6k
    if (another_blob_available && word->best_choice->unichar_id(num_leading) != 0 &&
153
32.6k
        first_char_certainty <= unlikely_threshold) {
154
276
      ScriptPos lpos;
155
276
      YOutlierPieces(word, num_leading, super_y_bottom, sub_y_top, &lpos, &num_remainder_leading,
156
276
                     nullptr, nullptr);
157
276
      if (num_leading > 0 && lpos != sp_leading) {
158
94
        num_remainder_leading = 0;
159
94
      }
160
276
      if (num_remainder_leading > 0 && first_char_certainty < leading_certainty) {
161
22
        leading_certainty = first_char_certainty;
162
22
      }
163
276
    }
164
32.6k
  }
165
166
  // If nothing to do, bail now.
167
41.0k
  if (num_leading + num_trailing + num_remainder_leading + num_remainder_trailing == 0) {
168
38.6k
    return false;
169
38.6k
  }
170
171
2.41k
  if (superscript_debug >= 1) {
172
0
    tprintf("Candidate for superscript detection: %s (",
173
0
            word->best_choice->unichar_string().c_str());
174
0
    if (num_leading || num_remainder_leading) {
175
0
      tprintf("%d.%d %s-leading ", num_leading, num_remainder_leading, leading_pos);
176
0
    }
177
0
    if (num_trailing || num_remainder_trailing) {
178
0
      tprintf("%d.%d %s-trailing ", num_trailing, num_remainder_trailing, trailing_pos);
179
0
    }
180
0
    tprintf(")\n");
181
0
  }
182
2.41k
  if (superscript_debug >= 3) {
183
0
    word->best_choice->print();
184
0
  }
185
2.41k
  if (superscript_debug >= 2) {
186
0
    tprintf(" Certainties -- Average: %.2f  Unlikely thresh: %.2f  ", avg_certainty,
187
0
            unlikely_threshold);
188
0
    if (num_leading) {
189
0
      tprintf("Orig. leading (min): %.2f  ", leading_certainty);
190
0
    }
191
0
    if (num_trailing) {
192
0
      tprintf("Orig. trailing (min): %.2f  ", trailing_certainty);
193
0
    }
194
0
    tprintf("\n");
195
0
  }
196
197
  // We've now calculated the number of rebuilt blobs we want to carve off.
198
  // However, split_word() works from TBLOBs in chopped_word, so we need to
199
  // convert to those.
200
2.41k
  int num_chopped_leading = LeadingUnicharsToChopped(word, num_leading) + num_remainder_leading;
201
2.41k
  int num_chopped_trailing = TrailingUnicharsToChopped(word, num_trailing) + num_remainder_trailing;
202
203
2.41k
  int retry_leading = 0;
204
2.41k
  int retry_trailing = 0;
205
2.41k
  bool is_good = false;
206
2.41k
  WERD_RES *revised = TrySuperscriptSplits(num_chopped_leading, leading_certainty, sp_leading,
207
2.41k
                                           num_chopped_trailing, trailing_certainty, sp_trailing,
208
2.41k
                                           word, &is_good, &retry_leading, &retry_trailing);
209
2.41k
  if (is_good) {
210
258
    word->ConsumeWordResults(revised);
211
2.15k
  } else if (retry_leading || retry_trailing) {
212
229
    int retry_chopped_leading = LeadingUnicharsToChopped(revised, retry_leading);
213
229
    int retry_chopped_trailing = TrailingUnicharsToChopped(revised, retry_trailing);
214
229
    WERD_RES *revised2 = TrySuperscriptSplits(
215
229
        retry_chopped_leading, leading_certainty, sp_leading, retry_chopped_trailing,
216
229
        trailing_certainty, sp_trailing, revised, &is_good, &retry_leading, &retry_trailing);
217
229
    if (is_good) {
218
209
      word->ConsumeWordResults(revised2);
219
209
    }
220
229
    delete revised2;
221
229
  }
222
2.41k
  delete revised;
223
2.41k
  return is_good;
224
41.0k
}
225
226
/**
227
 * Determine how many characters (rebuilt blobs) on each end of a given word
228
 * might plausibly be superscripts so SubAndSuperscriptFix can try to
229
 * re-recognize them.  Even if we find no whole blobs at either end,
230
 * we will set *unlikely_threshold to a certainty that might be used to
231
 * select "bad enough" outlier characters.  If *unlikely_threshold is set to 0,
232
 * though, there's really no hope.
233
 *
234
 * @param[in]  word    The word to examine.
235
 * @param[out] num_rebuilt_leading   the number of rebuilt blobs at the start
236
 *                                   of the word which are all up or down and
237
 *                                   seem badly classified.
238
 * @param[out] leading_pos        "super" or "sub" (for debugging)
239
 * @param[out] leading_certainty  the worst certainty in the leading blobs.
240
 * @param[out] num_rebuilt_trailing   the number of rebuilt blobs at the end
241
 *                                    of the word which are all up or down and
242
 *                                    seem badly classified.
243
 * @param[out] trailing_pos        "super" or "sub" (for debugging)
244
 * @param[out] trailing_certainty  the worst certainty in the trailing blobs.
245
 * @param[out] avg_certainty       the average certainty of "normal" blobs in
246
 *                                 the word.
247
 * @param[out] unlikely_threshold  the threshold (on certainty) we used to
248
 *                                 select "bad enough" outlier characters.
249
 */
250
void Tesseract::GetSubAndSuperscriptCandidates(const WERD_RES *word, int *num_rebuilt_leading,
251
                                               ScriptPos *leading_pos, float *leading_certainty,
252
                                               int *num_rebuilt_trailing, ScriptPos *trailing_pos,
253
                                               float *trailing_certainty, float *avg_certainty,
254
41.0k
                                               float *unlikely_threshold) {
255
41.0k
  *avg_certainty = *unlikely_threshold = 0.0f;
256
41.0k
  *num_rebuilt_leading = *num_rebuilt_trailing = 0;
257
41.0k
  *leading_certainty = *trailing_certainty = 0.0f;
258
259
41.0k
  int super_y_bottom = kBlnBaselineOffset + kBlnXHeight * superscript_min_y_bottom;
260
41.0k
  int sub_y_top = kBlnBaselineOffset + kBlnXHeight * subscript_max_y_top;
261
262
  // Step one: Get an average certainty for "normally placed" characters.
263
264
  // Counts here are of blobs in the rebuild_word / unichars in best_choice.
265
41.0k
  *leading_pos = *trailing_pos = SP_NORMAL;
266
41.0k
  int leading_outliers = 0;
267
41.0k
  int trailing_outliers = 0;
268
41.0k
  int num_normal = 0;
269
41.0k
  float normal_certainty_total = 0.0f;
270
41.0k
  float worst_normal_certainty = 0.0f;
271
41.0k
  ScriptPos last_pos = SP_NORMAL;
272
41.0k
  int num_blobs = word->rebuild_word->NumBlobs();
273
193k
  for (int b = 0; b < num_blobs; ++b) {
274
152k
    TBOX box = word->rebuild_word->blobs[b]->bounding_box();
275
152k
    ScriptPos pos = SP_NORMAL;
276
152k
    if (box.bottom() >= super_y_bottom) {
277
33.9k
      pos = SP_SUPERSCRIPT;
278
118k
    } else if (box.top() <= sub_y_top) {
279
39.2k
      pos = SP_SUBSCRIPT;
280
39.2k
    }
281
152k
    if (pos == SP_NORMAL) {
282
79.3k
      if (word->best_choice->unichar_id(b) != 0) {
283
79.3k
        float char_certainty = word->best_choice->certainty(b);
284
79.3k
        if (char_certainty < worst_normal_certainty) {
285
42.7k
          worst_normal_certainty = char_certainty;
286
42.7k
        }
287
79.3k
        num_normal++;
288
79.3k
        normal_certainty_total += char_certainty;
289
79.3k
      }
290
79.3k
      if (trailing_outliers == b) {
291
32.2k
        leading_outliers = trailing_outliers;
292
32.2k
        *leading_pos = last_pos;
293
32.2k
      }
294
79.3k
      trailing_outliers = 0;
295
79.3k
    } else {
296
73.1k
      if (last_pos == pos) {
297
36.6k
        trailing_outliers++;
298
36.6k
      } else {
299
36.5k
        trailing_outliers = 1;
300
36.5k
      }
301
73.1k
    }
302
152k
    last_pos = pos;
303
152k
  }
304
41.0k
  *trailing_pos = last_pos;
305
41.0k
  if (num_normal >= 3) { // throw out the worst as an outlier.
306
7.10k
    num_normal--;
307
7.10k
    normal_certainty_total -= worst_normal_certainty;
308
7.10k
  }
309
41.0k
  if (num_normal > 0) {
310
32.6k
    *avg_certainty = normal_certainty_total / num_normal;
311
32.6k
    *unlikely_threshold = superscript_worse_certainty * (*avg_certainty);
312
32.6k
  }
313
41.0k
  if (num_normal == 0 || (leading_outliers == 0 && trailing_outliers == 0)) {
314
28.4k
    return;
315
28.4k
  }
316
317
  // Step two: Try to split off bits of the word that are both outliers
318
  //           and have much lower certainty than average
319
  // Calculate num_leading and leading_certainty.
320
14.8k
  for (*leading_certainty = 0.0f, *num_rebuilt_leading = 0; *num_rebuilt_leading < leading_outliers;
321
12.5k
       (*num_rebuilt_leading)++) {
322
7.93k
    float char_certainty = word->best_choice->certainty(*num_rebuilt_leading);
323
7.93k
    if (char_certainty > *unlikely_threshold) {
324
5.71k
      break;
325
5.71k
    }
326
2.22k
    if (char_certainty < *leading_certainty) {
327
1.43k
      *leading_certainty = char_certainty;
328
1.43k
    }
329
2.22k
  }
330
331
  // Calculate num_trailing and trailing_certainty.
332
12.5k
  for (*trailing_certainty = 0.0f, *num_rebuilt_trailing = 0;
333
15.3k
       *num_rebuilt_trailing < trailing_outliers; (*num_rebuilt_trailing)++) {
334
10.8k
    int blob_idx = num_blobs - 1 - *num_rebuilt_trailing;
335
10.8k
    float char_certainty = word->best_choice->certainty(blob_idx);
336
10.8k
    if (char_certainty > *unlikely_threshold) {
337
8.04k
      break;
338
8.04k
    }
339
2.77k
    if (char_certainty < *trailing_certainty) {
340
1.66k
      *trailing_certainty = char_certainty;
341
1.66k
    }
342
2.77k
  }
343
12.5k
}
344
345
/**
346
 * Try splitting off the given number of (chopped) blobs from the front and
347
 * back of the given word and recognizing the pieces.
348
 *
349
 * @param[in]  num_chopped_leading   how many chopped blobs from the left
350
 *                    end of the word to chop off and try recognizing as a
351
 *                    superscript (or subscript)
352
 * @param[in]  leading_certainty     the (minimum) certainty had by the
353
 *                    characters in the original leading section.
354
 * @param[in]  leading_pos    "super" or "sub" (for debugging)
355
 * @param[in]  num_chopped_trailing  how many chopped blobs from the right
356
 *                    end of the word to chop off and try recognizing as a
357
 *                    superscript (or subscript)
358
 * @param[in]  trailing_certainty    the (minimum) certainty had by the
359
 *                    characters in the original trailing section.
360
 * @param[in]  trailing_pos      "super" or "sub" (for debugging)
361
 * @param[in]  word              the word to try to chop up.
362
 * @param[out] is_good           do we believe our result?
363
 * @param[out] retry_rebuild_leading, retry_rebuild_trailing
364
 *         If non-zero, and !is_good, then the caller may have luck trying
365
 *         to split the returned word with this number of (rebuilt) leading
366
 *         and trailing blobs / unichars.
367
 * @return A word which is the result of re-recognizing as asked.
368
 */
369
WERD_RES *Tesseract::TrySuperscriptSplits(int num_chopped_leading, float leading_certainty,
370
                                          ScriptPos leading_pos, int num_chopped_trailing,
371
                                          float trailing_certainty, ScriptPos trailing_pos,
372
                                          WERD_RES *word, bool *is_good, int *retry_rebuild_leading,
373
2.64k
                                          int *retry_rebuild_trailing) {
374
2.64k
  int num_chopped = word->chopped_word->NumBlobs();
375
376
2.64k
  *retry_rebuild_leading = *retry_rebuild_trailing = 0;
377
378
  // Chop apart the word into up to three pieces.
379
380
2.64k
  BlamerBundle *bb0 = nullptr;
381
2.64k
  BlamerBundle *bb1 = nullptr;
382
2.64k
  WERD_RES *prefix = nullptr;
383
2.64k
  WERD_RES *core = nullptr;
384
2.64k
  WERD_RES *suffix = nullptr;
385
2.64k
  if (num_chopped_leading > 0) {
386
1.30k
    prefix = new WERD_RES(*word);
387
1.30k
    split_word(prefix, num_chopped_leading, &core, &bb0);
388
1.33k
  } else {
389
1.33k
    core = new WERD_RES(*word);
390
1.33k
  }
391
392
2.64k
  if (num_chopped_trailing > 0) {
393
1.52k
    int split_pt = num_chopped - num_chopped_trailing - num_chopped_leading;
394
1.52k
    split_word(core, split_pt, &suffix, &bb1);
395
1.52k
  }
396
397
  //  Recognize the pieces in turn.
398
2.64k
  int saved_cp_multiplier = classify_class_pruner_multiplier;
399
2.64k
  int saved_im_multiplier = classify_integer_matcher_multiplier;
400
2.64k
  if (prefix) {
401
    // Turn off Tesseract's y-position penalties for the leading superscript.
402
1.30k
    classify_class_pruner_multiplier.set_value(0);
403
1.30k
    classify_integer_matcher_multiplier.set_value(0);
404
405
    // Adjust our expectations about the baseline for this prefix.
406
1.30k
    if (superscript_debug >= 3) {
407
0
      tprintf(" recognizing first %d chopped blobs\n", num_chopped_leading);
408
0
    }
409
1.30k
    recog_word_recursive(prefix);
410
1.30k
    if (superscript_debug >= 2) {
411
0
      tprintf(" The leading bits look like %s %s\n", ScriptPosToString(leading_pos),
412
0
              prefix->best_choice->unichar_string().c_str());
413
0
    }
414
415
    // Restore the normal y-position penalties.
416
1.30k
    classify_class_pruner_multiplier.set_value(saved_cp_multiplier);
417
1.30k
    classify_integer_matcher_multiplier.set_value(saved_im_multiplier);
418
1.30k
  }
419
420
2.64k
  if (superscript_debug >= 3) {
421
0
    tprintf(" recognizing middle %d chopped blobs\n",
422
0
            num_chopped - num_chopped_leading - num_chopped_trailing);
423
0
  }
424
425
2.64k
  if (suffix) {
426
    // Turn off Tesseract's y-position penalties for the trailing superscript.
427
1.52k
    classify_class_pruner_multiplier.set_value(0);
428
1.52k
    classify_integer_matcher_multiplier.set_value(0);
429
430
1.52k
    if (superscript_debug >= 3) {
431
0
      tprintf(" recognizing last %d chopped blobs\n", num_chopped_trailing);
432
0
    }
433
1.52k
    recog_word_recursive(suffix);
434
1.52k
    if (superscript_debug >= 2) {
435
0
      tprintf(" The trailing bits look like %s %s\n", ScriptPosToString(trailing_pos),
436
0
              suffix->best_choice->unichar_string().c_str());
437
0
    }
438
439
    // Restore the normal y-position penalties.
440
1.52k
    classify_class_pruner_multiplier.set_value(saved_cp_multiplier);
441
1.52k
    classify_integer_matcher_multiplier.set_value(saved_im_multiplier);
442
1.52k
  }
443
444
  // Evaluate whether we think the results are believably better
445
  // than what we already had.
446
2.64k
  bool good_prefix =
447
2.64k
      !prefix || BelievableSuperscript(superscript_debug >= 1, *prefix,
448
1.30k
                                       superscript_bettered_certainty * leading_certainty,
449
1.30k
                                       retry_rebuild_leading, nullptr);
450
2.64k
  bool good_suffix =
451
2.64k
      !suffix || BelievableSuperscript(superscript_debug >= 1, *suffix,
452
1.52k
                                       superscript_bettered_certainty * trailing_certainty, nullptr,
453
1.52k
                                       retry_rebuild_trailing);
454
455
2.64k
  *is_good = good_prefix && good_suffix;
456
2.64k
  if (!*is_good && !*retry_rebuild_leading && !*retry_rebuild_trailing) {
457
    // None of it is any good. Quit now.
458
1.94k
    delete core;
459
1.94k
    delete prefix;
460
1.94k
    delete suffix;
461
1.94k
    delete bb1;
462
1.94k
    return nullptr;
463
1.94k
  }
464
699
  recog_word_recursive(core);
465
466
  // Now paste the results together into core.
467
699
  if (suffix) {
468
423
    suffix->SetAllScriptPositions(trailing_pos);
469
423
    join_words(core, suffix, bb1);
470
423
  }
471
699
  if (prefix) {
472
313
    prefix->SetAllScriptPositions(leading_pos);
473
313
    join_words(prefix, core, bb0);
474
313
    core = prefix;
475
313
    prefix = nullptr;
476
313
  }
477
478
699
  if (superscript_debug >= 1) {
479
0
    tprintf("%s superscript fix: %s\n", *is_good ? "ACCEPT" : "REJECT",
480
0
            core->best_choice->unichar_string().c_str());
481
0
  }
482
699
  return core;
483
2.64k
}
484
485
/**
486
 * Return whether this is believable superscript or subscript text.
487
 *
488
 * We insist that:
489
 *   + there are no punctuation marks.
490
 *   + there are no italics.
491
 *   + no normal-sized character is smaller than superscript_scaledown_ratio
492
 *     of what it ought to be, and
493
 *   + each character is at least as certain as certainty_threshold.
494
 *
495
 *  @param[in]  debug  If true, spew debug output
496
 *  @param[in]  word   The word whose best_choice we're evaluating
497
 *  @param[in]  certainty_threshold   If any of the characters have less
498
 *                    certainty than this, reject.
499
 *  @param[out]  left_ok  How many left-side characters were ok?
500
 *  @param[out]  right_ok  How many right-side characters were ok?
501
 *  @return  Whether the complete best choice is believable as a superscript.
502
 */
503
bool Tesseract::BelievableSuperscript(bool debug, const WERD_RES &word, float certainty_threshold,
504
2.83k
                                      int *left_ok, int *right_ok) const {
505
2.83k
  unsigned initial_ok_run_count = 0;
506
2.83k
  unsigned ok_run_count = 0;
507
2.83k
  float worst_certainty = 0.0f;
508
2.83k
  const WERD_CHOICE &wc = *word.best_choice;
509
510
2.83k
  const UnicityTable<FontInfo> &fontinfo_table = get_fontinfo_table();
511
9.40k
  for (unsigned i = 0; i < wc.length(); i++) {
512
6.57k
    TBLOB *blob = word.rebuild_word->blobs[i];
513
6.57k
    UNICHAR_ID unichar_id = wc.unichar_id(i);
514
6.57k
    float char_certainty = wc.certainty(i);
515
6.57k
    bool bad_certainty = char_certainty < certainty_threshold;
516
6.57k
    bool is_punc = wc.unicharset()->get_ispunctuation(unichar_id);
517
6.57k
    bool is_italic = word.fontinfo && word.fontinfo->is_italic();
518
6.57k
    BLOB_CHOICE *choice = word.GetBlobChoice(i);
519
6.57k
    if (choice && fontinfo_table.size() > 0) {
520
      // Get better information from the specific choice, if available.
521
6.57k
      int font_id1 = choice->fontinfo_id();
522
6.57k
      bool font1_is_italic = font_id1 >= 0 ? fontinfo_table.at(font_id1).is_italic() : false;
523
6.57k
      int font_id2 = choice->fontinfo_id2();
524
6.57k
      is_italic = font1_is_italic && (font_id2 < 0 || fontinfo_table.at(font_id2).is_italic());
525
6.57k
    }
526
527
6.57k
    float height_fraction = 1.0f;
528
6.57k
    float char_height = blob->bounding_box().height();
529
6.57k
    float normal_height = char_height;
530
6.57k
    if (wc.unicharset()->top_bottom_useful()) {
531
6.57k
      int min_bot, max_bot, min_top, max_top;
532
6.57k
      wc.unicharset()->get_top_bottom(unichar_id, &min_bot, &max_bot, &min_top, &max_top);
533
6.57k
      float hi_height = max_top - max_bot;
534
6.57k
      float lo_height = min_top - min_bot;
535
6.57k
      normal_height = (hi_height + lo_height) / 2;
536
6.57k
      if (normal_height >= kBlnXHeight) {
537
        // Only ding characters that we have decent information for because
538
        // they're supposed to be normal sized, not tiny specks or dashes.
539
3.04k
        height_fraction = char_height / normal_height;
540
3.04k
      }
541
6.57k
    }
542
6.57k
    bool bad_height = height_fraction < superscript_scaledown_ratio;
543
544
6.57k
    if (debug) {
545
0
      if (is_italic) {
546
0
        tprintf(" Rejecting: superscript is italic.\n");
547
0
      }
548
0
      if (is_punc) {
549
0
        tprintf(" Rejecting: punctuation present.\n");
550
0
      }
551
0
      const char *char_str = wc.unicharset()->id_to_unichar(unichar_id);
552
0
      if (bad_certainty) {
553
0
        tprintf(
554
0
            " Rejecting: don't believe character %s with certainty %.2f "
555
0
            "which is less than threshold %.2f\n",
556
0
            char_str, char_certainty, certainty_threshold);
557
0
      }
558
0
      if (bad_height) {
559
0
        tprintf(
560
0
            " Rejecting: character %s seems too small @ %.2f versus "
561
0
            "expected %.2f\n",
562
0
            char_str, char_height, normal_height);
563
0
      }
564
0
    }
565
6.57k
    if (bad_certainty || bad_height || is_punc || is_italic) {
566
4.67k
      if (ok_run_count == i) {
567
2.33k
        initial_ok_run_count = ok_run_count;
568
2.33k
      }
569
4.67k
      ok_run_count = 0;
570
4.67k
    } else {
571
1.89k
      ok_run_count++;
572
1.89k
    }
573
6.57k
    if (char_certainty < worst_certainty) {
574
3.81k
      worst_certainty = char_certainty;
575
3.81k
    }
576
6.57k
  }
577
2.83k
  bool all_ok = ok_run_count == wc.length();
578
2.83k
  if (all_ok && debug) {
579
0
    tprintf(" Accept: worst revised certainty is %.2f\n", worst_certainty);
580
0
  }
581
2.83k
  if (!all_ok) {
582
2.33k
    if (left_ok) {
583
1.10k
      *left_ok = initial_ok_run_count;
584
1.10k
    }
585
2.33k
    if (right_ok) {
586
1.23k
      *right_ok = ok_run_count;
587
1.23k
    }
588
2.33k
  }
589
2.83k
  return all_ok;
590
2.83k
}
591
592
} // namespace tesseract