OpenTTD Source  14.0-beta3
gfx_layout_icu.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "stdafx.h"
11 #include "gfx_layout_icu.h"
12 
13 #include "debug.h"
14 #include "strings_func.h"
15 #include "language.h"
16 #include "table/control_codes.h"
17 #include "zoom_func.h"
18 
19 #include "3rdparty/icu/scriptrun.h"
20 
21 #include <unicode/ubidi.h>
22 #include <unicode/brkiter.h>
23 
24 #include <hb.h>
25 #include <hb-ft.h>
26 
27 #include "safeguards.h"
28 
30 constexpr float FONT_SCALE = 64.0;
31 
37 class ICURun {
38 public:
39  int start;
40  int length;
41  UBiDiLevel level;
42  UScriptCode script;
44 
45  std::vector<GlyphID> glyphs;
46  std::vector<int> advance;
47  std::vector<int> glyph_to_char;
48  std::vector<Point> positions;
49  int total_advance = 0;
50 
51  ICURun(int start, int length, UBiDiLevel level, UScriptCode script = USCRIPT_UNKNOWN, Font *font = nullptr) : start(start), length(length), level(level), script(script), font(font) {}
52 
53  void Shape(UChar *buff, size_t length);
54 };
55 
60 public:
63  private:
64  std::vector<GlyphID> glyphs;
65  std::vector<Point> positions;
66  std::vector<int> glyph_to_char;
67 
68  int total_advance;
69  const Font *font;
70 
71  public:
72  ICUVisualRun(const ICURun &run, int x);
73 
74  const std::vector<GlyphID> &GetGlyphs() const override { return this->glyphs; }
75  const std::vector<Point> &GetPositions() const override { return this->positions; }
76  const std::vector<int> &GetGlyphToCharMap() const override { return this->glyph_to_char; }
77 
78  const Font *GetFont() const override { return this->font; }
79  int GetLeading() const override { return this->font->fc->GetHeight(); }
80  int GetGlyphCount() const override { return this->glyphs.size(); }
81  int GetAdvance() const { return this->total_advance; }
82  };
83 
85  class ICULine : public std::vector<ICUVisualRun>, public ParagraphLayouter::Line {
86  public:
87  int GetLeading() const override;
88  int GetWidth() const override;
89  int CountRuns() const override { return (uint)this->size(); }
90  const VisualRun &GetVisualRun(int run) const override { return this->at(run); }
91 
92  int GetInternalCharLength(char32_t c) const override
93  {
94  /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */
95  return c >= 0x010000U ? 2 : 1;
96  }
97  };
98 
99 private:
100  std::vector<ICURun> runs;
101  UChar *buff;
102  size_t buff_length;
103  std::vector<ICURun>::iterator current_run;
104  int partial_offset;
105 
106 public:
107  ICUParagraphLayout(std::vector<ICURun> &runs, UChar *buff, size_t buff_length) : runs(runs), buff(buff), buff_length(buff_length)
108  {
109  this->Reflow();
110  }
111 
112  ~ICUParagraphLayout() override { }
113 
114  void Reflow() override
115  {
116  this->current_run = this->runs.begin();
117  this->partial_offset = 0;
118  }
119 
120  std::unique_ptr<const Line> NextLine(int max_width) override;
121 };
122 
132  glyphs(run.glyphs), glyph_to_char(run.glyph_to_char), total_advance(run.total_advance), font(run.font)
133 {
134  /* If there are no positions, the ICURun was not Shaped; that should never happen. */
135  assert(!run.positions.empty());
136  this->positions.reserve(run.positions.size());
137 
138  /* Copy positions, moving x coordinate by x offset. */
139  for (const Point &pt : run.positions) {
140  this->positions.emplace_back(pt.x + x, pt.y);
141  }
142 }
143 
150 void ICURun::Shape(UChar *buff, size_t buff_length)
151 {
152  auto hbfont = hb_ft_font_create_referenced(*(static_cast<const FT_Face *>(font->fc->GetOSHandle())));
153  /* Match the flags with how we render the glyphs. */
154  hb_ft_font_set_load_flags(hbfont, GetFontAAState(this->font->fc->GetSize()) ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
155 
156  /* ICU buffer is in UTF-16. */
157  auto hbbuf = hb_buffer_create();
158  hb_buffer_add_utf16(hbbuf, reinterpret_cast<uint16_t *>(buff), buff_length, this->start, this->length);
159 
160  /* Set all the properties of this segment. */
161  hb_buffer_set_direction(hbbuf, (this->level & 1) == 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
162  hb_buffer_set_script(hbbuf, hb_script_from_string(uscript_getShortName(this->script), -1));
163  hb_buffer_set_language(hbbuf, hb_language_from_string(_current_language->isocode, -1));
164  hb_buffer_set_cluster_level(hbbuf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES);
165 
166  /* Shape the segment. */
167  hb_shape(hbfont, hbbuf, nullptr, 0);
168 
169  unsigned int glyph_count;
170  auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count);
171  auto glyph_pos = hb_buffer_get_glyph_positions(hbbuf, &glyph_count);
172 
173  /* Make sure any former run is lost. */
174  this->glyphs.clear();
175  this->glyph_to_char.clear();
176  this->positions.clear();
177  this->advance.clear();
178 
179  /* Reserve space, as we already know the size. */
180  this->glyphs.reserve(glyph_count);
181  this->glyph_to_char.reserve(glyph_count);
182  this->positions.reserve(glyph_count + 1);
183  this->advance.reserve(glyph_count);
184 
185  /* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */
186  hb_position_t advance = 0;
187  for (unsigned int i = 0; i < glyph_count; i++) {
188  int x_advance;
189 
190  if (buff[glyph_info[i].cluster] >= SCC_SPRITE_START && buff[glyph_info[i].cluster] <= SCC_SPRITE_END && glyph_info[i].codepoint == 0) {
191  auto glyph = this->font->fc->MapCharToGlyph(buff[glyph_info[i].cluster]);
192 
193  this->glyphs.push_back(glyph);
194  this->positions.emplace_back(advance, (this->font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->font->fc->GetSize()))) / 2); // Align sprite font to centre
195  x_advance = this->font->fc->GetGlyphWidth(glyph);
196  } else {
197  this->glyphs.push_back(glyph_info[i].codepoint);
198  this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].y_offset / FONT_SCALE);
199  x_advance = glyph_pos[i].x_advance / FONT_SCALE;
200  }
201 
202  this->glyph_to_char.push_back(glyph_info[i].cluster);
203  this->advance.push_back(x_advance);
204  advance += x_advance;
205  }
206 
207  /* End-of-run position. */
208  this->positions.emplace_back(advance, 0);
209 
210  /* Track the total advancement we made. */
211  this->total_advance = advance;
212 
213  hb_buffer_destroy(hbbuf);
214  hb_font_destroy(hbfont);
215 }
216 
222 {
223  int leading = 0;
224  for (const auto &run : *this) {
225  leading = std::max(leading, run.GetLeading());
226  }
227 
228  return leading;
229 }
230 
236 {
237  int length = 0;
238  for (const auto &run : *this) {
239  length += run.GetAdvance();
240  }
241 
242  return length;
243 }
244 
254 std::vector<ICURun> ItemizeBidi(UChar *buff, size_t length)
255 {
256  auto ubidi = ubidi_open();
257 
258  auto parLevel = _current_text_dir == TD_RTL ? UBIDI_RTL : UBIDI_LTR;
259 
260  UErrorCode err = U_ZERO_ERROR;
261  ubidi_setPara(ubidi, buff, length, parLevel, nullptr, &err);
262  if (U_FAILURE(err)) {
263  Debug(fontcache, 0, "Failed to set paragraph: %s", u_errorName(err));
264  ubidi_close(ubidi);
265  return std::vector<ICURun>();
266  }
267 
268  int32_t count = ubidi_countRuns(ubidi, &err);
269  if (U_FAILURE(err)) {
270  Debug(fontcache, 0, "Failed to count runs: %s", u_errorName(err));
271  ubidi_close(ubidi);
272  return std::vector<ICURun>();
273  }
274 
275  std::vector<ICURun> runs;
276  runs.reserve(count);
277 
278  /* Find the breakpoints for the logical runs. So we get runs that say "from START to END". */
279  int32_t logical_pos = 0;
280  while (static_cast<size_t>(logical_pos) < length) {
281  auto start_pos = logical_pos;
282 
283  /* Fetch the embedding level, so we can order bidi correctly later on. */
284  UBiDiLevel level;
285  ubidi_getLogicalRun(ubidi, start_pos, &logical_pos, &level);
286 
287  runs.emplace_back(ICURun(start_pos, logical_pos - start_pos, level));
288  }
289 
290  assert(static_cast<size_t>(count) == runs.size());
291 
292  ubidi_close(ubidi);
293  return runs;
294 }
295 
306 std::vector<ICURun> ItemizeScript(UChar *buff, size_t length, std::vector<ICURun> &runs_current)
307 {
308  std::vector<ICURun> runs;
309  icu::ScriptRun script_itemizer(buff, length);
310 
311  int cur_pos = 0;
312  auto cur_run = runs_current.begin();
313  while (true) {
314  while (cur_pos < script_itemizer.getScriptEnd() && cur_run != runs_current.end()) {
315  int stop_pos = std::min(script_itemizer.getScriptEnd(), cur_run->start + cur_run->length);
316  assert(stop_pos - cur_pos > 0);
317 
318  runs.push_back(ICURun(cur_pos, stop_pos - cur_pos, cur_run->level, script_itemizer.getScriptCode()));
319 
320  if (stop_pos == cur_run->start + cur_run->length) cur_run++;
321  cur_pos = stop_pos;
322  }
323 
324  if (!script_itemizer.next()) break;
325  }
326 
327  return runs;
328 }
329 
339 std::vector<ICURun> ItemizeStyle(std::vector<ICURun> &runs_current, FontMap &font_mapping)
340 {
341  std::vector<ICURun> runs;
342 
343  int cur_pos = 0;
344  auto cur_run = runs_current.begin();
345  for (auto const &font_map : font_mapping) {
346  while (cur_pos < font_map.first && cur_run != runs_current.end()) {
347  int stop_pos = std::min(font_map.first, cur_run->start + cur_run->length);
348  assert(stop_pos - cur_pos > 0);
349 
350  runs.push_back(ICURun(cur_pos, stop_pos - cur_pos, cur_run->level, cur_run->script, font_map.second));
351 
352  if (stop_pos == cur_run->start + cur_run->length) cur_run++;
353  cur_pos = stop_pos;
354  }
355  }
356 
357  return runs;
358 }
359 
360 /* static */ ParagraphLayouter *ICUParagraphLayoutFactory::GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &font_mapping)
361 {
362  size_t length = buff_end - buff;
363  /* Can't layout an empty string. */
364  if (length == 0) return nullptr;
365 
366  /* Can't layout our in-built sprite fonts. */
367  for (auto const &pair : font_mapping) {
368  if (pair.second->fc->IsBuiltInFont()) return nullptr;
369  }
370 
371  auto runs = ItemizeBidi(buff, length);
372  runs = ItemizeScript(buff, length, runs);
373  runs = ItemizeStyle(runs, font_mapping);
374 
375  if (runs.empty()) return nullptr;
376 
377  for (auto &run : runs) {
378  run.Shape(buff, length);
379  }
380 
381  return new ICUParagraphLayout(runs, buff, length);
382 }
383 
384 /* static */ std::unique_ptr<icu::BreakIterator> ICUParagraphLayoutFactory::break_iterator;
385 
390 {
391  auto locale = icu::Locale(_current_language->isocode);
392  UErrorCode status = U_ZERO_ERROR;
393  ICUParagraphLayoutFactory::break_iterator.reset(icu::BreakIterator::createLineInstance(locale, status));
394  assert(U_SUCCESS(status));
395 }
396 
401 /* static */ std::unique_ptr<icu::BreakIterator> ICUParagraphLayoutFactory::GetBreakIterator()
402 {
403  assert(ICUParagraphLayoutFactory::break_iterator != nullptr);
404 
405  return std::unique_ptr<icu::BreakIterator>(ICUParagraphLayoutFactory::break_iterator->clone());
406 }
407 
408 std::unique_ptr<const ICUParagraphLayout::Line> ICUParagraphLayout::NextLine(int max_width)
409 {
410  std::vector<ICURun>::iterator start_run = this->current_run;
411  std::vector<ICURun>::iterator last_run = this->current_run;
412 
413  if (start_run == this->runs.end()) return nullptr;
414 
415  int cur_width = 0;
416 
417  /* Add remaining width of the first run if it is a broken run. */
418  if (this->partial_offset > 0) {
419  if ((start_run->level & 1) == 0) {
420  for (size_t i = this->partial_offset; i < start_run->advance.size(); i++) {
421  cur_width += start_run->advance[i];
422  }
423  } else {
424  for (int i = 0; i < this->partial_offset; i++) {
425  cur_width += start_run->advance[i];
426  }
427  }
428  last_run++;
429  }
430 
431  /* Gather runs until the line is full. */
432  while (last_run != this->runs.end() && cur_width < max_width) {
433  cur_width += last_run->total_advance;
434  last_run++;
435  }
436 
437  /* If the text does not fit into the available width, find a suitable breaking point. */
438  int new_partial_length = 0;
439  if (cur_width > max_width) {
440  /* Create a break-iterator to find a good place to break lines. */
441  auto break_iterator = ICUParagraphLayoutFactory::GetBreakIterator();
442  break_iterator->setText(icu::UnicodeString(this->buff, this->buff_length));
443 
444  auto overflow_run = last_run - 1;
445 
446  /* Find the last glyph that fits. */
447  size_t index;
448  if ((overflow_run->level & 1) == 0) {
449  /* LTR */
450  for (index = overflow_run->glyphs.size(); index > 0; index--) {
451  cur_width -= overflow_run->advance[index - 1];
452  if (cur_width <= max_width) break;
453  }
454  index--;
455  } else {
456  /* RTL */
457  for (index = 0; index < overflow_run->glyphs.size(); index++) {
458  cur_width -= overflow_run->advance[index];
459  if (cur_width <= max_width) break;
460  }
461  }
462 
463  /* Find the character that matches; this is the start of the cluster. */
464  auto char_pos = overflow_run->glyph_to_char[index];
465 
466  /* See if there is a good breakpoint inside this run. */
467  int32_t break_pos = break_iterator->preceding(char_pos + 1);
468  if (break_pos != icu::BreakIterator::DONE && break_pos > overflow_run->start + this->partial_offset) {
469  /* There is a line-break inside this run that is suitable. */
470  new_partial_length = break_pos - overflow_run->start - this->partial_offset;
471  } else if (overflow_run != start_run) {
472  /* There is no suitable line-break in this run, but it is also not
473  * the only run on this line. So we remove the run. */
474  last_run--;
475  } else {
476  /* There is no suitable line-break and this is the only run on the
477  * line. So we break at the cluster. This is not pretty, but the
478  * best we can do. */
479  new_partial_length = char_pos - overflow_run->start - this->partial_offset;
480  }
481  }
482 
483  /* Reorder the runs on this line for display. */
484  std::vector<UBiDiLevel> bidi_level;
485  for (auto run = start_run; run != last_run; run++) {
486  bidi_level.push_back(run->level);
487  }
488  std::vector<int32_t> vis_to_log(bidi_level.size());
489  ubidi_reorderVisual(bidi_level.data(), bidi_level.size(), vis_to_log.data());
490 
491  /* Create line. */
492  std::unique_ptr<ICULine> line(new ICULine());
493 
494  int cur_pos = 0;
495  for (auto &i : vis_to_log) {
496  auto i_run = start_run + i;
497  /* Copy the ICURun here, so we can modify it in case of a partial. */
498  ICURun run = *i_run;
499 
500  if (i_run == last_run - 1 && new_partial_length > 0) {
501  if (i_run == start_run && this->partial_offset > 0) {
502  assert(run.length > this->partial_offset);
503  run.start += this->partial_offset;
504  run.length -= this->partial_offset;
505  }
506 
507  assert(run.length > new_partial_length);
508  run.length = new_partial_length;
509 
510  run.Shape(this->buff, this->buff_length);
511  } else if (i_run == start_run && this->partial_offset > 0) {
512  assert(run.length > this->partial_offset);
513 
514  run.start += this->partial_offset;
515  run.length -= this->partial_offset;
516 
517  run.Shape(this->buff, this->buff_length);
518  }
519 
520  auto total_advance = run.total_advance;
521  line->emplace_back(std::move(run), cur_pos);
522  cur_pos += total_advance;
523  }
524 
525  if (new_partial_length > 0) {
526  this->current_run = last_run - 1;
527  this->partial_offset += new_partial_length;
528  } else {
529  this->current_run = last_run;
530  this->partial_offset = 0;
531  }
532 
533  return line;
534 }
535 
536 /* static */ size_t ICUParagraphLayoutFactory::AppendToBuffer(UChar *buff, const UChar *buffer_last, char32_t c)
537 {
538  assert(buff < buffer_last);
539  /* Transform from UTF-32 to internal ICU format of UTF-16. */
540  int32_t length = 0;
541  UErrorCode err = U_ZERO_ERROR;
542  u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
543  return length;
544 }
ICURun::level
UBiDiLevel level
Embedding level of the run.
Definition: gfx_layout_icu.cpp:41
ICURun::script
UScriptCode script
Script of the run.
Definition: gfx_layout_icu.cpp:42
Font::fc
FontCache * fc
The font we are using.
Definition: gfx_layout.h:77
ICURun
Helper class to store the information of all the runs of a paragraph in.
Definition: gfx_layout_icu.cpp:37
FontCache::GetHeight
int GetHeight() const
Get the height of the font.
Definition: fontcache.h:49
ICURun::start
int start
Start of the run in the buffer.
Definition: gfx_layout_icu.cpp:39
zoom_func.h
gfx_layout_icu.h
FONT_SCALE
constexpr float FONT_SCALE
HarfBuzz FreeType integration sets the font scaling, which is always in 1/64th of a pixel.
Definition: gfx_layout_icu.cpp:30
ICUParagraphLayout::ICULine::GetWidth
int GetWidth() const override
Get the width of this line.
Definition: gfx_layout_icu.cpp:235
ICUParagraphLayout::ICUVisualRun
Visual run contains data about the bit of text with the same font.
Definition: gfx_layout_icu.cpp:62
Debug
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
ParagraphLayouter
Interface to glue fallback and normal layouter into one.
Definition: gfx_layout.h:89
control_codes.h
ParagraphLayouter::Line
A single line worth of VisualRuns.
Definition: gfx_layout.h:106
ICURun::positions
std::vector< Point > positions
The positions of the glyphs. Valid after Shape() is called.
Definition: gfx_layout_icu.cpp:48
ICURun::total_advance
int total_advance
The total advance of the run. Valid after Shape() is called.
Definition: gfx_layout_icu.cpp:49
_current_language
const LanguageMetadata * _current_language
The currently loaded language.
Definition: strings.cpp:54
safeguards.h
ICURun::glyph_to_char
std::vector< int > glyph_to_char
The mapping from glyphs to characters. Valid after Shape() is called.
Definition: gfx_layout_icu.cpp:47
Point
Coordinates of a point in 2D.
Definition: geometry_type.hpp:21
language.h
stdafx.h
ICUParagraphLayoutFactory::GetBreakIterator
static std::unique_ptr< icu::BreakIterator > GetBreakIterator()
Get a thread-safe line break iterator.
Definition: gfx_layout_icu.cpp:401
LanguagePackHeader::isocode
char isocode[16]
the ISO code for the language (not country code)
Definition: language.h:31
Font
Container with information about a font.
Definition: gfx_layout.h:75
ParagraphLayouter::VisualRun
Visual run contains data about the bit of text with the same font.
Definition: gfx_layout.h:94
strings_func.h
ICUParagraphLayoutFactory::InitializeLayouter
static void InitializeLayouter()
Initialize data needed for the ICU layouter.
Definition: gfx_layout_icu.cpp:389
ScaleSpriteTrad
int ScaleSpriteTrad(int value)
Scale traditional pixel dimensions to GUI zoom level, for drawing sprites.
Definition: zoom_func.h:107
ICUParagraphLayout::ICUVisualRun::ICUVisualRun
ICUVisualRun(const ICURun &run, int x)
Constructor for a new ICUVisualRun.
Definition: gfx_layout_icu.cpp:131
ICUParagraphLayout
Wrapper for doing layouts with ICU.
Definition: gfx_layout_icu.cpp:59
ItemizeBidi
std::vector< ICURun > ItemizeBidi(UChar *buff, size_t length)
Itemize the string into runs per embedding level.
Definition: gfx_layout_icu.cpp:254
ICUParagraphLayout::ICULine::GetLeading
int GetLeading() const override
Get the height of the line.
Definition: gfx_layout_icu.cpp:221
ICURun::glyphs
std::vector< GlyphID > glyphs
The glyphs of the run. Valid after Shape() is called.
Definition: gfx_layout_icu.cpp:45
ICURun::font
Font * font
Font of the run.
Definition: gfx_layout_icu.cpp:43
FontMap
std::map< int, Font * > FontMap
Mapping from index to font.
Definition: gfx_layout.h:84
ICUParagraphLayout::ICULine
A single line worth of VisualRuns.
Definition: gfx_layout_icu.cpp:85
TD_RTL
@ TD_RTL
Text is written right-to-left by default.
Definition: strings_type.h:24
_current_text_dir
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition: strings.cpp:56
ICURun::Shape
void Shape(UChar *buff, size_t length)
Shape a single run.
Definition: gfx_layout_icu.cpp:150
ItemizeStyle
std::vector< ICURun > ItemizeStyle(std::vector< ICURun > &runs_current, FontMap &font_mapping)
Itemize the string into runs per style, based on the previous created runs.
Definition: gfx_layout_icu.cpp:339
ItemizeScript
std::vector< ICURun > ItemizeScript(UChar *buff, size_t length, std::vector< ICURun > &runs_current)
Itemize the string into runs per script, based on the previous created runs.
Definition: gfx_layout_icu.cpp:306
debug.h
ICURun::length
int length
Length of the run in the buffer.
Definition: gfx_layout_icu.cpp:40
ICURun::advance
std::vector< int > advance
The advance (width) of the glyphs. Valid after Shape() is called.
Definition: gfx_layout_icu.cpp:46