Ion
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
freetypefontutils.cc
Go to the documentation of this file.
1 
19 
20 #include <algorithm>
21 #include <cctype>
22 #include <locale>
23 #include <mutex> // NOLINT(build/c++11): only using std::call_once, not mutex.
24 #include <vector>
25 
26 #include "ion/base/invalid.h"
28 #include "ion/base/stringutils.h"
29 #include "ion/base/utf8iterator.h"
30 #include "ion/math/range.h"
31 #include "ion/math/rangeutils.h"
32 #include "ion/port/environment.h"
33 #include "ion/port/fileutils.h"
35 #include "ion/text/freetypefont.h"
36 
37 #if defined(ION_USE_ICU)
38 #include "third_party/icu/icu4c/source/common/unicode/udata.h"
39 #include "third_party/icu/icu4c/source/common/unicode/uloc.h"
40 #include "third_party/icu/icu4c/source/common/unicode/unistr.h"
41 #include "third_party/iculx_hb/include/layout/ParagraphLayout.h"
42 #endif // ION_USE_ICU
43 
44 namespace ion {
45 namespace text {
46 
47 using math::Point2f;
48 using math::Point2i;
49 using math::Point3f;
50 using math::Range2f;
51 using math::Vector2f;
52 
54 
59 
60 
62 static bool IsSpace(CharIndex c) {
64  return c <= 255U && std::isspace(static_cast<char>(c));
65 }
66 
72 static float ComputeLineWidth(const FreeTypeFont& font,
73  const std::string& line) {
78  float x_min = 0.0f;
79  float x_max = 0.0f;
80  base::Utf8Iterator it(line);
81  CharIndex prev_c = 0;
82  CharIndex c;
83  while ((c = it.Next()) != base::Utf8Iterator::kInvalidCharIndex) {
84  const GlyphIndex g = font.GetDefaultGlyphForChar(c);
85  const FreeTypeFont::GlyphMetrics& glyph_metrics = font.GetGlyphMetrics(g);
86  if (base::IsInvalidReference(glyph_metrics)) {
88  x_min = x_max;
89  } else {
90  if (prev_c) {
91  const Vector2f kerning = font.GetKerning(prev_c, c);
92  x_min += kerning[0];
93  }
94  x_max = x_min + glyph_metrics.bitmap_offset[0] + glyph_metrics.size[0];
95  x_min += glyph_metrics.advance[0];
96  }
97  prev_c = c;
98  }
99  return it.GetState() == base::Utf8Iterator::kEndOfString ? x_max : 0.f;
100 }
101 
103  const LayoutOptions& options,
104  const Lines& lines) {
105  const Font::FontMetrics& font_metrics = font.GetFontMetrics();
106 
107  TextSize text_size;
108  text_size.line_height_in_pixels = font_metrics.line_advance_height;
109 
110  const size_t num_lines = lines.size();
111  DCHECK(num_lines);
112 
117  float first_line_above_baseline = 0.f;
118  CharIndex c;
119  base::Utf8Iterator fit(lines.front());
120  while ((c = fit.Next()) != base::Utf8Iterator::kInvalidCharIndex) {
121  const GlyphIndex g = font.GetDefaultGlyphForChar(c);
122  const FreeTypeFont::GlyphMetrics& metrics = font.GetGlyphMetrics(g);
123  if (!base::IsInvalidReference(metrics)) {
124  first_line_above_baseline = std::max(
125  first_line_above_baseline, metrics.bitmap_offset[1]);
126  }
127  }
128  text_size.first_line_above_baseline = first_line_above_baseline;
129 
132  float last_line_below_baseline = 0.f;
133  base::Utf8Iterator bit(lines.back());
134  while ((c = bit.Next()) != base::Utf8Iterator::kInvalidCharIndex) {
135  const GlyphIndex g = font.GetDefaultGlyphForChar(c);
136  const FreeTypeFont::GlyphMetrics& metrics = font.GetGlyphMetrics(g);
137  if (!base::IsInvalidReference(metrics)) {
138  last_line_below_baseline = std::max(
139  last_line_below_baseline, metrics.size[1] - metrics.bitmap_offset[1]);
140  }
141  }
142 
147  const float spacing =
148  options.line_spacing * static_cast<float>(num_lines - 1U);
149  text_size.text_height_in_pixels =
150  first_line_above_baseline + last_line_below_baseline +
151  font_metrics.line_advance_height * spacing;
152 
154  const float height =
155  (1.0f + spacing) * static_cast<float>(font.GetSizeInPixels());
156 
159  float width = 0.0f;
160  text_size.line_widths_in_pixels.resize(num_lines);
161 
162  for (size_t i = 0; i < num_lines; ++i) {
163  const std::string& line = lines[i];
164  const float line_width = ComputeLineWidth(font, line);
165  text_size.line_widths_in_pixels[i] = line_width;
166  width = std::max(width, line_width);
167  }
168  text_size.rect_size_in_pixels.Set(width, height);
169 
170  return text_size;
171 }
172 
176 static float ComputeVerticalAlignmentTranslation(
177  const LayoutOptions& options,
178  const TextSize& text_size, float scale) {
181  float offset_in_pixels; // Positive value means push text down.
182  switch (options.vertical_alignment) {
183  case kAlignTop:
184  offset_in_pixels = text_size.first_line_above_baseline;
185  break;
186  case kAlignVCenter:
187  offset_in_pixels = text_size.first_line_above_baseline -
188  0.5f * text_size.text_height_in_pixels;
189  break;
190  case kAlignBaseline:
192  offset_in_pixels = 0.0f;
193  break;
194  case kAlignBottom:
195  offset_in_pixels =
196  text_size.first_line_above_baseline - text_size.text_height_in_pixels;
197  break;
198  default:
199  LOG(ERROR) << "Invalid vertical alignment";
200  offset_in_pixels = 0.0f;
201  }
202  return options.target_point[1] - scale * offset_in_pixels;
203 }
204 
207 static float ComputeHorizontalAlignmentTranslation(
208  const LayoutOptions& options, float line_width_in_pixels,
209  float rect_width_in_pixels, float scale) {
211  float offset_in_pixels;
212  switch (options.horizontal_alignment) {
213  case kAlignLeft:
214  offset_in_pixels = 0.0f;
215  break;
216  case kAlignHCenter:
217  offset_in_pixels = 0.5f * line_width_in_pixels;
218  break;
219  case kAlignRight:
220  offset_in_pixels = line_width_in_pixels;
221  break;
222  default:
223  LOG(ERROR) << "Invalid horizontal alignment";
224  offset_in_pixels = 0.0f;
225  }
226  return options.target_point[0] - scale * offset_in_pixels;
227 }
228 
237  const Font& font, const LayoutOptions& options, const TextSize& text_size) {
238  const Vector2f& target_size = options.target_size;
239  const Vector2f& rect_size = text_size.rect_size_in_pixels;
240  FreeTypeFontTransformData transform_data;
241 
244  if (target_size[0] == 0.0f) {
245  DCHECK_GT(target_size[1], 0.0f);
246  const float s = target_size[1] / rect_size[1];
247  transform_data.scale.Set(s, s);
248  } else if (target_size[1] == 0.0f) {
249  DCHECK_GT(target_size[0], 0.0f);
250  const float s = target_size[0] / rect_size[0];
251  transform_data.scale.Set(s, s);
252  } else {
253  transform_data.scale.Set(target_size[0] / rect_size[0],
254  target_size[1] / rect_size[1]);
255  }
256 
259  const float y_translation = ComputeVerticalAlignmentTranslation(
260  options, text_size, transform_data.scale[1]);
261  const size_t num_lines = text_size.line_widths_in_pixels.size();
262  transform_data.line_translations.resize(num_lines);
263  for (size_t i = 0; i < num_lines; ++i) {
264  const float x_translation = ComputeHorizontalAlignmentTranslation(
265  options, text_size.line_widths_in_pixels[i],
266  rect_size[0], transform_data.scale[0]);
267  transform_data.line_translations[i].Set(x_translation, y_translation);
268  }
269 
271  transform_data.line_y_offset_in_pixels =
272  -options.line_spacing * text_size.line_height_in_pixels;
273 
274  return transform_data;
275 }
276 
278 static const Layout::Quad BuildXyQuad(const Range2f& rect) {
279  const Point2f& min = rect.GetMinPoint();
280  const Point2f& max = rect.GetMaxPoint();
281  return Layout::Quad(Point3f(min[0], min[1], 0.0f),
282  Point3f(max[0], min[1], 0.0f),
283  Point3f(max[0], max[1], 0.0f),
284  Point3f(min[0], max[1], 0.0f));
285 }
286 
290 static void AddGlyphToLayout(GlyphIndex glyph_index, size_t line_index,
291  const Point2f& glyph_min,
292  const FreeTypeFont::GlyphMetrics& glyph_metrics,
293  const FreeTypeFontTransformData& transform_data,
294  size_t sdf_padding, Layout* layout) {
295  const Vector2f& glyph_size = glyph_metrics.size;
296 
297  Range2f quad_rect = Range2f::BuildWithSize(
298  Point2f(glyph_min[0] * transform_data.scale[0],
299  glyph_min[1] * transform_data.scale[1]) +
300  transform_data.line_translations[line_index],
301  Vector2f(glyph_size[0] * transform_data.scale[0],
302  glyph_size[1] * transform_data.scale[1]));
303  const Range2f tight_bounds(quad_rect);
304 
306  if (sdf_padding && (glyph_size[0] * glyph_size[1] != 0.f)) {
307  const float padding = static_cast<float>(2 * sdf_padding);
308  const Vector2f scale((glyph_size[0] + padding) / glyph_size[0],
309  (glyph_size[1] + padding) / glyph_size[1]);
310  quad_rect = math::ScaleRangeNonUniformly(quad_rect, scale);
311  }
312  const Vector2f offset(
313  glyph_metrics.bitmap_offset[0] * transform_data.scale[0],
316  (glyph_metrics.bitmap_offset[1] - glyph_metrics.size[1])
317  * transform_data.scale[1]);
318 
319  CHECK(layout->AddGlyph(
320  Layout::Glyph(glyph_index, BuildXyQuad(quad_rect),
321  tight_bounds, offset)));
322 }
323 
324 #if defined(ION_USE_ICU)
325 
326 std::once_flag icu_initialize_once_flag;
327 
330 static bool CheckIcuStatus(UErrorCode status) {
331  if (U_FAILURE(status)) {
332  LOG(ERROR) << "ICU library error: " << u_errorName(status);
333  return false;
334  } else {
335  return true;
336  }
337 }
338 
339 static void TryInitializeIcu(bool* success) {
340  *success = false;
341 
350  const std::string icu_data_directory =
351 #if defined(ION_PLATFORM_ANDROID)
352  "/system/usr/icu/";
353 #elif defined(ION_PLATFORM_MAC)
354  "/usr/share/icu/";
355 #else
356  port::GetEnvironmentVariableValue("ION_ICU_DIR");
357 #endif
358 
359  std::string icu_data;
360  std::vector<std::string> files = port::ListDirectory(icu_data_directory);
361  for (auto it = files.begin(); icu_data.empty() && it != files.end(); ++it) {
362  if (base::StartsWith(*it, "icudt") && base::EndsWith(*it, ".dat"))
363  icu_data = icu_data_directory + *it;
364  }
365  if (icu_data.empty()) {
366  LOG(ERROR) << "Unable to find ICU data file in: " << icu_data_directory;
367  return;
368  }
369  port::MemoryMappedFile icu_mmap(icu_data);
370  if (!icu_mmap.GetData() || !icu_mmap.GetLength())
371  return;
372 
373  UErrorCode error = U_ZERO_ERROR;
374  udata_setAppData(icu_data.c_str(), icu_mmap.GetData(), &error);
375  CHECK(CheckIcuStatus(error));
376 
377  *success = true;
378 }
379 
380 static bool InitializeIcu() {
381  static bool icu_initialized = false;
382  std::call_once(icu_initialize_once_flag,
383  std::bind(TryInitializeIcu, &icu_initialized));
384  return icu_initialized;
385 }
386 
389 static void GetGlyphFromRun(const iculx::ParagraphLayout::VisualRun& run,
390  int which_glyph_in_run, int32* glyph_index,
391  float* glyph_x, float* glyph_y) {
392  *glyph_index = run.getGlyphs()[which_glyph_in_run];
393  *glyph_x = run.getPositions()[which_glyph_in_run * 2];
394  *glyph_y = -run.getPositions()[which_glyph_in_run * 2 + 1];
395 }
396 
399 static float IcuLayoutEngineLayoutLine(
400  const FreeTypeFont& font,
401  const std::string& text,
402  size_t line_index,
403  const FreeTypeFontTransformData& transform_data,
404  Layout* layout) {
405  if (!InitializeIcu()) {
406  return 0.0f;
407  }
408 
410  icu::UnicodeString chars = icu::UnicodeString::fromUTF8(text);
411  if (chars.isEmpty()) {
412  DLOG(ERROR) << "Empty text for layout, or corrupt utf8? [" << text << "]";
413  return 0.0f;
414  }
415 
417  iculx::FontRuns runs(0);
418  font.GetFontRunsForText(chars, &runs);
419  LEErrorCode status = LE_NO_ERROR;
420  std::unique_ptr<iculx::ParagraphLayout> icu_layout(new iculx::ParagraphLayout(
421  chars.getBuffer(), chars.length(), &runs, NULL, NULL, NULL,
422  UBIDI_DEFAULT_LTR, false /* is_vertical */, status));
423  if (status != LE_NO_ERROR) {
424  DLOG(ERROR) << "new ParagraphLayout error: " << status;
425  return 0.0f;
426  }
427 
430  icu_layout->reflow();
431  std::unique_ptr<iculx::ParagraphLayout::Line> line(icu_layout->nextLine(0));
432  if (!line.get()) {
433  return 0.0f;
434  }
435 
436  enum { kImpossibleGlyphIndex = -1 };
437  int32 glyph_id = kImpossibleGlyphIndex;
438  float glyph_x = -1;
439  float glyph_y = -1;
440 
441  if (layout != NULL) { // Caller wants all the glyph descriptors
442  layout->Reserve(chars.length());
443  for (int i = 0; i < line->countRuns(); ++i) {
444  const iculx::ParagraphLayout::VisualRun *run = line->getVisualRun(i);
445  const icu::LEFontInstance* run_font = run->getFont();
446  for (int j = 0; j < run->getGlyphCount(); ++j) {
447  GetGlyphFromRun(*run, j, &glyph_id, &glyph_x, &glyph_y);
448  if (glyph_id == 0 || glyph_id >= 0xffff)
449  continue;
450  GlyphIndex glyph_index = font.GlyphIndexForICUFont(run_font, glyph_id);
451  const FreeTypeFont::GlyphMetrics& metrics =
452  font.GetGlyphMetrics(glyph_index);
453  if (base::IsInvalidReference(metrics))
454  continue;
455  glyph_x += metrics.bitmap_offset[0];
456  glyph_y += transform_data.line_y_offset_in_pixels *
457  static_cast<float>(line_index) +
458  (metrics.bitmap_offset[1] - metrics.size[1]);
459  AddGlyphToLayout(glyph_index, line_index, Point2f(glyph_x, glyph_y),
460  metrics, transform_data,
461  font.GetSdfPadding(), layout);
462  }
463  }
464  } else {
466  for (int i = line->countRuns() - 1;
467  i >= 0 && glyph_id == kImpossibleGlyphIndex; --i) {
468  const iculx::ParagraphLayout::VisualRun *run = line->getVisualRun(i);
469  for (int j = run->getGlyphCount() - 1; j >= 0; --j) {
470  if (run->getGlyphs()[j] < 0xffff) {
471  GetGlyphFromRun(*run, j, &glyph_id, &glyph_x, &glyph_y);
472  break;
473  }
474  }
475  }
476  }
477 
478  if (glyph_id == kImpossibleGlyphIndex) {
479  return 0.0f;
480  }
481 
483  LEPoint advance_p;
484  runs.getFont(runs.getCount() - 1)->getGlyphAdvance(glyph_id, advance_p);
485  float final_advance = advance_p.fX;
486  float final_position = glyph_x;
487  return final_advance + final_position;
488 }
489 
495 static bool IsInFastUnicodeRange(const std::string& text) {
498  static const CharIndex g_fast_unicode_ranges[] = {
499  0x0020, 0x007f, // Common punctuation, digits, LATIN
500  0x00a0, 0x02b0, // LATIN
501  0x0370, 0x0483, // GREEK, COPTIC, CYRILLIC
502  0x048a, 0x0524, // CYRILLIC
503  0x3041, 0x3097, // HIRAGANA
504  0x30a0, 0x3100, // KATAKANA
505  0x31f0, 0x3200, // KATAKANA LETTER SMALL
506  0x3400, 0x4db5, // CJK Ideograph Extension A
507  0x4e00, 0x9fc4, // CJK Ideographs
508  };
509 
510  const CharIndex* begin = g_fast_unicode_ranges;
511  const CharIndex* end = begin + arraysize(g_fast_unicode_ranges);
512  base::Utf8Iterator it(text);
513  CharIndex c;
514  while ((c = it.Next()) != base::Utf8Iterator::kInvalidCharIndex) {
515  const CharIndex* search = std::upper_bound(begin, end, c);
519  if (((search - begin) & 1) == 0) return false;
520  }
521  return true;
522 }
523 
524 #else
525 static bool IsInFastUnicodeRange(const std::string& text) { return true; }
529 static float IcuLayoutEngineLayoutLine(
530  const FreeTypeFont& font,
531  const std::string& text,
532  size_t line_index,
533  const FreeTypeFontTransformData& transform_data,
534  Layout* layout) {
535  return 0.0f;
536 }
537 
538 #endif // ION_USE_ICU
539 
541 static void SimpleLayOutLine(
542  const FreeTypeFont& font,
543  const std::string& line,
544  size_t line_index,
545  const FreeTypeFontTransformData& transform_data,
546  Layout* layout) {
547  float x_min = 0.0f;
548  base::Utf8Iterator it(line);
549  CharIndex prev_c = 0;
550  CharIndex c;
551  while ((c = it.Next()) != base::Utf8Iterator::kInvalidCharIndex) {
552  const GlyphIndex g = font.GetDefaultGlyphForChar(c);
553  const FreeTypeFont::GlyphMetrics& glyph_metrics = font.GetGlyphMetrics(g);
554  if (base::IsInvalidReference(glyph_metrics)) {
556  } else if (IsSpace(c)) {
557  x_min += glyph_metrics.advance[0];
558  } else {
559  float y_min =
560  transform_data.line_y_offset_in_pixels *
561  static_cast<float>(line_index) +
562  (glyph_metrics.bitmap_offset[1] - glyph_metrics.size[1]);
563  if (prev_c) {
564  const Vector2f kerning = font.GetKerning(prev_c, c);
565  x_min += kerning[0];
566  y_min += kerning[1];
567  }
568  Point2f glyph_min(x_min + glyph_metrics.bitmap_offset[0], y_min);
569  AddGlyphToLayout(g, line_index, glyph_min, glyph_metrics, transform_data,
570  font.GetSdfPadding(), layout);
571  x_min += glyph_metrics.advance[0];
572  }
573  prev_c = c;
574  }
575 }
576 
578 const Layout LayOutText(const FreeTypeFont& font, bool use_icu,
579  const Lines& lines,
580  const FreeTypeFontTransformData& transform_data) {
581  const size_t num_lines = lines.size();
582  Layout layout;
583  layout.SetLineAdvanceHeight(transform_data.scale[1] *
584  -transform_data.line_y_offset_in_pixels);
585  for (size_t i = 0; i < num_lines; ++i) {
586  if (use_icu && !IsInFastUnicodeRange(lines[i])) {
587  IcuLayoutEngineLayoutLine(
588  font, lines[i], i, transform_data, &layout);
589  } else {
590  SimpleLayOutLine(font, lines[i], i, transform_data, &layout);
591  }
592  }
593  return layout;
594 }
595 
596 } // namespace text
597 } // namespace ion
static const uint32 kInvalidCharIndex
An invalid Unicode character index.
Definition: utf8iterator.h:59
bool IsInvalidReference(const T &value)
IsInvalidReference() returns true if a passed const reference of type T has an address of InvalidRefe...
Definition: invalid.h:41
void SetLineAdvanceHeight(float line_advance)
Sets the vertical distance between successive baselines.
Definition: layout.cc:56
const std::string GetEnvironmentVariableValue(const std::string &name)
Returns the value of the named environment variable.
Definition: environment.cc:29
std::vector< std::string > Lines
Lines of text from a single string (usually split on ' ').
std::vector< float > line_widths_in_pixels
Width of each line of text in pixels.
bool StartsWith(const std::string &target, const std::string &start)
Returns whether target begins with start.
Definition: stringutils.h:76
const FreeTypeFontTransformData ComputeTransformData(const Font &font, const LayoutOptions &options, const TextSize &text_size)
Sets the scale and translation fields of the LayoutData instance with the scale and translation requi...
math::Vector2f size
Width and height of the glyph, in pixels.
Definition: freetypefont.h:49
math::Vector2f rect_size_in_pixels
Size of the entire text rectangle in pixels.
#define CHECK(expr)
Definition: logging.h:323
This derived Font class represents a FreeType2 font.
Definition: freetypefont.h:35
GlyphIndex GetDefaultGlyphForChar(CharIndex char_index) const override
Font overrides.
const Layout LayOutText(const FreeTypeFont &font, bool use_icu, const Lines &lines, const FreeTypeFontTransformData &transform_data)
Returns a Layout populated by glyphs representing lines of text.
std::vector< std::string > ListDirectory(const std::string &path)
Returns the contents of path, non-recursively.
Definition: fileutils.cc:219
std::string text
#define DLOG(severity)
Same as LOG(severity), but only logs in debug mode.
Definition: logging.h:230
#define DCHECK(expr)
Definition: logging.h:331
float line_advance_height
Nominal font-wide line-advance height, in pixels.
Definition: font.h:71
#define LOG(severity)
Logs the streamed message unconditionally with a severity of severity.
Definition: logging.h:216
#define DCHECK_GT(val1, val2)
Definition: logging.h:337
std::vector< math::Vector2f > line_translations
Translation to apply to position glyphs for each line of text.
uint32 offset
Range< 2, float > Range2f
Definition: range.h:373
The Utf8Iterator class iterates over characters in strings encoded with UTF-8, extracting the Unicode...
Definition: utf8iterator.h:49
uint32 CharIndex
Typedef for a Unicode index of a character.
Definition: font.h:35
size_t GetSizeInPixels() const
Returns the size of the font in pixels.
Definition: font.h:78
This struct defines parameters affecting layout of a single text string when passed to BuildLayout()...
Definition: layout.h:101
float text_height_in_pixels
Height of the text inside the rectangle in pixels.
float first_line_above_baseline
Max height above baseline of the first line of text (depends on contents!).
const FontMetrics & GetFontMetrics() const
Returns the FontMetrics for the font.
Definition: font.h:87
math::Vector2f scale
Scale to apply to resize glyphs.
float line_height_in_pixels
Height of a single line of text in pixels.
const TextSize ComputeTextSize(const FreeTypeFont &font, const LayoutOptions &options, const Lines &lines)
Computes the size of text and returns it as a TextSize instance.
math::Vector2f target_size
Target width and height of the text rectangle. (Default: 0 in x, 1 in y)
Definition: layout.h:112
A Layout instance specifies how glyphs are arranged to form text.
Definition: layout.h:127
int width
float line_y_offset_in_pixels
How much to translate each successive line in y, in pixels.
bool EndsWith(const std::string &target, const std::string &end)
Returns whether target ends with end.
Definition: stringutils.h:81
TextSize contains information about the size of multi-line text.
math::Vector2f bitmap_offset
Distance in X and Y from the baseline to the top left pixel of the glyph bitmap, in pixels...
Definition: freetypefont.h:54
float line_spacing
Spacing between baselines of lines of multi-line text, expressed as a fraction of the font's FontMetr...
Definition: layout.h:119
Vector2d scale
Definition: coretextfont.mm:63
uint64 GlyphIndex
Definition: layout.h:46
This struct represents the metrics for a single glyph.
Definition: freetypefont.h:40
This contains the values needed to transform glyph rectangles into the correct coordinates.
const Range< Dimension, T > ScaleRangeNonUniformly(const Range< Dimension, T > &r, const Vector< Dimension, T > scale_factors)
Returns a Range that is the input Range scaled nonuniformly about its center by the given per-dimensi...
Definition: rangeutils.h:123
This struct represents the cumulative metrics for the font.
Definition: font.h:66
const GlyphMetrics & GetGlyphMetrics(GlyphIndex glyph_index) const
Returns the GlyphMetrics for a glyph.
Font is a base class for implementation-specific representations of fonts.
Definition: font.h:43