YYTextLine.m 5.4 KB

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