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>
24#ifndef HAVE_OSX_109_SDK
26 typedef const struct __CTRunDelegate * CTRunDelegateRef;
28 typedef void (*CTRunDelegateDeallocateCallback) (
void *refCon);
29 typedef CGFloat (*CTRunDelegateGetAscentCallback) (
void *refCon);
30 typedef CGFloat (*CTRunDelegateGetDescentCallback) (
void *refCon);
31 typedef CGFloat (*CTRunDelegateGetWidthCallback) (
void *refCon);
34 CTRunDelegateDeallocateCallback dealloc;
35 CTRunDelegateGetAscentCallback getAscent;
36 CTRunDelegateGetDescentCallback getDescent;
37 CTRunDelegateGetWidthCallback getWidth;
41 kCTRunDelegateVersion1 = 1,
42 kCTRunDelegateCurrentVersion = kCTRunDelegateVersion1
45 extern const CFStringRef kCTRunDelegateAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
47 CTRunDelegateRef CTRunDelegateCreate(
const CTRunDelegateCallbacks *callbacks,
void *refCon) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
74 std::vector<GlyphID> glyphs;
75 std::vector<Position> positions;
76 std::vector<int> glyph_to_char;
78 int total_advance = 0;
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; }
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; }
100 CFArrayRef runs = CTLineGetGlyphRuns(line.get());
101 for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
102 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
105 CFRange chars = CTRunGetStringRange(run);
106 auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font *>::first);
108 this->emplace_back(run, map->second, buff);
114 int CountRuns()
const override {
return this->size(); }
115 const VisualRun &GetVisualRun(
int run)
const override {
return this->at(run); }
117 int GetInternalCharLength(
char32_t c)
const override
120 return c >= 0x010000U ? 2 : 1;
129 void Reflow()
override
131 this->cur_offset = 0;
134 std::unique_ptr<const Line> NextLine(
int max_width)
override;
142 char32_t c = (char32_t)((
size_t)ref_con & 0xFFFFFF);
148 kCTRunDelegateCurrentVersion,
nullptr,
nullptr,
nullptr,
157 ptrdiff_t length = buff_end - buff;
158 if (length == 0)
return nullptr;
161 for (
const auto &[position, font] : font_mapping) {
162 if (font->fc->IsBuiltInFont())
return nullptr;
167 CFAttributedStringBeginEditing(str.get());
170 CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
172 const UniChar replacment_char = 0xFFFC;
178 for (
const auto &[position, font] : font_mapping) {
179 if (position - last == 0)
continue;
181 CTFontRef font_handle =
static_cast<CTFontRef
>(font->fc->GetOSHandle());
182 if (font_handle ==
nullptr) {
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));
188 font_handle =
_font_cache[font->fc->GetSize()].get();
190 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
192 CGColorRef color = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f);
193 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color);
194 CGColorRelease(color);
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) {
201 CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get());
202 CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
208 CFAttributedStringEndEditing(str.get());
213 return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), buff, length, font_mapping) :
nullptr;
216 std::unique_ptr<const ParagraphLayouter::Line> CoreTextParagraphLayout::NextLine(
int max_width)
218 if (this->
cur_offset >= this->length)
return nullptr;
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);
228 if (!line)
return nullptr;
229 return std::make_unique<CoreTextLine>(std::move(line), this->font_map, this->text_buffer);
234 this->glyphs.resize(CTRunGetGlyphCount(run));
237 CFIndex map[this->glyphs.size()];
238 CTRunGetStringIndices(run, CFRangeMake(0, 0), map);
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];
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());
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)) {
259 this->glyphs[i] = gl[i];
260 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
263 this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0),
nullptr,
nullptr,
nullptr));
273 for (
const auto &run : *
this) {
274 leading = std::max(leading, run.GetLeading());
286 if (this->empty())
return 0;
289 for (
const auto &run : *
this) {
290 total_width += run.GetAdvance();
309 CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle,
false));
311 CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess,
nullptr);
320 _osx_locale.reset(CFLocaleCreate(kCFAllocatorDefault, iso.get()));
333 if (!supported)
return 0;
335 CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
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));
341 if (cf1 ==
nullptr || cf2 ==
nullptr)
return 0;
343 return (
int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags,
_osx_locale.get()) + 2;
357 if (!supported)
return -1;
359 CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
360 if (case_insensitive) flags |= kCFCompareCaseInsensitive;
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));
366 if (cf_str ==
nullptr || cf_value ==
nullptr)
return -1;
368 return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags,
_osx_locale.get(),
nullptr) ? 1 : 0;
374 this->utf16_to_utf8.clear();
375 this->str_info.clear();
380 std::vector<UniChar> utf16_str;
382 for (
auto it = view.begin(), end = view.end(); it != end; ++it) {
383 size_t idx = it.GetByteOffset();
386 utf16_str.push_back((UniChar)c);
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);
393 this->utf16_to_utf8.push_back(idx);
395 this->utf16_to_utf8.push_back(s.size());
398 this->str_info.resize(utf16_to_utf8.size());
400 if (!utf16_str.empty()) {
401 CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
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;
414 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
415 while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer.get())) != kCFStringTokenizerTokenNone) {
417 if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
418 CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer.get());
419 this->str_info[r.location].word_stop =
true;
425 this->str_info.back().char_stop =
true;
426 this->str_info.back().word_stop =
true;
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) {
441 while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
442 this->cur_pos = utf16_pos;
444 return this->utf16_to_utf8[this->cur_pos];
449 assert(this->cur_pos <= this->utf16_to_utf8.size());
452 if (this->cur_pos == this->utf16_to_utf8.size())
return END;
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));
458 return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
463 assert(this->cur_pos <= this->utf16_to_utf8.size());
466 if (this->cur_pos == 0)
return END;
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));
472 return this->utf16_to_utf8[this->cur_pos];
475 std::unique_ptr<StringIterator> OSXStringIterator::Create()
479 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.
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.