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.
pull/73/head
Michael Lutz 6 years ago
parent 4bf216993a
commit 32ce1ce347

@ -2890,10 +2890,22 @@ detect_fontconfig() {
}
detect_icu_layout() {
if [ "$with_cocoa" != "0" ] && [ "$with_icu_layout" = "1" ]; then
log 1 "checking icu-lx... OSX, skipping"
icu_layout_config=""
return 0
fi
detect_pkg_config "$with_icu_layout" "icu-lx" "icu_layout_config" "4.8" "1"
}
detect_icu_sort() {
if [ "$with_cocoa" != "0" ] && [ "$with_icu_sort" = "1" ]; then
log 1 "checking icu-i18n... OSX, skipping"
icu_sort_config=""
return 0
fi
detect_pkg_config "$with_icu_sort" "icu-i18n" "icu_sort_config" "4.8" "1"
}

@ -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) {

@ -64,6 +64,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.

@ -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
}
/**

@ -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) */

@ -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);

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

Loading…
Cancel
Save