Simplified text drawing on OS X by using CTFramesetter exclusively.

This commit is contained in:
Pietro Gagliardi 2016-01-16 13:34:22 -05:00
parent 37f9bfd1b9
commit 9c6c16be53
2 changed files with 75 additions and 51 deletions

View File

@ -526,45 +526,54 @@ void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
layout->width = width; layout->width = width;
} }
struct framesetter {
CTFramesetterRef fs;
CFMutableDictionaryRef frameAttrib;
CGSize extents;
};
// TODO CTFrameProgression for RTL/LTR // TODO CTFrameProgression for RTL/LTR
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
static void mkFramesetter(uiDrawTextLayout *layout, struct framesetter *fs)
{
CFRange fitRange;
CGFloat width;
fs->fs = CTFramesetterCreateWithAttributedString(layout->mas);
if (fs->fs == NULL)
complain("error creating CTFramesetter object in mkFramesetter()");
// TODO kCTFramePathWidthAttributeName?
fs->frameAttrib = NULL;
width = layout->width;
if (width < 0)
width = CGFLOAT_MAX;
// TODO these seem to be floor()'d or truncated?
fs->extents = CTFramesetterSuggestFrameSizeWithConstraints(fs->fs,
CFRangeMake(0, 0),
fs->frameAttrib,
CGSizeMake(width, CGFLOAT_MAX),
&fitRange); // not documented as accepting NULL
}
static void freeFramesetter(struct framesetter *fs)
{
if (fs->frameAttrib != NULL)
CFRelease(fs->frameAttrib);
CFRelease(fs->fs);
}
// TODO document that the extent width can be greater than the requested width if the requested width is small enough that only one character can fit // TODO document that the extent width can be greater than the requested width if the requested width is small enough that only one character can fit
// TODO figure out how line separation and leading plays into this // TODO figure out how line separation and leading plays into this
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
{ {
CTLineRef line; struct framesetter fs;
CTFramesetterRef fs;
CGSize size;
CFRange fitRange;
double ascent, descent;
double w;
// one line mkFramesetter(layout, &fs);
// TODO actually could not constraining the width of the framesetter do the same thing? *width = fs.extents.width;
if (layout->width < 0) { *height = fs.extents.height;
line = CTLineCreateWithAttributedString(layout->mas); freeFramesetter(&fs);
if (line == NULL)
complain("error creating CTLine object in uiDrawTextLayoutExtents()");
w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
// though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error
CFRelease(line);
*width = w;
*height = ascent + descent;
return;
}
// multiple lines
fs = CTFramesetterCreateWithAttributedString(layout->mas);
if (fs == NULL)
complain("error creating CTFramesetter object in uiDrawTextLayoutExtents()");
size = CTFramesetterSuggestFrameSizeWithConstraints(fs,
CFRangeMake(0, 0),
NULL, // TODO kCTFramePathWidthAttributeName?
CGSizeMake(layout->width, CGFLOAT_MAX),
&fitRange); // not documented as accepting NULL
CFRelease(fs);
*width = size.width;
*height = size.height;
} }
// Core Text doesn't draw onto a flipped view correctly; we have to do this // Core Text doesn't draw onto a flipped view correctly; we have to do this
@ -583,29 +592,34 @@ static void prepareContextForText(CGContextRef c, CGFloat cheight, double *y)
void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout) void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout)
{ {
CTLineRef line; struct framesetter fs;
CGFloat yoff; CGRect rect;
CGPathRef path;
// TODO CTFrameRef frame;
if (layout->width < 0) {}
else return;
prepareContextForText(c, cheight, &y); prepareContextForText(c, cheight, &y);
mkFramesetter(layout, &fs);
line = CTLineCreateWithAttributedString(layout->mas); // oh, and since we're flipped, y is the bottom-left coordinate of the rectangle, not the top-left
if (line == NULL) // since we are flipped, we subtract
complain("error creating CTLine object in uiDrawText()"); y -= fs.extents.height;
// CGContextSetTextPosition() positions at the baseline; we need the top-left corner instead rect.origin = CGPointMake(x, y);
CTLineGetTypographicBounds(line, &yoff, NULL, NULL); rect.size = fs.extents;
// remember that we're flipped, so we subtract path = CGPathCreateWithRect(rect, NULL);
y -= yoff;
CGContextSetTextPosition(c, x, y);
// and now we can FINALLY draw the line frame = CTFramesetterCreateFrame(fs.fs,
CTLineDraw(line, c); CFRangeMake(0, 0),
CFRelease(line); path,
fs.frameAttrib);
if (frame == NULL)
complain("error creating CTFrame object in doDrawText()");
CTFrameDraw(frame, c);
CFRelease(frame);
CFRelease(path);
freeFramesetter(&fs);
CGContextRestoreGState(c); CGContextRestoreGState(c);
} }
@ -613,7 +627,17 @@ void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextL
// TODO keep this for TODO and documentation purposes // TODO keep this for TODO and documentation purposes
#if 0 #if 0
w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
// though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error
CFRelease(line);
// TODO provide a way to get the image bounds as a separate function later // TODO provide a way to get the image bounds as a separate function later
bounds = CTLineGetImageBounds(line, c); bounds = CTLineGetImageBounds(line, c);
// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error // though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
// remember that we're flipped, so we subtract
y -= yoff;
CGContextSetTextPosition(c, x, y);
#endif #endif

View File

@ -120,11 +120,11 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp)
uiDrawFreeTextLayout(layout); uiDrawFreeTextLayout(layout);
layout = uiDrawNewTextLayout("This is a second line", font, -1); layout = uiDrawNewTextLayout("This is a second line", font, -1);
if (/*TODO*/entryDouble(textWidth) < 0) { if (/*TODO reuse width*/entryDouble(textWidth) < 0) {
double ad; double ad;
ad = metrics.Ascent + metrics.Descent; ad = metrics.Ascent + metrics.Descent;
printf("ad:%g extent:%g", ad, height); printf("ad:%g extent:%g\n", ad, height);
} }
ypos += height; ypos += height;
if (uiCheckboxChecked(addLeading)) if (uiCheckboxChecked(addLeading))