tesseract 4.1.1
Loading...
Searching...
No Matches
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
23static 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
30static 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
38namespace tesseract {
39
46static 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 (",
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;
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 }
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",
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
const int kBlnBaselineOffset
Definition: normalis.h:25
const int kBlnXHeight
Definition: normalis.h:24
@ W_REP_CHAR
repeated character
Definition: werd.h:38
DLLSYM void tprintf(const char *format,...)
Definition: tprintf.cpp:35
int UNICHAR_ID
Definition: unichar.h:34
@ SP_SUBSCRIPT
Definition: ratngs.h:254
@ SP_NORMAL
Definition: ratngs.h:253
@ SP_SUPERSCRIPT
Definition: ratngs.h:255
const char * ScriptPosToString(enum ScriptPos script_pos)
Definition: ratngs.cpp:204
int size() const
Definition: genericvector.h:72
double superscript_bettered_certainty
double superscript_worse_certainty
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)
void split_word(WERD_RES *word, int split_pt, WERD_RES **right_piece, BlamerBundle **orig_blamer_bundle) const
Definition: tfacepp.cpp:176
bool SubAndSuperscriptFix(WERD_RES *word_res)
void recog_word_recursive(WERD_RES *word)
Definition: tfacepp.cpp:104
double superscript_scaledown_ratio
bool BelievableSuperscript(bool debug, const WERD_RES &word, float certainty_threshold, int *left_ok, int *right_ok) const
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 join_words(WERD_RES *word, WERD_RES *word2, BlamerBundle *orig_bb) const
Definition: tfacepp.cpp:234
Definition: blobs.h:284
TBOX bounding_box() const
Definition: blobs.cpp:468
int NumBlobs() const
Definition: blobs.h:448
GenericVector< TBLOB * > blobs
Definition: blobs.h:459
int size() const
Return the size used.
const T & get(int id) const
Return the object from an id.
bool is_italic() const
Definition: fontinfo.h:111
TWERD * rebuild_word
Definition: pageres.h:266
const FontInfo * fontinfo
Definition: pageres.h:309
WERD_CHOICE * best_choice
Definition: pageres.h:241
void ConsumeWordResults(WERD_RES *word)
Definition: pageres.cpp:765
void SetAllScriptPositions(tesseract::ScriptPos position)
Definition: pageres.cpp:865
bool tess_failed
Definition: pageres.h:295
GenericVector< int > best_state
Definition: pageres.h:285
TWERD * chopped_word
Definition: pageres.h:212
WERD * word
Definition: pageres.h:186
BLOB_CHOICE * GetBlobChoice(int index) const
Definition: pageres.cpp:750
int16_t fontinfo_id2() const
Definition: ratngs.h:89
int16_t fontinfo_id() const
Definition: ratngs.h:86
const STRING & unichar_string() const
Definition: ratngs.h:531
UNICHAR_ID unichar_id(int index) const
Definition: ratngs.h:305
const UNICHARSET * unicharset() const
Definition: ratngs.h:290
float certainty() const
Definition: ratngs.h:320
int length() const
Definition: ratngs.h:293
void print() const
Definition: ratngs.h:570
Definition: rect.h:34
int16_t top() const
Definition: rect.h:58
int16_t height() const
Definition: rect.h:108
int16_t bottom() const
Definition: rect.h:65
bool flag(WERD_FLAGS mask) const
Definition: werd.h:117
const char * string() const
Definition: strngs.cpp:194
bool get_ispunctuation(UNICHAR_ID unichar_id) const
Definition: unicharset.h:519
bool top_bottom_useful() const
Definition: unicharset.h:537
const char * id_to_unichar(UNICHAR_ID id) const
Definition: unicharset.cpp:291
void get_top_bottom(UNICHAR_ID unichar_id, int *min_bottom, int *max_bottom, int *min_top, int *max_top) const
Definition: unicharset.h:568
int classify_class_pruner_multiplier
Definition: classify.h:501
int classify_integer_matcher_multiplier
Definition: classify.h:505
UnicityTable< FontInfo > & get_fontinfo_table()
Definition: classify.h:386