// // MacOS font selection routines for the Fast Light Tool Kit (FLTK). // // Copyright 1998-2026 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // https://www.fltk.org/COPYING.php // // Please see the following page on how to report bugs and issues: // // https://www.fltk.org/bugs.php // /* Implementation of support for two text drawing APIs: Core Text (current) and ATSU (legacy) The HAS_ATSU macro (defined in Fl_Quartz_Graphics_Driver.H) is true if and only if ATSU is available at compile time. The condition #if HAS_ATSU && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 is true if and only if both APIs are available at compile time. Depending on what MacOS SDK and what deployment target are used, the code can be compiled in 2 ways: 1) both APIs are available at compile time and which one is used is determined at runtime, or 2) only one API is available. When both APIs are compiled in, the choice of which one is used at runtime is done the first time the Fl_Quartz_Graphics_Driver constructor runs, and remains unchanged. Core Text is selected if the running Mac OS version is ≥ 10.5. The static function init_CoreText_or_ATSU() does this by setting the value of the class variable CoreText_or_ATSU to either use_CoreText or use_ATSU. If both APIs are available, several member functions come in groups of 3. For example, functions draw(), draw_CoreText() and draw_ATSU() are defined. The only task of draw() is to call either draw_CoreText() or draw_ATSU() depending on what API has been selected at program start. If the compilation condition authorizes a single API, one member function is defined instead of 3 in the other case. For example, a single draw() function is compiled, and contains the same code as what is called either draw_CoreText() or draw_ATSU() in the other case. The ADD_SUFFIX(name, suffix) macro is used so that each function has the short (e.g., draw) or long (e.g., draw_CoreText) name adequate for each compilation condition. The 2 most often used text functions are draw() and width(). Two pointers to member function are defined. Function init_CoreText_or_ATSU() assigns one with either draw_CoreText() or draw_ATSU(), and the other with either width_CoreText() or width_ATSU(). The draw() and width() functions only have to dereference one pointer to member function to call the adequate code. If the compilation condition authorizes a single text API, only the code related to this API (say, CoreText) is compiled whereas all code related to the other API (thus, ATSU) is excluded from compilation. Furthermore, none of the code to choose API at runtime and to select which member function is called is compiled. Consequently, no pointer to member function is used. The condition for both APIs to be compiled-in is - target i386 or ppc architectures and - use SDK ≥ 10.5 and < 10.11 and - set MacOS deployment target < 10.5 (through the -mmacosx-version-min= compilation option) */ #include "Fl_Quartz_Graphics_Driver.H" #include "Fl_Font.H" #include #include #include #include // for fl_utf8toUtf16() #include // fl_strdup() #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 const NSUInteger kCTFontOrientationHorizontal = kCTFontHorizontalOrientation; #endif Fl_Fontdesc* fl_fonts = NULL; static CGAffineTransform font_mx = { 1, 0, 0, -1, 0, 0 }; static int fl_free_font = FL_FREE_FONT; #if HAS_ATSU && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 # define ADD_SUFFIX(name, suffix) name##suffix #else # define ADD_SUFFIX(name, suffix) name #endif #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 static CFMutableDictionaryRef attributes = NULL; static Fl_Fontdesc built_in_table_PS[] = { // PostScript font names preferred when Mac OS ≥ 10.5 {"ArialMT"}, {"Arial-BoldMT"}, {"Arial-ItalicMT"}, {"Arial-BoldItalicMT"}, {"Courier"}, {"Courier-Bold"}, {"Courier-Oblique"}, {"Courier-BoldOblique"}, {"TimesNewRomanPSMT"}, {"TimesNewRomanPS-BoldMT"}, {"TimesNewRomanPS-ItalicMT"}, {"TimesNewRomanPS-BoldItalicMT"}, {"Symbol"}, {"Monaco"}, {"AndaleMono"}, // there is no bold Monaco font on standard Mac {"ZapfDingbatsITC"} }; #endif #if HAS_ATSU static Fl_Fontdesc built_in_table_full[] = { // full font names used before 10.5 {"Arial"}, {"Arial Bold"}, {"Arial Italic"}, {"Arial Bold Italic"}, {"Courier"}, {"Courier Bold"}, {"Courier New Italic"}, {"Courier New Bold Italic"}, {"Times New Roman"}, {"Times New Roman Bold"}, {"Times New Roman Italic"}, {"Times New Roman Bold Italic"}, {"Symbol"}, {"Monaco"}, {"Andale Mono"}, // there is no bold Monaco font on standard Mac {"Webdings"} }; #endif // Bug: older versions calculated the value for *ap as a side effect of // making the name, and then forgot about it. To avoid having to change // the header files I decided to store this value in the last character // of the font name array. #define ENDOFBUFFER sizeof(fl_fonts->fontname)-1 // turn a stored font name into a pretty name: const char* Fl_Quartz_Graphics_Driver::get_font_name(Fl_Font fnum, int* ap) { if (!fl_fonts) fl_fonts = calc_fl_fonts(); Fl_Fontdesc *f = fl_fonts + fnum; if (!f->fontname[0]) { this->set_fontname_in_fontdesc(f); const char* thisFont = f->name; if (!thisFont || !*thisFont) {if (ap) *ap = 0; return "";} int type = 0; if (strstr(f->name, "Bold")) type |= FL_BOLD; if (strstr(f->name, "Italic") || strstr(f->name, "Oblique")) type |= FL_ITALIC; f->fontname[ENDOFBUFFER] = (char)type; } if (ap) *ap = f->fontname[ENDOFBUFFER]; return f->fontname; } int Fl_Quartz_Graphics_Driver::get_font_sizes(Fl_Font fnum, int*& sizep) { static int array[128]; if (!fl_fonts) fl_fonts = calc_fl_fonts(); Fl_Fontdesc *s = fl_fonts+fnum; if (!s->name) s = fl_fonts; // empty slot in table, use entry 0 int cnt = 0; // ATS supports all font size array[0] = 0; sizep = array; cnt = 1; return cnt; } Fl_Quartz_Font_Descriptor::Fl_Quartz_Font_Descriptor(const char* name, Fl_Fontsize Size) : Fl_Font_Descriptor(name, Size) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 fontref = NULL; #endif #if HAS_ATSU layout = NULL; #endif Fl_Quartz_Graphics_Driver *driver = (Fl_Quartz_Graphics_Driver*)&Fl_Graphics_Driver::default_driver(); #if HAS_ATSU && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 if (Fl_Quartz_Graphics_Driver::CoreText_or_ATSU == Fl_Quartz_Graphics_Driver::use_ATSU) { driver->descriptor_init_ATSU(name, size, this); return; } driver->descriptor_init_CoreText(name, size, this); #else driver->descriptor_init(name, size, this); #endif } Fl_Quartz_Font_Descriptor::~Fl_Quartz_Font_Descriptor() { /* #if HAVE_GL // ++ todo: remove OpenGL font allocations // Delete list created by gl_draw(). This is not done by this code // as it will link in GL unnecessarily. There should be some kind // of "free" routine pointer, or a subclass? #endif */ if (this == fl_graphics_driver->font_descriptor()) fl_graphics_driver->font_descriptor(NULL); #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 if (fontref) { CFRelease(fontref); for (unsigned i = 0; i < sizeof(width)/sizeof(float*); i++) { if (width[i]) free(width[i]); } } #endif #if HAS_ATSU if (layout) { ATSUDisposeTextLayout(layout); ATSUDisposeStyle(style); } #endif } static UniChar *utfWbuf = 0; static unsigned utfWlen = 0; static UniChar *mac_Utf8_to_Utf16(const char *txt, int len, int *new_len) { unsigned wlen = fl_utf8toUtf16(txt, len, (unsigned short*)utfWbuf, utfWlen); if (wlen >= utfWlen) { utfWlen = wlen + 100; if (utfWbuf) free(utfWbuf); utfWbuf = (UniChar*)malloc((utfWlen)*sizeof(UniChar)); wlen = fl_utf8toUtf16(txt, len, (unsigned short*)utfWbuf, utfWlen); } *new_len = wlen; return utfWbuf; } static Fl_Font_Descriptor* find(Fl_Font fnum, Fl_Fontsize size) { if (!fl_fonts) fl_fonts = Fl_Graphics_Driver::default_driver().calc_fl_fonts(); Fl_Fontdesc* s = fl_fonts+fnum; if (!s->name) s = fl_fonts; // use 0 if fnum undefined Fl_Font_Descriptor* f; for (f = s->first; f; f = f->next) if (f->size == size) return f; f = new Fl_Quartz_Font_Descriptor(s->name, size); f->next = s->first; s->first = f; return f; } void Fl_Quartz_Graphics_Driver::font(Fl_Font fnum, Fl_Fontsize size) { if (fnum == -1) { Fl_Graphics_Driver::font(0, 0); return; } Fl_Graphics_Driver::font(fnum, size); font_descriptor( find(fnum, size) ); } Fl_Quartz_Font_Descriptor *Fl_Quartz_Graphics_Driver::valid_font_descriptor() { // avoid a crash if no font has been selected by user yet if (!font_descriptor()) font(FL_HELVETICA, FL_NORMAL_SIZE); return (Fl_Quartz_Font_Descriptor*)font_descriptor(); } int Fl_Quartz_Graphics_Driver::height() { Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); return fl_fontsize->ascent + fl_fontsize->descent; } int Fl_Quartz_Graphics_Driver::descent() { Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); return fl_fontsize->descent + 1; } void Fl_Quartz_Graphics_Driver::draw(const char* str, int n, int x, int y) { draw(str, n, (float)x, y+0.5f); } void Fl_Quartz_Graphics_Driver::draw(int angle, const char *str, int n, int x, int y) { CGContextSaveGState(gc_); CGContextTranslateCTM(gc_, x, y); CGContextRotateCTM(gc_, - angle*(M_PI/180) ); draw(str, n, 0, 0); CGContextRestoreGState(gc_); } void Fl_Quartz_Graphics_Driver::rtl_draw(const char* c, int n, int x, int y) { int dx, dy, w, h; text_extents(c, n, dx, dy, w, h); draw(c, n, x - w - dx, y); } double Fl_Quartz_Graphics_Driver::width(const char* txt, int n) { if (n == 0) return 0; int len1 = fl_utf8len1(*txt); if (len1 > 0 && n > len1) { // a text with several codepoints: compute its typographical width CFStringRef str = CFStringCreateWithBytes(NULL, (const UInt8*)txt, n, kCFStringEncodingUTF8, false); if (str) { CFDictionarySetValue(attributes, kCTFontAttributeName, valid_font_descriptor()->fontref); CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str, attributes); CFRelease(str); CTLineRef ctline = CTLineCreateWithAttributedString(mastr); CFRelease(mastr); double d = CTLineGetTypographicBounds(ctline, NULL, NULL, NULL); CFRelease(ctline); return d; } } int wc_len = n; UniChar *uniStr = mac_Utf8_to_Utf16(txt, n, &wc_len); return width(uniStr, wc_len); } double Fl_Quartz_Graphics_Driver::width(unsigned int wc) { UniChar utf16[3]; int l = 1; if (wc <= 0xFFFF) { *utf16 = wc; } else { l = (int)fl_ucs_to_Utf16(wc, utf16, 3); } return width(utf16, l); } #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 static void set_fontname_CoreText(Fl_Fontdesc *f) { CFStringRef cfname = CFStringCreateWithCString(NULL, f->name, kCFStringEncodingUTF8); CTFontRef ctfont = cfname ? CTFontCreateWithName(cfname, 0, NULL) : NULL; if (cfname) { CFRelease(cfname); cfname = NULL; } if (ctfont) { cfname = CTFontCopyFullName(ctfont); CFRelease(ctfont); if (cfname) { CFStringGetCString(cfname, f->fontname, ENDOFBUFFER, kCFStringEncodingUTF8); CFRelease(cfname); } } if (!cfname) strlcpy(f->fontname, f->name, ENDOFBUFFER); } #endif void Fl_Quartz_Graphics_Driver::set_fontname_in_fontdesc(Fl_Fontdesc *f) { #if HAS_ATSU && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 if (CoreText_or_ATSU == use_ATSU) strlcpy(f->fontname, f->name, ENDOFBUFFER); else set_fontname_CoreText(f); #elif HAS_ATSU strlcpy(f->fontname, f->name, ENDOFBUFFER); #else set_fontname_CoreText(f); #endif } const char *Fl_Quartz_Graphics_Driver::font_name(int num) { if (!fl_fonts) fl_fonts = calc_fl_fonts(); return fl_fonts[num].name; } void Fl_Quartz_Graphics_Driver::font_name(int num, const char *name) { Fl_Fontdesc *s = fl_fonts + num; if (s->name) { if (!strcmp(s->name, name)) {s->name = name; return;} for (Fl_Font_Descriptor* f = s->first; f;) { Fl_Font_Descriptor* n = f->next; delete f; f = n; } s->first = 0; } s->name = name; s->fontname[0] = 0; s->first = 0; } Fl_Fontdesc* Fl_Quartz_Graphics_Driver::calc_fl_fonts(void) { #if HAS_ATSU && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 if (CoreText_or_ATSU == use_ATSU) return built_in_table_full; return built_in_table_PS; #elif HAS_ATSU return built_in_table_full; #else return built_in_table_PS; #endif } #if HAS_ATSU && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 void Fl_Quartz_Graphics_Driver::draw(const char *str, int n, float x, float y) { (this->*CoreText_or_ATSU_draw)(str, n, x, y); } double Fl_Quartz_Graphics_Driver::width(const UniChar* txt, int n) { return (this->*CoreText_or_ATSU_width)(txt, n); } Fl_Font Fl_Quartz_Graphics_Driver::set_fonts(const char* xstarname) { if (CoreText_or_ATSU == use_ATSU) return set_fonts_ATSU(xstarname); return set_fonts_CoreText(xstarname); } void Fl_Quartz_Graphics_Driver::text_extents(const char* txt, int n, int& dx, int& dy, int& w, int& h) { if (CoreText_or_ATSU == use_ATSU) { text_extents_ATSU(txt, n, dx, dy, w, h); return; } text_extents_CoreText(txt, n, dx, dy, w, h); } #endif /// CoreText-based code #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 void Fl_Quartz_Graphics_Driver::ADD_SUFFIX(descriptor_init, _CoreText)(const char* name, Fl_Fontsize size, Fl_Quartz_Font_Descriptor *d) { CFStringRef str = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); d->fontref = CTFontCreateWithName(str, size, NULL); CGGlyph glyph[2]; const UniChar A[2]={'W','.'}; CTFontGetGlyphsForCharacters(d->fontref, A, glyph, 2); CGSize advances[2]; double w; CTFontGetAdvancesForGlyphs(d->fontref, kCTFontOrientationHorizontal, glyph, advances, 2); w = advances[0].width; if ( fabs(advances[0].width - advances[1].width) < 1E-2 ) {//this is a fixed-width font // slightly rescale fixed-width fonts so the character width has an integral value CFRelease(d->fontref); CGFloat fsize = size / ( w/floor(w + 0.5) ); d->fontref = CTFontCreateWithName(str, fsize, NULL); w = CTFontGetAdvancesForGlyphs(d->fontref, kCTFontOrientationHorizontal, glyph, NULL, 1); } CFRelease(str); d->ascent = (short)(CTFontGetAscent(d->fontref) + 0.5); d->descent = (short)(CTFontGetDescent(d->fontref) + 0.5); d->q_width = w + 0.5; for (unsigned i = 0; i < sizeof(d->width)/sizeof(float*); i++) d->width[i] = NULL; if (!attributes) { static CFNumberRef zero_ref; float zero = 0.; zero_ref = CFNumberCreate(NULL, kCFNumberFloat32Type, &zero); // deactivate kerning for all fonts, so that string width = sum of character widths // which allows fast fl_width() implementation. attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue (attributes, kCTKernAttributeName, zero_ref); } if (d->ascent == 0) { // this may happen with some third party fonts CFDictionarySetValue (attributes, kCTFontAttributeName, d->fontref); CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, CFSTR("Wj"), attributes); CTLineRef ctline = CTLineCreateWithAttributedString(mastr); CFRelease(mastr); CGFloat fascent, fdescent; CTLineGetTypographicBounds(ctline, &fascent, &fdescent, NULL); CFRelease(ctline); d->ascent = (short)(fascent + 0.5); d->descent = (short)(fdescent + 0.5); } } // returns width of a pair of UniChar's in the surrogate range static CGFloat surrogate_width(const UniChar *txt, Fl_Quartz_Font_Descriptor *fl_fontsize) { CTFontRef font2 = fl_fontsize->fontref; bool must_release = false; CGGlyph glyphs[2]; bool b = CTFontGetGlyphsForCharacters(font2, txt, glyphs, 2); CGSize a; if(!b) { // the current font doesn't contain this char CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, txt, 2, kCFAllocatorNull); // find a font that contains it font2 = CTFontCreateForString(font2, str, CFRangeMake(0,2)); must_release = true; CFRelease(str); b = CTFontGetGlyphsForCharacters(font2, txt, glyphs, 2); } if (b) CTFontGetAdvancesForGlyphs(font2, kCTFontOrientationHorizontal, glyphs, &a, 1); else a.width = fl_fontsize->q_width; if(must_release) CFRelease(font2); return a.width; } static CGFloat variation_selector_width(CFStringRef str16, Fl_Quartz_Font_Descriptor *fl_fontsize) { CGFloat retval; CFDictionarySetValue(attributes, kCTFontAttributeName, fl_fontsize->fontref); CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes); CTLineRef ctline = CTLineCreateWithAttributedString(mastr); CFRelease(mastr); retval = CTLineGetOffsetForStringIndex(ctline, 2, NULL); CFRelease(ctline); return retval; } double Fl_Quartz_Graphics_Driver::ADD_SUFFIX(width, _CoreText)(const UniChar* txt, int n) { double retval = 0; UniChar uni; int i; Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); for (i = 0; i < n; i++) { // loop over txt uni = txt[i]; if (uni >= 0xD800 && uni <= 0xDBFF) { // handles the surrogate range retval += surrogate_width(&txt[i], fl_fontsize); i++; // because a pair of UniChar's represent a single character continue; } if (i+1 < n && txt[i+1] >= 0xFE00 && txt[i+1] <= 0xFE0F) { // handles variation selectors CFStringRef substr = CFStringCreateWithCharacters(NULL, txt + i, 2); retval += variation_selector_width(substr, fl_fontsize); CFRelease(substr); i++; continue; } const int block = 0x10000 / (sizeof(fl_fontsize->width)/sizeof(float*)); // block size // r: index of the character block containing uni unsigned int r = uni >> 7; // change 7 if sizeof(width) is changed if (!fl_fontsize->width[r]) { // this character block has not been hit yet //fprintf(stderr,"r=%d size=%d name=%s\n",r,fl_fontsize->size,fl_fonts[fl_font()].name); // allocate memory to hold width of each character in the block fl_fontsize->width[r] = (float*) malloc(sizeof(float) * block); UniChar ii = r * block; CGSize advance_size; CGGlyph glyph; for (int j = 0; j < block; j++) { // loop over the block // ii spans all characters of this block bool b = CTFontGetGlyphsForCharacters(fl_fontsize->fontref, &ii, &glyph, 1); if (b) CTFontGetAdvancesForGlyphs(fl_fontsize->fontref, kCTFontOrientationHorizontal, &glyph, &advance_size, 1); else advance_size.width = -1e9; // calculate this later // the width of one character of this block of characters fl_fontsize->width[r][j] = advance_size.width; ii++; } } // sum the widths of all characters of txt double wdt = fl_fontsize->width[r][uni & (block-1)]; if (wdt == -1e9) { CGSize advance_size; CGGlyph glyph; CTFontRef font2 = fl_fontsize->fontref; bool must_release = false; bool b = CTFontGetGlyphsForCharacters(font2, &uni, &glyph, 1); if (!b) { // the current font doesn't contain this char CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, &uni, 1, kCFAllocatorNull); // find a font that contains it font2 = CTFontCreateForString(font2, str, CFRangeMake(0,1)); must_release = true; CFRelease(str); b = CTFontGetGlyphsForCharacters(font2, &uni, &glyph, 1); } if (b) CTFontGetAdvancesForGlyphs(font2, kCTFontOrientationHorizontal, &glyph, &advance_size, 1); else advance_size.width = 0.; // the width of the 'uni' character wdt = fl_fontsize->width[r][uni & (block-1)] = advance_size.width; if (must_release) CFRelease(font2); } retval += wdt; } return retval; } // text extent calculation void Fl_Quartz_Graphics_Driver::ADD_SUFFIX(text_extents, _CoreText)(const char *str8, int n, int &dx, int &dy, int &w, int &h) { Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); UniChar *txt = mac_Utf8_to_Utf16(str8, n, &n); CFStringRef str16 = CFStringCreateWithCharactersNoCopy(NULL, txt, n, kCFAllocatorNull); CFDictionarySetValue (attributes, kCTFontAttributeName, fl_fontsize->fontref); CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes); CFRelease(str16); CTLineRef ctline = CTLineCreateWithAttributedString(mastr); CFRelease(mastr); CGContextSetTextPosition(gc_, 0, 0); CGContextSetShouldAntialias(gc_, true); CGRect rect = CTLineGetImageBounds(ctline, gc_); CGContextSetShouldAntialias(gc_, false); CFRelease(ctline); dx = floor(rect.origin.x + 0.5); dy = floor(- rect.origin.y - rect.size.height + 0.5); w = rect.size.width + 0.5; h = rect.size.height + 0.5; } static CGColorRef flcolortocgcolor(Fl_Color i) { uchar r, g, b; Fl::get_color(i, r, g, b); CGFloat components[4] = {r/255.0f, g/255.0f, b/255.0f, 1.}; static CGColorSpaceRef cspace = NULL; if (cspace == NULL) { cspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); } return CGColorCreate(cspace, components); } void Fl_Quartz_Graphics_Driver::ADD_SUFFIX(draw, _CoreText)(const char *str, int n, float x, float y) { Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); // convert to UTF-16 first UniChar *uniStr = mac_Utf8_to_Utf16(str, n, &n); CGContextRef gc = (CGContextRef)this->gc(); CFMutableStringRef str16 = CFStringCreateMutableWithExternalCharactersNoCopy(NULL, uniStr, n, n, kCFAllocatorNull); if (str16 == NULL) return; // shd not happen CGColorRef color = flcolortocgcolor(this->color()); CFDictionarySetValue (attributes, kCTFontAttributeName, fl_fontsize->fontref); CFDictionarySetValue (attributes, kCTForegroundColorAttributeName, color); CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes); CFRelease(str16); CFRelease(color); CTLineRef ctline = CTLineCreateWithAttributedString(mastr); CFRelease(mastr); CGContextSetTextMatrix(gc, font_mx); CGContextSetTextPosition(gc, x, y); CGContextSetShouldAntialias(gc, true); CTLineDraw(ctline, gc); CGContextSetShouldAntialias(gc, false); CFRelease(ctline); } // Skip over bold/italic/oblique qualifiers part of PostScript font names // Example: // input: '-Regular_Light-Condensed' // return: '_Light-Condensed' // static char *skip(char *p, int& derived) { // 0 5 10 // | | | if (strncmp(p, "-BoldItalic", 11) == 0) { p += 11; derived = 3; } else if (strncmp(p, "-BoldOblique", 12) == 0) { p += 12; derived = 3; } else if (strncmp(p, "-Bold", 5) == 0) { p += 5; derived = 1; } else if (strncmp(p, "-Italic", 7) == 0) { p += 7; derived = 2; } else if (strncmp(p, "-Oblique", 8) == 0) { p += 8; derived = 2; } else if (strncmp(p, "-Regular", 8) == 0) { p += 8; } else if (strncmp(p, "-Roman", 6) == 0) { p += 6; } return p; } static int name_compare(const void *a, const void *b) { /* Compare PostScript font names. First compare font family names ignoring bold, italic and oblique qualifiers. When families are identical, order them according to regular, bold, italic, bolditalic. */ char *n1 = *(char**)a; char *n2 = *(char**)b; int derived1 = 0; int derived2 = 0; while (true) { if (*n1 == '-') n1 = skip(n1, derived1); if (*n2 == '-') n2 = skip(n2, derived2); if (*n1 < *n2) return -1; if (*n1 > *n2) return +1; if (*n1 == 0) { return derived1 - derived2; } n1++; n2++; } } Fl_Font Fl_Quartz_Graphics_Driver::ADD_SUFFIX(set_fonts, _CoreText)(const char* xstarname) { #pragma unused ( xstarname ) if (fl_free_font > FL_FREE_FONT) return (Fl_Font)fl_free_font; // if already called int value[1] = {1}; CFDictionaryRef dict = CFDictionaryCreate(NULL, (const void **)kCTFontCollectionRemoveDuplicatesOption, (const void **)&value, 1, NULL, NULL); CTFontCollectionRef fcref = CTFontCollectionCreateFromAvailableFonts(dict); CFRelease(dict); CFArrayRef arrayref = CTFontCollectionCreateMatchingFontDescriptors(fcref); CFRelease(fcref); CFIndex count = CFArrayGetCount(arrayref); CFIndex i; char **tabfontnames = new char*[count]; for (i = 0; i < count; i++) { CTFontDescriptorRef fdesc = (CTFontDescriptorRef)CFArrayGetValueAtIndex(arrayref, i); CTFontRef font = CTFontCreateWithFontDescriptor(fdesc, 0., NULL); CFStringRef cfname = CTFontCopyPostScriptName(font); CFRelease(font); CFDataRef cfdata = CFStringCreateExternalRepresentation(NULL, cfname, kCFStringEncodingUTF8, '?'); CFIndex l = CFDataGetLength(cfdata); tabfontnames[i] = (char*)malloc(l+1); // never free'ed memcpy(tabfontnames[i], CFDataGetBytePtr(cfdata), l); tabfontnames[i][l] = 0; CFRelease(cfdata); CFRelease(cfname); } CFRelease(arrayref); qsort(tabfontnames, count, sizeof(char*), name_compare); for (i = 0; i < count; i++) { Fl::set_font((Fl_Font)(fl_free_font++), tabfontnames[i]); } delete[] tabfontnames; return (Fl_Font)fl_free_font; } #endif // >= 10.5 /// ATSU-based code to support Mac OS < 10.5 #if HAS_ATSU void Fl_Quartz_Graphics_Driver::ADD_SUFFIX(descriptor_init, _ATSU)(const char* name, Fl_Fontsize size, Fl_Quartz_Font_Descriptor *d) { OSStatus err; // fill our structure with a few default values d->ascent = size*3/4.; d->descent = size-d->ascent; d->q_width = size*2/3.; // now we allocate everything needed to render text in this font later // get us the default layout and style err = ATSUCreateTextLayout(&d->layout); UniChar mTxt[2] = { 65, 0 }; err = ATSUSetTextPointerLocation(d->layout, mTxt, kATSUFromTextBeginning, 1, 1); err = ATSUCreateStyle(&d->style); err = ATSUSetRunStyle(d->layout, d->style, kATSUFromTextBeginning, kATSUToTextEnd); // now set the actual font, size and attributes. We also set the font matrix to // render our font up-side-down, so when rendered through our inverted CGContext, // text will appear normal again. Fixed fsize = IntToFixed(size); ATSUFontID fontID; ATSUFindFontFromName(name, strlen(name), kFontFullName, kFontMacintoshPlatform, kFontNoScriptCode, kFontEnglishLanguage, &fontID); // draw the font upside-down... Compensate for fltk/OSX origin differences ATSUAttributeTag sTag[] = { kATSUFontTag, kATSUSizeTag, kATSUFontMatrixTag }; ByteCount sBytes[] = { sizeof(ATSUFontID), sizeof(Fixed), sizeof(CGAffineTransform) }; ATSUAttributeValuePtr sAttr[] = { &fontID, &fsize, &font_mx }; if (fontID != kATSUInvalidFontID) err = ATSUSetAttributes(d->style, 1, sTag, sBytes, sAttr); // set the font attribute err = ATSUSetAttributes(d->style, 2, sTag + 1, sBytes + 1, sAttr + 1); // then the size and matrix attributes // next, make sure that Quartz will only render at integer coordinates ATSLineLayoutOptions llo = kATSLineUseDeviceMetrics | kATSLineDisableAllLayoutOperations; ATSUAttributeTag aTag[] = { kATSULineLayoutOptionsTag }; ByteCount aBytes[] = { sizeof(ATSLineLayoutOptions) }; ATSUAttributeValuePtr aAttr[] = { &llo }; err = ATSUSetLineControls (d->layout, kATSUFromTextBeginning, 1, aTag, aBytes, aAttr); // now we are finally ready to measure some letter to get the bounding box Fixed bBefore, bAfter, bAscent, bDescent; err = ATSUGetUnjustifiedBounds(d->layout, kATSUFromTextBeginning, 1, &bBefore, &bAfter, &bAscent, &bDescent); // Requesting a certain height font on Mac does not guarantee that ascent+descent // equal the requested height. fl_height will reflect the actual height that we got. // The font "Apple Chancery" is a pretty extreme example of overlapping letters. float fa = -FixedToFloat(bAscent), fd = -FixedToFloat(bDescent); if (fa>0.0f && fd>0.0f) { //float f = Size/(fa+fd); d->ascent = int(fa); //int(fa*f+0.5f); d->descent = int(fd); //Size - ascent; } int w = FixedToInt(bAfter); if (w) d->q_width = FixedToInt(bAfter); // Now, by way of experiment, try enabling Transient Font Matching, this will // cause ATSU to find a suitable font to render any chars the current font can't do... ATSUSetTransientFontMatching (d->layout, true); } void Fl_Quartz_Graphics_Driver::ADD_SUFFIX(draw, _ATSU)(const char *str, int n, float x, float y) { Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); // convert to UTF-16 first UniChar *uniStr = mac_Utf8_to_Utf16(str, n, &n); CGContextRef gc = (CGContextRef)this->gc(); OSStatus err; // now collect our ATSU resources ATSUTextLayout layout = fl_fontsize->layout; ByteCount iSize = sizeof(CGContextRef); ATSUAttributeTag iTag = kATSUCGContextTag; ATSUAttributeValuePtr iValuePtr=&gc; ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr); err = ATSUSetTextPointerLocation(layout, uniStr, kATSUFromTextBeginning, n, n); CGContextSetShouldAntialias(gc, true); err = ATSUDrawText(layout, kATSUFromTextBeginning, n, FloatToFixed(x), FloatToFixed(y)); CGContextSetShouldAntialias(gc, false); } double Fl_Quartz_Graphics_Driver::ADD_SUFFIX(width, _ATSU)(const UniChar* txt, int n) { OSStatus err; Fixed bBefore, bAfter, bAscent, bDescent; ATSUTextLayout layout; ByteCount iSize; ATSUAttributeTag iTag; ATSUAttributeValuePtr iValuePtr; Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); // Here's my ATSU text measuring attempt... This seems to do the Right Thing // now collect our ATSU resources and measure our text string layout = fl_fontsize->layout; // activate the current GC iSize = sizeof(CGContextRef); iTag = kATSUCGContextTag; CGContextRef value = (CGContextRef)fl_graphics_driver->gc(); iValuePtr = &value; ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr); // now measure the bounding box err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n); err = ATSUGetUnjustifiedBounds(layout, kATSUFromTextBeginning, n, &bBefore, &bAfter, &bAscent, &bDescent); // If err is OK then return length, else return 0. Or something... int len = FixedToInt(bAfter); return len; } void Fl_Quartz_Graphics_Driver::ADD_SUFFIX(text_extents, _ATSU)(const char *str8, int n, int &dx, int &dy, int &w, int &h) { Fl_Quartz_Font_Descriptor *fl_fontsize = valid_font_descriptor(); UniChar *txt = mac_Utf8_to_Utf16(str8, n, &n); OSStatus err; ATSUTextLayout layout; ByteCount iSize; ATSUAttributeTag iTag; ATSUAttributeValuePtr iValuePtr; // Here's my ATSU text measuring attempt... This seems to do the Right Thing // now collect our ATSU resources and measure our text string layout = fl_fontsize->layout; // activate the current GC iSize = sizeof(CGContextRef); iTag = kATSUCGContextTag; CGContextRef value = (CGContextRef)gc(); iValuePtr = &value; ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr); // now measure the bounding box err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n); Rect bbox; err = ATSUMeasureTextImage(layout, kATSUFromTextBeginning, n, 0, 0, &bbox); w = bbox.right - bbox.left; h = bbox.bottom - bbox.top; dx = bbox.left; dy = -bbox.bottom; //printf("r: %d l: %d t: %d b: %d w: %d h: %d\n", bbox.right, bbox.left, bbox.top, bbox.bottom, w, h); } Fl_Font Fl_Quartz_Graphics_Driver::ADD_SUFFIX(set_fonts, _ATSU)(const char* xstarname) { #pragma unused ( xstarname ) if (fl_free_font > FL_FREE_FONT) return (Fl_Font)fl_free_font; // if already called ItemCount oFontCount, oCountAgain; ATSUFontID *oFontIDs; // How many fonts? ATSUFontCount (&oFontCount); // now allocate space for them... oFontIDs = (ATSUFontID *)malloc((oFontCount+1) * sizeof(ATSUFontID)); ATSUGetFontIDs (oFontIDs, oFontCount, &oCountAgain); // Now oFontIDs should contain a list of all the available Unicode fonts // Iterate through the list to get each font name for (ItemCount idx = 0; idx < oFontCount; idx++) { // ByteCount actualLength = 0; // Ptr oName; // How to get the name - Apples docs say call this twice, once to get the length, then again // to get the actual name... // ATSUFindFontName (oFontIDs[idx], kFontFullName, kFontMacintoshPlatform, kFontRomanScript, kFontEnglishLanguage, // 0, NULL, &actualLength, NULL); // Now actualLength tells us the length of buffer we need // oName = (Ptr)malloc(actualLength + 8); // But who's got time for that nonsense? Let's just hard code a fixed buffer (urgh!) ByteCount actualLength = 511; char oName[512]; ATSUFindFontName (oFontIDs[idx], kFontFullName, kFontMacintoshPlatform, kFontRomanScript, kFontEnglishLanguage, actualLength, oName, &actualLength, &oCountAgain); // bounds check and terminate the returned name if(actualLength > 511) oName[511] = 0; else oName[actualLength] = 0; Fl::set_font((Fl_Font)(fl_free_font++), fl_strdup(oName)); // free(oName); } free(oFontIDs); return (Fl_Font)fl_free_font; } #endif // HAS_ATSU