YYTextLine.m 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. //
  2. // YYYTextLine.m
  3. // YYText <https://github.com/ibireme/YYText>
  4. //
  5. // Created by ibireme on 15/3/3.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYTextLine.h"
  12. #import "YYTextUtilities.h"
  13. @implementation YYTextLine {
  14. CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0.
  15. }
  16. + (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical {
  17. if (!CTLine) return nil;
  18. YYTextLine *line = [self new];
  19. line->_position = position;
  20. line->_vertical = isVertical;
  21. [line setCTLine:CTLine];
  22. return line;
  23. }
  24. - (void)dealloc {
  25. if (_CTLine) CFRelease(_CTLine);
  26. }
  27. - (void)setCTLine:(_Nonnull CTLineRef)CTLine {
  28. if (_CTLine != CTLine) {
  29. if (CTLine) CFRetain(CTLine);
  30. if (_CTLine) CFRelease(_CTLine);
  31. _CTLine = CTLine;
  32. if (_CTLine) {
  33. _lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading);
  34. CFRange range = CTLineGetStringRange(_CTLine);
  35. _range = NSMakeRange(range.location, range.length);
  36. if (CTLineGetGlyphCount(_CTLine) > 0) {
  37. CFArrayRef runs = CTLineGetGlyphRuns(_CTLine);
  38. CTRunRef run = CFArrayGetValueAtIndex(runs, 0);
  39. CGPoint pos;
  40. CTRunGetPositions(run, CFRangeMake(0, 1), &pos);
  41. _firstGlyphPos = pos.x;
  42. } else {
  43. _firstGlyphPos = 0;
  44. }
  45. _trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine);
  46. } else {
  47. _lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0;
  48. _range = NSMakeRange(0, 0);
  49. }
  50. [self reloadBounds];
  51. }
  52. }
  53. - (void)setPosition:(CGPoint)position {
  54. _position = position;
  55. [self reloadBounds];
  56. }
  57. - (void)reloadBounds {
  58. if (_vertical) {
  59. _bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth);
  60. _bounds.origin.y += _firstGlyphPos;
  61. } else {
  62. _bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent);
  63. _bounds.origin.x += _firstGlyphPos;
  64. }
  65. _attachments = nil;
  66. _attachmentRanges = nil;
  67. _attachmentRects = nil;
  68. if (!_CTLine) return;
  69. CFArrayRef runs = CTLineGetGlyphRuns(_CTLine);
  70. NSUInteger runCount = CFArrayGetCount(runs);
  71. if (runCount == 0) return;
  72. NSMutableArray *attachments = [NSMutableArray new];
  73. NSMutableArray *attachmentRanges = [NSMutableArray new];
  74. NSMutableArray *attachmentRects = [NSMutableArray new];
  75. for (NSUInteger r = 0; r < runCount; r++) {
  76. CTRunRef run = CFArrayGetValueAtIndex(runs, r);
  77. CFIndex glyphCount = CTRunGetGlyphCount(run);
  78. if (glyphCount == 0) continue;
  79. NSDictionary *attrs = (id)CTRunGetAttributes(run);
  80. YYTextAttachment *attachment = attrs[YYTextAttachmentAttributeName];
  81. if (attachment) {
  82. CGPoint runPosition = CGPointZero;
  83. CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition);
  84. CGFloat ascent, descent, leading, runWidth;
  85. CGRect runTypoBounds;
  86. runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading);
  87. if (_vertical) {
  88. YYTEXT_SWAP(runPosition.x, runPosition.y);
  89. runPosition.y = _position.y + runPosition.y;
  90. runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth);
  91. } else {
  92. runPosition.x += _position.x;
  93. runPosition.y = _position.y - runPosition.y;
  94. runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent);
  95. }
  96. NSRange runRange = YYTextNSRangeFromCFRange(CTRunGetStringRange(run));
  97. [attachments addObject:attachment];
  98. [attachmentRanges addObject:[NSValue valueWithRange:runRange]];
  99. [attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]];
  100. }
  101. }
  102. _attachments = attachments.count ? attachments : nil;
  103. _attachmentRanges = attachmentRanges.count ? attachmentRanges : nil;
  104. _attachmentRects = attachmentRects.count ? attachmentRects : nil;
  105. }
  106. - (CGSize)size {
  107. return _bounds.size;
  108. }
  109. - (CGFloat)width {
  110. return CGRectGetWidth(_bounds);
  111. }
  112. - (CGFloat)height {
  113. return CGRectGetHeight(_bounds);
  114. }
  115. - (CGFloat)top {
  116. return CGRectGetMinY(_bounds);
  117. }
  118. - (CGFloat)bottom {
  119. return CGRectGetMaxY(_bounds);
  120. }
  121. - (CGFloat)left {
  122. return CGRectGetMinX(_bounds);
  123. }
  124. - (CGFloat)right {
  125. return CGRectGetMaxX(_bounds);
  126. }
  127. - (NSString *)description {
  128. NSMutableString *desc = @"".mutableCopy;
  129. NSRange range = self.range;
  130. [desc appendFormat:@"<YYTextLine: %p> row:%zd range:%tu,%tu",self, self.row, range.location, range.length];
  131. [desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)];
  132. [desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)];
  133. return desc;
  134. }
  135. @end
  136. @implementation YYTextRunGlyphRange
  137. + (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode {
  138. YYTextRunGlyphRange *one = [self new];
  139. one.glyphRangeInRun = range;
  140. one.drawMode = mode;
  141. return one;
  142. }
  143. @end