pajevic
pajevic

Reputation: 4657

Wrong number of characters in line returned by CTLineGetStringRange and CTLineGetGlyphCount

I have an app where I use CoreText to draw text highlight. It works well except that when I try to get the number of characters in a line by using CTLineGetStringRange it usually gives me a number larger then it actually is. For instance in a line containing 156 characters the length of the range was 164. CTLineGetGlyphCountreturned the same number.

Does anybody have an idea why this happens? The NSAttributedStringI use to create the framesetter is using the exact same font as my UITextView.

Here is my code:

// Build the attributed string from our text data and string attribute data
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:self.text attributes:self.attributes];    

// Create the Core Text framesetter using the attributed string
_framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attributedString);
// Create the Core Text frame using our current view rect bounds
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
_frame =  CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0), path.CGPath, NULL);

NSArray *lines = (__bridge NSArray *) CTFrameGetLines(_frame);
for (int i = 0; i < lines.count; i++) {
    CTLineRef line = (__bridge CTLineRef) [lines objectAtIndex:i];
    CFRange lineRange = CTLineGetStringRange(line);
    NSLog(@"lineRange: %ld, %ld", lineRange.location, lineRange.length);
    CFIndex glyphCount = CTLineGetGlyphCount(line);
    NSLog(@"glyphCount: %ld", glyphCount);
}

My class is a subclass of UIView which is added as subview to an instance of a subclass of UITextView.

EDIT: Here is an example string I am testin with:

textView.text = @"Marvel's The Avengers is a 2012 American superhero film produced by Marvel Studios and distributed by Walt Disney Pictures, based on the Marvel Comics superhero team of the same name.";

In this case the first line contains
"Marvel's The Avengers is a 2012 American superhero film produced by Marvel Studios and distributed by Walt Disney Pictures, based on the " which is 137 characters long. But it gives me a range for the line with length 144.

However, when I tried with the following text the results were different:

textView.text = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";

Now the first line contains
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " with length 142. But here it gives me the correct range with length 142.

I then tried with several more texts:

Text: "The Asgardian Loki encounters the Other, the leader of an alien race known as the Chitauri. In exchange for retrieving the Tesseract, a powerful energy source of unknown potential, the Other promises Loki a Chitauri army with which he can subjugate the Earth."
Result: "The Asgardian Loki encounters the Other, the leader of an alien race known as the Chitauri. In exchange for retrieving the Tesseract, a powerful " with length 145.
Line range length: 145

Text: "Stark and Rogers realize that simply defeating them will not be enough for Loki; he needs to overpower them publicly to validate himself as ruler of Earth."
Result: "Stark and Rogers realize that simply defeating them will not be enough for Loki; he needs to overpower them publicly to validate himself as ruler " with length 146.
Line range length: 149

So as you can see sometimes it is correct and sometimes it is not. I can't find the explanation.

Upvotes: 0

Views: 734

Answers (1)

pajevic
pajevic

Reputation: 4657

I solved the problem based on the hint given in this answer.

It seems that because UITextViewinherits from UIScrollView it has an 8 pixel inset at every edge. This meant that my internal UIViewhad an 16 pixel wider room for text then my UITextView and sometimes that difference meant that it could fit one more word before going to a new line, which gave the wrong number of characters in the line.

So subtracting 16 pixels from the width of the view solved the issue for me.

However, this was only part of my solution. The other part was to remove kerning and ligatures from my attributed string in core text:

CFAttributedStringSetAttribute(string, textRange, kCTKernAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:0.0]));
CFAttributedStringSetAttribute(string, textRange, kCTLigatureAttributeName, (__bridge CFTypeRef)([NSNumber numberWithInt:0]));

Now the lines fit perfectly.

Upvotes: 1

Related Questions