summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/fontcache.cpp3
-rw-r--r--src/fontcache.h6
-rw-r--r--src/gfx_layout.cpp19
-rw-r--r--src/os/macosx/string_osx.cpp262
-rw-r--r--src/os/macosx/string_osx.h45
-rw-r--r--src/strings.cpp2
6 files changed, 335 insertions, 2 deletions
diff --git a/src/fontcache.cpp b/src/fontcache.cpp
index 3a5dd886c..55da0c55c 100644
--- a/src/fontcache.cpp
+++ b/src/fontcache.cpp
@@ -210,6 +210,7 @@ class FreeTypeFontCache : public FontCache {
private:
FT_Face face; ///< The font face associated with this font.
int req_size; ///< Requested font size.
+ int used_size; ///< Used font size.
typedef SmallMap<uint32, SmallPair<size_t, const void*> > FontTable; ///< Table with font table cache
FontTable font_tables; ///< Cached font tables.
@@ -243,6 +244,7 @@ private:
public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
~FreeTypeFontCache();
+ virtual int GetFontSize() const { return this->used_size; }
virtual SpriteID GetUnicodeGlyph(WChar key) { return this->parent->GetUnicodeGlyph(key); }
virtual void SetUnicodeGlyph(WChar key, SpriteID sprite) { this->parent->SetUnicodeGlyph(key, sprite); }
virtual void InitializeUnicodeGlyphMap() { this->parent->InitializeUnicodeGlyphMap(); }
@@ -291,6 +293,7 @@ void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels)
pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, scaled_height, MAX_FONT_SIZE);
}
}
+ this->used_size = pixels;
FT_Error err = FT_Set_Pixel_Sizes(this->face, 0, pixels);
if (err != FT_Err_Ok) {
diff --git a/src/fontcache.h b/src/fontcache.h
index 8d66ed366..1f5e56d92 100644
--- a/src/fontcache.h
+++ b/src/fontcache.h
@@ -65,6 +65,12 @@ public:
inline int GetUnitsPerEM() const { return this->units_per_em; }
/**
+ * Get the nominal font size of the font.
+ * @return The nominal font size.
+ */
+ virtual int GetFontSize() const { return this->height; }
+
+ /**
* Get the SpriteID mapped to the given key
* @param key The key to get the sprite for.
* @return The sprite.
diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp
index c493e69c3..c65ead90e 100644
--- a/src/gfx_layout.cpp
+++ b/src/gfx_layout.cpp
@@ -25,6 +25,10 @@
#include "os/windows/string_uniscribe.h"
#endif /* WITH_UNISCRIBE */
+#ifdef WITH_COCOA
+#include "os/macosx/string_osx.h"
+#endif
+
#include "safeguards.h"
@@ -670,7 +674,7 @@ Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsi
} else {
/* Line is new, layout it */
FontState old_state = state;
-#if defined(WITH_ICU_LAYOUT) || defined(WITH_UNISCRIBE)
+#if defined(WITH_ICU_LAYOUT) || defined(WITH_UNISCRIBE) || defined(WITH_COCOA)
const char *old_str = str;
#endif
@@ -698,6 +702,16 @@ Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsi
}
#endif
+#ifdef WITH_COCOA
+ if (line.layout == NULL) {
+ GetLayouter<CoreTextParagraphLayoutFactory>(line, str, state);
+ if (line.layout == NULL) {
+ state = old_state;
+ str = old_str;
+ }
+ }
+#endif
+
if (line.layout == NULL) {
GetLayouter<FallbackParagraphLayoutFactory>(line, str, state);
}
@@ -841,6 +855,9 @@ void Layouter::ResetFontCache(FontSize size)
#if defined(WITH_UNISCRIBE)
UniscribeResetScriptCache(size);
#endif
+#if defined(WITH_COCOA)
+ MacOSResetScriptCache(size);
+#endif
}
/**
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);
diff --git a/src/strings.cpp b/src/strings.cpp
index eaaa38758..164d8a3c9 100644
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -2138,7 +2138,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
/* Update the font with cache */
LoadStringWidthTable(searcher->Monospace());
-#if !defined(WITH_ICU_LAYOUT) && !defined(WITH_UNISCRIBE)
+#if !defined(WITH_ICU_LAYOUT) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
/*
* For right-to-left languages we need the ICU library. If
* we do not have support for that library we warn the user