tesseract  4.00.00dev
stringrenderer.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: stringrenderer.cpp
3  * Description: Class for rendering UTF-8 text to an image, and retrieving
4  * bounding boxes around each grapheme cluster.
5  * Author: Ranjith Unnikrishnan
6  * Created: Mon Nov 18 2013
7  *
8  * (C) Copyright 2013, Google Inc.
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * http://www.apache.org/licenses/LICENSE-2.0
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  **********************************************************************/
20 
21 #include "stringrenderer.h"
22 
23 #include <assert.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <algorithm>
27 #include <map>
28 #include <utility>
29 #include <vector>
30 
31 #include "allheaders.h" // from leptonica
32 #include "boxchar.h"
33 #include "ligature_table.h"
34 #include "normstrngs.h"
35 #include "pango/pango-font.h"
36 #include "pango/pango-glyph-item.h"
37 #include "tlog.h"
38 #include "unichar.h"
39 #include "unicode/uchar.h" // from libicu
40 #include "util.h"
41 
42 #ifdef USE_STD_NAMESPACE
43 using std::map;
44 using std::max;
45 using std::min;
46 using std::swap;
47 #endif
48 
49 namespace tesseract {
50 
51 static const int kDefaultOutputResolution = 300;
52 
53 // Word joiner (U+2060) inserted after letters in ngram mode, as per
54 // recommendation in http://unicode.org/reports/tr14/ to avoid line-breaks at
55 // hyphens and other non-alpha characters.
56 static const char* kWordJoinerUTF8 = "\xE2\x81\xA0"; // u8"\u2060";
57 static const char32 kWordJoiner = 0x2060;
58 
59 static bool IsCombiner(int ch) {
60  const int char_type = u_charType(ch);
61  return ((char_type == U_NON_SPACING_MARK) ||
62  (char_type == U_ENCLOSING_MARK) ||
63  (char_type == U_COMBINING_SPACING_MARK));
64 }
65 
66 static string EncodeAsUTF8(const char32 ch32) {
67  UNICHAR uni_ch(ch32);
68  return string(uni_ch.utf8(), uni_ch.utf8_len());
69 }
70 
71 // Returns true with probability 'prob'.
72 static bool RandBool(const double prob, TRand* rand) {
73  if (prob == 1.0) return true;
74  if (prob == 0.0) return false;
75  return rand->UnsignedRand(1.0) < prob;
76 }
77 
78 /* static */
79 Pix* CairoARGB32ToPixFormat(cairo_surface_t *surface) {
80  if (cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32) {
81  printf("Unexpected surface format %d\n",
82  cairo_image_surface_get_format(surface));
83  return nullptr;
84  }
85  const int width = cairo_image_surface_get_width(surface);
86  const int height = cairo_image_surface_get_height(surface);
87  Pix* pix = pixCreate(width, height, 32);
88  int byte_stride = cairo_image_surface_get_stride(surface);
89 
90  for (int i = 0; i < height; ++i) {
91  memcpy(reinterpret_cast<unsigned char*>(pix->data + i * pix->wpl) + 1,
92  cairo_image_surface_get_data(surface) + i * byte_stride,
93  byte_stride - ((i == height - 1) ? 1 : 0));
94  }
95  return pix;
96 }
97 
98 StringRenderer::StringRenderer(const string& font_desc, int page_width,
99  int page_height)
100  : page_width_(page_width),
101  page_height_(page_height),
102  h_margin_(50),
103  v_margin_(50),
104  char_spacing_(0),
105  leading_(0),
106  vertical_text_(false),
107  gravity_hint_strong_(false),
108  render_fullwidth_latin_(false),
109  underline_start_prob_(0),
110  underline_continuation_prob_(0),
111  underline_style_(PANGO_UNDERLINE_SINGLE),
112  features_(nullptr),
113  drop_uncovered_chars_(true),
114  strip_unrenderable_words_(false),
115  add_ligatures_(false),
116  output_word_boxes_(false),
117  surface_(nullptr),
118  cr_(nullptr),
119  layout_(nullptr),
120  start_box_(0),
121  page_(0),
122  box_padding_(0),
123  total_chars_(0),
124  font_index_(0),
125  last_offset_(0) {
126  pen_color_[0] = 0.0;
127  pen_color_[1] = 0.0;
128  pen_color_[2] = 0.0;
129  set_font(font_desc);
130  set_resolution(kDefaultOutputResolution);
131  page_boxes_ = nullptr;
132 }
133 
134 bool StringRenderer::set_font(const string& desc) {
135  bool success = font_.ParseFontDescriptionName(desc);
137  return success;
138 }
139 
140 void StringRenderer::set_resolution(const int resolution) {
141  resolution_ = resolution;
142  font_.set_resolution(resolution);
143 }
144 
146  underline_start_prob_ = min(max(frac, 0.0), 1.0);
147 }
148 
150  underline_continuation_prob_ = min(max(frac, 0.0), 1.0);
151 }
152 
154  free(features_);
155  ClearBoxes();
156  FreePangoCairo();
157 }
158 
160  FreePangoCairo();
161  surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, page_width_,
162  page_height_);
163  cr_ = cairo_create(surface_);
164  {
166  layout_ = pango_cairo_create_layout(cr_);
167  }
168 
169  if (vertical_text_) {
170  PangoContext* context = pango_layout_get_context(layout_);
171  pango_context_set_base_gravity(context, PANGO_GRAVITY_EAST);
172  if (gravity_hint_strong_) {
173  pango_context_set_gravity_hint(context, PANGO_GRAVITY_HINT_STRONG);
174  }
175  pango_layout_context_changed(layout_);
176  }
177 
179 }
180 
182  string font_desc = font_.DescriptionName();
183  // Specify the font via a description name
184  PangoFontDescription *desc =
185  pango_font_description_from_string(font_desc.c_str());
186  // Assign the font description to the layout
187  pango_layout_set_font_description(layout_, desc);
188  pango_font_description_free(desc); // free the description
189  pango_cairo_context_set_resolution(pango_layout_get_context(layout_),
190  resolution_);
191 
192  int max_width = page_width_ - 2 * h_margin_;
193  int max_height = page_height_ - 2 * v_margin_;
194  tlog(3, "max_width = %d, max_height = %d\n", max_width, max_height);
195  if (vertical_text_) {
196  swap(max_width, max_height);
197  }
198  pango_layout_set_width(layout_, max_width * PANGO_SCALE);
199  pango_layout_set_wrap(layout_, PANGO_WRAP_WORD);
200 
201  // Adjust character spacing
202  PangoAttrList* attr_list = pango_attr_list_new();
203  if (char_spacing_) {
204  PangoAttribute* spacing_attr = pango_attr_letter_spacing_new(
205  static_cast<int>(char_spacing_ * PANGO_SCALE + 0.5));
206  spacing_attr->start_index = 0;
207  spacing_attr->end_index = static_cast<guint>(-1);
208  pango_attr_list_change(attr_list, spacing_attr);
209  }
210 #if (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 38)
211  if (add_ligatures_) {
212  set_features("liga, clig, dlig, hlig");
213  PangoAttribute* feature_attr = pango_attr_font_features_new(features_);
214  pango_attr_list_change(attr_list, feature_attr);
215  }
216 #endif
217  pango_layout_set_attributes(layout_, attr_list);
218  pango_attr_list_unref(attr_list);
219  // Adjust line spacing
220  if (leading_) {
221  pango_layout_set_spacing(layout_, leading_ * PANGO_SCALE);
222  }
223 }
224 
226  if (layout_) {
227  g_object_unref(layout_);
228  layout_ = nullptr;
229  }
230  if (cr_) {
231  cairo_destroy(cr_);
232  cr_ = nullptr;
233  }
234  if (surface_) {
235  cairo_surface_destroy(surface_);
236  surface_ = nullptr;
237  }
238 }
239 
240 void StringRenderer::SetWordUnderlineAttributes(const string& page_text) {
241  if (underline_start_prob_ == 0) return;
242  PangoAttrList* attr_list = pango_layout_get_attributes(layout_);
243 
244  const char* text = page_text.c_str();
245  size_t offset = 0;
246  TRand rand;
247  bool started_underline = false;
248  PangoAttribute* und_attr = nullptr;
249 
250  while (offset < page_text.length()) {
251  offset += SpanUTF8Whitespace(text + offset);
252  if (offset == page_text.length()) break;
253 
254  int word_start = offset;
255  int word_len = SpanUTF8NotWhitespace(text + offset);
256  offset += word_len;
257  if (started_underline) {
258  // Should we continue the underline to the next word?
259  if (RandBool(underline_continuation_prob_, &rand)) {
260  // Continue the current underline to this word.
261  und_attr->end_index = word_start + word_len;
262  } else {
263  // Otherwise end the current underline attribute at the end of the
264  // previous word.
265  pango_attr_list_insert(attr_list, und_attr);
266  started_underline = false;
267  und_attr = nullptr;
268  }
269  }
270  if (!started_underline && RandBool(underline_start_prob_, &rand)) {
271  // Start a new underline attribute
272  und_attr = pango_attr_underline_new(underline_style_);
273  und_attr->start_index = word_start;
274  und_attr->end_index = word_start + word_len;
275  started_underline = true;
276  }
277  }
278  // Finish the current underline attribute at the end of the page.
279  if (started_underline) {
280  und_attr->end_index = page_text.length();
281  pango_attr_list_insert(attr_list, und_attr);
282  }
283 }
284 
285 // Returns offset in utf8 bytes to first page.
287  int text_length) {
288  if (!text_length) return 0;
289  const int max_height = (page_height_ - 2 * v_margin_);
290  const int max_width = (page_width_ - 2 * h_margin_);
291  const int max_layout_height = vertical_text_ ? max_width : max_height;
292 
293  UNICHAR::const_iterator it = UNICHAR::begin(text, text_length);
294  const UNICHAR::const_iterator it_end = UNICHAR::end(text, text_length);
295  const int kMaxUnicodeBufLength = 15000;
296  for (int i = 0; i < kMaxUnicodeBufLength && it != it_end; ++it, ++i);
297  int buf_length = it.utf8_data() - text;
298  tlog(1, "len = %d buf_len = %d\n", text_length, buf_length);
299  pango_layout_set_text(layout_, text, buf_length);
300 
301  PangoLayoutIter* line_iter = nullptr;
302  { // Fontconfig caches some info here that is not freed before exit.
304  line_iter = pango_layout_get_iter(layout_);
305  }
306  bool first_page = true;
307  int page_top = 0;
308  int offset = buf_length;
309  do {
310  // Get bounding box of the current line
311  PangoRectangle line_ink_rect;
312  pango_layout_iter_get_line_extents(line_iter, &line_ink_rect, nullptr);
313  pango_extents_to_pixels(&line_ink_rect, nullptr);
314  PangoLayoutLine* line = pango_layout_iter_get_line_readonly(line_iter);
315  if (first_page) {
316  page_top = line_ink_rect.y;
317  first_page = false;
318  }
319  int line_bottom = line_ink_rect.y + line_ink_rect.height;
320  if (line_bottom - page_top > max_layout_height) {
321  offset = line->start_index;
322  tlog(1, "Found offset = %d\n", offset);
323  break;
324  }
325  } while (pango_layout_iter_next_line(line_iter));
326  pango_layout_iter_free(line_iter);
327  return offset;
328 }
329 
330 const std::vector<BoxChar*>& StringRenderer::GetBoxes() const {
331  return boxchars_;
332 }
333 
335  return page_boxes_;
336 }
337 
338 void StringRenderer::RotatePageBoxes(float rotation) {
339  BoxChar::RotateBoxes(rotation, page_width_ / 2, page_height_ / 2,
340  start_box_, boxchars_.size(), &boxchars_);
341 }
342 
343 
345  for (size_t i = 0; i < boxchars_.size(); ++i)
346  delete boxchars_[i];
347  boxchars_.clear();
348  boxaDestroy(&page_boxes_);
349 }
350 
354 }
355 
359 }
360 
361 // Returns cluster strings in logical order.
362 bool StringRenderer::GetClusterStrings(std::vector<string>* cluster_text) {
363  std::map<int, string> start_byte_to_text;
364  PangoLayoutIter* run_iter = pango_layout_get_iter(layout_);
365  const char* full_text = pango_layout_get_text(layout_);
366  do {
367  PangoLayoutRun* run = pango_layout_iter_get_run_readonly(run_iter);
368  if (!run) {
369  // End of line nullptr run marker
370  tlog(2, "Found end of line marker\n");
371  continue;
372  }
373  PangoGlyphItemIter cluster_iter;
374  gboolean have_cluster;
375  for (have_cluster = pango_glyph_item_iter_init_start(&cluster_iter,
376  run, full_text);
377  have_cluster;
378  have_cluster = pango_glyph_item_iter_next_cluster(&cluster_iter)) {
379  const int start_byte_index = cluster_iter.start_index;
380  const int end_byte_index = cluster_iter.end_index;
381  string text = string(full_text + start_byte_index,
382  end_byte_index - start_byte_index);
383  if (IsUTF8Whitespace(text.c_str())) {
384  tlog(2, "Found whitespace\n");
385  text = " ";
386  }
387  tlog(2, "start_byte=%d end_byte=%d : '%s'\n", start_byte_index,
388  end_byte_index, text.c_str());
389  if (add_ligatures_) {
390  // Make sure the output box files have ligatured text in case the font
391  // decided to use an unmapped glyph.
392  text = LigatureTable::Get()->AddLigatures(text, nullptr);
393  }
394  start_byte_to_text[start_byte_index] = text;
395  }
396  } while (pango_layout_iter_next_run(run_iter));
397  pango_layout_iter_free(run_iter);
398 
399  cluster_text->clear();
400  for (std::map<int, string>::const_iterator it = start_byte_to_text.begin();
401  it != start_byte_to_text.end(); ++it) {
402  cluster_text->push_back(it->second);
403  }
404  return !cluster_text->empty();
405 }
406 
407 // Merges an array of BoxChars into words based on the identification of
408 // BoxChars containing the space character as inter-word separators.
409 //
410 // Sometime two adjacent characters in the sequence may be detected as lying on
411 // different lines based on their spatial positions. This may be the result of a
412 // newline character at end of the last word on a line in the source text, or of
413 // a discretionary line-break created by Pango at intra-word locations like
414 // hyphens. When this is detected the word is split at that location into
415 // multiple BoxChars. Otherwise, each resulting BoxChar will contain a word and
416 // its bounding box.
417 static void MergeBoxCharsToWords(std::vector<BoxChar*>* boxchars) {
418  std::vector<BoxChar*> result;
419  bool started_word = false;
420  for (size_t i = 0; i < boxchars->size(); ++i) {
421  if (boxchars->at(i)->ch() == " " || boxchars->at(i)->box() == nullptr) {
422  result.push_back(boxchars->at(i));
423  boxchars->at(i) = nullptr;
424  started_word = false;
425  continue;
426  }
427 
428  if (!started_word) {
429  // Begin new word
430  started_word = true;
431  result.push_back(boxchars->at(i));
432  boxchars->at(i) = nullptr;
433  } else {
434  BoxChar* last_boxchar = result.back();
435  // Compute bounding box union
436  const Box* box = boxchars->at(i)->box();
437  Box* last_box = last_boxchar->mutable_box();
438  int left = min(last_box->x, box->x);
439  int right = max(last_box->x + last_box->w, box->x + box->w);
440  int top = min(last_box->y, box->y);
441  int bottom = max(last_box->y + last_box->h, box->y + box->h);
442  // Conclude that the word was broken to span multiple lines based on the
443  // size of the merged bounding box in relation to those of the individual
444  // characters seen so far.
445  if (right - left > last_box->w + 5 * box->w) {
446  tlog(1, "Found line break after '%s'", last_boxchar->ch().c_str());
447  // Insert a fake interword space and start a new word with the current
448  // boxchar.
449  result.push_back(new BoxChar(" ", 1));
450  result.push_back(boxchars->at(i));
451  boxchars->at(i) = nullptr;
452  continue;
453  }
454  // Append to last word
455  last_boxchar->mutable_ch()->append(boxchars->at(i)->ch());
456  last_box->x = left;
457  last_box->w = right - left;
458  last_box->y = top;
459  last_box->h = bottom - top;
460  delete boxchars->at(i);
461  boxchars->at(i) = nullptr;
462  }
463  }
464  boxchars->swap(result);
465 }
466 
467 
469  const char* text = pango_layout_get_text(layout_);
470  PangoLayoutIter* cluster_iter = pango_layout_get_iter(layout_);
471 
472  // Do a first pass to store cluster start indexes.
473  std::vector<int> cluster_start_indices;
474  do {
475  cluster_start_indices.push_back(pango_layout_iter_get_index(cluster_iter));
476  tlog(3, "Added %d\n", cluster_start_indices.back());
477  } while (pango_layout_iter_next_cluster(cluster_iter));
478  pango_layout_iter_free(cluster_iter);
479  cluster_start_indices.push_back(strlen(text));
480  tlog(3, "Added last index %d\n", cluster_start_indices.back());
481  // Sort the indices and create a map from start to end indices.
482  std::sort(cluster_start_indices.begin(), cluster_start_indices.end());
483  std::map<int, int> cluster_start_to_end_index;
484  for (size_t i = 0; i + 1 < cluster_start_indices.size(); ++i) {
485  cluster_start_to_end_index[cluster_start_indices[i]]
486  = cluster_start_indices[i + 1];
487  }
488 
489  // Iterate again to compute cluster boxes and their text with the obtained
490  // cluster extent information.
491  cluster_iter = pango_layout_get_iter(layout_);
492  // Store BoxChars* sorted by their byte start positions
493  std::map<int, BoxChar*> start_byte_to_box;
494  do {
495  PangoRectangle cluster_rect;
496  pango_layout_iter_get_cluster_extents(cluster_iter, &cluster_rect, nullptr);
497  pango_extents_to_pixels(&cluster_rect, nullptr);
498  const int start_byte_index = pango_layout_iter_get_index(cluster_iter);
499  const int end_byte_index = cluster_start_to_end_index[start_byte_index];
500  string cluster_text = string(text + start_byte_index,
501  end_byte_index - start_byte_index);
502  if (!cluster_text.empty() && cluster_text[0] == '\n') {
503  tlog(2, "Skipping newlines at start of text.\n");
504  continue;
505  }
506  if (!cluster_rect.width || !cluster_rect.height ||
507  IsUTF8Whitespace(cluster_text.c_str())) {
508  tlog(2, "Skipping whitespace with boxdim (%d,%d) '%s'\n",
509  cluster_rect.width, cluster_rect.height, cluster_text.c_str());
510  BoxChar* boxchar = new BoxChar(" ", 1);
511  boxchar->set_page(page_);
512  start_byte_to_box[start_byte_index] = boxchar;
513  continue;
514  }
515  // Prepare a boxchar for addition at this byte position.
516  tlog(2, "[%d %d], %d, %d : start_byte=%d end_byte=%d : '%s'\n",
517  cluster_rect.x, cluster_rect.y,
518  cluster_rect.width, cluster_rect.height,
519  start_byte_index, end_byte_index,
520  cluster_text.c_str());
521  ASSERT_HOST_MSG(cluster_rect.width,
522  "cluster_text:%s start_byte_index:%d\n",
523  cluster_text.c_str(), start_byte_index);
524  ASSERT_HOST_MSG(cluster_rect.height,
525  "cluster_text:%s start_byte_index:%d\n",
526  cluster_text.c_str(), start_byte_index);
527  if (box_padding_) {
528  cluster_rect.x = max(0, cluster_rect.x - box_padding_);
529  cluster_rect.width += 2 * box_padding_;
530  cluster_rect.y = max(0, cluster_rect.y - box_padding_);
531  cluster_rect.height += 2 * box_padding_;
532  }
533  if (add_ligatures_) {
534  // Make sure the output box files have ligatured text in case the font
535  // decided to use an unmapped glyph.
536  cluster_text = LigatureTable::Get()->AddLigatures(cluster_text, nullptr);
537  }
538  BoxChar* boxchar = new BoxChar(cluster_text.c_str(), cluster_text.size());
539  boxchar->set_page(page_);
540  boxchar->AddBox(cluster_rect.x, cluster_rect.y,
541  cluster_rect.width, cluster_rect.height);
542  start_byte_to_box[start_byte_index] = boxchar;
543  } while (pango_layout_iter_next_cluster(cluster_iter));
544  pango_layout_iter_free(cluster_iter);
545 
546  // There is a subtle bug in the cluster text reported by the PangoLayoutIter
547  // on ligatured characters (eg. The word "Lam-Aliph" in arabic). To work
548  // around this, we use text reported using the PangoGlyphIter which is
549  // accurate.
550  // TODO(ranjith): Revisit whether this is still needed in newer versions of
551  // pango.
552  std::vector<string> cluster_text;
553  if (GetClusterStrings(&cluster_text)) {
554  ASSERT_HOST(cluster_text.size() == start_byte_to_box.size());
555  int ind = 0;
556  for (std::map<int, BoxChar*>::iterator it = start_byte_to_box.begin();
557  it != start_byte_to_box.end(); ++it, ++ind) {
558  it->second->mutable_ch()->swap(cluster_text[ind]);
559  }
560  }
561 
562  // Append to the boxchars list in byte order.
563  std::vector<BoxChar*> page_boxchars;
564  page_boxchars.reserve(start_byte_to_box.size());
565  string last_ch;
566  for (std::map<int, BoxChar*>::const_iterator it = start_byte_to_box.begin();
567  it != start_byte_to_box.end(); ++it) {
568  if (it->second->ch() == kWordJoinerUTF8) {
569  // Skip zero-width joiner characters (ZWJs) here.
570  delete it->second;
571  } else {
572  page_boxchars.push_back(it->second);
573  }
574  }
575  CorrectBoxPositionsToLayout(&page_boxchars);
576 
578  for (std::map<int, BoxChar*>::iterator it = start_byte_to_box.begin();
579  it != start_byte_to_box.end(); ++it) {
580  // Convert fullwidth Latin characters to their halfwidth forms.
581  string half(ConvertFullwidthLatinToBasicLatin(it->second->ch()));
582  it->second->mutable_ch()->swap(half);
583  }
584  }
585 
586  // Merge the character boxes into word boxes if we are rendering n-grams.
587  if (output_word_boxes_) {
588  MergeBoxCharsToWords(&page_boxchars);
589  }
590 
591  boxchars_.insert(boxchars_.end(), page_boxchars.begin(), page_boxchars.end());
592 
593  // Compute the page bounding box
594  Box* page_box = nullptr;
595  Boxa* all_boxes = nullptr;
596  for (size_t i = 0; i < page_boxchars.size(); ++i) {
597  if (page_boxchars[i]->box() == nullptr) continue;
598  if (all_boxes == nullptr) all_boxes = boxaCreate(0);
599  boxaAddBox(all_boxes, page_boxchars[i]->mutable_box(), L_CLONE);
600  }
601  if (all_boxes != nullptr) {
602  boxaGetExtent(all_boxes, nullptr, nullptr, &page_box);
603  boxaDestroy(&all_boxes);
604  if (page_boxes_ == nullptr) page_boxes_ = boxaCreate(0);
605  boxaAddBox(page_boxes_, page_box, L_INSERT);
606  }
607 }
608 
609 
611  std::vector<BoxChar*>* boxchars) {
612  if (vertical_text_) {
613  const double rotation = - pango_gravity_to_rotation(
614  pango_context_get_base_gravity(pango_layout_get_context(layout_)));
617  0, boxchars->size(), boxchars);
618  } else {
620  }
621 }
622 
623 int StringRenderer::StripUnrenderableWords(string* utf8_text) const {
624  string output_text;
625  const char* text = utf8_text->c_str();
626  size_t offset = 0;
627  int num_dropped = 0;
628  while (offset < utf8_text->length()) {
629  int space_len = SpanUTF8Whitespace(text + offset);
630  output_text.append(text + offset, space_len);
631  offset += space_len;
632  if (offset == utf8_text->length()) break;
633 
634  int word_len = SpanUTF8NotWhitespace(text + offset);
635  if (font_.CanRenderString(text + offset, word_len)) {
636  output_text.append(text + offset, word_len);
637  } else {
638  ++num_dropped;
639  }
640  offset += word_len;
641  }
642  utf8_text->swap(output_text);
643 
644  if (num_dropped > 0) {
645  tprintf("Stripped %d unrenderable words\n", num_dropped);
646  }
647  return num_dropped;
648 }
649 
650 int StringRenderer::RenderToGrayscaleImage(const char* text, int text_length,
651  Pix** pix) {
652  Pix* orig_pix = nullptr;
653  int offset = RenderToImage(text, text_length, &orig_pix);
654  if (orig_pix) {
655  *pix = pixConvertTo8(orig_pix, false);
656  pixDestroy(&orig_pix);
657  }
658  return offset;
659 }
660 
661 int StringRenderer::RenderToBinaryImage(const char* text, int text_length,
662  int threshold, Pix** pix) {
663  Pix* orig_pix = nullptr;
664  int offset = RenderToImage(text, text_length, &orig_pix);
665  if (orig_pix) {
666  Pix* gray_pix = pixConvertTo8(orig_pix, false);
667  pixDestroy(&orig_pix);
668  *pix = pixThresholdToBinary(gray_pix, threshold);
669  pixDestroy(&gray_pix);
670  } else {
671  *pix = orig_pix;
672  }
673  return offset;
674 }
675 
676 // Add word joiner (WJ) characters between adjacent non-space characters except
677 // immediately before a combiner.
678 /* static */
679 string StringRenderer::InsertWordJoiners(const string& text) {
680  string out_str;
681  const UNICHAR::const_iterator it_end = UNICHAR::end(text.c_str(),
682  text.length());
683  for (UNICHAR::const_iterator it = UNICHAR::begin(text.c_str(), text.length());
684  it != it_end; ++it) {
685  // Add the symbol to the output string.
686  out_str.append(it.utf8_data(), it.utf8_len());
687  // Check the next symbol.
688  UNICHAR::const_iterator next_it = it;
689  ++next_it;
690  bool next_char_is_boundary = (next_it == it_end || *next_it == ' ');
691  bool next_char_is_combiner = (next_it == it_end) ?
692  false : IsCombiner(*next_it);
693  if (*it != ' ' && *it != '\n' && !next_char_is_boundary &&
694  !next_char_is_combiner) {
695  out_str += kWordJoinerUTF8;
696  }
697  }
698  return out_str;
699 }
700 
701 // Convert halfwidth Basic Latin characters to their fullwidth forms.
703  string full_str;
704  const UNICHAR::const_iterator it_end = UNICHAR::end(str.c_str(),
705  str.length());
706  for (UNICHAR::const_iterator it = UNICHAR::begin(str.c_str(), str.length());
707  it != it_end; ++it) {
708  // Convert printable and non-space 7-bit ASCII characters to
709  // their fullwidth forms.
710  if (IsInterchangeValid7BitAscii(*it) && isprint(*it) && !isspace(*it)) {
711  // Convert by adding 0xFEE0 to the codepoint of 7-bit ASCII.
712  char32 full_char = *it + 0xFEE0;
713  full_str.append(EncodeAsUTF8(full_char));
714  } else {
715  full_str.append(it.utf8_data(), it.utf8_len());
716  }
717  }
718  return full_str;
719 }
720 
721 // Convert fullwidth Latin characters to their halfwidth forms.
723  string half_str;
724  UNICHAR::const_iterator it_end = UNICHAR::end(str.c_str(), str.length());
725  for (UNICHAR::const_iterator it = UNICHAR::begin(str.c_str(), str.length());
726  it != it_end; ++it) {
727  char32 half_char = FullwidthToHalfwidth(*it);
728  // Convert fullwidth Latin characters to their halfwidth forms
729  // only if halfwidth forms are printable and non-space 7-bit ASCII.
730  if (IsInterchangeValid7BitAscii(half_char) &&
731  isprint(half_char) && !isspace(half_char)) {
732  half_str.append(EncodeAsUTF8(half_char));
733  } else {
734  half_str.append(it.utf8_data(), it.utf8_len());
735  }
736  }
737  return half_str;
738 }
739 
740 // Returns offset to end of text substring rendered in this method.
741 int StringRenderer::RenderToImage(const char* text, int text_length,
742  Pix** pix) {
743  if (pix && *pix) pixDestroy(pix);
744  InitPangoCairo();
745 
746  const int page_offset = FindFirstPageBreakOffset(text, text_length);
747  if (!page_offset) {
748  return 0;
749  }
750  start_box_ = boxchars_.size();
751 
752  if (!vertical_text_) {
753  // Translate by the specified margin
754  cairo_translate(cr_, h_margin_, v_margin_);
755  } else {
756  // Vertical text rendering is achieved by a two-step process of first
757  // performing regular horizontal layout with character orientation set to
758  // EAST, and then translating and rotating the layout before rendering onto
759  // the desired image surface. The settings required for the former step are
760  // done within InitPangoCairo().
761  //
762  // Translate to the top-right margin of page
763  cairo_translate(cr_, page_width_ - h_margin_, v_margin_);
764  // Rotate the layout
765  double rotation = - pango_gravity_to_rotation(
766  pango_context_get_base_gravity(pango_layout_get_context(layout_)));
767  tlog(2, "Rotating by %f radians\n", rotation);
768  cairo_rotate(cr_, rotation);
769  pango_cairo_update_layout(cr_, layout_);
770  }
771  string page_text(text, page_offset);
773  // Convert Basic Latin to their fullwidth forms.
774  page_text = ConvertBasicLatinToFullwidthLatin(page_text);
775  }
777  StripUnrenderableWords(&page_text);
778  }
779  if (drop_uncovered_chars_ &&
780  !font_.CoversUTF8Text(page_text.c_str(), page_text.length())) {
781  int num_dropped = font_.DropUncoveredChars(&page_text);
782  if (num_dropped) {
783  tprintf("WARNING: Dropped %d uncovered characters\n", num_dropped);
784  }
785  }
786  if (add_ligatures_) {
787  // Add ligatures wherever possible, including custom ligatures.
788  page_text = LigatureTable::Get()->AddLigatures(page_text, &font_);
789  }
790  if (underline_start_prob_ > 0) {
791  SetWordUnderlineAttributes(page_text);
792  }
793 
794  pango_layout_set_text(layout_, page_text.c_str(), page_text.length());
795 
796  if (pix) {
797  // Set a white background for the target image surface.
798  cairo_set_source_rgb(cr_, 1.0, 1.0, 1.0); // sets drawing colour to white
799  // Fill the surface with the active colour (if you don't do this, you will
800  // be given a surface with a transparent background to draw on)
801  cairo_paint(cr_);
802  // Set the ink color to black
803  cairo_set_source_rgb(cr_, pen_color_[0], pen_color_[1], pen_color_[2]);
804  // If the target surface or transformation properties of the cairo instance
805  // have changed, update the pango layout to reflect this
806  pango_cairo_update_layout(cr_, layout_);
807  {
808  DISABLE_HEAP_LEAK_CHECK; // for Fontconfig
809  // Draw the pango layout onto the cairo surface
810  pango_cairo_show_layout(cr_, layout_);
811  }
813  }
815  FreePangoCairo();
816  // Update internal state variables.
817  ++page_;
818  return page_offset;
819 }
820 
821 // Render a string to an image, returning it as an 8 bit pix. Behaves as
822 // RenderString, except that it ignores the font set at construction and works
823 // through all the fonts, returning 0 until they are exhausted, at which point
824 // it returns the value it should have returned all along, but no pix this time.
825 // Fonts that don't contain a given proportion of the characters in the string
826 // get skipped.
827 // Fonts that work each get rendered and the font name gets added
828 // to the image.
829 // NOTE that no boxes are produced by this function.
830 //
831 // Example usage: To render a null terminated char-array "txt"
832 //
833 // int offset = 0;
834 // do {
835 // Pix *pix;
836 // offset += renderer.RenderAllFontsToImage(min_proportion, txt + offset,
837 // strlen(txt + offset), nullptr,
838 // &pix);
839 // ...
840 // } while (offset < strlen(text));
841 //
843  const char* text, int text_length,
844  string* font_used, Pix** image) {
845  *image = nullptr;
846  // Select a suitable font to render the title with.
847  const char kTitleTemplate[] = "%s : %d hits = %.2f%%, raw = %d = %.2f%%";
848  string title_font;
849  if (!FontUtils::SelectFont(kTitleTemplate, strlen(kTitleTemplate),
850  &title_font, nullptr)) {
851  tprintf("WARNING: Could not find a font to render image title with!\n");
852  title_font = "Arial";
853  }
854  title_font += " 8";
855  tlog(1, "Selected title font: %s\n", title_font.c_str());
856  if (font_used) font_used->clear();
857 
858  string orig_font = font_.DescriptionName();
859  if (char_map_.empty()) {
860  total_chars_ = 0;
861  // Fill the hash table and use that for computing which fonts to use.
862  for (UNICHAR::const_iterator it = UNICHAR::begin(text, text_length);
863  it != UNICHAR::end(text, text_length); ++it) {
864  ++total_chars_;
865  ++char_map_[*it];
866  }
867  tprintf("Total chars = %d\n", total_chars_);
868  }
869  const std::vector<string>& all_fonts = FontUtils::ListAvailableFonts();
870  assert(0 <= font_index_);
871  for (unsigned int i = static_cast<unsigned int>(font_index_); i < all_fonts.size(); ++i) {
872  ++font_index_;
873  int raw_score = 0;
874  int ok_chars =
875  FontUtils::FontScore(char_map_, all_fonts[i], &raw_score, nullptr);
876  if (ok_chars > 0 && ok_chars >= total_chars_ * min_coverage) {
877  set_font(all_fonts[i]);
878  int offset = RenderToBinaryImage(text, text_length, 128, image);
879  ClearBoxes(); // Get rid of them as they are garbage.
880  const int kMaxTitleLength = 1024;
881  char title[kMaxTitleLength];
882  snprintf(title, kMaxTitleLength, kTitleTemplate,
883  all_fonts[i].c_str(), ok_chars,
884  100.0 * ok_chars / total_chars_, raw_score,
885  100.0 * raw_score / char_map_.size());
886  tprintf("%s\n", title);
887  // This is a good font! Store the offset to return once we've tried all
888  // the fonts.
889  if (offset) {
891  if (font_used) *font_used = all_fonts[i];
892  }
893  // Add the font to the image.
894  set_font(title_font);
895  v_margin_ /= 8;
896  Pix* title_image = nullptr;
897  RenderToBinaryImage(title, strlen(title), 128, &title_image);
898  pixOr(*image, *image, title_image);
899  pixDestroy(&title_image);
900 
901  v_margin_ *= 8;
902  set_font(orig_font);
903  // We return the real offset only after cycling through the list of fonts.
904  return 0;
905  } else {
906  tprintf("Font %s failed with %d hits = %.2f%%\n",
907  all_fonts[i].c_str(), ok_chars, 100.0 * ok_chars / total_chars_);
908  }
909  }
910  font_index_ = 0;
911  char_map_.clear();
912  return last_offset_ == 0 ? -1 : last_offset_;
913 }
914 
915 } // namespace tesseract
static const_iterator begin(const char *utf8_str, const int byte_length)
Definition: unichar.cpp:200
int SpanUTF8NotWhitespace(const char *text)
Definition: normstrngs.cpp:205
void WriteAllBoxes(const string &filename)
static void TranslateBoxes(int xshift, int yshift, std::vector< BoxChar *> *boxes)
Definition: boxchar.cpp:52
bool GetClusterStrings(std::vector< string > *cluster_text)
PangoUnderline underline_style_
std::vector< BoxChar * > boxchars_
#define tlog(level,...)
Definition: tlog.h:33
bool IsInterchangeValid7BitAscii(const char32 ch)
Definition: normstrngs.cpp:240
#define ASSERT_HOST_MSG(x,...)
Definition: errcode.h:90
int DropUncoveredChars(string *utf8_text) const
bool set_font(const string &desc)
static const std::vector< string > & ListAvailableFonts()
static int FontScore(const std::unordered_map< char32, inT64 > &ch_map, const string &fontname, int *raw_score, std::vector< bool > *ch_flags)
Pix * CairoARGB32ToPixFormat(cairo_surface_t *surface)
static const_iterator end(const char *utf8_str, const int byte_length)
Definition: unichar.cpp:204
signed int char32
Definition: normstrngs.h:27
static string ConvertBasicLatinToFullwidthLatin(const string &text)
bool CanRenderString(const char *utf8_word, int len, std::vector< string > *graphemes) const
char32 FullwidthToHalfwidth(const char32 ch)
Definition: normstrngs.cpp:247
const string & ch() const
Definition: boxchar.h:47
StringRenderer(const string &font_desc, int page_width, int page_height)
#define tprintf(...)
Definition: tprintf.h:31
voidpf uLong offset
Definition: ioapi.h:42
int RenderToImage(const char *text, int text_length, Pix **pix)
int RenderAllFontsToImage(double min_coverage, const char *text, int text_length, string *font_used, Pix **pix)
void set_features(const char *features)
#define DISABLE_HEAP_LEAK_CHECK
Definition: util.h:63
static void WriteTesseractBoxFile(const string &name, int height, const std::vector< BoxChar *> &boxes)
Definition: boxchar.cpp:294
const char * utf8_data() const
Definition: unichar.h:130
string AddLigatures(const string &str, const PangoFontInfo *font) const
#define ASSERT_HOST(x)
Definition: errcode.h:84
static bool SelectFont(const char *utf8_word, const int utf8_len, string *font_name, std::vector< string > *graphemes)
Box * mutable_box()
Definition: boxchar.h:58
bool CoversUTF8Text(const char *utf8_text, int byte_length) const
int StripUnrenderableWords(string *utf8_text) const
int FindFirstPageBreakOffset(const char *text, int text_length)
void set_page(int page)
Definition: boxchar.h:55
static LigatureTable * Get()
int RenderToBinaryImage(const char *text, int text_length, int threshold, Pix **pix)
void set_underline_continuation_prob(const double frac)
static string ConvertFullwidthLatinToBasicLatin(const string &text)
void SetWordUnderlineAttributes(const string &page_text)
void set_underline_start_prob(const double frac)
std::unordered_map< char32, inT64 > char_map_
void set_resolution(const int resolution)
void set_resolution(const int resolution)
static string InsertWordJoiners(const string &text)
static string GetTesseractBoxStr(int height, const std::vector< BoxChar *> &boxes)
Definition: boxchar.cpp:301
static void RotateBoxes(float rotation, int xcenter, int ycenter, int start_box, int end_box, std::vector< BoxChar *> *boxes)
Definition: boxchar.cpp:273
const char * filename
Definition: ioapi.h:38
static void PrepareToWrite(std::vector< BoxChar *> *boxes)
Definition: boxchar.cpp:66
int RenderToGrayscaleImage(const char *text, int text_length, Pix **pix)
const int max
bool IsUTF8Whitespace(const char *text)
Definition: normstrngs.cpp:184
const std::vector< BoxChar * > & GetBoxes() const
void AddBox(int x, int y, int width, int height)
Definition: boxchar.cpp:47
bool ParseFontDescriptionName(const string &name)
void RotatePageBoxes(float rotation)
int SpanUTF8Whitespace(const char *text)
Definition: normstrngs.cpp:194
void CorrectBoxPositionsToLayout(std::vector< BoxChar *> *boxchars)
string * mutable_ch()
Definition: boxchar.h:57
cairo_surface_t * surface_