#include <Gosu/Platform.hpp> #if defined(GOSU_IS_MAC) #include <Gosu/Text.hpp> #include <Gosu/Bitmap.hpp> #include <Gosu/Math.hpp> #include <Gosu/Utility.hpp> #include <cmath> #include <map> #if defined(GOSU_IS_IPHONE) #import <CoreGraphics/CoreGraphics.h> #import <UIKit/UIKit.h> typedef UIFont AppleFont; #else #import <AppKit/AppKit.h> typedef NSFont AppleFont; #endif // If a font is a filename, loads the font and returns its family name that can be used // like any system font. Otherwise, just returns the family name. static std::string normalize_font(const std::string& font_name) { #ifdef GOSU_IS_IPHONE // On iOS, we have no support for loading font files yet. However, if you register your fonts // via your app's Info.plist, you should be able to reference them by name. return font_name; #else static std::map<std::string, std::string> family_of_files; // Not a path name: It is already a family name. if (font_name.find("/") == font_name.npos) { return font_name; } // Already activated font & extracted family name. if (family_of_files.count(font_name) > 0) { return family_of_files[font_name]; } NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:font_name.c_str()] isDirectory:NO]; if (url == nullptr) { return family_of_files[font_name] = Gosu::default_font_name(); } CFURLRef url_ref = (__bridge CFURLRef) url; NSArray* descriptors = CFBridgingRelease(CTFontManagerCreateFontDescriptorsFromURL(url_ref)); if (descriptors.count < 1 || !CTFontManagerRegisterFontsForURL(url_ref, kCTFontManagerScopeProcess, nullptr)) { return family_of_files[font_name] = Gosu::default_font_name(); } CTFontDescriptorRef ref = (__bridge CTFontDescriptorRef) descriptors[0]; CFTypeRef family_name_ref = CTFontDescriptorCopyAttribute(ref, kCTFontFamilyNameAttribute); NSString* family_name = CFBridgingRelease(family_name_ref); return family_of_files[font_name] = family_name.UTF8String ?: ""; #endif } static AppleFont* get_font(std::string font_name, unsigned font_flags, double height) { font_name = normalize_font(font_name); static std::map<std::pair<std::string, std::pair<unsigned, double>>, AppleFont*> used_fonts; auto key = std::make_pair(font_name, std::make_pair(font_flags, height)); AppleFont* result = used_fonts[key]; if (!result) { NSString* name = [NSString stringWithUTF8String:font_name.c_str()]; #ifdef GOSU_IS_IPHONE result = [AppleFont fontWithName:name size:height]; #else NSFontDescriptor* desc = [[NSFontDescriptor fontDescriptorWithFontAttributes:nil] fontDescriptorWithFamily:name]; result = [NSFont fontWithDescriptor:desc size:height]; if (result && (font_flags & Gosu::FF_BOLD)) { result = [[NSFontManager sharedFontManager] convertFont:result toHaveTrait:NSFontBoldTrait]; } if (result && (font_flags & Gosu::FF_ITALIC)) { result = [[NSFontManager sharedFontManager] convertFont:result toHaveTrait:NSFontItalicTrait]; } #endif if (result == nullptr) { if (font_name != Gosu::default_font_name()) { result = get_font(Gosu::default_font_name(), 0, height); } else { throw std::runtime_error("Cannot load default font"); } } used_fonts[key] = result; } return result; } std::string Gosu::default_font_name() { return "Arial"; } #ifndef GOSU_IS_IPHONE static NSDictionary* attribute_dictionary(NSFont* font, unsigned font_flags) { auto underline_style = (font_flags & Gosu::FF_UNDERLINE) ? NSUnderlineStyleSingle : NSUnderlineStyleNone; return @{ NSFontAttributeName: font, NSForegroundColorAttributeName: [NSColor whiteColor], NSUnderlineStyleAttributeName: @(underline_style) }; } #endif unsigned Gosu::text_width(const std::string& text, const std::string& font_name, unsigned font_height, unsigned font_flags) { if (text.find_first_of("\r\n") != text.npos) { throw std::invalid_argument("text_width cannot handle line breaks"); } AppleFont* font = get_font(font_name, font_flags, font_height); // This will, of course, compute a too large size; font_height is in pixels, // the method expects point. NSString* string = [NSString stringWithUTF8String:text.c_str()]; #ifndef GOSU_IS_IPHONE NSDictionary* attributes = attribute_dictionary(font, font_flags); NSSize size = [string sizeWithAttributes:attributes]; #else CGSize size = [string sizeWithFont:font]; #endif // Now adjust the scaling... return ceil(size.width / size.height * font_height); } void Gosu::draw_text(Bitmap& bitmap, const std::string& text, int x, int y, Color c, const std::string& font_name, unsigned font_height, unsigned font_flags) { if (text.find_first_of("\r\n") != text.npos) { throw std::invalid_argument("the argument to draw_text cannot contain line breaks"); } AppleFont* font = get_font(font_name, font_flags, font_height); NSString* string = [NSString stringWithUTF8String:text.c_str()]; #ifndef GOSU_IS_IPHONE NSDictionary* attributes = attribute_dictionary(font, font_flags); NSSize size = [string sizeWithAttributes:attributes]; #else CGSize size = [string sizeWithFont:font]; #endif unsigned width = static_cast<unsigned>(round(size.width / size.height * font_height)); // Get the width and height of the image Bitmap bmp(width, font_height, 0x00ffffff); // Use a temporary context to draw the CGImage to the buffer. CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(bmp.data(), bmp.width(), bmp.height(), 8, bmp.width() * 4, color_space, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(color_space); #ifdef GOSU_IS_IPHONE CGFloat color[] = { 1.f, 1.f, 1.f, 0.f }; CGContextSetStrokeColor(context, color); CGContextSetFillColor(context, color); #endif // Use new font with proper size this time. font = get_font(font_name, font_flags, font_height * font_height / size.height); #ifdef GOSU_IS_IPHONE CGContextTranslateCTM(context, 0, font_height); CGContextScaleCTM(context, 1, -1); UIGraphicsPushContext(context); [string drawAtPoint:CGPointZero withFont:font]; UIGraphicsPopContext(); #else NSPoint NSPointZero = { 0, 0 }; attributes = attribute_dictionary(font, font_flags); [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:(void*)context flipped:false]]; [string drawAtPoint:NSPointZero withAttributes:attributes]; [NSGraphicsContext restoreGraphicsState]; #endif CGContextRelease(context); int effective_width = Gosu::clamp<int>(width, 0, bitmap.width() - x); int effective_height = Gosu::clamp<int>(font_height, 0, bitmap.height() - y); // Now copy the set pixels back. for (int rel_y = 0; rel_y < effective_height; ++rel_y) { for (int rel_x = 0; rel_x < effective_width; ++rel_x) { c.set_alpha(bmp.get_pixel(rel_x, rel_y).alpha()); if (c.alpha()) { bitmap.set_pixel(x + rel_x, y + rel_y, c); } } } } #endif