New file |
| | |
| | | // |
| | | // YYYTextLine.m |
| | | // YYText <https://github.com/ibireme/YYText> |
| | | // |
| | | // Created by ibireme on 15/3/3. |
| | | // Copyright (c) 2015 ibireme. |
| | | // |
| | | // This source code is licensed under the MIT-style license found in the |
| | | // LICENSE file in the root directory of this source tree. |
| | | // |
| | | |
| | | #import "YYTextLine.h" |
| | | #import "YYTextUtilities.h" |
| | | |
| | | |
| | | @implementation YYTextLine { |
| | | CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0. |
| | | } |
| | | |
| | | + (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical { |
| | | if (!CTLine) return nil; |
| | | YYTextLine *line = [self new]; |
| | | line->_position = position; |
| | | line->_vertical = isVertical; |
| | | [line setCTLine:CTLine]; |
| | | return line; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | if (_CTLine) CFRelease(_CTLine); |
| | | } |
| | | |
| | | - (void)setCTLine:(_Nonnull CTLineRef)CTLine { |
| | | if (_CTLine != CTLine) { |
| | | if (CTLine) CFRetain(CTLine); |
| | | if (_CTLine) CFRelease(_CTLine); |
| | | _CTLine = CTLine; |
| | | if (_CTLine) { |
| | | _lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading); |
| | | CFRange range = CTLineGetStringRange(_CTLine); |
| | | _range = NSMakeRange(range.location, range.length); |
| | | if (CTLineGetGlyphCount(_CTLine) > 0) { |
| | | CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); |
| | | CTRunRef run = CFArrayGetValueAtIndex(runs, 0); |
| | | CGPoint pos; |
| | | CTRunGetPositions(run, CFRangeMake(0, 1), &pos); |
| | | _firstGlyphPos = pos.x; |
| | | } else { |
| | | _firstGlyphPos = 0; |
| | | } |
| | | _trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine); |
| | | } else { |
| | | _lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0; |
| | | _range = NSMakeRange(0, 0); |
| | | } |
| | | [self reloadBounds]; |
| | | } |
| | | } |
| | | |
| | | - (void)setPosition:(CGPoint)position { |
| | | _position = position; |
| | | [self reloadBounds]; |
| | | } |
| | | |
| | | - (void)reloadBounds { |
| | | if (_vertical) { |
| | | _bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth); |
| | | _bounds.origin.y += _firstGlyphPos; |
| | | } else { |
| | | _bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent); |
| | | _bounds.origin.x += _firstGlyphPos; |
| | | } |
| | | |
| | | _attachments = nil; |
| | | _attachmentRanges = nil; |
| | | _attachmentRects = nil; |
| | | if (!_CTLine) return; |
| | | CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); |
| | | NSUInteger runCount = CFArrayGetCount(runs); |
| | | if (runCount == 0) return; |
| | | |
| | | NSMutableArray *attachments = [NSMutableArray new]; |
| | | NSMutableArray *attachmentRanges = [NSMutableArray new]; |
| | | NSMutableArray *attachmentRects = [NSMutableArray new]; |
| | | for (NSUInteger r = 0; r < runCount; r++) { |
| | | CTRunRef run = CFArrayGetValueAtIndex(runs, r); |
| | | CFIndex glyphCount = CTRunGetGlyphCount(run); |
| | | if (glyphCount == 0) continue; |
| | | NSDictionary *attrs = (id)CTRunGetAttributes(run); |
| | | YYTextAttachment *attachment = attrs[YYTextAttachmentAttributeName]; |
| | | if (attachment) { |
| | | CGPoint runPosition = CGPointZero; |
| | | CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); |
| | | |
| | | CGFloat ascent, descent, leading, runWidth; |
| | | CGRect runTypoBounds; |
| | | runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); |
| | | |
| | | if (_vertical) { |
| | | YYTEXT_SWAP(runPosition.x, runPosition.y); |
| | | runPosition.y = _position.y + runPosition.y; |
| | | runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth); |
| | | } else { |
| | | runPosition.x += _position.x; |
| | | runPosition.y = _position.y - runPosition.y; |
| | | runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent); |
| | | } |
| | | |
| | | NSRange runRange = YYTextNSRangeFromCFRange(CTRunGetStringRange(run)); |
| | | [attachments addObject:attachment]; |
| | | [attachmentRanges addObject:[NSValue valueWithRange:runRange]]; |
| | | [attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]]; |
| | | } |
| | | } |
| | | _attachments = attachments.count ? attachments : nil; |
| | | _attachmentRanges = attachmentRanges.count ? attachmentRanges : nil; |
| | | _attachmentRects = attachmentRects.count ? attachmentRects : nil; |
| | | } |
| | | |
| | | - (CGSize)size { |
| | | return _bounds.size; |
| | | } |
| | | |
| | | - (CGFloat)width { |
| | | return CGRectGetWidth(_bounds); |
| | | } |
| | | |
| | | - (CGFloat)height { |
| | | return CGRectGetHeight(_bounds); |
| | | } |
| | | |
| | | - (CGFloat)top { |
| | | return CGRectGetMinY(_bounds); |
| | | } |
| | | |
| | | - (CGFloat)bottom { |
| | | return CGRectGetMaxY(_bounds); |
| | | } |
| | | |
| | | - (CGFloat)left { |
| | | return CGRectGetMinX(_bounds); |
| | | } |
| | | |
| | | - (CGFloat)right { |
| | | return CGRectGetMaxX(_bounds); |
| | | } |
| | | |
| | | - (NSString *)description { |
| | | NSMutableString *desc = @"".mutableCopy; |
| | | NSRange range = self.range; |
| | | [desc appendFormat:@"<YYTextLine: %p> row:%zd range:%tu,%tu",self, self.row, range.location, range.length]; |
| | | [desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)]; |
| | | [desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)]; |
| | | return desc; |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation YYTextRunGlyphRange |
| | | + (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode { |
| | | YYTextRunGlyphRange *one = [self new]; |
| | | one.glyphRangeInRun = range; |
| | | one.drawMode = mode; |
| | | return one; |
| | | } |
| | | @end |