40 #include "allheaders.h"
53 #ifdef USE_STD_NAMESPACE
70 "Degrade rendered image with speckle noise, dilation/erosion "
97 "Fraction of words to underline (value in [0,1])");
101 "Fraction of words to underline (value in [0,1])");
108 "Specify one of the following writing"
110 "'horizontal' : Render regular horizontal text. (default)\n"
111 "'vertical' : Render vertical text. Glyph orientation is"
112 " selected by Pango.\n"
113 "'vertical-upright' : Render vertical text. Glyph "
114 " orientation is set to be upright.");
116 INT_PARAM_FLAG(box_padding, 0,
"Padding around produced bounding boxes");
119 "Remove unrenderable words from source text");
125 "Rebuild and render ligatures");
128 "Search for all fonts that can render the text");
130 "If find_fonts==true, render each font to its own image. "
131 "Image filenames are of the form output_name.font_name.tif");
133 "If find_fonts==true, the minimum coverage the font has of "
134 "the characters in the text file to include it, between "
137 BOOL_PARAM_FLAG(list_available_fonts,
false,
"List available fonts and quit.");
139 BOOL_PARAM_FLAG(render_ngrams,
false,
"Put each space-separated entity from the"
140 " input file into one bounding box. The ngrams in the input"
141 " file will be randomly permuted before rendering (so that"
142 " there is sufficient variety of characters on each line).");
145 "Output word bounding boxes instead of character boxes. "
146 "This is used for Cube training, and implied by "
150 "File with characters in the unicharset. If --render_ngrams"
151 " is true and --unicharset_file is specified, ngrams with"
152 " characters that are not in unicharset will be omitted");
155 "Rotate the generated characters both ways.");
158 "Assumes that the input file contains a list of ngrams. Renders"
159 " each ngram, extracts spacing properties and records them in"
160 " output_base/[font_name].fontinfo file.");
164 "If true also outputs individual character images");
166 "Each glyph is square with this side length in pixels");
168 "Final_size=glyph_resized_size+2*glyph_num_border_pixels_to_pad");
182 static bool IsWhitespaceBox(
const BoxChar* boxchar) {
183 return (boxchar->
box() ==
NULL ||
187 static string StringReplace(
const string& in,
188 const string& oldsub,
const string& newsub) {
192 int pos = in.find(oldsub, start_pos);
193 if (pos == string::npos)
break;
194 out.append(in.data() + start_pos, pos - start_pos);
195 out.append(newsub.data(), newsub.length());
196 start_pos = pos + oldsub.length();
198 out.append(in.data() + start_pos, in.length() - start_pos);
214 const string &output_base) {
215 map<string, SpacingProperties> spacing_map;
216 map<string, SpacingProperties>::iterator spacing_map_it0;
217 map<string, SpacingProperties>::iterator spacing_map_it1;
218 int x_bearing, x_advance;
219 int len = utf8_text.length();
221 const char* text = utf8_text.c_str();
222 while (offset < len) {
224 const vector<BoxChar*> &boxes = render->
GetBoxes();
228 if (boxes.size() > 2 && !IsWhitespaceBox(boxes[boxes.size() - 1]) &&
229 IsWhitespaceBox(boxes[boxes.size() - 2])) {
230 if (boxes.size() > 3) {
231 tprintf(
"WARNING: Adjusting to bad page break after '%s%s'\n",
232 boxes[boxes.size() - 4]->ch().c_str(),
233 boxes[boxes.size() - 3]->ch().c_str());
235 offset -= boxes[boxes.size() - 1]->ch().size();
238 for (
int b = 0; b < boxes.size(); b += 2) {
239 while (b < boxes.size() && IsWhitespaceBox(boxes[b])) ++b;
240 if (b + 1 >= boxes.size())
break;
241 const string &ch0 = boxes[b]->ch();
252 if (IsWhitespaceBox(boxes[b+1])) {
255 int xgap = (boxes[b+1]->box()->x -
256 (boxes[b]->box()->x + boxes[b]->box()->w));
257 spacing_map_it0 = spacing_map.find(ch0);
259 if (spacing_map_it0 == spacing_map.end() &&
262 x_bearing, x_advance - x_bearing - boxes[b]->box()->w);
263 spacing_map_it0 = spacing_map.find(ch0);
266 const string &ch1 = boxes[b+1]->ch();
267 tlog(3,
"%s%s\n", ch0.c_str(), ch1.c_str());
268 spacing_map_it1 = spacing_map.find(ch1);
269 if (spacing_map_it1 == spacing_map.end() &&
272 x_bearing, x_advance - x_bearing - boxes[b+1]->box()->w);
273 spacing_map_it1 = spacing_map.find(ch1);
276 if (ok_count == 2 && xgap != (spacing_map_it0->second.x_gap_after +
277 spacing_map_it1->second.x_gap_before)) {
278 spacing_map_it0->second.kerned_x_gaps[ch1] = xgap;
283 string output_string;
284 const int kBufSize = 1024;
286 snprintf(buf, kBufSize,
"%d\n", static_cast<int>(spacing_map.size()));
287 output_string.append(buf);
288 map<string, SpacingProperties>::const_iterator spacing_map_it;
289 for (spacing_map_it = spacing_map.begin();
290 spacing_map_it != spacing_map.end(); ++spacing_map_it) {
291 snprintf(buf, kBufSize,
292 "%s %d %d %d", spacing_map_it->first.c_str(),
293 spacing_map_it->second.x_gap_before,
294 spacing_map_it->second.x_gap_after,
295 static_cast<int>(spacing_map_it->second.kerned_x_gaps.size()));
296 output_string.append(buf);
297 map<string, int>::const_iterator kern_it;
298 for (kern_it = spacing_map_it->second.kerned_x_gaps.begin();
299 kern_it != spacing_map_it->second.kerned_x_gaps.end(); ++kern_it) {
300 snprintf(buf, kBufSize,
301 " %s %d", kern_it->first.c_str(), kern_it->second);
302 output_string.append(buf);
304 output_string.append(
"\n");
310 const vector<BoxChar*>& vbox,
311 const int input_tiff_page) {
314 tprintf(
"ERROR: MakeIndividualGlyphs(): Input Pix* is NULL\n");
316 }
else if (FLAGS_glyph_resized_size <= 0) {
317 tprintf(
"ERROR: --glyph_resized_size must be positive\n");
319 }
else if (FLAGS_glyph_num_border_pixels_to_pad < 0) {
320 tprintf(
"ERROR: --glyph_num_border_pixels_to_pad must be 0 or positive\n");
324 const int n_boxes = vbox.size();
325 int n_boxes_saved = 0;
326 int current_tiff_page = 0;
328 static int glyph_count = 0;
329 for (
int i = 0; i < n_boxes; i++) {
331 Box* b = vbox[i]->mutable_box();
338 if (y < y_previous-pixGetHeight(pix)/10) {
339 tprintf(
"ERROR: Wrap-around encountered, at i=%d\n", i);
342 if (current_tiff_page < input_tiff_page)
continue;
343 else if (current_tiff_page > input_tiff_page)
break;
345 if (x < 0 || y < 0 ||
346 (x+w-1) >= pixGetWidth(pix) ||
347 (y+h-1) >= pixGetHeight(pix)) {
348 tprintf(
"ERROR: MakeIndividualGlyphs(): Index out of range, at i=%d"
349 " (x=%d, y=%d, w=%d, h=%d\n)", i, x, y, w, h);
351 }
else if (w < FLAGS_glyph_num_border_pixels_to_pad &&
352 h < FLAGS_glyph_num_border_pixels_to_pad) {
353 tprintf(
"ERROR: Input image too small to be a character, at i=%d\n", i);
357 Pix* pix_glyph = pixClipRectangle(pix, b,
NULL);
359 tprintf(
"ERROR: MakeIndividualGlyphs(): Failed to clip, at i=%d\n", i);
363 Pix* pix_glyph_sq = pixScaleToSize(pix_glyph,
364 FLAGS_glyph_resized_size,
365 FLAGS_glyph_resized_size);
367 tprintf(
"ERROR: MakeIndividualGlyphs(): Failed to resize, at i=%d\n", i);
371 Pix* pix_glyph_sq_pad = pixAddBorder(pix_glyph_sq,
372 FLAGS_glyph_num_border_pixels_to_pad,
374 if (!pix_glyph_sq_pad) {
375 tprintf(
"ERROR: MakeIndividualGlyphs(): Failed to zero-pad, at i=%d\n",
380 Pix* pix_glyph_sq_pad_8 = pixConvertTo8(pix_glyph_sq_pad,
false);
382 snprintf(filename, 1024,
"%s_%d.jpg", FLAGS_outputbase.c_str(),
384 if (pixWriteJpeg(filename, pix_glyph_sq_pad_8, 100, 0)) {
385 tprintf(
"ERROR: MakeIndividualGlyphs(): Failed to write JPEG to %s,"
386 " at i=%d\n", filename, i);
390 pixDestroy(&pix_glyph);
391 pixDestroy(&pix_glyph_sq);
392 pixDestroy(&pix_glyph_sq_pad);
393 pixDestroy(&pix_glyph_sq_pad_8);
397 if (n_boxes_saved == 0) {
400 tprintf(
"Total number of characters saved = %d\n", n_boxes_saved);
414 int main(
int argc,
char** argv) {
417 if (FLAGS_list_available_fonts) {
418 const vector<string>& all_fonts = FontUtils::ListAvailableFonts();
419 for (
int i = 0; i < all_fonts.size(); ++i) {
420 tprintf(
"%3d: %s\n", i, all_fonts[i].c_str());
422 "Font %s is unrecognized.\n", all_fonts[i].c_str());
430 "Use --unicharset_file only if --render_ngrams is set.\n");
432 if (!FLAGS_find_fonts && !FontUtils::IsAvailableFont(FLAGS_font.c_str())) {
434 if (!FontUtils::IsAvailableFont(FLAGS_font.c_str(), &pango_name)) {
435 tprintf(
"Could not find font named %s. Pango suggested font %s\n",
436 FLAGS_font.c_str(), pango_name.c_str());
441 if (FLAGS_render_ngrams)
442 FLAGS_output_word_boxes =
true;
444 char font_desc_name[1024];
445 snprintf(font_desc_name, 1024,
"%s %d", FLAGS_font.c_str(),
446 static_cast<int>(FLAGS_ptsize));
461 if (FLAGS_writing_mode ==
"horizontal") {
466 }
else if (FLAGS_writing_mode ==
"vertical") {
471 }
else if (FLAGS_writing_mode ==
"vertical-upright") {
481 TLOG_FATAL(
"Invalid writing mode : %s\n", FLAGS_writing_mode.c_str());
486 File::ReadFileToStringOrDie(FLAGS_text.c_str(), &src_utf8);
489 if (strncmp(src_utf8.c_str(),
"\xef\xbb\xbf", 3) == 0) {
490 src_utf8.erase(0, 3);
492 tlog(1,
"Render string of size %d\n", src_utf8.length());
494 if (FLAGS_render_ngrams || FLAGS_only_extract_font_properties) {
497 const string kSeparator = FLAGS_render_ngrams ?
" " :
" ";
501 const int kCharsPerLine = (FLAGS_ptsize > 20) ? 50 : 100;
504 if (FLAGS_render_ngrams && !FLAGS_unicharset_file.empty() &&
506 TLOG_FATAL(
"Failed to load unicharset from file %s\n",
507 FLAGS_unicharset_file.c_str());
513 const char *str8 = src_utf8.c_str();
514 int len = src_utf8.length();
516 vector<pair<int, int> > offsets;
518 while (offset < len) {
520 offsets.push_back(make_pair(offset, step));
524 if (FLAGS_render_ngrams)
525 std::random_shuffle(offsets.begin(), offsets.end());
527 for (
int i = 0, line = 1; i < offsets.size(); ++i) {
528 const char *curr_pos = str8 + offsets[i].first;
529 int ngram_len = offsets[i].second;
531 if (!FLAGS_unicharset_file.empty() &&
535 rand_utf8.append(curr_pos, ngram_len);
536 if (rand_utf8.length() > line * kCharsPerLine) {
537 rand_utf8.append(
" \n");
539 if (line & 0x1) rand_utf8.append(kSeparator);
541 rand_utf8.append(kSeparator);
544 tlog(1,
"Rendered ngram string of size %d\n", rand_utf8.length());
545 src_utf8.swap(rand_utf8);
547 if (FLAGS_only_extract_font_properties) {
548 tprintf(
"Extracting font properties only\n");
555 vector<float> page_rotation;
556 const char* to_render_utf8 = src_utf8.c_str();
560 vector<string> font_names;
564 int num_pass = FLAGS_bidirectional_rotation ? 2 : 1;
565 for (
int pass = 0; pass < num_pass; ++pass) {
568 for (
int offset = 0; offset < strlen(to_render_utf8); ++im, ++page_num) {
569 tlog(1,
"Starting page %d\n", im);
571 if (FLAGS_find_fonts) {
573 to_render_utf8 + offset,
574 strlen(to_render_utf8 + offset),
578 strlen(to_render_utf8 + offset), &pix);
584 rotation = -1 * page_rotation[page_num];
586 if (FLAGS_degrade_image) {
587 pix =
DegradeImage(pix, FLAGS_exposure, &randomizer, &rotation);
593 page_rotation.push_back(rotation);
596 Pix* gray_pix = pixConvertTo8(pix,
false);
598 Pix* binary = pixThresholdToBinary(gray_pix, 128);
599 pixDestroy(&gray_pix);
600 char tiff_name[1024];
601 if (FLAGS_find_fonts) {
602 if (FLAGS_render_per_font) {
603 string fontname_for_file = tesseract::StringReplace(
604 font_used,
" ",
"_");
605 snprintf(tiff_name, 1024,
"%s.%s.tif", FLAGS_outputbase.c_str(),
606 fontname_for_file.c_str());
607 pixWriteTiff(tiff_name, binary, IFF_TIFF_G4,
"w");
608 tprintf(
"Rendered page %d to file %s\n", im, tiff_name);
610 font_names.push_back(font_used);
613 snprintf(tiff_name, 1024,
"%s.tif", FLAGS_outputbase.c_str());
614 pixWriteTiff(tiff_name, binary, IFF_TIFF_G4, im == 0 ?
"w" :
"a");
615 tprintf(
"Rendered page %d to file %s\n", im, tiff_name);
618 if (FLAGS_output_individual_glyph_images) {
620 tprintf(
"ERROR: Individual glyphs not saved\n");
625 if (FLAGS_find_fonts && offset != 0) {
632 if (!FLAGS_find_fonts) {
633 string box_name = FLAGS_outputbase.c_str();
636 }
else if (!FLAGS_render_per_font && !font_names.empty()) {
637 string filename = FLAGS_outputbase.c_str();
638 filename +=
".fontlist.txt";
639 FILE* fp = fopen(filename.c_str(),
"wb");
641 tprintf(
"Failed to create output font list %s\n", filename.c_str());
643 for (
int i = 0; i < font_names.size(); ++i) {
644 fprintf(fp,
"%s\n", font_names[i].c_str());
STRING_PARAM_FLAG(text,"","File name of text input to process")
void set_gravity_hint_strong(bool gravity_hint_strong)
void set_render_fullwidth_latin(bool render_fullwidth_latin)
void WriteAllBoxes(const string &filename)
const PangoFontInfo & font() const
bool GetSpacingProperties(const string &utf8_char, int *x_bearing, int *x_advance) const
bool MakeIndividualGlyphs(Pix *pix, const vector< BoxChar * > &vbox, const int input_tiff_page)
void set_strip_unrenderable_words(bool val)
BOOL_PARAM_FLAG(degrade_image, true,"Degrade rendered image with speckle noise, dilation/erosion ""and rotation")
void set_resolution(const int resolution)
void set_leading(int leading)
void ParseCommandLineFlags(const char *usage, int *argc, char ***argv, const bool remove_flags)
void set_underline_start_prob(const double frac)
void set_vertical_text(bool vertical_text)
bool load_from_file(const char *const filename, bool skip_fragments)
void RotatePageBoxes(float rotation)
const string & ch() const
static void WriteStringToFileOrDie(const string &str, const string &filename)
map< string, int > kerned_x_gaps
int SpanUTF8Whitespace(const char *text)
#define ASSERT_HOST_MSG(x, msg...)
void set_box_padding(int val)
int SpanUTF8NotWhitespace(const char *text)
void set_underline_continuation_prob(const double frac)
void set_char_spacing(double char_spacing)
void set_seed(uinT64 seed)
const vector< BoxChar * > & GetBoxes() const
DOUBLE_PARAM_FLAG(char_spacing, 0,"Inter-character space in ems")
#define TLOG_FATAL(msg...)
void set_add_ligatures(bool add_ligatures)
void set_h_margin(const int h_margin)
void set_v_margin(const int v_margin)
bool encodable_string(const char *str, int *first_bad_position) const
Pix * DegradeImage(Pix *input, int exposure, TRand *randomizer, float *rotation)
INT_PARAM_FLAG(exposure, 0,"Exposure level in photocopier")
SpacingProperties(int b, int a)
void ExtractFontProperties(const string &utf8_text, StringRenderer *render, const string &output_base)
int RenderAllFontsToImage(double min_coverage, const char *text, int text_length, string *font_used, Pix **pix)
void set_output_word_boxes(bool val)
int RenderToImage(const char *text, int text_length, Pix **pix)
int main(int argc, char **argv)