OpenTTD Source 20250524-master-gc366e6a48e
string_osx.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 "string_osx.h"
12#include "../../string_func.h"
13#include "../../strings_func.h"
14#include "../../core/utf8.hpp"
15#include "../../table/control_codes.h"
16#include "../../fontcache.h"
17#include "../../zoom_func.h"
18#include "macos.h"
19
20#include <CoreFoundation/CoreFoundation.h>
21#include "../../safeguards.h"
22
23
24/* CTRunDelegateCreate is supported since MacOS X 10.5, but was only included in the SDKs starting with the 10.9 SDK. */
25#ifndef HAVE_OSX_109_SDK
26extern "C" {
27 typedef const struct __CTRunDelegate * CTRunDelegateRef;
28
29 typedef void (*CTRunDelegateDeallocateCallback) (void *refCon);
30 typedef CGFloat (*CTRunDelegateGetAscentCallback) (void *refCon);
31 typedef CGFloat (*CTRunDelegateGetDescentCallback) (void *refCon);
32 typedef CGFloat (*CTRunDelegateGetWidthCallback) (void *refCon);
33 typedef struct {
34 CFIndex version;
35 CTRunDelegateDeallocateCallback dealloc;
36 CTRunDelegateGetAscentCallback getAscent;
37 CTRunDelegateGetDescentCallback getDescent;
38 CTRunDelegateGetWidthCallback getWidth;
40
41 enum : int32_t {
42 kCTRunDelegateVersion1 = 1,
43 kCTRunDelegateCurrentVersion = kCTRunDelegateVersion1
44 };
45
46 extern const CFStringRef kCTRunDelegateAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
47
48 CTRunDelegateRef CTRunDelegateCreate(const CTRunDelegateCallbacks *callbacks, void *refCon) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
49}
50#endif /* HAVE_OSX_109_SDK */
51
56
57
62private:
64 ptrdiff_t length;
65 const FontMap &font_map;
66
68
69 CFIndex cur_offset = 0;
70
71public:
74 private:
75 std::vector<GlyphID> glyphs;
76 std::vector<Position> positions;
77 std::vector<int> glyph_to_char;
78
79 int total_advance = 0;
80 Font *font;
81
82 public:
83 CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff);
84 CoreTextVisualRun(CoreTextVisualRun &&other) = default;
85
86 std::span<const GlyphID> GetGlyphs() const override { return this->glyphs; }
87 std::span<const Position> GetPositions() const override { return this->positions; }
88 std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
89
90 const Font *GetFont() const override { return this->font; }
91 int GetLeading() const override { return this->font->fc->GetHeight(); }
92 int GetGlyphCount() const override { return (int)this->glyphs.size(); }
93 int GetAdvance() const { return this->total_advance; }
94 };
95
97 class CoreTextLine : public std::vector<CoreTextVisualRun>, public ParagraphLayouter::Line {
98 public:
100 {
101 CFArrayRef runs = CTLineGetGlyphRuns(line.get());
102 for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
103 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
104
105 /* Extract font information for this run. */
106 CFRange chars = CTRunGetStringRange(run);
107 auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font *>::first);
108
109 this->emplace_back(run, map->second, buff);
110 }
111 }
112
113 int GetLeading() const override;
114 int GetWidth() const override;
115 int CountRuns() const override { return this->size(); }
116 const VisualRun &GetVisualRun(int run) const override { return this->at(run); }
117
118 int GetInternalCharLength(char32_t c) const override
119 {
120 /* CoreText uses UTF-16 internally which means we need to account for surrogate pairs. */
121 return c >= 0x010000U ? 2 : 1;
122 }
123 };
124
125 CoreTextParagraphLayout(CFAutoRelease<CTTypesetterRef> typesetter, const CoreTextParagraphLayoutFactory::CharType *buffer, ptrdiff_t len, const FontMap &font_mapping) : text_buffer(buffer), length(len), font_map(font_mapping), typesetter(std::move(typesetter))
126 {
127 this->Reflow();
128 }
129
130 void Reflow() override
131 {
132 this->cur_offset = 0;
133 }
134
135 std::unique_ptr<const Line> NextLine(int max_width) override;
136};
137
138
140static CGFloat SpriteFontGetWidth(void *ref_con)
141{
142 FontSize fs = (FontSize)((size_t)ref_con >> 24);
143 char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF);
144
145 return GetGlyphWidth(fs, c);
146}
147
148static const CTRunDelegateCallbacks _sprite_font_callback = {
149 kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr,
151};
152
153/* static */ std::unique_ptr<ParagraphLayouter> CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping)
154{
155 if (!MacOSVersionIsAtLeast(10, 5, 0)) return nullptr;
156
157 /* Can't layout an empty string. */
158 ptrdiff_t length = buff_end - buff;
159 if (length == 0) return nullptr;
160
161 /* Can't layout our in-built sprite fonts. */
162 for (const auto &[position, font] : font_mapping) {
163 if (font->fc->IsBuiltInFont()) return nullptr;
164 }
165
166 /* Make attributed string with embedded font information. */
167 CFAutoRelease<CFMutableAttributedStringRef> str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0));
168 CFAttributedStringBeginEditing(str.get());
169
170 CFAutoRelease<CFStringRef> base(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buff, length, kCFAllocatorNull));
171 CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
172
173 const UniChar replacment_char = 0xFFFC;
174 CFAutoRelease<CFStringRef> replacment_str(CFStringCreateWithCharacters(kCFAllocatorDefault, &replacment_char, 1));
175
176 /* Apply font and colour ranges to our string. This is important to make sure
177 * that we get proper glyph boundaries on style changes. */
178 int last = 0;
179 for (const auto &[position, font] : font_mapping) {
180 if (position - last == 0) continue;
181
182 CTFontRef font_handle = static_cast<CTFontRef>(font->fc->GetOSHandle());
183 if (font_handle == nullptr) {
184 if (!_font_cache[font->fc->GetSize()]) {
185 /* Cache font information. */
186 CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8));
187 _font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr));
188 }
189 font_handle = _font_cache[font->fc->GetSize()].get();
190 }
191 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
192
193 CGColorRef color = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
194 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color);
195 CGColorRelease(color);
196
197 /* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */
198 for (ssize_t c = last; c < position; c++) {
199 if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c], false) == 0) {
200 CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (font->fc->GetSize() << 24))));
201 /* According to the official documentation, if a run delegate is used, the char should always be 0xFFFC. */
202 CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get());
203 CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
204 }
205 }
206
207 last = position;
208 }
209 CFAttributedStringEndEditing(str.get());
210
211 /* Create and return typesetter for the string. */
212 CFAutoRelease<CTTypesetterRef> typesetter(CTTypesetterCreateWithAttributedString(str.get()));
213
214 return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), buff, length, font_mapping) : nullptr;
215}
216
217/* virtual */ std::unique_ptr<const ParagraphLayouter::Line> CoreTextParagraphLayout::NextLine(int max_width)
218{
219 if (this->cur_offset >= this->length) return nullptr;
220
221 /* Get line break position, trying word breaking first and breaking somewhere if that doesn't work. */
222 CFIndex len = CTTypesetterSuggestLineBreak(this->typesetter.get(), this->cur_offset, max_width);
223 if (len <= 0) len = CTTypesetterSuggestClusterBreak(this->typesetter.get(), this->cur_offset, max_width);
224
225 /* Create line. */
226 CFAutoRelease<CTLineRef> line(CTTypesetterCreateLine(this->typesetter.get(), CFRangeMake(this->cur_offset, len)));
227 this->cur_offset += len;
228
229 if (!line) return nullptr;
230 return std::make_unique<CoreTextLine>(std::move(line), this->font_map, this->text_buffer);
231}
232
233CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff) : font(font)
234{
235 this->glyphs.resize(CTRunGetGlyphCount(run));
236
237 /* Query map of glyphs to source string index. */
238 CFIndex map[this->glyphs.size()];
239 CTRunGetStringIndices(run, CFRangeMake(0, 0), map);
240
241 this->glyph_to_char.resize(this->glyphs.size());
242 for (size_t i = 0; i < this->glyph_to_char.size(); i++) this->glyph_to_char[i] = (int)map[i];
243
244 CGPoint pts[this->glyphs.size()];
245 CTRunGetPositions(run, CFRangeMake(0, 0), pts);
246 CGSize advs[this->glyphs.size()];
247 CTRunGetAdvances(run, CFRangeMake(0, 0), advs);
248 this->positions.reserve(this->glyphs.size());
249
250 /* Convert glyph array to our data type. At the same time, substitute
251 * the proper glyphs for our private sprite glyphs. */
252 CGGlyph gl[this->glyphs.size()];
253 CTRunGetGlyphs(run, CFRangeMake(0, 0), gl);
254 for (size_t i = 0; i < this->glyphs.size(); i++) {
255 if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END && (gl[i] == 0 || gl[i] == 3)) {
256 /* A glyph of 0 indidicates not found, while apparently 3 is what char 0xFFFC maps to. */
257 this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]);
258 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre
259 } else {
260 this->glyphs[i] = gl[i];
261 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
262 }
263 }
264 this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr));
265}
266
272{
273 int leading = 0;
274 for (const auto &run : *this) {
275 leading = std::max(leading, run.GetLeading());
276 }
277
278 return leading;
279}
280
286{
287 if (this->empty()) return 0;
288
289 int total_width = 0;
290 for (const auto &run : *this) {
291 total_width += run.GetAdvance();
292 }
293
294 return total_width;
295}
296
297
300{
301 _font_cache[size].reset();
302}
303
305void MacOSRegisterExternalFont(std::string_view file_path)
306{
307 if (!MacOSVersionIsAtLeast(10, 6, 0)) return;
308
309 CFAutoRelease<CFStringRef> path(CFStringCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(file_path.data()), file_path.size(), kCFStringEncodingUTF8, false));
310 CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false));
311
312 CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess, nullptr);
313}
314
316void MacOSSetCurrentLocaleName(std::string_view iso_code)
317{
318 if (!MacOSVersionIsAtLeast(10, 5, 0)) return;
319
320 CFAutoRelease<CFStringRef> iso(CFStringCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(iso_code.data()), iso_code.size(), kCFStringEncodingUTF8, false));
321 _osx_locale.reset(CFLocaleCreate(kCFAllocatorDefault, iso.get()));
322}
323
331int MacOSStringCompare(std::string_view s1, std::string_view s2)
332{
333 static const bool supported = MacOSVersionIsAtLeast(10, 5, 0);
334 if (!supported) return 0;
335
336 CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
337
338 CFAutoRelease<CFStringRef> cf1(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)s1.data(), s1.size(), kCFStringEncodingUTF8, false));
339 CFAutoRelease<CFStringRef> cf2(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)s2.data(), s2.size(), kCFStringEncodingUTF8, false));
340
341 /* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
342 if (cf1 == nullptr || cf2 == nullptr) return 0;
343
344 return (int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags, _osx_locale.get()) + 2;
345}
346
355int MacOSStringContains(std::string_view str, std::string_view value, bool case_insensitive)
356{
357 static const bool supported = MacOSVersionIsAtLeast(10, 5, 0);
358 if (!supported) return -1;
359
360 CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
361 if (case_insensitive) flags |= kCFCompareCaseInsensitive;
362
363 CFAutoRelease<CFStringRef> cf_str(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)str.data(), str.size(), kCFStringEncodingUTF8, false));
364 CFAutoRelease<CFStringRef> cf_value(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)value.data(), value.size(), kCFStringEncodingUTF8, false));
365
366 /* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
367 if (cf_str == nullptr || cf_value == nullptr) return -1;
368
369 return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags, _osx_locale.get(), nullptr) ? 1 : 0;
370}
371
372
373/* virtual */ void OSXStringIterator::SetString(std::string_view s)
374{
375 this->utf16_to_utf8.clear();
376 this->str_info.clear();
377 this->cur_pos = 0;
378
379 /* CoreText operates on UTF-16, thus we have to convert the input string.
380 * To be able to return proper offsets, we have to create a mapping at the same time. */
381 std::vector<UniChar> utf16_str;
382 Utf8View view(s);
383 for (auto it = view.begin(), end = view.end(); it != end; ++it) {
384 size_t idx = it.GetByteOffset();
385 char32_t c = *it;
386 if (c < 0x10000) {
387 utf16_str.push_back((UniChar)c);
388 } else {
389 /* Make a surrogate pair. */
390 utf16_str.push_back((UniChar)(0xD800 + ((c - 0x10000) >> 10)));
391 utf16_str.push_back((UniChar)(0xDC00 + ((c - 0x10000) & 0x3FF)));
392 this->utf16_to_utf8.push_back(idx);
393 }
394 this->utf16_to_utf8.push_back(idx);
395 }
396 this->utf16_to_utf8.push_back(s.size());
397
398 /* Query CoreText for word and cluster break information. */
399 this->str_info.resize(utf16_to_utf8.size());
400
401 if (!utf16_str.empty()) {
402 CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
403
404 /* Get cluster breaks. */
405 for (CFIndex i = 0; i < CFStringGetLength(str.get()); ) {
406 CFRange r = CFStringGetRangeOfComposedCharactersAtIndex(str.get(), i);
407 this->str_info[r.location].char_stop = true;
408
409 i += r.length;
410 }
411
412 /* Get word breaks. */
413 CFAutoRelease<CFStringTokenizerRef> tokenizer(CFStringTokenizerCreate(kCFAllocatorDefault, str.get(), CFRangeMake(0, CFStringGetLength(str.get())), kCFStringTokenizerUnitWordBoundary, _osx_locale.get()));
414
415 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
416 while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer.get())) != kCFStringTokenizerTokenNone) {
417 /* Skip tokens that are white-space or punctuation tokens. */
418 if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
419 CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer.get());
420 this->str_info[r.location].word_stop = true;
421 }
422 }
423 }
424
425 /* End-of-string is always a valid stopping point. */
426 this->str_info.back().char_stop = true;
427 this->str_info.back().word_stop = true;
428}
429
430/* virtual */ size_t OSXStringIterator::SetCurPosition(size_t pos)
431{
432 /* Convert incoming position to an UTF-16 string index. */
433 size_t utf16_pos = 0;
434 for (size_t i = 0; i < this->utf16_to_utf8.size(); i++) {
435 if (this->utf16_to_utf8[i] == pos) {
436 utf16_pos = i;
437 break;
438 }
439 }
440
441 /* Sanitize in case we get a position inside a grapheme cluster. */
442 while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
443 this->cur_pos = utf16_pos;
444
445 return this->utf16_to_utf8[this->cur_pos];
446}
447
448/* virtual */ size_t OSXStringIterator::Next(IterType what)
449{
450 assert(this->cur_pos <= this->utf16_to_utf8.size());
452
453 if (this->cur_pos == this->utf16_to_utf8.size()) return END;
454
455 do {
456 this->cur_pos++;
457 } 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));
458
459 return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
460}
461
462/* virtual */ size_t OSXStringIterator::Prev(IterType what)
463{
464 assert(this->cur_pos <= this->utf16_to_utf8.size());
466
467 if (this->cur_pos == 0) return END;
468
469 do {
470 this->cur_pos--;
471 } while (this->cur_pos > 0 && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
472
473 return this->utf16_to_utf8[this->cur_pos];
474}
475
476/* static */ std::unique_ptr<StringIterator> OSXStringIterator::Create()
477{
478 if (!MacOSVersionIsAtLeast(10, 5, 0)) return nullptr;
479
480 return std::make_unique<OSXStringIterator>();
481}
UniChar CharType
Helper for GetLayouter, to get the right type.
Definition string_osx.h:44
static std::unique_ptr< ParagraphLayouter > GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping)
Get the actual ParagraphLayout for the given buffer.
A single line worth of VisualRuns.
int GetWidth() const override
Get the width of this line.
int GetLeading() const override
Get the height of the line.
Visual run contains data about the bit of text with the same font.
Wrapper for doing layouts with CoreText.
CFIndex cur_offset
Offset from the start of the current run from where to output.
int GetHeight() const
Get the height of the font.
Definition fontcache.h:48
virtual GlyphID MapCharToGlyph(char32_t key, bool fallback=true)=0
Map a character into a glyph.
FontSize GetSize() const
Get the FontSize of the font.
Definition fontcache.h:42
Container with information about a font.
Definition gfx_layout.h:97
FontCache * fc
The font we are using.
Definition gfx_layout.h:99
size_t Prev(IterType what) override
Move the cursor back by one iteration unit.
size_t Next(IterType what) override
Advance the cursor by one iteration unit.
size_t SetCurPosition(size_t pos) override
Change the current string cursor.
void SetString(std::string_view s) override
Set a new iteration string.
A single line worth of VisualRuns.
Definition gfx_layout.h:141
Visual run contains data about the bit of text with the same font.
Definition gfx_layout.h:129
Interface to glue fallback and normal layouter into one.
Definition gfx_layout.h:111
IterType
Type of the iterator.
Definition string_base.h:17
@ ITER_WORD
Iterate over words.
Definition string_base.h:19
@ ITER_CHARACTER
Iterate over characters (or more exactly grapheme clusters).
Definition string_base.h:18
Constant span of UTF-8 encoded data.
Definition utf8.hpp:30
uint GetGlyphWidth(FontSize size, char32_t key)
Get the width of a glyph.
Definition fontcache.h:180
std::vector< std::pair< int, Font * > > FontMap
Mapping from index to font.
Definition gfx_layout.h:106
FontSize
Available font sizes.
Definition gfx_type.h:250
Functions related to MacOS support.
std::unique_ptr< typename std::remove_pointer< T >::type, CFDeleter< typename std::remove_pointer< T >::type > > CFAutoRelease
Specialisation of std::unique_ptr for CoreFoundation objects.
Definition macos.h:54
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:25
void MacOSRegisterExternalFont(std::string_view file_path)
Register an external font file with the CoreText system.
int MacOSStringCompare(std::string_view s1, std::string_view s2)
Compares two strings using case insensitive natural sort.
void MacOSSetCurrentLocaleName(std::string_view iso_code)
Store current language locale as a CoreFoundation locale.
static CGFloat SpriteFontGetWidth(void *ref_con)
Get the width of an encoded sprite font character.
int MacOSStringContains(std::string_view str, std::string_view value, bool case_insensitive)
Search if a string is contained in another string using the current locale.
static CFAutoRelease< CFLocaleRef > _osx_locale
Cached current locale.
void MacOSResetScriptCache(FontSize size)
Delete CoreText font reference for a specific font size.
static CFAutoRelease< CTFontRef > _font_cache[FS_END]
CoreText cache for font information, cleared when OTTD changes fonts.
Functions related to localized text support on OSX.
int ScaleSpriteTrad(int value)
Scale traditional pixel dimensions to GUI zoom level, for drawing sprites.
Definition zoom_func.h:107