Adding CoreText font driver for iOS and OS X

The font path settings should be changed to support entering a font
name on these platforms and potentially working with the front-end to
get a list of possible names.
This commit is contained in:
Jay McCarthy 2014-10-18 20:19:44 -04:00
parent 3f637a7815
commit 1f788543e7
6 changed files with 269 additions and 13 deletions

View File

@ -12,6 +12,8 @@
50535530185E0F4000926C26 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5053552F185E0F4000926C26 /* CoreLocation.framework */; };
50C374A919F04F7A00984F8D /* CFExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 50C374A819F04F7A00984F8D /* CFExtensions.m */; };
50D66298199F344700CF54E3 /* Cg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50D66297199F344700CF54E3 /* Cg.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
696012EE19F2B7C0006A1088 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 696012ED19F2B7C0006A1088 /* CoreText.framework */; };
696012F019F2B7D8006A1088 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 696012EF19F2B7D8006A1088 /* CoreGraphics.framework */; };
962EE0E2178B3DF6004224FF /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 962EE0E1178B3DF6004224FF /* IOKit.framework */; };
96355CE31788E72A0010DBFA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96355CE21788E72A0010DBFA /* Cocoa.framework */; };
964DE7C417D84B57001CBB6C /* Settings.xib in Resources */ = {isa = PBXBuildFile; fileRef = 964DE7C617D84B57001CBB6C /* Settings.xib */; };
@ -35,6 +37,8 @@
50C374A819F04F7A00984F8D /* CFExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CFExtensions.m; path = ../../common/CFExtensions.m; sourceTree = SOURCE_ROOT; };
50D66295199F28AC00CF54E3 /* Cg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Cg.framework; sourceTree = "<group>"; };
50D66297199F344700CF54E3 /* Cg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cg.framework; path = ../../../../../../../Library/Frameworks/Cg.framework; sourceTree = "<group>"; };
696012ED19F2B7C0006A1088 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
696012EF19F2B7D8006A1088 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
962EE0E1178B3DF6004224FF /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = ../../../../../../../System/Library/Frameworks/IOKit.framework; sourceTree = "<group>"; };
96355CDF1788E72A0010DBFA /* RetroArch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArch.app; sourceTree = BUILT_PRODUCTS_DIR; };
96355CE21788E72A0010DBFA /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
@ -61,6 +65,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
696012F019F2B7D8006A1088 /* CoreGraphics.framework in Frameworks */,
696012EE19F2B7C0006A1088 /* CoreText.framework in Frameworks */,
50D66298199F344700CF54E3 /* Cg.framework in Frameworks */,
50535530185E0F4000926C26 /* CoreLocation.framework in Frameworks */,
962EE0E2178B3DF6004224FF /* IOKit.framework in Frameworks */,
@ -111,6 +117,8 @@
96355CE11788E72A0010DBFA /* Frameworks */ = {
isa = PBXGroup;
children = (
696012EF19F2B7D8006A1088 /* CoreGraphics.framework */,
696012ED19F2B7C0006A1088 /* CoreText.framework */,
50D66297199F344700CF54E3 /* Cg.framework */,
50D66295199F28AC00CF54E3 /* Cg.framework */,
5053552F185E0F4000926C26 /* CoreLocation.framework */,

View File

@ -21,6 +21,7 @@
50CCC828185E0E7D001F5BC8 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CCC827185E0E7D001F5BC8 /* CoreLocation.framework */; };
50D00E8E19D117C400EBA71E /* cc_resampler_neon.S in Sources */ = {isa = PBXBuildFile; fileRef = 50D00E8D19D117C400EBA71E /* cc_resampler_neon.S */; };
50E7189F184B88AA001956CE /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50E7189E184B88AA001956CE /* CoreVideo.framework */; };
696012F219F3389A006A1088 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 696012F119F3389A006A1088 /* CoreText.framework */; };
83D632DF19ECFCC4009E3161 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83D632D819ECFCC4009E3161 /* Default-568h@2x.png */; };
83D632E019ECFCC4009E3161 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 83D632D919ECFCC4009E3161 /* Default.png */; };
83D632E119ECFCC4009E3161 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83D632DA19ECFCC4009E3161 /* Default@2x.png */; };
@ -56,6 +57,7 @@
50CCC827185E0E7D001F5BC8 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
50D00E8D19D117C400EBA71E /* cc_resampler_neon.S */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; name = cc_resampler_neon.S; path = ../../audio/resamplers/cc_resampler_neon.S; sourceTree = "<group>"; };
50E7189E184B88AA001956CE /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
696012F119F3389A006A1088 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
83D632D819ECFCC4009E3161 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
83D632D919ECFCC4009E3161 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; };
83D632DA19ECFCC4009E3161 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; };
@ -85,6 +87,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
696012F219F3389A006A1088 /* CoreText.framework in Frameworks */,
963C3C34186E3DED00A6EB1E /* GameController.framework in Frameworks */,
50CCC828185E0E7D001F5BC8 /* CoreLocation.framework in Frameworks */,
501881EE184BB54C006F665D /* CoreMedia.framework in Frameworks */,
@ -158,6 +161,7 @@
96AFAE2816C1D4EA009DE44C /* Frameworks */ = {
isa = PBXGroup;
children = (
696012F119F3389A006A1088 /* CoreText.framework */,
963C3C33186E3DED00A6EB1E /* GameController.framework */,
50CCC827185E0E7D001F5BC8 /* CoreLocation.framework */,
501881ED184BB54C006F665D /* CoreMedia.framework */,
@ -347,7 +351,7 @@
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
@ -404,8 +408,8 @@
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 2;
@ -452,10 +456,10 @@
96AFAE5516C1D4EA009DE44C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
armv7s,
armv7,
);
ARCHS = (
armv7s,
armv7,
);
CLANG_CXX_LIBRARY = "libstdc++";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist";
@ -505,13 +509,12 @@
96AFAE5616C1D4EA009DE44C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
armv7s,
armv7,
);
ARCHS = (
armv7s,
armv7,
);
CLANG_CXX_LIBRARY = "libstdc++";
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist";
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
@ -587,7 +590,7 @@
);
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
VALID_ARCHS = "armv7 armv7s";
WRAPPER_EXTENSION = app;
};

