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