OpenTTD
string_osx.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "../../stdafx.h"
13 #include "string_osx.h"
14 #include "../../string_func.h"
15 #include "../../strings_func.h"
16 #include "../../table/control_codes.h"
17 #include "../../fontcache.h"
18 #include "macos.h"
19 
20 #include <CoreFoundation/CoreFoundation.h>
21 
22 
23 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
24 
25 static CFLocaleRef _osx_locale = NULL;
27 static CTFontRef _font_cache[FS_END];
28 
29 
34 private:
36  ptrdiff_t length;
37  const FontMap& font_map;
38 
39  CTTypesetterRef typesetter;
40 
41  CFIndex cur_offset = 0;
42 
43 public:
46  private:
47  std::vector<GlyphID> glyphs;
48  std::vector<float> positions;
49  std::vector<int> glyph_to_char;
50 
51  int total_advance = 0;
52  Font *font;
53 
54  public:
55  CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff);
56 
57  virtual const GlyphID *GetGlyphs() const { return &this->glyphs[0]; }
58  virtual const float *GetPositions() const { return &this->positions[0]; }
59  virtual const int *GetGlyphToCharMap() const { return &this->glyph_to_char[0]; }
60 
61  virtual const Font *GetFont() const { return this->font; }
62  virtual int GetLeading() const { return this->font->fc->GetHeight(); }
63  virtual int GetGlyphCount() const { return (int)this->glyphs.size(); }
64  int GetAdvance() const { return this->total_advance; }
65  };
66 
68  class CoreTextLine : public AutoDeleteSmallVector<CoreTextVisualRun *, 4>, public ParagraphLayouter::Line {
69  public:
70  CoreTextLine(CTLineRef line, const FontMap &fontMapping, const CoreTextParagraphLayoutFactory::CharType *buff)
71  {
72  CFArrayRef runs = CTLineGetGlyphRuns(line);
73  for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
74  CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
75 
76  /* Extract font information for this run. */
77  CFRange chars = CTRunGetStringRange(run);
78  FontMap::const_iterator map = fontMapping.Begin();
79  while (map < fontMapping.End() - 1 && map->first <= chars.location) map++;
80 
81  *this->Append() = new CoreTextVisualRun(run, map->second, buff);
82  }
83  CFRelease(line);
84  }
85 
86  virtual int GetLeading() const;
87  virtual int GetWidth() const;
88  virtual int CountRuns() const { return this->Length(); }
89  virtual const VisualRun *GetVisualRun(int run) const { return *this->Get(run); }
90 
91  int GetInternalCharLength(WChar c) const
92  {
93  /* CoreText uses UTF-16 internally which means we need to account for surrogate pairs. */
94  return c >= 0x010000U ? 2 : 1;
95  }
96  };
97 
98  CoreTextParagraphLayout(CTTypesetterRef typesetter, const CoreTextParagraphLayoutFactory::CharType *buffer, ptrdiff_t len, const FontMap &fontMapping) : text_buffer(buffer), length(len), font_map(fontMapping), typesetter(typesetter)
99  {
100  this->Reflow();
101  }
102 
103  virtual ~CoreTextParagraphLayout()
104  {
105  CFRelease(this->typesetter);
106  }
107 
108  virtual void Reflow()
109  {
110  this->cur_offset = 0;
111  }
112 
113  virtual const Line *NextLine(int max_width);
114 };
115 
116 
118 static CGFloat SpriteFontGetWidth(void *ref_con)
119 {
120  FontSize fs = (FontSize)((size_t)ref_con >> 24);
121  WChar c = (WChar)((size_t)ref_con & 0xFFFFFF);
122 
123  return GetGlyphWidth(fs, c);
124 }
125 
126 static CTRunDelegateCallbacks _sprite_font_callback = {
127  kCTRunDelegateCurrentVersion, NULL, NULL, NULL,
129 };
130 
132 {
133  if (!MacOSVersionIsAtLeast(10, 5, 0)) return NULL;
134 
135  /* Can't layout an empty string. */
136  ptrdiff_t length = buff_end - buff;
137  if (length == 0) return NULL;
138 
139  /* Can't layout our in-built sprite fonts. */
140  for (FontMap::const_iterator i = fontMapping.Begin(); i != fontMapping.End(); i++) {
141  if (i->second->fc->IsBuiltInFont()) return NULL;
142  }
143 
144  /* Make attributed string with embedded font information. */
145  CFMutableAttributedStringRef str = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
146  CFAttributedStringBeginEditing(str);
147 
148  CFStringRef base = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buff, length, kCFAllocatorNull);
149  CFAttributedStringReplaceString(str, CFRangeMake(0, 0), base);
150  CFRelease(base);
151 
152  /* Apply font and colour ranges to our string. This is important to make sure
153  * that we get proper glyph boundaries on style changes. */
154  int last = 0;
155  for (FontMap::const_iterator i = fontMapping.Begin(); i != fontMapping.End(); i++) {
156  if (i->first - last == 0) continue;
157 
158  if (_font_cache[i->second->fc->GetSize()] == NULL) {
159  /* Cache font information. */
160  CFStringRef font_name = CFStringCreateWithCString(kCFAllocatorDefault, i->second->fc->GetFontName(), kCFStringEncodingUTF8);
161  _font_cache[i->second->fc->GetSize()] = CTFontCreateWithName(font_name, i->second->fc->GetFontSize(), NULL);
162  CFRelease(font_name);
163  }
164  CFAttributedStringSetAttribute(str, CFRangeMake(last, i->first - last), kCTFontAttributeName, _font_cache[i->second->fc->GetSize()]);
165 
166  CGColorRef color = CGColorCreateGenericGray((uint8)i->second->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
167  CFAttributedStringSetAttribute(str, CFRangeMake(last, i->first - last), kCTForegroundColorAttributeName, color);
168  CGColorRelease(color);
169 
170  /* Install a size callback for our special sprite glyphs. */
171  for (ssize_t c = last; c < i->first; c++) {
172  if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END) {
173  CTRunDelegateRef del = CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (i->second->fc->GetSize() << 24)));
174  CFAttributedStringSetAttribute(str, CFRangeMake(c, 1), kCTRunDelegateAttributeName, del);
175  CFRelease(del);
176  }
177  }
178 
179  last = i->first;
180  }
181  CFAttributedStringEndEditing(str);
182 
183  /* Create and return typesetter for the string. */
184  CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(str);
185  CFRelease(str);
186 
187  return typesetter != NULL ? new CoreTextParagraphLayout(typesetter, buff, length, fontMapping) : NULL;
188 }
189 
190 /* virtual */ const CoreTextParagraphLayout::Line *CoreTextParagraphLayout::NextLine(int max_width)
191 {
192  if (this->cur_offset >= this->length) return NULL;
193 
194  /* Get line break position, trying word breaking first and breaking somewhere if that doesn't work. */
195  CFIndex len = CTTypesetterSuggestLineBreak(this->typesetter, this->cur_offset, max_width);
196  if (len <= 0) len = CTTypesetterSuggestClusterBreak(this->typesetter, this->cur_offset, max_width);
197 
198  /* Create line. */
199  CTLineRef line = CTTypesetterCreateLine(this->typesetter, CFRangeMake(this->cur_offset, len));
200  this->cur_offset += len;
201 
202  return line != NULL ? new CoreTextLine(line, this->font_map, this->text_buffer) : NULL;
203 }
204 
205 CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff) : font(font)
206 {
207  this->glyphs.resize(CTRunGetGlyphCount(run));
208 
209  /* Query map of glyphs to source string index. */
210  CFIndex map[this->glyphs.size()];
211  CTRunGetStringIndices(run, CFRangeMake(0, 0), map);
212 
213  this->glyph_to_char.resize(this->glyphs.size());
214  for (size_t i = 0; i < this->glyph_to_char.size(); i++) this->glyph_to_char[i] = (int)map[i];
215 
216  CGPoint pts[this->glyphs.size()];
217  CTRunGetPositions(run, CFRangeMake(0, 0), pts);
218  this->positions.resize(this->glyphs.size() * 2 + 2);
219 
220  /* Convert glyph array to our data type. At the same time, substitute
221  * the proper glyphs for our private sprite glyphs. */
222  CGGlyph gl[this->glyphs.size()];
223  CTRunGetGlyphs(run, CFRangeMake(0, 0), gl);
224  for (size_t i = 0; i < this->glyphs.size(); i++) {
225  if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END) {
226  this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]);
227  this->positions[i * 2 + 0] = pts[i].x;
228  this->positions[i * 2 + 1] = font->fc->GetAscender() - font->fc->GetGlyph(this->glyphs[i])->height - 1; // Align sprite glyphs to font baseline.
229  } else {
230  this->glyphs[i] = gl[i];
231  this->positions[i * 2 + 0] = pts[i].x;
232  this->positions[i * 2 + 1] = pts[i].y;
233  }
234  }
235  this->total_advance = (int)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
236  this->positions[this->glyphs.size() * 2] = this->positions[0] + this->total_advance;
237 }
238 
244 {
245  int leading = 0;
246  for (const CoreTextVisualRun * const *run = this->Begin(); run != this->End(); run++) {
247  leading = max(leading, (*run)->GetLeading());
248  }
249 
250  return leading;
251 }
252 
258 {
259  if (this->Length() == 0) return 0;
260 
261  int total_width = 0;
262  for (const CoreTextVisualRun * const *run = this->Begin(); run != this->End(); run++) {
263  total_width += (*run)->GetAdvance();
264  }
265 
266  return total_width;
267 }
268 
269 
272 {
273  if (_font_cache[size] != NULL) {
274  CFRelease(_font_cache[size]);
275  _font_cache[size] = NULL;
276  }
277 }
278 
280 void MacOSSetCurrentLocaleName(const char *iso_code)
281 {
282  if (!MacOSVersionIsAtLeast(10, 5, 0)) return;
283 
284  if (_osx_locale != NULL) CFRelease(_osx_locale);
285 
286  CFStringRef iso = CFStringCreateWithCString(kCFAllocatorNull, iso_code, kCFStringEncodingUTF8);
287  _osx_locale = CFLocaleCreate(kCFAllocatorDefault, iso);
288  CFRelease(iso);
289 }
290 
298 int MacOSStringCompare(const char *s1, const char *s2)
299 {
300  static bool supported = MacOSVersionIsAtLeast(10, 5, 0);
301  if (!supported) return 0;
302 
303  CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
304 
305  CFStringRef cf1 = CFStringCreateWithCString(kCFAllocatorDefault, s1, kCFStringEncodingUTF8);
306  CFStringRef cf2 = CFStringCreateWithCString(kCFAllocatorDefault, s2, kCFStringEncodingUTF8);
307 
308  /* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
309  if (cf1 == nullptr || cf2 == nullptr) {
310  if (cf1 != nullptr) CFRelease(cf1);
311  if (cf2 != nullptr) CFRelease(cf2);
312  return 0;
313  }
314 
315  CFComparisonResult res = CFStringCompareWithOptionsAndLocale(cf1, cf2, CFRangeMake(0, CFStringGetLength(cf1)), flags, _osx_locale);
316 
317  CFRelease(cf1);
318  CFRelease(cf2);
319 
320  return (int)res + 2;
321 }
322 
323 
324 /* virtual */ void OSXStringIterator::SetString(const char *s)
325 {
326  const char *string_base = s;
327 
328  this->utf16_to_utf8.clear();
329  this->str_info.clear();
330  this->cur_pos = 0;
331 
332  /* CoreText operates on UTF-16, thus we have to convert the input string.
333  * To be able to return proper offsets, we have to create a mapping at the same time. */
334  std::vector<UniChar> utf16_str;
335  while (*s != '\0') {
336  size_t idx = s - string_base;
337 
338  WChar c = Utf8Consume(&s);
339  if (c < 0x10000) {
340  utf16_str.push_back((UniChar)c);
341  } else {
342  /* Make a surrogate pair. */
343  utf16_str.push_back((UniChar)(0xD800 + ((c - 0x10000) >> 10)));
344  utf16_str.push_back((UniChar)(0xDC00 + ((c - 0x10000) & 0x3FF)));
345  this->utf16_to_utf8.push_back(idx);
346  }
347  this->utf16_to_utf8.push_back(idx);
348  }
349  this->utf16_to_utf8.push_back(s - string_base);
350 
351  /* Query CoreText for word and cluster break information. */
352  this->str_info.resize(utf16_to_utf8.size());
353 
354  if (utf16_str.size() > 0) {
355  CFStringRef str = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull);
356 
357  /* Get cluster breaks. */
358  for (CFIndex i = 0; i < CFStringGetLength(str); ) {
359  CFRange r = CFStringGetRangeOfComposedCharactersAtIndex(str, i);
360  this->str_info[r.location].char_stop = true;
361 
362  i += r.length;
363  }
364 
365  /* Get word breaks. */
366  CFStringTokenizerRef tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, str, CFRangeMake(0, CFStringGetLength(str)), kCFStringTokenizerUnitWordBoundary, _osx_locale);
367 
368  CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
369  while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)) != kCFStringTokenizerTokenNone) {
370  /* Skip tokens that are white-space or punctuation tokens. */
371  if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
372  CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer);
373  this->str_info[r.location].word_stop = true;
374  }
375  }
376 
377  CFRelease(tokenizer);
378  CFRelease(str);
379  }
380 
381  /* End-of-string is always a valid stopping point. */
382  this->str_info.back().char_stop = true;
383  this->str_info.back().word_stop = true;
384 }
385 
386 /* virtual */ size_t OSXStringIterator::SetCurPosition(size_t pos)
387 {
388  /* Convert incoming position to an UTF-16 string index. */
389  size_t utf16_pos = 0;
390  for (size_t i = 0; i < this->utf16_to_utf8.size(); i++) {
391  if (this->utf16_to_utf8[i] == pos) {
392  utf16_pos = i;
393  break;
394  }
395  }
396 
397  /* Sanitize in case we get a position inside a grapheme cluster. */
398  while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
399  this->cur_pos = utf16_pos;
400 
401  return this->utf16_to_utf8[this->cur_pos];
402 }
403 
404 /* virtual */ size_t OSXStringIterator::Next(IterType what)
405 {
406  assert(this->cur_pos <= this->utf16_to_utf8.size());
408 
409  if (this->cur_pos == this->utf16_to_utf8.size()) return END;
410 
411  do {
412  this->cur_pos++;
413  } while (this->cur_pos < this->utf16_to_utf8.size() && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
414 
415  return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
416 }
417 
418 /* virtual */ size_t OSXStringIterator::Prev(IterType what)
419 {
420  assert(this->cur_pos <= this->utf16_to_utf8.size());
422 
423  if (this->cur_pos == 0) return END;
424 
425  do {
426  this->cur_pos--;
427  } while (this->cur_pos > 0 && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
428 
429  return this->utf16_to_utf8[this->cur_pos];
430 }
431 
432 /* static */ StringIterator *OSXStringIterator::Create()
433 {
434  if (!MacOSVersionIsAtLeast(10, 5, 0)) return NULL;
435 
436  return new OSXStringIterator();
437 }
438 
439 #else
440 void MacOSResetScriptCache(FontSize size) {}
441 void MacOSSetCurrentLocaleName(const char *iso_code) {}
442 
443 int MacOSStringCompare(const char *s1, const char *s2)
444 {
445  return 0;
446 }
447 
448 /* static */ StringIterator *OSXStringIterator::Create()
449 {
450  return NULL;
451 }
452 
453 /* static */ ParagraphLayouter *CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping)
454 {
455  return NULL;
456 }
457 #endif /* (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) */
Wrapper for doing layouts with CoreText.
Definition: string_osx.cpp:33
int MacOSStringCompare(const char *s1, const char *s2)
Compares two strings using case insensitive natural sort.
Definition: string_osx.cpp:298
Implementation of simple mapping class.
String iterator using CoreText as a backend.
Definition: string_osx.h:20
static bool MacOSVersionIsAtLeast(long major, long minor, long bugfix)
Check if we are at least running on the specified version of Mac OS.
Definition: macos.h:27
const T * Begin() const
Get the pointer to the first item (const)
Visual run contains data about the bit of text with the same font.
Definition: gfx_layout.h:122
void MacOSResetScriptCache(FontSize size)
Delete CoreText font reference for a specific font size.
Definition: string_osx.cpp:271
static T max(const T a, const T b)
Returns the maximum of two values.
Definition: math_func.hpp:26
const T * End() const
Get the pointer behind the last valid item (const)
static uint GetGlyphWidth(FontSize size, WChar key)
Get the width of a glyph.
Definition: fontcache.h:194
virtual int GetLeading() const
Get the height of the line.
Definition: string_osx.cpp:243
Iterate over characters (or more exactly grapheme clusters).
Definition: string_base.h:20
Visual run contains data about the bit of text with the same font.
Definition: string_osx.cpp:45
A single line worth of VisualRuns.
Definition: gfx_layout.h:134
Simple pair of data.
virtual void SetString(const char *s)
Set a new iteration string.
Definition: string_osx.cpp:324
static CTFontRef _font_cache[FS_END]
CoreText cache for font information, cleared when OTTD changes fonts.
Definition: string_osx.cpp:27
A single line worth of VisualRuns.
Definition: string_osx.cpp:68
Interface to glue fallback and normal layouter into one.
Definition: gfx_layout.h:117
Simple vector template class, with automatic delete.
CFIndex cur_offset
Offset from the start of the current run from where to output.
Definition: string_osx.cpp:41
IterType
Type of the iterator.
Definition: string_base.h:19
Functions related to localized text support on OSX.
virtual size_t Next(IterType what)
Advance the cursor by one iteration unit.
Definition: string_osx.cpp:404
Iterate over words.
Definition: string_base.h:21
static CFLocaleRef _osx_locale
Cached current locale.
Definition: string_osx.cpp:25
virtual int GetWidth() const
Get the width of this line.
Definition: string_osx.cpp:257
FontSize
Available font sizes.
Definition: gfx_type.h:203
Functions related to MacOS support.
virtual size_t SetCurPosition(size_t pos)
Change the current string cursor.
Definition: string_osx.cpp:386
Class for iterating over different kind of parts of a string.
Definition: string_base.h:16
static ParagraphLayouter * GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping)
Get the actual ParagraphLayout for the given buffer.
Definition: string_osx.cpp:131
UniChar CharType
Helper for GetLayouter, to get the right type.
Definition: string_osx.h:47
static CGFloat SpriteFontGetWidth(void *ref_con)
Get the width of an encoded sprite font character.
Definition: string_osx.cpp:118
uint32 GlyphID
Glyphs are characters from a font.
Definition: fontcache.h:19
virtual size_t Prev(IterType what)
Move the cursor back by one iteration unit.
Definition: string_osx.cpp:418
uint32 WChar
Type for wide characters, i.e.
Definition: string_type.h:35
void MacOSSetCurrentLocaleName(const char *iso_code)
Store current language locale as a CoreFounation locale.
Definition: string_osx.cpp:280