20#include <CoreFoundation/CoreFoundation.h>
47 std::vector<GlyphID> glyphs;
48 std::vector<Position> positions;
49 std::vector<int> glyph_to_char;
51 int total_advance = 0;
56 CoreTextVisualRun(CoreTextVisualRun &&other) =
default;
58 std::span<const GlyphID>
GetGlyphs()
const override {
return this->glyphs; }
59 std::span<const Position>
GetPositions()
const override {
return this->positions; }
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 size_t CountRuns()
const override {
return this->size(); }
93 return c >= 0x010000U ? 2 : 1;
104 this->cur_offset = 0;
107 std::unique_ptr<const Line>
NextLine(
int max_width)
override;
119 char32_t c = (char32_t)((
size_t)ref_con & 0xFFFFFF);
124static const CTRunDelegateCallbacks _sprite_font_callback = {
125 kCTRunDelegateCurrentVersion,
nullptr,
nullptr,
nullptr,
132 ptrdiff_t length = buff_end - buff;
133 if (length == 0)
return nullptr;
136 for (
const auto &[position, font] : font_mapping) {
137 if (font->fc->IsBuiltInFont())
return nullptr;
142 CFAttributedStringBeginEditing(str.get());
145 CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
147 const UniChar replacement_char = 0xFFFC;
153 for (
const auto &[position, font] : font_mapping) {
154 if (position - last == 0)
continue;
156 CTFontRef font_handle =
static_cast<CTFontRef
>(font->fc->GetOSHandle());
157 if (font_handle ==
nullptr) {
160 CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8));
161 _font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(),
nullptr));
163 font_handle =
_font_cache[font->fc->GetSize()].get();
165 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
167 CGColorRef colour = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f);
168 CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, colour);
169 CGColorRelease(colour);
172 for (ssize_t c = last; c < position; c++) {
173 if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c],
false) == 0) {
176 CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacement_str.get());
177 CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
183 CFAttributedStringEndEditing(str.get());
188 return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), buff, length, font_mapping) :
nullptr;
193 if (this->
cur_offset >= this->length)
return nullptr;
196 CFIndex len = CTTypesetterSuggestLineBreak(this->typesetter.get(), this->cur_offset, max_width);
197 if (len <= 0) len = CTTypesetterSuggestClusterBreak(this->typesetter.get(), this->cur_offset, max_width);
203 if (!line)
return nullptr;
204 return std::make_unique<CoreTextLine>(std::move(line), this->font_map, this->text_buffer);
209 this->glyphs.resize(CTRunGetGlyphCount(run));
212 auto map = std::make_unique<CFIndex[]>(this->glyphs.size());
213 CTRunGetStringIndices(run, CFRangeMake(0, 0), map.get());
215 this->glyph_to_char.resize(this->glyphs.size());
216 for (
size_t i = 0; i < this->glyph_to_char.size(); i++) this->glyph_to_char[i] = (
int)map[i];
218 auto pts = std::make_unique<CGPoint[]>(this->glyphs.size());
219 CTRunGetPositions(run, CFRangeMake(0, 0), pts.get());
220 auto advs = std::make_unique<CGSize[]>(this->glyphs.size());
221 CTRunGetAdvances(run, CFRangeMake(0, 0), advs.get());
222 this->positions.reserve(this->glyphs.size());
226 auto gl = std::make_unique<CGGlyph[]>(this->glyphs.size());
227 CTRunGetGlyphs(run, CFRangeMake(0, 0), gl.get());
228 for (
size_t i = 0; i < this->glyphs.size(); i++) {
229 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)) {
234 this->glyphs[i] = gl[i];
235 this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
238 this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0),
nullptr,
nullptr,
nullptr));
248 for (
const auto &run : *
this) {
249 leading = std::max(leading, run.GetLeading());
261 if (this->empty())
return 0;
264 for (
const auto &run : *
this) {
265 total_width += run.GetAdvance();
287 CFAutoRelease<CFStringRef> path(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *
>(file_path.data()), file_path.size(), kCFStringEncodingUTF8,
false));
288 CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle,
false));
290 CTFontManagerRegisterFontsForURL(url.get(), kCTFontManagerScopeProcess,
nullptr);
299 CFAutoRelease<CFStringRef> iso(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *
>(iso_code.data()), iso_code.size(), kCFStringEncodingUTF8,
false));
300 _osx_locale.reset(CFLocaleCreate(kCFAllocatorDefault, iso.get()));
312 CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
314 CFAutoRelease<CFStringRef> cf1(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)s1.data(), s1.size(), kCFStringEncodingUTF8,
false));
315 CFAutoRelease<CFStringRef> cf2(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)s2.data(), s2.size(), kCFStringEncodingUTF8,
false));
318 if (cf1 ==
nullptr || cf2 ==
nullptr)
return 0;
320 return (
int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags,
_osx_locale.get()) + 2;
333 CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
334 if (case_insensitive) flags |= kCFCompareCaseInsensitive;
336 CFAutoRelease<CFStringRef> cf_str(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)str.data(), str.size(), kCFStringEncodingUTF8,
false));
337 CFAutoRelease<CFStringRef> cf_value(CFStringCreateWithBytes(kCFAllocatorDefault, (
const UInt8 *)value.data(), value.size(), kCFStringEncodingUTF8,
false));
340 if (cf_str ==
nullptr || cf_value ==
nullptr)
return -1;
342 return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags,
_osx_locale.get(),
nullptr) ? 1 : 0;
354 std::vector<UniChar> utf16_str;
356 for (
auto it = view.begin(), end = view.end(); it != end; ++it) {
357 size_t idx = it.GetByteOffset();
360 utf16_str.push_back((UniChar)c);
363 utf16_str.push_back((UniChar)(0xD800 + ((c - 0x10000) >> 10)));
364 utf16_str.push_back((UniChar)(0xDC00 + ((c - 0x10000) & 0x3FF)));
374 if (!utf16_str.empty()) {
375 CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
378 for (CFIndex i = 0; i < CFStringGetLength(str.get()); ) {
379 CFRange r = CFStringGetRangeOfComposedCharactersAtIndex(str.get(), i);
380 this->
str_info[r.location].char_stop =
true;
388 CFStringTokenizerTokenType tokenType = kCFStringTokenizerTokenNone;
389 while ((tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer.get())) != kCFStringTokenizerTokenNone) {
391 if ((tokenType & kCFStringTokenizerTokenHasNonLettersMask) != kCFStringTokenizerTokenHasNonLettersMask) {
392 CFRange r = CFStringTokenizerGetCurrentTokenRange(tokenizer.get());
393 this->
str_info[r.location].word_stop =
true;
399 this->
str_info.back().char_stop =
true;
400 this->
str_info.back().word_stop =
true;
406 size_t utf16_pos = 0;
415 while (utf16_pos > 0 && !this->
str_info[utf16_pos].char_stop) utf16_pos--;
430 }
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 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.
const VisualRun & GetVisualRun(size_t run) const override
Get a reference to the given run.
int GetWidth() const override
Get the width of this line.
size_t CountRuns() const override
Get the number of runs in this line.
int GetLeading() const override
Get the height of the line.
int GetInternalCharLength(char32_t c) const override
Get the number of elements the given character occupies in the underlying text buffer of the Layouter...
std::span< const int > GetGlyphToCharMap() const override
The offset for each of the glyphs to the character run that was passed to the Layouter.
const Font * GetFont() const override
Get the font.
int GetLeading() const override
Get the font leading, or distance between the baselines of consecutive lines.
size_t GetGlyphCount() const override
Get the number of glyphs.
std::span< const Position > GetPositions() const override
Get the positions for each of the glyphs.
std::span< const GlyphID > GetGlyphs() const override
Get the glyphs to draw.
Wrapper for doing layouts with CoreText.
std::unique_ptr< const Line > NextLine(int max_width) override
Construct a new line with a maximum width.
CFIndex cur_offset
Offset from the start of the current run from where to output.
void Reflow() override
Reset the position to the start of the paragraph.
A sort-of mixin that implements 'at(pos)' and 'operator[](pos)' only for a specific enum class.
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.
std::vector< CharInfo > str_info
Break information for each code point.
size_t SetCurPosition(size_t pos) override
Change the current string cursor.
size_t cur_pos
Current iteration position.
std::vector< size_t > utf16_to_utf8
Mapping from UTF-16 code point position to index in the UTF-8 source string.
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.
static const size_t END
Sentinel to indicate end-of-iteration.
static std::unique_ptr< StringIterator > Create()
Create a new iterator instance.
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.
Control codes that are embedded in the translation strings.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Functions to read fonts from files and cache them.
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.
@ End
Marker for the end of the enumerations.
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.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
Functions related to low-level strings.
static EnumClassIndexContainer< std::array< CFAutoRelease< CTFontRef >, to_underlying(FontSize::End)>, FontSize > _font_cache
CoreText cache for font information, cleared when OTTD changes fonts.
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.
Functions related to localized text support on OSX.
Functions related to OTTD's strings.
Handling of UTF-8 encoded data.
Functions related to zooming.
int ScaleSpriteTrad(int value)
Scale traditional pixel dimensions to GUI zoom level, for drawing sprites.