10#include "../../stdafx.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"
20#include <CoreFoundation/CoreFoundation.h>
21#include "../../safeguards.h"
25#ifndef HAVE_OSX_109_SDK
27 typedef const struct __CTRunDelegate * CTRunDelegateRef;
29 typedef void (*CTRunDelegateDeallocateCallback) (
void *refCon);
30 typedef CGFloat (*CTRunDelegateGetAscentCallback) (
void *refCon);
31 typedef CGFloat (*CTRunDelegateGetDescentCallback) (
void *refCon);
32 typedef CGFloat (*CTRunDelegateGetWidthCallback) (
void *refCon);
35 CTRunDelegateDeallocateCallback dealloc;
36 CTRunDelegateGetAscentCallback getAscent;
37 CTRunDelegateGetDescentCallback getDescent;
38 CTRunDelegateGetWidthCallback getWidth;
42 kCTRunDelegateVersion1 = 1,
43 kCTRunDelegateCurrentVersion = kCTRunDelegateVersion1
46 extern const CFStringRef kCTRunDelegateAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
48 CTRunDelegateRef CTRunDelegateCreate(
const CTRunDelegateCallbacks *callbacks,
void *refCon) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
75 std::vector<GlyphID> glyphs;
76 std::vector<Position> positions;
77 std::vector<int> glyph_to_char;
79 int total_advance = 0;
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; }
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; }
101 CFArrayRef runs = CTLineGetGlyphRuns(line.get());
102 for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
103 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
106 CFRange chars = CTRunGetStringRange(run);
107 auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font *>::first);
109 this->emplace_back(run, map->second, buff);
115 int CountRuns()
const override {
return this->size(); }
116 const VisualRun &GetVisualRun(
int run)
const override {
return this->at(run); }
118 int GetInternalCharLength(
char32_t c)
const override
121 return c >= 0x010000U ? 2 : 1;
130 void Reflow()
override
132 this->cur_offset = 0;
135 std::unique_ptr<const Line> NextLine(
int max_width)
override;
143 char32_t c = (char32_t)((
size_t)ref_con & 0xFFFFFF);
149 kCTRunDelegateCurrentVersion,
nullptr,
nullptr,
nullptr,
158 ptrdiff_t length = buff_end - buff;
159 if (length == 0)
return nullptr;
162 for (
const auto &[position, font] : font_mapping) {
163 if (font->fc->IsBuiltInFont())
return nullptr;
168 CFAttributedStringBeginEditing(str.get());
171 CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
173 const UniChar replacment_char = 0xFFFC;
179 for (
const auto &[position, font] : font_mapping) {
180 if (position - last == 0)
continue;
182 CTFontRef font_handle =
static_cast<CTFontRef
>(font->fc->GetOSHandle());
183 if (font_handle ==
nullptr) {
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));
189 font_handle =
_font_cache[font->fc->GetSize()].get();
191 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
193 CGColorRef color = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f);
194 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color);
195 CGColorRelease(color);
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) {
202 CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get());
203 CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
209 CFAttributedStringEndEditing(str.get());
214 return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), buff, length, font_mapping) :
nullptr;
217 std::unique_ptr<const ParagraphLayouter::Line> CoreTextParagraphLayout::NextLine(
int max_width)
219 if (this->
cur_offset >= this->length)
return nullptr;
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);
229 if (!line)
return nullptr;
230 return std::make_unique<CoreTextLine>(std::move(line), this->font_map, this->text_buffer);
235 this->glyphs.resize(CTRunGetGlyphCount(run));
238 CFIndex map[this->glyphs.size()];
239 CTRunGetStringIndices(run, CFRangeMake(0, 0), map);
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];
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());
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)) {
260 this->glyphs[i] = gl[i];
261 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
264 this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0),
nullptr,
nullptr,
nullptr));
274 for (
const auto &run : *
this) {
275 leading = std::max(leading, run.GetLeading());
287 if (this->empty())
return 0;
290 for (
const auto &run : *
this) {
291 total_width += run.GetAdvance();
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));
312 CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess,
nullptr);
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()));
334 if (!supported)
return 0;
336 CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
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));
342 if (cf1 ==
nullptr || cf2 ==
nullptr)
return 0;
344 return (
int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags,
_osx_locale.get()) + 2;
358 if (!supported)
return -1;
360 CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
361 if (case_insensitive) flags |= kCFCompareCaseInsensitive;
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));
367 if (cf_str ==
nullptr || cf_value ==
nullptr)
return -1;
369 return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags,
_osx_locale.get(),
nullptr) ? 1 : 0;
375 this->utf16_to_utf8.clear();
376 this->str_info.clear();
381 std::vector<UniChar> utf16_str;
383 for (
auto it = view.begin(), end = view.end(); it != end; ++it) {
384 size_t idx = it.GetByteOffset();
387 utf16_str.push_back((UniChar)c);
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);
394 this->utf16_to_utf8.push_back(idx);
396 this->utf16_to_utf8.push_back(s.size());
399 this->str_info.resize(utf16_to_utf8.size());
401 if (!utf16_str.empty()) {
402 CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
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;
415 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
416 while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer.get())) != kCFStringTokenizerTokenNone) {
418 if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
419 CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer.get());
420 this->str_info[r.location].word_stop =
true;
426 this->str_info.back().char_stop =
true;
427 this->str_info.back().word_stop =
true;
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) {
442 while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
443 this->cur_pos = utf16_pos;
445 return this->utf16_to_utf8[this->cur_pos];
450 assert(this->cur_pos <= this->utf16_to_utf8.size());
453 if (this->cur_pos == this->utf16_to_utf8.size())
return END;
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));
459 return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
464 assert(this->cur_pos <= this->utf16_to_utf8.size());
467 if (this->cur_pos == 0)
return END;
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));
473 return this->utf16_to_utf8[this->cur_pos];
476 std::unique_ptr<StringIterator> OSXStringIterator::Create()
480 return std::make_unique<OSXStringIterator>();
UniChar CharType
Helper for GetLayouter, to get the right type.
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.
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.
Container with information about a font.
FontCache * fc
The font we are using.
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.
Visual run contains data about the bit of text with the same font.
Interface to glue fallback and normal layouter into one.
IterType
Type of the iterator.
@ ITER_WORD
Iterate over words.
@ ITER_CHARACTER
Iterate over characters (or more exactly grapheme clusters).
Constant span of UTF-8 encoded data.
uint GetGlyphWidth(FontSize size, char32_t key)
Get the width of a glyph.
std::vector< std::pair< int, Font * > > FontMap
Mapping from index to font.
FontSize
Available font sizes.
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.
bool MacOSVersionIsAtLeast(long major, long minor, long bugfix)
Check if we are at least running on the specified version of Mac OS.
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.