Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

529 lines (416 sloc) 11.807 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"
#import "DTWeakSupport.h"
@interface DTCoreTextGlyphRun ()
@property (nonatomic, assign) CGRect frame;
@property (nonatomic, assign) NSInteger numberOfGlyphs;
@property (nonatomic, DT_WEAK_PROPERTY, 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;
DT_WEAK_VARIABLE DTCoreTextLayoutLine *_line; // retain cycle, since these objects are retained by the _line
DT_WEAK_VARIABLE 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;
// LTR line frames include trailing whitespace in width
// we need to subtract it so that we don't highlight/underline it
if (!_line.writingDirectionIsRightToLeft)
{
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)
{
CGRect backgroundColorRect = CGRectIntegral(CGRectMake(runStrokeBounds.origin.x, lineFrame.origin.y, runStrokeBounds.size.width, lineFrame.size.height));
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillRect(context, backgroundColorRect);
}
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;
// use lowest underline position of all glyph runs in same line
CGFloat underlinePosition = [_line underlineOffset];
y = DTRoundWithContentScale(_line.baselineOrigin.y + underlinePosition - fontUnderlineThickness/2.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 + _line.stringLocationOffset, 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;
}
BOOL isTrailing;
if (_line.writingDirectionIsRightToLeft)
{
isTrailing = (self == [[_line glyphRuns] objectAtIndex:0]);
}
else
{
isTrailing = (self == [[_line glyphRuns] lastObject]);
}
if (isTrailing)
{
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.