/* RetroArch - A frontend for libretro. * Copyright (C) 2014-2015 - Jay McCarthy * Copyright (C) 2011-2017 - Daniel De Matteis * * 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 . */ #include #include #include #include #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #ifdef IOS #include #include #else #include #endif #include #include "../font_driver.h" #define CT_ATLAS_ROWS 16 #define CT_ATLAS_COLS 16 #define CT_ATLAS_SIZE (CT_ATLAS_ROWS * CT_ATLAS_COLS) typedef struct coretext_atlas_slot { struct font_glyph glyph; unsigned charcode; unsigned last_used; struct coretext_atlas_slot *next; } coretext_atlas_slot_t; typedef struct coretext_renderer { struct font_atlas atlas; coretext_atlas_slot_t atlas_slots[CT_ATLAS_SIZE]; coretext_atlas_slot_t *uc_map[0x100]; struct font_line_metrics line_metrics; unsigned usage_counter; CTFontRef font_face; CFDictionaryRef attr_dict; /* Reused for all glyphs */ float cached_ascent; /* Cached font ascent */ } ct_font_renderer_t; static bool coretext_font_renderer_render_glyph(CTFontRef face, ct_font_renderer_t *handle, coretext_atlas_slot_t *slot, uint32_t charcode); static struct font_atlas *font_renderer_ct_get_atlas(void *data) { ct_font_renderer_t *handle = (ct_font_renderer_t*)data; if (!handle) return NULL; return &handle->atlas; } static coretext_atlas_slot_t* coretext_font_renderer_get_slot(ct_font_renderer_t *handle) { int i, map_id; unsigned oldest = 0; for (i = 1; i < CT_ATLAS_SIZE; i++) if ((handle->usage_counter - handle->atlas_slots[i].last_used) > (handle->usage_counter - handle->atlas_slots[oldest].last_used)) oldest = i; /* remove from map */ map_id = handle->atlas_slots[oldest].charcode & 0xFF; if (handle->uc_map[map_id] == &handle->atlas_slots[oldest]) handle->uc_map[map_id] = handle->atlas_slots[oldest].next; else if (handle->uc_map[map_id]) { coretext_atlas_slot_t* ptr = handle->uc_map[map_id]; while (ptr->next && ptr->next != &handle->atlas_slots[oldest]) ptr = ptr->next; ptr->next = handle->atlas_slots[oldest].next; } return &handle->atlas_slots[oldest]; } static const struct font_glyph *font_renderer_ct_get_glyph( void *data, uint32_t charcode) { unsigned map_id; coretext_atlas_slot_t *atlas_slot = NULL; ct_font_renderer_t *handle = (ct_font_renderer_t*)data; if (!handle) return NULL; map_id = charcode & 0xFF; atlas_slot = handle->uc_map[map_id]; while (atlas_slot) { if (atlas_slot->charcode == charcode) { atlas_slot->last_used = handle->usage_counter++; return &atlas_slot->glyph; } atlas_slot = atlas_slot->next; } /* Character not found, need to create it */ atlas_slot = coretext_font_renderer_get_slot(handle); atlas_slot->charcode = charcode; atlas_slot->next = handle->uc_map[map_id]; handle->uc_map[map_id] = atlas_slot; /* Render the glyph on demand */ if (handle->font_face) coretext_font_renderer_render_glyph(handle->font_face, handle, atlas_slot, charcode); atlas_slot->last_used = handle->usage_counter++; handle->atlas.dirty = true; return &atlas_slot->glyph; } static void font_renderer_ct_free(void *data) { ct_font_renderer_t *handle = (ct_font_renderer_t*)data; if (!handle) return; if (handle->font_face) { CFRelease(handle->font_face); handle->font_face = NULL; } if (handle->attr_dict) { CFRelease(handle->attr_dict); handle->attr_dict = NULL; } free(handle->atlas.buffer); free(handle); } static bool coretext_font_renderer_create_atlas(CTFontRef face, ct_font_renderer_t *handle, float font_size) { unsigned i, x, y; coretext_atlas_slot_t* slot = NULL; int max_glyph_size = (font_size < 0) ? -font_size : font_size; float ascent, descent; handle->atlas.width = max_glyph_size * CT_ATLAS_COLS; handle->atlas.height = max_glyph_size * CT_ATLAS_ROWS; handle->atlas.buffer = (uint8_t*)calloc( handle->atlas.width * handle->atlas.height, 1); if (!handle->atlas.buffer) return false; ascent = CTFontGetAscent(face); descent = CTFontGetDescent(face); /* Cache ascent for performance */ handle->cached_ascent = ascent; handle->line_metrics.ascender = ascent; handle->line_metrics.descender = (descent < 0.0f) ? (-1.0f * descent) : descent; handle->line_metrics.height = handle->line_metrics.ascender + handle->line_metrics.descender + (float)CTFontGetLeading(face); slot = handle->atlas_slots; for (y = 0; y < CT_ATLAS_ROWS; y++) { for (x = 0; x < CT_ATLAS_COLS; x++) { slot->glyph.atlas_offset_x = x * max_glyph_size; slot->glyph.atlas_offset_y = y * max_glyph_size; slot->glyph.width = max_glyph_size; slot->glyph.height = max_glyph_size; slot++; } } /* Pre-generate common ASCII characters */ for (i = 32; i < 128; i++) font_renderer_ct_get_glyph(handle, i); return true; } static bool coretext_font_renderer_render_glyph(CTFontRef face, ct_font_renderer_t *handle, coretext_atlas_slot_t *slot, uint32_t charcode) { CGGlyph glyph; CGRect bounds; CGSize advance; CGContextRef offscreen; void *bitmapData; UniChar character = (UniChar)charcode; CFStringRef glyph_cfstr; CFAttributedStringRef attrString; CTLineRef line; uint8_t *dst; const uint8_t *src; unsigned r, c; /* Get glyph for character */ bool has_glyph = CTFontGetGlyphsForCharacters(face, &character, &glyph, 1); /* If character is not available in font, render a missing glyph rectangle */ if (!has_glyph) { /* Draw rectangle directly in atlas buffer */ dst = (uint8_t*)handle->atlas.buffer + slot->glyph.atlas_offset_x + slot->glyph.atlas_offset_y * handle->atlas.width; /* Only draw rectangle if glyph is large enough and within atlas bounds */ if (slot->glyph.width >= 6 && slot->glyph.height >= 6) { unsigned max_r = slot->glyph.height - 2; unsigned max_c = slot->glyph.width - 2; int atlas_size = handle->atlas.width * handle->atlas.height; /* Draw 2-pixel border rectangle */ for (r = 2; r < max_r; r++) { int left_pos = r * handle->atlas.width + 2; int right_pos = r * handle->atlas.width + max_c - 1; if (left_pos < atlas_size && right_pos < atlas_size) { dst[left_pos] = 255; /* Left */ dst[right_pos] = 255; /* Right */ } } for (c = 2; c < max_c; c++) { int top_pos = 2 * handle->atlas.width + c; int bottom_pos = (max_r - 1) * handle->atlas.width + c; if (top_pos < atlas_size && bottom_pos < atlas_size) { dst[top_pos] = 255; /* Top */ dst[bottom_pos] = 255; /* Bottom */ } } } /* Set basic metrics using cached ascent */ slot->glyph.draw_offset_x = 0; slot->glyph.draw_offset_y = (int)floor(-handle->cached_ascent); slot->glyph.advance_x = slot->glyph.width; slot->glyph.advance_y = 0; return true; } CTFontGetBoundingRectsForGlyphs(face, #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 kCTFontOrientationDefault, #else kCTFontDefaultOrientation, #endif &glyph, &bounds, 1); CTFontGetAdvancesForGlyphs(face, #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 kCTFontOrientationDefault, #else kCTFontDefaultOrientation, #endif &glyph, &advance, 1); /* Set up glyph metrics using cached ascent */ slot->glyph.draw_offset_x = (int)ceil(bounds.origin.x); slot->glyph.draw_offset_y = (int)floor(-bounds.origin.y) - (int)floor(handle->cached_ascent) + 1; slot->glyph.advance_x = (int)round(advance.width); slot->glyph.advance_y = (int)round(advance.height); /* Create bitmap context */ bitmapData = calloc(slot->glyph.height, slot->glyph.width); offscreen = CGBitmapContextCreate(bitmapData, slot->glyph.width, slot->glyph.height, 8, slot->glyph.width, NULL, kCGImageAlphaOnly); if (!offscreen) { free(bitmapData); return false; } CGContextSetTextMatrix(offscreen, CGAffineTransformIdentity); /* Create string from Unicode character using cached dictionary */ glyph_cfstr = CFStringCreateWithCharacters(NULL, &character, 1); attrString = CFAttributedStringCreate(NULL, glyph_cfstr, handle->attr_dict); CFRelease(glyph_cfstr); line = CTLineCreateWithAttributedString(attrString); CFRelease(attrString); /* Render glyph */ CGContextSetTextPosition(offscreen, -bounds.origin.x, -bounds.origin.y); CTLineDraw(line, offscreen); CFRelease(line); /* Copy bitmap to atlas */ dst = (uint8_t*)handle->atlas.buffer + slot->glyph.atlas_offset_x + slot->glyph.atlas_offset_y * handle->atlas.width; src = (const uint8_t*)bitmapData; for (r = 0; r < slot->glyph.height; r++) for (c = 0; c < slot->glyph.width; c++) dst[r * handle->atlas.width + c] = src[r * slot->glyph.width + c]; CGContextRelease(offscreen); free(bitmapData); return true; } static void *font_renderer_ct_init(const char *font_path, float font_size) { char err = 0; CFStringRef cf_font_path = NULL; CTFontRef face = NULL; CFURLRef url = NULL; CGDataProviderRef dataProvider = NULL; CGFontRef theCGFont = NULL; ct_font_renderer_t *handle = (ct_font_renderer_t*)calloc(1, sizeof(*handle)); if (!handle || !path_is_valid(font_path)) { err = 1; goto error; } if (!(cf_font_path = CFStringCreateWithCString( NULL, font_path, kCFStringEncodingASCII))) { err = 1; goto error; } url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, cf_font_path, kCFURLPOSIXPathStyle, false); dataProvider = CGDataProviderCreateWithURL(url); theCGFont = CGFontCreateWithDataProvider(dataProvider); face = CTFontCreateWithGraphicsFont(theCGFont, font_size, NULL, NULL); if (!face) { err = 1; goto error; } /* Store the font face for on-demand glyph rendering */ handle->font_face = face; CFRetain(face); /* Create reusable attribute dictionary for performance */ { CFTypeRef values[1] = {face}; CFStringRef keys[1] = {kCTFontAttributeName}; handle->attr_dict = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } if (!coretext_font_renderer_create_atlas(face, handle, font_size)) { 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; } if (url) { CFRelease(url); url = NULL; } if (dataProvider) { CFRelease(dataProvider); dataProvider = NULL; } if (theCGFont) { CFRelease(theCGFont); theCGFont = NULL; } return handle; } static const char *font_renderer_ct_get_default_font(void) { /* 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. */ return "Verdana"; } static void font_renderer_ct_get_line_metrics( void* data, struct font_line_metrics **metrics) { ct_font_renderer_t *handle = (ct_font_renderer_t*)data; *metrics = &handle->line_metrics; } 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, "font_renderer_ct", font_renderer_ct_get_line_metrics };