#include #if !defined(GOSU_IS_IPHONE) && !defined(__LP64__) #include #include #include #include #include #include #include "../AppleUtility.hpp" #include #include #include #include #include std::wstring Gosu::defaultFontName() { // OF COURSE Helvetica is better - but the dots above my capital umlauts get // eaten when I use it with Gosu. Until this is fixed, keep Arial. (TODO) return L"Arial"; } namespace Gosu { std::vector wstringToUniChars(const std::wstring& ws); } namespace { class MacBitmap { std::tr1::uint32_t* buf; unsigned width, height; CGContextRef ctx; MacBitmap(const MacBitmap&); MacBitmap& operator=(const MacBitmap&); public: MacBitmap(std::tr1::uint32_t* buf, unsigned width, unsigned height) : buf(buf), width(width), height(height) { CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); ctx = CGBitmapContextCreate (buf, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease( colorSpace ); } ~MacBitmap() { CGContextRelease(ctx); } CGContextRef context() const { return ctx; } }; struct CachedFontInfo { ATSUFontID fontId; double heightAt1Pt; double descentAt1Pt; }; CachedFontInfo& getFont(const std::wstring& fontName) { static std::map fonts; if (fonts.count(fontName)) return fonts[fontName]; // Get reference to font, loaded from the system or a file. ATSFontRef atsRef; if (fontName.find(L"/") == std::wstring::npos) { // System font CFStringRef cfName = CFStringCreateWithCString(NULL, Gosu::narrow(fontName).c_str(), kCFStringEncodingASCII); atsRef = ATSFontFindFromName(cfName, kATSOptionFlagsDefault); if (!atsRef) throw std::runtime_error("Cannot find font " + Gosu::narrow(fontName)); CFRelease(cfName); } else { // Filename to font Gosu::Buffer buf; Gosu::loadFile(buf, fontName); ATSFontContainerRef container; CHECK_OS( ATSFontActivateFromMemory(buf.data(), buf.size(), kATSFontContextLocal, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &container) ); ATSFontRef fontRefs[1024]; ItemCount fontCount; CHECK_OS( ATSFontFindFromContainer(container, kATSOptionFlagsDefault, 1024, fontRefs, &fontCount) ); if (fontCount == 0) throw std::runtime_error("No font found in " + Gosu::narrow(fontName)); atsRef = fontRefs[0]; } // Calculate metrics (for space allocations) and create CachedFontInfo entry. CachedFontInfo newFont; newFont.fontId = (ATSUFontID)atsRef; ATSFontMetrics metrics; CHECK_OS(ATSFontGetHorizontalMetrics(newFont.fontId, kATSOptionFlagsDefault, &metrics)); newFont.heightAt1Pt = metrics.ascent - metrics.descent; newFont.descentAt1Pt = -metrics.descent; fonts[fontName] = newFont; return fonts[fontName]; } class ATSULayoutAndStyle { ATSUStyle style; ATSUTextLayout layout; std::vector utf16; // More like UCS-2-INTERNAL. template void setAttribute(ATSUAttributeTag tag, T value) { ByteCount size = sizeof value; ATSUAttributeValuePtr ptr = &value; CHECK_OS( ATSUSetAttributes(style, 1, &tag, &size, &ptr) ); } template void setLayoutControl(ATSUAttributeTag tag, T value) { ByteCount size = sizeof value; ATSUAttributeValuePtr ptr = &value; CHECK_OS( ATSUSetLayoutControls(layout, 1, &tag, &size, &ptr) ); } public: ATSULayoutAndStyle(const std::wstring& text, const std::wstring& fontName, unsigned fontHeightPx, unsigned fontFlags) { utf16 = Gosu::wstringToUniChars(text); CHECK_OS( ATSUCreateStyle(&style) ); CachedFontInfo& font = getFont(fontName); setAttribute(kATSUFontTag, font.fontId); setAttribute(kATSUSizeTag, X2Fix(fontHeightPx / font.heightAt1Pt)); if (fontFlags & Gosu::ffBold) setAttribute(kATSUQDBoldfaceTag, TRUE); if (fontFlags & Gosu::ffItalic) setAttribute(kATSUQDItalicTag, TRUE); if (fontFlags & Gosu::ffUnderline) setAttribute(kATSUQDUnderlineTag, TRUE); UniCharCount runLength = utf16.size(); CHECK_OS( ATSUCreateTextLayoutWithTextPtr(&utf16[0], kATSUFromTextBeginning, kATSUToTextEnd, utf16.size(), 1, &runLength, &style, &layout) ); } ~ATSULayoutAndStyle() { CHECK_OS( ATSUDisposeStyle(style) ); CHECK_OS( ATSUDisposeTextLayout(layout) ); } Rect textExtents() const { Rect rect; CHECK_OS( ATSUSetTransientFontMatching(layout, TRUE) ); CHECK_OS( ATSUMeasureTextImage(layout, kATSUFromTextBeginning, kATSUToTextEnd, X2Fix(0), X2Fix(0), &rect) ); return rect; } void drawToContext(Fixed x, Fixed y, CGContextRef context) { // Always draw in black - recoloring to white happens in drawText itself. // Reason: Text drawn using fallback fonts seems to always be black anyway :( RGBColor color = { 0, 0, 0 }; setAttribute(kATSUColorTag, color); setLayoutControl(kATSUCGContextTag, context); CHECK_OS( ATSUSetTransientFontMatching(layout, TRUE) ); CHECK_OS( ATSUDrawText(layout, kATSUFromTextBeginning, kATSUToTextEnd, x, y) ); } }; } unsigned Gosu::textWidth(const std::wstring& text, const std::wstring& fontName, unsigned fontHeight, unsigned fontFlags) { if (text.find_first_of(L"\r\n") != std::wstring::npos) throw std::invalid_argument("the argument to textWidth cannot contain line breaks"); // TODO: Why 1? if (text.empty()) return 1; // TODO: special case :( // TODO: Why? I guess it was empty otherwise? if (text == L" ") return fontHeight / 3; ATSULayoutAndStyle atlas(text, fontName, fontHeight, fontFlags); Rect rect = atlas.textExtents(); return rect.right + 1 - rect.left + 1; // add one pixel on OS X } void Gosu::drawText(Bitmap& bitmap, const std::wstring& text, int x, int y, Color c, const std::wstring& fontName, unsigned fontHeight, unsigned fontFlags) { if (text.find_first_of(L"\r\n") != std::wstring::npos) throw std::invalid_argument("the argument to drawText cannot contain line breaks"); if (text.empty()) return; CachedFontInfo& font = getFont(fontName); ATSULayoutAndStyle atlas(text, fontName, fontHeight, fontFlags); Rect rect = atlas.textExtents(); int width = rect.right + 1 - rect.left + 1; // add one pixel on OS X std::vector buf(width * fontHeight); { MacBitmap helper(&buf[0], width, fontHeight); atlas.drawToContext(X2Fix(-rect.left), X2Fix(fontHeight / font.heightAt1Pt * font.descentAt1Pt), helper.context()); } int effectiveWidth = Gosu::clamp(width, 0, bitmap.width() - x); int effectiveHeight = Gosu::clamp(fontHeight, 0, bitmap.height() - y); for (int relY = 0; relY < effectiveHeight; ++relY) for (int relX = 0; relX < effectiveWidth; ++relX) { #ifdef __BIG_ENDIAN__ Color::Channel alpha = buf[relY * width + relX]; #else Color::Channel alpha = Color(buf[relY * width + relX]).alpha(); #endif if (alpha != 0) bitmap.setPixel(x + relX, y + relY, multiply(c, Color(alpha, 0xff, 0xff, 0xff))); } } #endif