237
gfx/fonts/coretext.c Normal file
View File

@ -0,0 +1,237 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "fonts.h"
#include "../../file.h"
#include "../../general.h"
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>
#define CT_ATLAS_ROWS 8
#define CT_ATLAS_COLS 16
#define CT_ATLAS_SIZE (CT_ATLAS_ROWS * CT_ATLAS_COLS)
typedef struct coretext_renderer
{
struct font_atlas atlas;
struct font_glyph glyphs[CT_ATLAS_SIZE];
} font_renderer_t;
static const struct font_atlas *font_renderer_ct_get_atlas(void *data)
{
font_renderer_t *handle = (font_renderer_t*)data;
return &handle->atlas;
}
static const struct font_glyph *font_renderer_ct_get_glyph(void *data, uint32_t code)
{
font_renderer_t *handle = (font_renderer_t*)data;
struct font_glyph *result = code < CT_ATLAS_SIZE ? &handle->glyphs[code] : NULL;
return result;
}
static void font_renderer_ct_free(void *data)
{
font_renderer_t *handle = (font_renderer_t*)data;
if (!handle)
return;
free(handle->atlas.buffer);
free(handle);
}
static bool font_renderer_create_atlas(CTFontRef face, font_renderer_t *handle)
{
unsigned i;
bool ret = true;
UniChar characters[CT_ATLAS_SIZE] = {0};
for (i = 0; i < CT_ATLAS_SIZE; i++) {
characters[i] = (UniChar)i;
}
CGGlyph glyphs[CT_ATLAS_SIZE];
CTFontGetGlyphsForCharacters(face, characters, glyphs, CT_ATLAS_SIZE);
CGRect bounds[CT_ATLAS_SIZE];
CTFontGetBoundingRectsForGlyphs(face, kCTFontDefaultOrientation,
glyphs, bounds, CT_ATLAS_SIZE);
CGSize advances[CT_ATLAS_SIZE];
CTFontGetAdvancesForGlyphs(face, kCTFontDefaultOrientation,
glyphs, advances, CT_ATLAS_SIZE);
CGFloat ascent = CTFontGetAscent( face );
CGFloat descent = CTFontGetDescent( face );
int max_width = 0;
int max_height = 0;
for (i = 0; i < CT_ATLAS_SIZE; i++) {
struct font_glyph *glyph = &handle->glyphs[i];
int origin_x = ceil(bounds[i].origin.x);
int origin_y = ceil(bounds[i].origin.y);
glyph->draw_offset_x = 0;
glyph->draw_offset_y = -1 * (ascent - descent);
glyph->width = ceil(bounds[i].size.width);
glyph->height = ceil(bounds[i].size.height);
glyph->advance_x = ceil(advances[i].width);
glyph->advance_y = ceil(advances[i].height);
max_width = max(max_width, (origin_x + glyph->width));
max_height = max(max_height, (origin_y + glyph->height));
}
max_height = max(max_height, ceil(ascent+descent));
handle->atlas.width = max_width * CT_ATLAS_COLS;
handle->atlas.height = max_height * CT_ATLAS_ROWS;
handle->atlas.buffer = (uint8_t*)
calloc(handle->atlas.width * handle->atlas.height, 1);
if (!handle->atlas.buffer) {
ret = false;
goto end;
}
size_t bitsPerComponent = 8;
size_t bytesPerRow = max_width;
void *bitmapData = calloc(max_height, bytesPerRow);
CGContextRef offscreen =
CGBitmapContextCreate(bitmapData, max_width, max_height,
bitsPerComponent, bytesPerRow, NULL, kCGImageAlphaOnly);
CGContextSetTextMatrix(offscreen, CGAffineTransformIdentity);
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { face };
CFDictionaryRef attr =
CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
for (i = 0; i < CT_ATLAS_SIZE; i++) {
struct font_glyph *glyph = &handle->glyphs[i];
glyph->width = max_width;
glyph->height = max_height;
unsigned offset_x = (i % CT_ATLAS_COLS) * max_width;
unsigned offset_y = (i / CT_ATLAS_COLS) * max_height;
glyph->atlas_offset_x = offset_x;
glyph->atlas_offset_y = offset_y;
char glyph_cstr[2];
glyph_cstr[0] = i;
glyph_cstr[1] = 0;
CFStringRef glyph_cfstr =
CFStringCreateWithCString( NULL, glyph_cstr, kCFStringEncodingASCII );
CFAttributedStringRef attrString =
CFAttributedStringCreate(NULL, glyph_cfstr, attr);
CFRelease(glyph_cfstr), glyph_cfstr = NULL;
CTLineRef line = CTLineCreateWithAttributedString(attrString);
CFRelease(attrString), attrString = NULL;
memset( bitmapData, 0, max_height * bytesPerRow );
CGContextSetTextPosition(offscreen, 0, descent);
CTLineDraw(line, offscreen);
CGContextFlush( offscreen );
CFRelease( line ), line = NULL;
uint8_t *dst = (uint8_t*)handle->atlas.buffer;
const uint8_t *src = (const uint8_t*)bitmapData;
for (unsigned r = 0; r < max_height; r++ ) {
for (unsigned c = 0; c < max_width; c++) {
unsigned src_idx = r * bytesPerRow + c;
unsigned dest_idx =
(r + offset_y) * (CT_ATLAS_COLS * max_width) + (c + offset_x);
uint8_t v = src[src_idx];
dst[dest_idx] = v;
}
}
}
CFRelease(attr), attr = NULL;
CGContextRelease(offscreen), offscreen = NULL;
free(bitmapData);
end:
return ret;
}
static void *font_renderer_ct_init(const char *font_path, float font_size)
{
char err = 0;
CFStringRef cf_font_path = NULL;
CTFontRef face = NULL;
font_renderer_t *handle = (font_renderer_t*)
calloc(1, sizeof(*handle));
if (!handle) {
err = 1; goto error;
}
cf_font_path = CFStringCreateWithCString( NULL, font_path, kCFStringEncodingASCII );
if ( ! cf_font_path ) {
err = 1; goto error;
}
face = CTFontCreateWithName( cf_font_path, font_size, NULL );
if ( ! face ) {
err = 1; goto error;
}
if (! font_renderer_create_atlas(face, handle)) {
err = 1; goto error;
}
error:
if ( err ) {
font_renderer_ct_free(handle);
handle = NULL;
}
if ( cf_font_path ) {
CFRelease( cf_font_path ), cf_font_path = NULL ; }
if ( face ) {
CFRelease(face), face = NULL; }
return handle;
}
/* We can't tell if a font is going to be there until we actually
initialize CoreText and the best way to get fonts is by name, not
by path. */
static const char *default_font = "Verdana";
static const char *font_renderer_ct_get_default_font(void)
{
return default_font;
}
font_renderer_driver_t coretext_font_renderer = {
font_renderer_ct_init,
font_renderer_ct_get_atlas,
font_renderer_ct_get_glyph,
font_renderer_ct_free,
font_renderer_ct_get_default_font,
"coretext",
};

View File

@ -23,6 +23,9 @@
static const font_renderer_driver_t *font_backends[] = {
#ifdef HAVE_FREETYPE
&freetype_font_renderer,
#endif
#ifdef __APPLE__
&coretext_font_renderer,
#endif
&bitmap_font_renderer,
NULL

View File

@ -71,6 +71,7 @@ typedef struct font_renderer_driver
} font_renderer_driver_t;
extern font_renderer_driver_t freetype_font_renderer;
extern font_renderer_driver_t coretext_font_renderer;
extern font_renderer_driver_t bitmap_font_renderer;
/* font_path can be NULL for default font. */

View File

@ -259,6 +259,10 @@ FONTS
#include "../gfx/fonts/freetype.c"
#endif
#if defined(__APPLE__)
#include "../gfx/fonts/coretext.c"
#endif
#ifdef HAVE_OPENGL
#include "../gfx/fonts/gl_font.c"
#endif