summaryrefslogtreecommitdiff
path: root/src/os/macosx
diff options
context:
space:
mode:
authorMichael Lutz <michi@icosahedron.de>2018-10-28 23:30:49 +0100
committerMichael Lutz <michi@icosahedron.de>2018-12-08 20:13:27 +0100
commit32ce1ce3473253644d1237227706e4f8ce55ab7a (patch)
tree59731354a3b4ced7e0f6040e696ff670e39d2d19 /src/os/macosx
parent4bf216993a1df7a29922bf34e2d8191460842452 (diff)
downloadopenttd-32ce1ce3473253644d1237227706e4f8ce55ab7a.tar.xz
Add: [OSX] Text layout using the native CoreText API.
By default, the native API will be used instead of ICU, but if ICU is forced in using configure, it will take precedence.
Diffstat (limited to 'src/os/macosx')
-rw-r--r--src/os/macosx/string_osx.cpp262
-rw-r--r--src/os/macosx/string_osx.h45
2 files changed, 307 insertions, 0 deletions
diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp
index 4698f601b..1c5d55885 100644
--- a/src/os/macosx/string_osx.cpp
+++ b/src/os/macosx/string_osx.cpp
@@ -12,13 +12,269 @@
#include "../../stdafx.h"
#include "string_osx.h"
#include "../../string_func.h"
+#include "../../strings_func.h"
+#include "../../table/control_codes.h"
+#include "../../fontcache.h"
#include "macos.h"
#include <CoreFoundation/CoreFoundation.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+/** Cached current locale. */
static CFLocaleRef _osx_locale = NULL;
+/** CoreText cache for font information, cleared when OTTD changes fonts. */
+static CTFontRef _font_cache[FS_END];
+
+
+/**
+ * Wrapper for doing layouts with CoreText.
+ */
+class CoreTextParagraphLayout : public ParagraphLayouter {
+private:
+ const CoreTextParagraphLayoutFactory::CharType *text_buffer;
+ ptrdiff_t length;
+ const FontMap& font_map;
+
+ CTTypesetterRef typesetter;
+
+ CFIndex cur_offset = 0; ///< Offset from the start of the current run from where to output.
+
+public:
+ /** Visual run contains data about the bit of text with the same font. */
+ class CoreTextVisualRun : public ParagraphLayouter::VisualRun {
+ private:
+ std::vector<GlyphID> glyphs;
+ std::vector<float> positions;
+ std::vector<int> glyph_to_char;
+
+ int total_advance = 0;
+ Font *font;
+
+ public:
+ CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff);
+
+ virtual const GlyphID *GetGlyphs() const { return &this->glyphs[0]; }
+ virtual const float *GetPositions() const { return &this->positions[0]; }
+ virtual const int *GetGlyphToCharMap() const { return &this->glyph_to_char[0]; }
+
+ virtual const Font *GetFont() const { return this->font; }
+ virtual int GetLeading() const { return this->font->fc->GetHeight(); }
+ virtual int GetGlyphCount() const { return (int)this->glyphs.size(); }
+ int GetAdvance() const { return this->total_advance; }
+ };
+
+ /** A single line worth of VisualRuns. */
+ class CoreTextLine : public AutoDeleteSmallVector<CoreTextVisualRun *, 4>, public ParagraphLayouter::Line {
+ public:
+ CoreTextLine(CTLineRef line, const FontMap &fontMapping, const CoreTextParagraphLayoutFactory::CharType *buff)
+ {
+ CFArrayRef runs = CTLineGetGlyphRuns(line);
+ for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
+ CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i);
+
+ /* Extract font information for this run. */
+ CFRange chars = CTRunGetStringRange(run);
+ FontMap::const_iterator map = fontMapping.Begin();
+ while (map < fontMapping.End() - 1 && map->first <= chars.location) map++;
+
+ *this->Append() = new CoreTextVisualRun(run, map->second, buff);
+ }
+ CFRelease(line);
+ }
+
+ virtual int GetLeading() const;
+ virtual int GetWidth() const;
+ virtual int CountRuns() const { return this->Length(); }
+ virtual const VisualRun *GetVisualRun(int run) const { return *this->Get(run); }
+
+ int GetInternalCharLength(WChar c) const
+ {
+ /* CoreText uses UTF-16 internally which means we need to account for surrogate pairs. */
+ return c >= 0x010000U ? 2 : 1;
+ }
+ };
+
+ CoreTextParagraphLayout(CTTypesetterRef typesetter, const CoreTextParagraphLayoutFactory::CharType *buffer, ptrdiff_t len, const FontMap &fontMapping) : text_buffer(buffer), length(len), font_map(fontMapping), typesetter(typesetter)
+ {
+ this->Reflow();
+ }
+
+ virtual ~CoreTextParagraphLayout()
+ {
+ CFRelease(this->typesetter);
+ }
+
+ virtual void Reflow()
+ {
+ this->cur_offset = 0;
+ }
+
+ virtual const Line *NextLine(int max_width);
+};
+
+
+/** Get the width of an encoded sprite font character. */
+static CGFloat SpriteFontGetWidth(void *ref_con)
+{
+ FontSize fs = (FontSize)((size_t)ref_con >> 24);
+ WChar c = (WChar)((size_t)ref_con & 0xFFFFFF);
+
+ return GetGlyphWidth(fs, c);
+}
+
+static CTRunDelegateCallbacks _sprite_font_callback = {
+ kCTRunDelegateCurrentVersion, NULL, NULL, NULL,
+ &SpriteFontGetWidth
+};
+
+/* static */ ParagraphLayouter *CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping)
+{
+ if (!MacOSVersionIsAtLeast(10, 5, 0)) return NULL;
+
+ /* Can't layout an empty string. */
+ ptrdiff_t length = buff_end - buff;
+ if (length == 0) return NULL;
+
+ /* Can't layout our in-built sprite fonts. */
+ for (FontMap::const_iterator i = fontMapping.Begin(); i != fontMapping.End(); i++) {
+ if (i->second->fc->IsBuiltInFont()) return NULL;
+ }
+
+ /* Make attributed string with embedded font information. */
+ CFMutableAttributedStringRef str = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
+ CFAttributedStringBeginEditing(str);
+
+ CFStringRef base = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buff, length, kCFAllocatorNull);
+ CFAttributedStringReplaceString(str, CFRangeMake(0, 0), base);
+ CFRelease(base);
+
+ /* Apply font and colour ranges to our string. This is important to make sure
+ * that we get proper glyph boundaries on style changes. */
+ int last = 0;
+ for (FontMap::const_iterator i = fontMapping.Begin(); i != fontMapping.End(); i++) {
+ if (i->first - last == 0) continue;
+
+ if (_font_cache[i->second->fc->GetSize()] == NULL) {
+ /* Cache font information. */
+ CFStringRef font_name = CFStringCreateWithCString(kCFAllocatorDefault, i->second->fc->GetFontName(), kCFStringEncodingUTF8);
+ _font_cache[i->second->fc->GetSize()] = CTFontCreateWithName(font_name, i->second->fc->GetFontSize(), NULL);
+ CFRelease(font_name);
+ }
+ CFAttributedStringSetAttribute(str, CFRangeMake(last, i->first - last), kCTFontAttributeName, _font_cache[i->second->fc->GetSize()]);
+
+ CGColorRef color = CGColorCreateGenericGray((uint8)i->second->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
+ CFAttributedStringSetAttribute(str, CFRangeMake(last, i->first - last), kCTForegroundColorAttributeName, color);
+ CGColorRelease(color);
+
+ /* Install a size callback for our special sprite glyphs. */
+ for (ssize_t c = last; c < i->first; c++) {
+ if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END) {
+ CTRunDelegateRef del = CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (i->second->fc->GetSize() << 24)));
+ CFAttributedStringSetAttribute(str, CFRangeMake(c, 1), kCTRunDelegateAttributeName, del);
+ CFRelease(del);
+ }
+ }
+
+ last = i->first;
+ }
+ CFAttributedStringEndEditing(str);
+
+ /* Create and return typesetter for the string. */
+ CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(str);
+ CFRelease(str);
+
+ return typesetter != NULL ? new CoreTextParagraphLayout(typesetter, buff, length, fontMapping) : NULL;
+}
+
+/* virtual */ const CoreTextParagraphLayout::Line *CoreTextParagraphLayout::NextLine(int max_width)
+{
+ if (this->cur_offset >= this->length) return NULL;
+
+ /* Get line break position, trying word breaking first and breaking somewhere if that doesn't work. */
+ CFIndex len = CTTypesetterSuggestLineBreak(this->typesetter, this->cur_offset, max_width);
+ if (len <= 0) len = CTTypesetterSuggestClusterBreak(this->typesetter, this->cur_offset, max_width);
+
+ /* Create line. */
+ CTLineRef line = CTTypesetterCreateLine(this->typesetter, CFRangeMake(this->cur_offset, len));
+ this->cur_offset += len;
+
+ return line != NULL ? new CoreTextLine(line, this->font_map, this->text_buffer) : NULL;
+}
+
+CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff) : font(font)
+{
+ this->glyphs.resize(CTRunGetGlyphCount(run));
+
+ /* Query map of glyphs to source string index. */
+ CFIndex map[this->glyphs.size()];
+ CTRunGetStringIndices(run, CFRangeMake(0, 0), map);
+
+ this->glyph_to_char.resize(this->glyphs.size());
+ for (size_t i = 0; i < this->glyph_to_char.size(); i++) this->glyph_to_char[i] = (int)map[i];
+
+ CGPoint pts[this->glyphs.size()];
+ CTRunGetPositions(run, CFRangeMake(0, 0), pts);
+ this->positions.resize(this->glyphs.size() * 2 + 2);
+
+ /* Convert glyph array to our data type. At the same time, substitute
+ * the proper glyphs for our private sprite glyphs. */
+ CGGlyph gl[this->glyphs.size()];
+ CTRunGetGlyphs(run, CFRangeMake(0, 0), gl);
+ for (size_t i = 0; i < this->glyphs.size(); i++) {
+ if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END) {
+ this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]);
+ this->positions[i * 2 + 0] = pts[i].x;
+ this->positions[i * 2 + 1] = font->fc->GetAscender() - font->fc->GetGlyph(this->glyphs[i])->height - 1; // Align sprite glyphs to font baseline.
+ } else {
+ this->glyphs[i] = gl[i];
+ this->positions[i * 2 + 0] = pts[i].x;
+ this->positions[i * 2 + 1] = pts[i].y;
+ }
+ }
+ this->total_advance = (int)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
+ this->positions[this->glyphs.size() * 2] = this->positions[0] + this->total_advance;
+}
+
+/**
+ * Get the height of the line.
+ * @return The maximum height of the line.
+ */
+int CoreTextParagraphLayout::CoreTextLine::GetLeading() const
+{
+ int leading = 0;
+ for (const CoreTextVisualRun * const *run = this->Begin(); run != this->End(); run++) {
+ leading = max(leading, (*run)->GetLeading());
+ }
+
+ return leading;
+}
+
+/**
+ * Get the width of this line.
+ * @return The width of the line.
+ */
+int CoreTextParagraphLayout::CoreTextLine::GetWidth() const
+{
+ if (this->Length() == 0) return 0;
+
+ int total_width = 0;
+ for (const CoreTextVisualRun * const *run = this->Begin(); run != this->End(); run++) {
+ total_width += (*run)->GetAdvance();
+ }
+
+ return total_width;
+}
+
+
+/** Delete CoreText font reference for a specific font size. */
+void MacOSResetScriptCache(FontSize size)
+{
+ if (_font_cache[size] != NULL) {
+ CFRelease(_font_cache[size]);
+ _font_cache[size] = NULL;
+ }
+}
/** Store current language locale as a CoreFounation locale. */
void MacOSSetCurrentLocaleName(const char *iso_code)
@@ -174,6 +430,7 @@ int MacOSStringCompare(const char *s1, const char *s2)
}
#else
+void MacOSResetScriptCache(FontSize size) {}
void MacOSSetCurrentLocaleName(const char *iso_code) {}
int MacOSStringCompare(const char *s1, const char *s2)
@@ -185,4 +442,9 @@ int MacOSStringCompare(const char *s1, const char *s2)
{
return NULL;
}
+
+/* static */ ParagraphLayouter *CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping)
+{
+ return NULL;
+}
#endif /* (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) */
diff --git a/src/os/macosx/string_osx.h b/src/os/macosx/string_osx.h
index ea7904f54..d632b0a44 100644
--- a/src/os/macosx/string_osx.h
+++ b/src/os/macosx/string_osx.h
@@ -38,7 +38,52 @@ public:
static StringIterator *Create();
};
+/**
+ * Helper class to construct a new #CoreTextParagraphLayout.
+ */
+class CoreTextParagraphLayoutFactory {
+public:
+ /** Helper for GetLayouter, to get the right type. */
+ typedef UniChar CharType;
+ /** Helper for GetLayouter, to get whether the layouter supports RTL. */
+ static const bool SUPPORTS_RTL = true;
+
+ /**
+ * Get the actual ParagraphLayout for the given buffer.
+ * @param buff The begin of the buffer.
+ * @param buff_end The location after the last element in the buffer.
+ * @param fontMapping THe mapping of the fonts.
+ * @return The ParagraphLayout instance.
+ */
+ static ParagraphLayouter *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
+
+ /**
+ * Append a wide character to the internal buffer.
+ * @param buff The buffer to append to.
+ * @param buffer_last The end of the buffer.
+ * @param c The character to add.
+ * @return The number of buffer spaces that were used.
+ */
+ static size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c)
+ {
+ if (c >= 0x010000U) {
+ /* Character is encoded using surrogates in UTF-16. */
+ if (buff + 1 <= buffer_last) {
+ buff[0] = (CharType)(((c - 0x010000U) >> 10) + 0xD800);
+ buff[1] = (CharType)(((c - 0x010000U) & 0x3FF) + 0xDC00);
+ } else {
+ /* Not enough space in buffer. */
+ *buff = 0;
+ }
+ return 2;
+ } else {
+ *buff = (CharType)(c & 0xFFFF);
+ return 1;
+ }
+ }
+};
+void MacOSResetScriptCache(FontSize size);
void MacOSSetCurrentLocaleName(const char *iso_code);
int MacOSStringCompare(const char *s1, const char *s2);