Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 1b4ea0e8ab7d94a46b1964a2354686c2e599c8c2

COCOAPODS: 1.10.0
COCOAPODS: 1.10.1
64 changes: 63 additions & 1 deletion Source/TextExperiment/Component/ASTextLayout.mm
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,53 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri
return [self layoutWithContainer:container text:text range:NSMakeRange(0, text.length)];
}

+ (BOOL)_isBeginningLineIndexOfParagraph:(NSAttributedString *)text index:(NSUInteger)index
{
NSString *string = [text string];
NSUInteger length = [string length];

NSRange paragraphRange = [self _rangeOfParagraphsContainingRange:string range:NSMakeRange(0, 0) parBegIndex:NULL parEndIndex:NULL];

while (paragraphRange.length)
{
if (index == paragraphRange.location) {
return true;
}
if (index < paragraphRange.location) {
return false;
}
NSUInteger nextParagraphBegin = NSMaxRange(paragraphRange);
if (nextParagraphBegin>=length)
{
break;
}
// next paragraph
paragraphRange = [self _rangeOfParagraphsContainingRange:string range:NSMakeRange(nextParagraphBegin, 0) parBegIndex:NULL parEndIndex:NULL];
}
return false;
}

+ (NSRange)_rangeOfParagraphsContainingRange:(NSString *)text range:(NSRange)range parBegIndex:(NSUInteger *)parBegIndex parEndIndex:(NSUInteger *)parEndIndex
{
// get beginning and end of paragraph containing the replaced range
CFIndex beginIndex;
CFIndex endIndex;

CFStringGetParagraphBounds((__bridge CFStringRef)text, CFRangeMake(range.location, range.length), &beginIndex, &endIndex, NULL);

if (parBegIndex)
{
*parBegIndex = beginIndex;
}

if (parEndIndex)
{
*parEndIndex = endIndex;
}
// endIndex is the first character of the following paragraph, so we don't need to add 1
return NSMakeRange(beginIndex, endIndex - beginIndex);
}

+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range {
ASTextLayout *layout = NULL;
CGPathRef cgPath = nil;
Expand Down Expand Up @@ -864,10 +911,25 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri
[lastLineText insertAttributedString:truncationToken atIndex:0];
}
}

CGFloat headIndent = 0;

BOOL isAtBeginOfParagraph = [self _isBeginningLineIndexOfParagraph:text index:lastLine.range.location];
// get the paragraph style at this index
CTParagraphStyleRef paragraphStyle = (__bridge CTParagraphStyleRef)[text attribute:(id)kCTParagraphStyleAttributeName atIndex:lastRange.location effectiveRange:NULL];

if (isAtBeginOfParagraph)
{
CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(headIndent), &headIndent);
}
else
{
CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(headIndent), &headIndent);
}

CTLineRef ctLastLineExtend = CTLineCreateWithAttributedString((CFAttributedStringRef) lastLineText);
if (ctLastLineExtend) {
CTLineRef ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth, type, truncationTokenLine);
CTLineRef ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth - headIndent, type, truncationTokenLine);
CFRelease(ctLastLineExtend);
if (ctTruncatedLine) {
truncatedLine = [ASTextLine lineWithCTLine:ctTruncatedLine position:lastLine.position vertical:isVerticalForm];
Expand Down
161 changes: 161 additions & 0 deletions Tests/ASTextNode2SnapshotTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,167 @@ - (void)testTextTruncationModes_ASTextNode2
}
}

- (void)testTextTruncationModes_with_headIndentAttribute_ASTextNode2
{
UIView *container = [[UIView alloc] initWithFrame:(CGRect) {CGPointZero, (CGSize) {375.0f, 667.0f}}];

UILabel *textNodeLabel = [[UILabel alloc] init];
UILabel *uiLabelLabel = [[UILabel alloc] init];
UILabel *description = [[UILabel alloc] init];
textNodeLabel.text = @"ASTextNode2:";
textNodeLabel.font = [UIFont boldSystemFontOfSize:16.0];
textNodeLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];
uiLabelLabel.text = @"UILabel:";
uiLabelLabel.font = [UIFont boldSystemFontOfSize:16.0];
uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];

description.text = @"<Description>";
description.font = [UIFont italicSystemFontOfSize:16.0];
description.numberOfLines = 0;

uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];

UILabel *reference = [[UILabel alloc] init];
ASTextNode *textNode = [[ASTextNode alloc] init]; // ASTextNode2

NSMutableAttributedString *refString = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:18.0f] }];

NSMutableAttributedString *asString = [refString mutableCopy];

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.headIndent = 20; // Head Indent for Testing

[refString addAttributes:@{ NSParagraphStyleAttributeName : paragraphStyle} range:NSMakeRange(0, [refString length])];
[asString addAttributes:@{ NSParagraphStyleAttributeName : paragraphStyle} range:NSMakeRange(0, [asString length])];

reference.attributedText = refString;
textNode.attributedText = asString;

