Pods/Texture/Source/Private/TextExperiment/Component/ASTextLine.m
//
//  ASYTextLine.m
//  Modified from 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 <AsyncDisplayKit/ASTextLine.h>
#import <AsyncDisplayKit/ASTextUtilities.h>

@implementation ASTextLine {
  CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0.
}

+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical {
  if (!CTLine) return nil;
  ASTextLine *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 = (CTRunRef)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 = (CTRunRef)CFArrayGetValueAtIndex(runs, r);
    CFIndex glyphCount = CTRunGetGlyphCount(run);
    if (glyphCount == 0) continue;
    NSDictionary *attrs = (id)CTRunGetAttributes(run);
    ASTextAttachment *attachment = attrs[ASTextAttachmentAttributeName];
    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) {
        ASTEXT_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 = ASTextNSRangeFromCFRange(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:@"<ASTextLine: %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 ASTextRunGlyphRange
+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode {
  ASTextRunGlyphRange *one = [self new];
  one.glyphRangeInRun = range;
  one.drawMode = mode;
  return one;
}
@end
