Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

516 lines (406 sloc) 11.413 kb
//
// DTCoreTextGlyphRun.m
// DTCoreText
//
// Created by Oliver Drobnik on 1/25/11.
// Copyright 2011 Drobnik.com. All rights reserved.
//
#import "DTCoreTextGlyphRun.h"
#import "DTCoreTextLayoutLine.h"
#import "DTTextAttachment.h"
#import "DTCoreTextConstants.h"
#import "DTCoreTextParagraphStyle.h"
#import "DTCoreTextFunctions.h"
#import "NSDictionary+DTCoreText.h"
@interface DTCoreTextGlyphRun ()
@property (nonatomic, assign) CGRect frame;
@property (nonatomic, assign) NSInteger numberOfGlyphs;
@property (nonatomic, unsafe_unretained, readwrite) NSDictionary *attributes;
@end
@implementation DTCoreTextGlyphRun
{
CTRunRef _run;
CGRect _frame;
CGFloat _offset; // x distance from line origin
CGFloat _ascent;
CGFloat _descent;
CGFloat _leading;
CGFloat _width;
BOOL _writingDirectionIsRightToLeft;
BOOL _isTrailingWhitespace;
NSInteger _numberOfGlyphs;
const CGPoint *_glyphPositionPoints;
__unsafe_unretained DTCoreTextLayoutLine *_line; // retain cycle, since these objects are retained by the _line
__unsafe_unretained NSDictionary *_attributes;
NSArray *_stringIndices;
DTTextAttachment *_attachment;
BOOL _hyperlink;
BOOL _didCheckForAttachmentInAttributes;
BOOL _didCheckForHyperlinkInAttributes;
BOOL _didCalculateMetrics;
BOOL _didDetermineTrailingWhitespace;
}
- (id)initWithRun:(CTRunRef)run layoutLine:(DTCoreTextLayoutLine *)layoutLine offset:(CGFloat)offset
{
self = [super init];
if (self)
{
_run = run;
CFRetain(_run);
_offset = offset;
_line = layoutLine;
}
return self;
}
- (void)dealloc
{
if (_run)
{
CFRelease(_run);
}
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@ glyphs=%d %@>", [self class], [self numberOfGlyphs], NSStringFromCGRect(_frame)];
}
#pragma mark - Drawing
- (void)drawInContext:(CGContextRef)context
{
if (!_run || !context)
{
return;
}
CGAffineTransform textMatrix = CTRunGetTextMatrix(_run);
if (CGAffineTransformIsIdentity(textMatrix))
{
CTRunDraw(_run, context, CFRangeMake(0, 0));
}
else
{
CGPoint pos = CGContextGetTextPosition(context);
// set tx and ty to current text pos according to docs
textMatrix.tx = pos.x;
textMatrix.ty = pos.y;
CGContextSetTextMatrix(context, textMatrix);
CTRunDraw(_run, context, CFRangeMake(0, 0));
// restore identity
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
}
}
- (void)drawDecorationInContext:(CGContextRef)context
{
// get the scaling factor of the current translation matrix
CGAffineTransform ctm = CGContextGetCTM(context);
CGFloat contentScale = ctm.a; // needed for rounding operations
CGFloat smallestPixelWidth = 1.0f/contentScale;
DTColor *backgroundColor = [_attributes backgroundColor];
// -------------- Line-Out, Underline, Background-Color
BOOL drawStrikeOut = [[_attributes objectForKey:DTStrikeOutAttribute] boolValue];
BOOL drawUnderline = [[_attributes objectForKey:(id)kCTUnderlineStyleAttributeName] boolValue];
if (drawStrikeOut||drawUnderline||backgroundColor)
{
// calculate area covered by non-whitespace
CGRect lineFrame = _line.frame;
lineFrame.size.width -= _line.trailingWhitespaceWidth;
// exclude trailing whitespace so that we don't underline too much
CGRect runStrokeBounds = CGRectIntersection(lineFrame, self.frame);
NSInteger superscriptStyle = [[_attributes objectForKey:(id)kCTSuperscriptAttributeName] integerValue];
switch (superscriptStyle)
{
case 1:
{
runStrokeBounds.origin.y -= _ascent * 0.47f;
break;
}
case -1:
{
runStrokeBounds.origin.y += _ascent * 0.25f;
break;
}
default:
break;
}
if (backgroundColor)
{
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, runStrokeBounds);
}
if (drawStrikeOut || drawUnderline)
{
CGContextSaveGState(context);
CTFontRef usedFont = (__bridge CTFontRef)([_attributes objectForKey:(id)kCTFontAttributeName]);
CGFloat fontUnderlineThickness;
if (usedFont)
{
fontUnderlineThickness = CTFontGetUnderlineThickness(usedFont);
}
else
{
fontUnderlineThickness = smallestPixelWidth;
}
CGFloat usedUnderlineThickness = DTCeilWithContentScale(fontUnderlineThickness, contentScale);
CGContextSetLineWidth(context, usedUnderlineThickness);
if (drawStrikeOut)
{
CGFloat y;
if (usedFont)
{
CGFloat strokePosition = CTFontGetXHeight(usedFont)/(CGFloat)2.0;
y = DTRoundWithContentScale(runStrokeBounds.origin.y + _ascent - strokePosition, contentScale);
}
else
{
y = DTRoundWithContentScale((runStrokeBounds.origin.y + self.frame.size.height/2.0f + 1), contentScale);
}
if ((int)(usedUnderlineThickness/smallestPixelWidth)%2) // odd line width
{
y += smallestPixelWidth/2.0f; // shift down half a pixel to avoid aliasing
}
CGContextMoveToPoint(context, runStrokeBounds.origin.x, y);
CGContextAddLineToPoint(context, runStrokeBounds.origin.x + runStrokeBounds.size.width, y);
}
if (drawUnderline)
{
CGFloat y;
if (usedFont)
{
CGFloat underlinePosition = CTFontGetUnderlinePosition(usedFont);
y = DTRoundWithContentScale(runStrokeBounds.origin.y + runStrokeBounds.size.height - _descent - underlinePosition - fontUnderlineThickness/2.0f, contentScale);
}
else
{
y = DTRoundWithContentScale((runStrokeBounds.origin.y + runStrokeBounds.size.height - self.descent + 1.0f), contentScale);
}
if ((int)(usedUnderlineThickness/smallestPixelWidth)%2) // odd line width
{
y += smallestPixelWidth/2.0f; // shift down half a pixel to avoid aliasing
}
CGContextMoveToPoint(context, runStrokeBounds.origin.x, y);
CGContextAddLineToPoint(context, runStrokeBounds.origin.x + runStrokeBounds.size.width, y);
}
CGContextStrokePath(context);
CGContextRestoreGState(context); // restore antialiasing
}
}
}
- (CGPathRef)newPathWithGlyphs
{
NSDictionary *attributes = self.attributes;
CTFontRef font = (__bridge CTFontRef)[attributes objectForKey:(id)kCTFontAttributeName];
if (!font)
{
NSLog(@"CTFont missing on %@", self);
return NULL;
}
const CGGlyph *glyphs = CTRunGetGlyphsPtr(_run);
const CGPoint *positions = CTRunGetPositionsPtr(_run);
CGMutablePathRef mutablePath = CGPathCreateMutable();
for (NSUInteger i = 0; i < CTRunGetGlyphCount(_run); i++)
{
CGGlyph glyph = glyphs[i];
CGPoint position = positions[i];
CGAffineTransform glyphTransform = CTRunGetTextMatrix(_run);
glyphTransform = CGAffineTransformScale(glyphTransform, 1, -1);
CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, &glyphTransform);
CGAffineTransform posTransform = CGAffineTransformMakeTranslation(position.x, position.y);
CGPathAddPath(mutablePath, &posTransform, glyphPath);
CGPathRelease(glyphPath);
}
return mutablePath;
}
#pragma mark - Calculations
- (void)calculateMetrics
{
// calculate metrics
@synchronized(self)
{
if (!_didCalculateMetrics)
{
_width = (CGFloat)CTRunGetTypographicBounds((CTRunRef)_run, CFRangeMake(0, 0), &_ascent, &_descent, &_leading);
_didCalculateMetrics = YES;
}
}
}
- (CGRect)frameOfGlyphAtIndex:(NSInteger)index
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
if (!_glyphPositionPoints)
{
// this is a pointer to the points inside the run, thus no retain necessary
_glyphPositionPoints = CTRunGetPositionsPtr(_run);
}
if (!_glyphPositionPoints || index >= self.numberOfGlyphs)
{
return CGRectNull;
}
CGPoint glyphPosition = _glyphPositionPoints[index];
CGRect rect = CGRectMake(_line.baselineOrigin.x + glyphPosition.x, _line.baselineOrigin.y - _ascent, _offset + _width - glyphPosition.x, _ascent + _descent);
if (index < self.numberOfGlyphs-1)
{
rect.size.width = _glyphPositionPoints[index+1].x - glyphPosition.x;
}
return rect;
}
// TODO: fix indices if the stringRange is modified
- (NSArray *)stringIndices
{
if (!_stringIndices)
{
const CFIndex *indices = CTRunGetStringIndicesPtr(_run);
NSInteger count = self.numberOfGlyphs;
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
NSInteger i;
for (i = 0; i < count; i++)
{
[array addObject:[NSNumber numberWithInteger:indices[i]]];
}
_stringIndices = array;
}
return _stringIndices;
}
// bounds of an image encompassing the entire run
- (CGRect)imageBoundsInContext:(CGContextRef)context
{
return CTRunGetImageBounds(_run, context, CFRangeMake(0, 0));
}
// range of the characters from the original string
- (NSRange)stringRange
{
if (!_stringRange.length)
{
CFRange range = CTRunGetStringRange(_run);
_stringRange = NSMakeRange(range.location, range.length);
}
return _stringRange;
}
- (void)fixMetricsFromAttachment
{
if (self.attachment)
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
_descent = 0;
_ascent = self.attachment.displaySize.height;
}
}
- (BOOL)isTrailingWhitespace
{
if (_didDetermineTrailingWhitespace)
{
return _isTrailingWhitespace;
}
if (self == [[_line glyphRuns] lastObject])
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
// this is trailing whitespace if it matches the lines's trailing whitespace
if (_line.trailingWhitespaceWidth >= _width)
{
_isTrailingWhitespace = YES;
}
}
_didDetermineTrailingWhitespace = YES;
return _isTrailingWhitespace;
}
#pragma mark Properites
- (NSInteger)numberOfGlyphs
{
if (!_numberOfGlyphs)
{
_numberOfGlyphs = CTRunGetGlyphCount(_run);
}
return _numberOfGlyphs;
}
- (NSDictionary *)attributes
{
if (!_attributes)
{
_attributes = (__bridge NSDictionary *)CTRunGetAttributes(_run);
}
return _attributes;
}
- (DTTextAttachment *)attachment
{
if (!_attachment)
{
if (!_didCheckForAttachmentInAttributes)
{
_attachment = [self.attributes objectForKey:NSAttachmentAttributeName];
_didCheckForAttachmentInAttributes = YES;
}
}
return _attachment;
}
- (BOOL)isHyperlink
{
if (!_hyperlink)
{
if (!_didCheckForHyperlinkInAttributes)
{
_hyperlink = [self.attributes objectForKey:DTLinkAttribute]!=nil;
_didCheckForHyperlinkInAttributes = YES;
}
}
return _hyperlink;
}
- (CGRect)frame
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
return CGRectMake(_line.baselineOrigin.x + _offset, _line.baselineOrigin.y - _ascent, _width, _ascent + _descent);
}
- (CGFloat)width
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
return _width;
}
- (CGFloat)ascent
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
return _ascent;
}
- (CGFloat)descent
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
return _descent;
}
- (CGFloat)leading
{
if (!_didCalculateMetrics)
{
[self calculateMetrics];
}
return _leading;
}
- (BOOL)writingDirectionIsRightToLeft
{
CTRunStatus status = CTRunGetStatus(_run);
return (status & kCTRunStatusRightToLeft)!=0;
}
@synthesize frame = _frame;
@synthesize numberOfGlyphs = _numberOfGlyphs;
@synthesize attributes = _attributes;
@synthesize ascent = _ascent;
@synthesize descent = _descent;
@synthesize leading = _leading;
@synthesize attachment = _attachment;
@synthesize writingDirectionIsRightToLeft = _writingDirectionIsRightToLeft;
@end
Jump to Line
Something went wrong with that request. Please try again.