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"
47 std::vector<GlyphID> glyphs;
48 std::vector<Position> positions;
49 std::vector<int> glyph_to_char;
51 int total_advance = 0;
58 std::span<const GlyphID> GetGlyphs()
const override {
return this->glyphs; }
59 std::span<const Position> GetPositions()
const override {
return this->positions; }
60 std::span<const int> GetGlyphToCharMap()
const override {
return this->glyph_to_char; }
62 const Font *GetFont()
const override {
return this->font; }
63 int GetLeading()
const override {
return this->font->
fc->
GetHeight(); }
64 int GetGlyphCount()
const override {
return (
int)this->glyphs.size(); }
65 int GetAdvance()
const {
return this->total_advance; }
73 CFArrayRef runs = CTLineGetGlyphRuns(line.get());
74 for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
75 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
78 CFRange chars = CTRunGetStringRange(run);
79 auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font *>::first);
81 this->emplace_back(run, map->second, buff);
87 int CountRuns()
const override {
return this->size(); }
88 const VisualRun &GetVisualRun(
int run)
const override {
return this->at(run); }
90 int GetInternalCharLength(
char32_t c)
const override
93 return c >= 0x010000U ? 2 : 1;
102 void Reflow()
override
104 this->cur_offset = 0;
107 std::unique_ptr<const Line> NextLine(
int max_width)
override;
115 char32_t c = (char32_t)((
size_t)ref_con & 0xFFFFFF);
120static const CTRunDelegateCallbacks _sprite_font_callback = {
121 kCTRunDelegateCurrentVersion,
nullptr,
nullptr,
nullptr,
130 ptrdiff_t length = buff_end - buff;
131 if (length == 0)
return nullptr;
134 for (
const auto &[position, font] : font_mapping) {
135 if (font->fc->IsBuiltInFont())
return nullptr;
140 CFAttributedStringBeginEditing(str.get());
143 CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
145 const UniChar replacement_char = 0xFFFC;
151 for (
const auto &[position, font] : font_mapping) {
152 if (position - last == 0)
continue;
154 CTFontRef font_handle =
static_cast<CTFontRef
>(font->fc->GetOSHandle());
155 if (font_handle ==
nullptr) {
158 CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8));
159 _font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(),
nullptr));
161 font_handle =
_font_cache[font->fc->GetSize()].get();
163 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
165 CGColorRef colour = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f);
166 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, colour);
167 CGColorRelease(colour);
170 for (ssize_t c = last; c < position; c++) {
171 if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c],
false) == 0) {
174 CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacement_str.get());
175 CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
181 CFAttributedStringEndEditing(str.get());
186 return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), buff, length, font_mapping) :
nullptr;
189 std::unique_ptr<const ParagraphLayouter::Line> CoreTextParagraphLayout::NextLine(
int max_width)
191 if (this->
cur_offset >= this->length)
return nullptr;
194 CFIndex len = CTTypesetterSuggestLineBreak(this->typesetter.get(), this->cur_offset, max_width);
195 if (len <= 0) len = CTTypesetterSuggestClusterBreak(this->typesetter.get(), this->cur_offset, max_width);
201 if (!line)
return nullptr;
202 return std::make_unique<CoreTextLine>(std::move(line), this->font_map, this->text_buffer);
207 this->glyphs.resize(CTRunGetGlyphCount(run));
210 auto map = std::make_unique<CFIndex[]>(this->glyphs.size());
211 CTRunGetStringIndices(run, CFRangeMake(0, 0), map.get());
213 this->glyph_to_char.resize(this->glyphs.size());
214 for (
size_t i = 0; i < this->glyph_to_char.size(); i++) this->glyph_to_char[i] = (
int)map[i];
216 auto pts = std::make_unique<CGPoint[]>(this->glyphs.size());
217 CTRunGetPositions(run, CFRangeMake(0, 0), pts.get());
218 auto advs = std::make_unique<CGSize[]>(this->glyphs.size());
219 CTRunGetAdvances(run, CFRangeMake(0, 0), advs.get());
220 this->positions.reserve(this->glyphs.size());
224 auto gl = std::make_unique<CGGlyph[]>(this->glyphs.size());
225 CTRunGetGlyphs(run, CFRangeMake(0, 0), gl.get());
226 for (
size_t i = 0; i < this->glyphs.size(); i++) {
227 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)) {
232 this->glyphs[i] = gl[i];
233 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
236 this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0),
nullptr,
nullptr,
nullptr));
246 for (
const auto &run : *
this) {
247 leading = std::max(leading, run.GetLeading());
259 if (this->empty())
return 0;
262 for (
const auto &run : *
this) {
263 total_width += run.GetAdvance();
281 CFAutoRelease<CFStringRef> path(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *
>(file_path.data()), file_path.size(), kCFStringEncodingUTF8,
false));
282 CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle,
false));
284 CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess,
nullptr);
292 CFAutoRelease<CFStringRef> iso(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *
>(iso_code.data()), iso_code.size(), kCFStringEncodingUTF8,
false));
293 _osx_locale.reset(CFLocaleCreate(kCFAllocatorDefault, iso.get()));
306 if (!supported)
return 0;
308 CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
310 CFAutoRelease<CFStringRef> cf1(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)s1.data(), s1.size(), kCFStringEncodingUTF8,
false));
311 CFAutoRelease<CFStringRef> cf2(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)s2.data(), s2.size(), kCFStringEncodingUTF8,
false));
314 if (cf1 ==
nullptr || cf2 ==
nullptr)
return 0;
316 return (
int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags,
_osx_locale.get()) + 2;
330 if (!supported)
return -1;
332 CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
333 if (case_insensitive) flags |= kCFCompareCaseInsensitive;
335 CFAutoRelease<CFStringRef> cf_str(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)str.data(), str.size(), kCFStringEncodingUTF8,
false));
336 CFAutoRelease<CFStringRef> cf_value(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)value.data(), value.size(), kCFStringEncodingUTF8,
false));
339 if (cf_str ==
nullptr || cf_value ==
nullptr)
return -1;
341 return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags,
_osx_locale.get(),
nullptr) ? 1 : 0;
347 this->utf16_to_utf8.clear();
348 this->str_info.clear();
353 std::vector<UniChar> utf16_str;
355 for (
auto it = view.begin(), end = view.end(); it != end; ++it) {
356 size_t idx = it.GetByteOffset();
359 utf16_str.push_back((UniChar)c);
362 utf16_str.push_back((UniChar)(0xD800 + ((c - 0x10000) >> 10)));
363 utf16_str.push_back((UniChar)(0xDC00 + ((c - 0x10000) & 0x3FF)));
364 this->utf16_to_utf8.push_back(idx);
366 this->utf16_to_utf8.push_back(idx);
368 this->utf16_to_utf8.push_back(s.size());
371 this->str_info.resize(utf16_to_utf8.size());
373 if (!utf16_str.empty()) {
374 CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
377 for (CFIndex i = 0; i < CFStringGetLength(str.get()); ) {
378 CFRange r = CFStringGetRangeOfComposedCharactersAtIndex(str.get(), i);
379 this->str_info[r.location].char_stop =
true;
387 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
388 while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer.get())) != kCFStringTokenizerTokenNone) {
390 if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
391 CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer.get());
392 this->str_info[r.location].word_stop =
true;
398 this->str_info.back().char_stop =
true;
399 this->str_info.back().word_stop =
true;
405 size_t utf16_pos = 0;
406 for (
size_t i = 0; i < this->utf16_to_utf8.size(); i++) {
407 if (this->utf16_to_utf8[i] == pos) {
414 while (utf16_pos > 0 && !this->str_info[utf16_pos].char_stop) utf16_pos--;
415 this->cur_pos = utf16_pos;
417 return this->utf16_to_utf8[this->cur_pos];
422 assert(this->cur_pos <= this->utf16_to_utf8.size());
425 if (this->cur_pos == this->utf16_to_utf8.size())
return END;
429 }
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));
431 return this->cur_pos == this->utf16_to_utf8.size() ? END : this->utf16_to_utf8[this->cur_pos];
436 assert(this->cur_pos <= this->utf16_to_utf8.size());
439 if (this->cur_pos == 0)
return END;
443 }
while (this->cur_pos > 0 && (what == ITER_WORD ? !this->str_info[this->cur_pos].word_stop : !this->str_info[this->cur_pos].char_stop));
445 return this->utf16_to_utf8[this->cur_pos];
448 std::unique_ptr<StringIterator> OSXStringIterator::Create()
452 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.