CGSize size = (CGSize) {container.bounds.size.width, 120.0};
CGPoint origin = (CGPoint) {CGRectGetWidth(container.bounds) / 2 - size.width / 2, CGRectGetHeight(container.bounds) / 2 - size.height / 2}; // center

textNode.frame = (CGRect) {origin, size};
reference.frame = CGRectOffset(textNode.frame, 0, -160.0f);

textNodeLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, textNodeLabel.font.lineHeight}};
origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - textNodeLabel.bounds.size.height};
textNodeLabel.frame = (CGRect) {origin, textNodeLabel.bounds.size};

uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height};
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};

uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - uiLabelLabel.bounds.size.height};
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};

uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height};
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};

description.bounds = textNode.bounds;
description.frame = (CGRect) {(CGPoint) {0, container.bounds.size.height * 0.8}, description.bounds.size};

[container addSubview:reference];
[container addSubview:textNode.view];
[container addSubview:textNodeLabel];
[container addSubview:uiLabelLabel];
[container addSubview:description];

NSArray<LineBreakConfig *> *c = [LineBreakConfig configs];
for (LineBreakConfig *config in c) {
reference.lineBreakMode = textNode.truncationMode = config.lineBreakMode;
reference.numberOfLines = textNode.maximumNumberOfLines = config.numberOfLines;
description.text = config.description;
[container setNeedsLayout];
NSString *identifier = [NSString stringWithFormat:@"%@_%luLines", [config breakModeDescription], (unsigned long)config.numberOfLines];
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
ASSnapshotVerifyViewWithTolerance(container, identifier, 0.01);
}
}

- (void)testTextTruncationModes_with_firstLineHeadIndentAttribute_ASTextNode2
{
UIView *container = [[UIView alloc] initWithFrame:(CGRect) {CGPointZero, (CGSize) {375.0f, 667.0f}}];

UILabel *textNodeLabel = [[UILabel alloc] init];
UILabel *uiLabelLabel = [[UILabel alloc] init];
UILabel *description = [[UILabel alloc] init];
textNodeLabel.text = @"ASTextNode2:";
textNodeLabel.font = [UIFont boldSystemFontOfSize:16.0];
textNodeLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];
uiLabelLabel.text = @"UILabel:";
uiLabelLabel.font = [UIFont boldSystemFontOfSize:16.0];
uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];

description.text = @"<Description>";
description.font = [UIFont italicSystemFontOfSize:16.0];
description.numberOfLines = 0;

uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];

UILabel *reference = [[UILabel alloc] init];
ASTextNode *textNode = [[ASTextNode alloc] init]; // ASTextNode2

NSMutableAttributedString *refString = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:18.0f] }];

NSMutableAttributedString *asString = [refString mutableCopy];

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.firstLineHeadIndent = 20; // Head Indent for Testing

[refString addAttributes:@{ NSParagraphStyleAttributeName : paragraphStyle} range:NSMakeRange(0, [refString length])];
[asString addAttributes:@{ NSParagraphStyleAttributeName : paragraphStyle} range:NSMakeRange(0, [asString length])];

reference.attributedText = refString;
textNode.attributedText = asString;

CGSize size = (CGSize) {container.bounds.size.width, 120.0};
CGPoint origin = (CGPoint) {CGRectGetWidth(container.bounds) / 2 - size.width / 2, CGRectGetHeight(container.bounds) / 2 - size.height / 2}; // center

textNode.frame = (CGRect) {origin, size};
reference.frame = CGRectOffset(textNode.frame, 0, -160.0f);

textNodeLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, textNodeLabel.font.lineHeight}};
origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - textNodeLabel.bounds.size.height};
textNodeLabel.frame = (CGRect) {origin, textNodeLabel.bounds.size};

uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height};
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};

uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - uiLabelLabel.bounds.size.height};
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};

uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height};
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};

description.bounds = textNode.bounds;
description.frame = (CGRect) {(CGPoint) {0, container.bounds.size.height * 0.8}, description.bounds.size};

[container addSubview:reference];
[container addSubview:textNode.view];
[container addSubview:textNodeLabel];
[container addSubview:uiLabelLabel];
[container addSubview:description];

NSArray<LineBreakConfig *> *c = [LineBreakConfig configs];
for (LineBreakConfig *config in c) {
reference.lineBreakMode = textNode.truncationMode = config.lineBreakMode;
reference.numberOfLines = textNode.maximumNumberOfLines = config.numberOfLines;
description.text = config.description;
[container setNeedsLayout];
NSString *identifier = [NSString stringWithFormat:@"%@_%luLines", [config breakModeDescription], (unsigned long)config.numberOfLines];
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
ASSnapshotVerifyViewWithTolerance(container, identifier, 0.01);
}
}


- (void)testTextContainerInsetIsIncludedWithSmallerConstrainedSize_ASTextNode2
{
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ...xtNode2SnapshotTests/[email protected]
Binary file modified ...TextNode2SnapshotTests/[email protected]