10#include "../../stdafx.h"
12#include "../../gfx_func.h"
13#include "../../string_func.h"
14#include "../../strings_func.h"
15#include "../../core/utf8.hpp"
16#include "../../table/control_codes.h"
17#include "../../fontcache.h"
18#include "../../zoom_func.h"
21#include <CoreFoundation/CoreFoundation.h>
22#include "../../safeguards.h"
26#ifndef HAVE_OSX_109_SDK
28 typedef const struct __CTRunDelegate * CTRunDelegateRef;
30 typedef void (*CTRunDelegateDeallocateCallback) (
void *refCon);
31 typedef CGFloat (*CTRunDelegateGetAscentCallback) (
void *refCon);
32 typedef CGFloat (*CTRunDelegateGetDescentCallback) (
void *refCon);
33 typedef CGFloat (*CTRunDelegateGetWidthCallback) (
void *refCon);
36 CTRunDelegateDeallocateCallback dealloc;
37 CTRunDelegateGetAscentCallback getAscent;
38 CTRunDelegateGetDescentCallback getDescent;
39 CTRunDelegateGetWidthCallback getWidth;
43 kCTRunDelegateVersion1 = 1,
44 kCTRunDelegateCurrentVersion = kCTRunDelegateVersion1
47 extern const CFStringRef kCTRunDelegateAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
49 CTRunDelegateRef CTRunDelegateCreate(
const CTRunDelegateCallbacks *callbacks,
void *refCon) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
56static std::unordered_map<FontIndex, CFAutoRelease<CTFontRef>>
_font_cache;
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; }
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 const auto &map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font>::first);
109 this->emplace_back(run, map->second);
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;
142 FontIndex fi =
static_cast<FontIndex
>(
reinterpret_cast<uintptr_t
>(ref_con) >> 24);
143 char32_t c =
static_cast<char32_t>(
reinterpret_cast<uintptr_t
>(ref_con) & 0xFFFFFF);
149 kCTRunDelegateCurrentVersion,
nullptr,
nullptr,
nullptr,
158 ptrdiff_t length = buff_end - buff;
159 if (length == 0)
return nullptr;
163 CFAttributedStringBeginEditing(str.get());
166 CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
168 const UniChar replacement_char = 0xFFFC;
174 for (
const auto &[position, font] : font_mapping) {
175 if (position - last == 0)
continue;
178 CTFontRef font_handle =
static_cast<CTFontRef
>(fc.
GetOSHandle());
179 if (font_handle ==
nullptr) {
187 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
189 CGColorRef colour = CGColorCreateGenericGray((uint8_t)font.colour / 255.0f, 1.0f);
190 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, colour);
191 CGColorRelease(colour);
195 for (ssize_t c = last; c < position; c++) {
196 CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback,
reinterpret_cast<void *
>(
static_cast<size_t>(buff[c] | (fc.GetIndex() << 24)))));
198 CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacement_str.get());
199 CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
205 CFAttributedStringEndEditing(str.get());
210 return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), length, font_mapping) :
nullptr;
213 std::unique_ptr<const ParagraphLayouter::Line> CoreTextParagraphLayout::NextLine(
int max_width)
215 if (this->
cur_offset >= this->length)
return nullptr;
218 CFIndex len = CTTypesetterSuggestLineBreak(this->typesetter.get(), this->cur_offset, max_width);
219 if (len <= 0) len = CTTypesetterSuggestClusterBreak(this->typesetter.get(), this->cur_offset, max_width);
225 if (!line)
return nullptr;
226 return std::make_unique<CoreTextLine>(std::move(line), this->font_map);
229CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run,
const Font &font) : font(font)
231 this->glyphs.resize(CTRunGetGlyphCount(run));
234 auto map = std::make_unique<CFIndex[]>(this->glyphs.size());
235 CTRunGetStringIndices(run, CFRangeMake(0, 0), map.get());
237 this->glyph_to_char.resize(this->glyphs.size());
238 for (
size_t i = 0; i < this->glyph_to_char.size(); i++) this->glyph_to_char[i] = (
int)map[i];
240 auto pts = std::make_unique<CGPoint[]>(this->glyphs.size());
241 CTRunGetPositions(run, CFRangeMake(0, 0), pts.get());
242 auto advs = std::make_unique<CGSize[]>(this->glyphs.size());
243 CTRunGetAdvances(run, CFRangeMake(0, 0), advs.get());
244 this->positions.reserve(this->glyphs.size());
246 int y_offset = this->font.GetFontCache().GetGlyphYOffset();
250 auto gl = std::make_unique<CGGlyph[]>(this->glyphs.size());
251 CTRunGetGlyphs(run, CFRangeMake(0, 0), gl.get());
252 for (
size_t i = 0; i < this->glyphs.size(); i++) {
253 this->glyphs[i] = gl[i];
254 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset);
256 this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0),
nullptr,
nullptr,
nullptr));
266 for (
const auto &run : *
this) {
267 leading = std::max(leading, run.GetLeading());
279 if (this->empty())
return 0;
282 for (
const auto &run : *
this) {
283 total_width += run.GetAdvance();
301 CFAutoRelease<CFStringRef> path(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *
>(file_path.data()), file_path.size(), kCFStringEncodingUTF8,
false));
302 CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle,
false));
304 CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess,
nullptr);
312 CFAutoRelease<CFStringRef> iso(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *
>(iso_code.data()), iso_code.size(), kCFStringEncodingUTF8,
false));
313 _osx_locale.reset(CFLocaleCreate(kCFAllocatorDefault, iso.get()));
326 if (!supported)
return 0;
328 CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
330 CFAutoRelease<CFStringRef> cf1(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)s1.data(), s1.size(), kCFStringEncodingUTF8,
false));
331 CFAutoRelease<CFStringRef> cf2(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)s2.data(), s2.size(), kCFStringEncodingUTF8,
false));
334 if (cf1 ==
nullptr || cf2 ==
nullptr)
return 0;
336 return (
int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags,
_osx_locale.get()) + 2;
350 if (!supported)
return -1;
352 CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
353 if (case_insensitive) flags |= kCFCompareCaseInsensitive;
355 CFAutoRelease<CFStringRef> cf_str(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)str.data(), str.size(), kCFStringEncodingUTF8,
false));
356 CFAutoRelease<CFStringRef> cf_value(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)value.data(), value.size(), kCFStringEncodingUTF8,
false));
359 if (cf_str ==
nullptr || cf_value ==
nullptr)
return -1;
361 return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags,
_osx_locale.get(),
nullptr) ? 1 : 0;
367 this->utf16_to_utf8.clear();
368 this->str_info.clear();
373 std::vector<UniChar> utf16_str;
375 for (
auto it = view.begin(), end = view.end(); it != end; ++it) {
376 size_t idx = it.GetByteOffset();
379 utf16_str.push_back((UniChar)c);
382 utf16_str.push_back((UniChar)(0xD800 + ((c - 0x10000) >> 10)));
383 utf16_str.push_back((UniChar)(0xDC00 + ((c - 0x10000) & 0x3FF)));
384 this->utf16_to_utf8.push_back(idx);
386 this->utf16_to_utf8.push_back(idx);
388 this->utf16_to_utf8.push_back(s.size());
391 this->str_info.resize(utf16_to_utf8.size());
393 if (!utf16_str.empty()) {
394 CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
397 for (CFIndex i = 0; i < CFStringGetLength(str.get()); ) {
398 CFRange r = CFStringGetRangeOfComposedCharactersAtIndex(str.get(), i);
399 this->str_info[r.location].char_stop =
true;
407 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
408 while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer.get())) != kCFStringTokenizerTokenNone) {
410 if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
411 CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer.get());
412 this->str_info[r.location].word_stop =
true;
418 this->str_info.back().char_stop =
true;
419 this->str_info.back().word_stop =
true;
425 size_t utf16_pos = 0;
426 for (
size_t i = 0; i < this->utf16_to_utf8.size(); i++) {
427 if (this->utf16_to_utf8[i] == pos) {
434 while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
435 this->cur_pos = utf16_pos;
437 return this->utf16_to_utf8[this->cur_pos];
442 assert(this->cur_pos <= this->utf16_to_utf8.size());
445 if (this->cur_pos == this->utf16_to_utf8.size())
return END;
449 }
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));
451 return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
456 assert(this->cur_pos <= this->utf16_to_utf8.size());
459 if (this->cur_pos == 0)
return END;
463 }
while (this->cur_pos > 0 && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
465 return this->utf16_to_utf8[this->cur_pos];
468 std::unique_ptr<StringIterator> OSXStringIterator::Create()
472 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.
Font cache for basic fonts.
virtual std::string GetFontName()=0
Get the name of this font.
static std::span< const std::unique_ptr< FontCache > > Get()
Get span of all FontCaches.
virtual int GetFontSize() const
Get the nominal font size of the font.
virtual const void * GetOSHandle()
Get the native OS font handle, if there is one.
virtual bool IsBuiltInFont()=0
Is this a built-in sprite font?
FontSize GetSize() const
Get the FontSize of the font.
Container with information about a font.
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.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
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.
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 std::unordered_map< FontIndex, CFAutoRelease< CTFontRef > > _font_cache
CoreText cache for font information, cleared when OTTD changes fonts.
static CFAutoRelease< CFLocaleRef > _osx_locale
Cached current locale.
static CGFloat CustomFontGetWidth(void *ref_con)
Get the width of an encoded sprite font character.
void MacOSResetScriptCache(FontSize size)
Delete CoreText font reference for a specific font size.
Functions related to localized text support on OSX.