Fix STR #2530 (Mac OS only). Implements a fast algorithm for fl_width() that memorizes the

width of all characters the first time they are seen and computes the width of a string
as the sum of the widths of its characters. Char widths are memorized in 256 blocks of 256 widths;
only blocks used in some text are allocated and computed. The width of characters beyond
U+FFFF is computed anew each time. Strings are drawn using core text, after having 
deactivated character kerning, so their width is the sum of the widths of their characters.
This is the same algorithm as used for WIN32.

git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@8305 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
This commit is contained in:
Manolo Gouy 2011-01-24 15:30:14 +00:00
parent 1b146a4837
commit f9363c16d0
2 changed files with 102 additions and 63 deletions

View File

@ -63,11 +63,11 @@ public:
ATSUTextLayout layout;
# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
CTFontRef fontref;
// the unicode span is divided in 256 blocks of 256 characters
float *width[256]; // array of arrays of character widths
# endif
ATSUStyle style;
short ascent, descent, q_width;
// short width[256];
// bool knowWidths;
char *q_name;
int size;
# elif USE_XFT

View File

@ -34,6 +34,7 @@ extern unsigned fl_utf8toUtf16(const char* src, unsigned srclen, unsigned short*
#define check_default_font() {if (!fl_fontsize) fl_font(0, 12);}
static CGAffineTransform font_mx = { 1, 0, 0, -1, 0, 0 };
static CFMutableDictionaryRef attributes = NULL;
Fl_Font_Descriptor::Fl_Font_Descriptor(const char* name, Fl_Fontsize Size) {
next = 0;
@ -59,7 +60,7 @@ if (fl_mac_os_version >= 0x1050) {//unfortunately, CTFontCreateWithName != NULL
CTFontGetAdvancesForGlyphs(fontref, kCTFontHorizontalOrientation, glyph, advances, 2);
w = advances[0].width;
if ( abs(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
// slightly rescale fixed-width fonts so the character width has an integral value
CFRelease(fontref);
CGFloat fsize = size / ( w/floor(w + 0.5) );
fontref = CTFontCreateWithName(str, fsize, NULL);
@ -69,7 +70,20 @@ if (fl_mac_os_version >= 0x1050) {//unfortunately, CTFontCreateWithName != NULL
ascent = (short)(CTFontGetAscent(fontref) + 0.5);
descent = (short)(CTFontGetDescent(fontref) + 0.5);
q_width = w + 0.5;
for (int i = 0; i < 256; i++) 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);
}
}
else {
#endif
#if ! __LP64__
@ -165,10 +179,12 @@ Fl_Font_Descriptor::~Fl_Font_Descriptor() {
*/
if (this == fl_fontsize) fl_fontsize = 0;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
if (fl_mac_os_version >= 0x1050) CFRelease(fontref);
#else
/* ATSUDisposeTextLayout(layout);
ATSUDisposeStyle(style); */
if (fl_mac_os_version >= 0x1050) {
CFRelease(fontref);
for (int i = 0; i < 256; i++) {
if (width[i]) free(width[i]);
}
}
#endif
}
@ -260,7 +276,24 @@ int fl_descent() {
else return -1;
}
double fl_width(const UniChar* txt, int n) {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
// returns width of a pair of UniChar's in the surrogate range
static CGFloat surrogate_width(const UniChar *txt)
{
CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, txt, 2, kCFAllocatorNull);
CTFontRef font2 = CTFontCreateForString(fl_fontsize->fontref, str, CFRangeMake(0,2));
CFRelease(str);
CGGlyph glyphs[2];
bool b = CTFontGetGlyphsForCharacters(font2, txt, glyphs, 2);
CGSize a;
if (b) CTFontGetAdvancesForGlyphs(font2, kCTFontHorizontalOrientation, glyphs, &a, 1);
else a.width = fl_fontsize->q_width;
CFRelease(font2);
return a.width;
}
#endif
static double fl_width(const UniChar* txt, int n) {
check_default_font();
if (!fl_fontsize) {
check_default_font(); // avoid a crash!
@ -269,38 +302,68 @@ double fl_width(const UniChar* txt, int n) {
}
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
if (fl_mac_os_version >= 0x1050) {
CTFontRef fontref = fl_fontsize->fontref;
CFStringRef str = CFStringCreateWithBytes(NULL, (const UInt8*)txt, n * sizeof(UniChar), kCFStringEncodingUTF16, false);
CFAttributedStringRef astr = CFAttributedStringCreate(NULL, str, NULL);
CFMutableAttributedStringRef mastr = CFAttributedStringCreateMutableCopy(NULL, 0, astr);
CFRelease(astr);
CFAttributedStringSetAttribute(mastr, CFRangeMake(0, CFStringGetLength(str)), kCTFontAttributeName, fontref);
CFRelease(str);
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
CFRelease(mastr);
double retval = CTLineGetTypographicBounds(ctline, NULL, NULL, NULL);
CFRelease(ctline);
return retval;
double retval = 0;
UniChar uni;
int i;
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);
i++; // because a pair of UniChar's represent a single character
continue;
}
unsigned int r = uni >> 8; // index of the character block containing uni
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_fontsize->q_name);
// allocate memory to hold width of each character in the block
fl_fontsize->width[r] = (float*) malloc(sizeof(float) * 0x100);
UniChar ii = r * 0x100;
CGGlyph glyph;
CGSize advance_size;
for (int j = 0; j < 0x100; j++) { // loop over the block
CTFontRef font2 = fl_fontsize->fontref;
bool must_release = false;
// ii spans all characters of this block
bool b = CTFontGetGlyphsForCharacters(font2, &ii, &glyph, 1);
if (!b) { // the current font doesn't contain this char
CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, &ii, 1, kCFAllocatorNull);
// find a font that contains it
font2 = CTFontCreateForString(font2, str, CFRangeMake(0,1));
must_release = true;
CFRelease(str);
b = CTFontGetGlyphsForCharacters(font2, &ii, &glyph, 1);
}
if (b) CTFontGetAdvancesForGlyphs(font2, kCTFontHorizontalOrientation, &glyph, &advance_size, 1);
else advance_size.width = 0.;
// the width of one character of this block of characters
fl_fontsize->width[r][j] = advance_size.width;
if (must_release) CFRelease(font2);
ii++;
}
}
// sum the widths of all characters of txt
retval += fl_fontsize->width[r][uni & 0xFF];
}
else {
return retval;
} else {
#endif
#if ! __LP64__
OSStatus err;
OSStatus err;
Fixed bBefore, bAfter, bAscent, bDescent;
ATSUTextLayout layout;
ByteCount iSize;
ATSUAttributeTag iTag;
ATSUAttributeValuePtr iValuePtr;
// Here's my ATSU text measuring attempt... This seems to do the Right Thing
// 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
// activate the current GC
iSize = sizeof(CGContextRef);
iTag = kATSUCGContextTag;
iValuePtr = &fl_gc;
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
// now measure the bounding box
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...
@ -310,7 +373,7 @@ else {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
}
#endif
return 0; // FIXME: I do not understand the shuffeling of the above ifdef's and why they are here!
return 0;
}
double fl_width(const char* txt, int n) {
@ -319,17 +382,13 @@ double fl_width(const char* txt, int n) {
return fl_width(uniStr, wc_len);
}
/*double fl_width(uchar c) {
return fl_width((const char*)(&c), 1);
}*/
double fl_width(unsigned int wc) {
UniChar uc = wc;
const UniChar uc = wc;
return fl_width(&uc, 1);
}
// text extent calculation
void fl_text_extents(const UniChar* txt, int n, int &dx, int &dy, int &w, int &h) {
void fl_text_extents(const char *str8, int n, int &dx, int &dy, int &w, int &h) {
if (!fl_fontsize) {
check_default_font(); // avoid a crash!
if (!fl_fontsize)
@ -339,12 +398,9 @@ void fl_text_extents(const UniChar* txt, int n, int &dx, int &dy, int &w, int &h
}
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
if (fl_mac_os_version >= 0x1050) {
CTFontRef fontref = fl_fontsize->fontref;
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)txt, n *sizeof(UniChar), kCFStringEncodingUTF16, false);
CFAttributedStringRef astr = CFAttributedStringCreate(NULL, str16, NULL);
CFMutableAttributedStringRef mastr = CFAttributedStringCreateMutableCopy(NULL, 0, astr);
CFRelease(astr);
CFAttributedStringSetAttribute(mastr, CFRangeMake(0, CFStringGetLength(str16)), kCTFontAttributeName, fontref);
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)str8, n, kCFStringEncodingUTF8, false);
CFDictionarySetValue (attributes, kCTFontAttributeName, fl_fontsize->fontref);
CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes);
CFRelease(str16);
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
CFRelease(mastr);
@ -376,6 +432,7 @@ else {
iValuePtr = &fl_gc;
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
// now measure the bounding box
UniChar *txt = mac_Utf8_to_Utf16(str8, n, &n);
err = ATSUSetTextPointerLocation(layout, txt, kATSUFromTextBeginning, n, n);
Rect bbox;
err = ATSUMeasureTextImage(layout, kATSUFromTextBeginning, n, 0, 0, &bbox);
@ -391,12 +448,6 @@ else {
return;
} // fl_text_extents
void fl_text_extents(const char *c, int n, int &dx, int &dy, int &w, int &h) {
int wc_len = n;
UniChar *uniStr = mac_Utf8_to_Utf16(c, n, &wc_len);
fl_text_extents(uniStr, wc_len, dx, dy, w, h);
} // fl_text_extents
void fl_draw(const char *str, int n, float x, float y);
@ -423,27 +474,14 @@ static CGColorRef flcolortocgcolor(Fl_Color i)
void fl_draw(const char *str, int n, float x, float y) {
// avoid a crash if no font has been selected by user yet !
check_default_font();
// convert to UTF-16 first
UniChar *uniStr = mac_Utf8_to_Utf16(str, n, &n);
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
if (fl_mac_os_version >= 0x1050) {
CFStringRef keys[2];
CFTypeRef values[2];
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)uniStr, n * sizeof(UniChar), kCFStringEncodingUTF16, false);
CFStringRef str16 = CFStringCreateWithBytes(NULL, (const UInt8*)str, n, kCFStringEncodingUTF8, false);
CGColorRef color = flcolortocgcolor(fl_color());
keys[0] = kCTFontAttributeName;
keys[1] = kCTForegroundColorAttributeName;
values[0] = fl_fontsize->fontref;
values[1] = color;
CFDictionaryRef attributes = CFDictionaryCreate(kCFAllocatorDefault,
(const void**)&keys,
(const void**)&values,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue (attributes, kCTFontAttributeName, fl_fontsize->fontref);
CFDictionarySetValue (attributes, kCTForegroundColorAttributeName, color);
CFAttributedStringRef mastr = CFAttributedStringCreate(kCFAllocatorDefault, str16, attributes);
CFRelease(str16);
CFRelease(attributes);
CFRelease(color);
CTLineRef ctline = CTLineCreateWithAttributedString(mastr);
CFRelease(mastr);
@ -453,8 +491,7 @@ void fl_draw(const char *str, int n, float x, float y) {
CTLineDraw(ctline, fl_gc);
CGContextSetShouldAntialias(fl_gc, false);
CFRelease(ctline);
}
else {
} else {
#endif
#if ! __LP64__
OSStatus err;
@ -466,6 +503,8 @@ void fl_draw(const char *str, int n, float x, float y) {
ATSUAttributeValuePtr iValuePtr=&fl_gc;
ATSUSetLayoutControls(layout, 1, &iTag, &iSize, &iValuePtr);
// convert to UTF-16 first
UniChar *uniStr = mac_Utf8_to_Utf16(str, n, &n);
err = ATSUSetTextPointerLocation(layout, uniStr, kATSUFromTextBeginning, n, n);
CGContextSetShouldAntialias(fl_gc, true);
err = ATSUDrawText(layout, kATSUFromTextBeginning, n, FloatToFixed(x), FloatToFixed(y));