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.
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.
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.
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.
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.
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.