核心文本在iOS中计算字母框架

我需要为NSAttributedString(核心文本)中的每个字符(字形)计算确切的边界框。 把一些用来解决类似问题的代码(核心文本select等)放在一起后,结果是相当不错的,但是只有几帧(红色)正在计算正确:

在这里输入图像说明

大多数框架是水平或垂直错位(微小的一点)。 这是什么原因? 我怎样才能完善这个代码?

-(void)recalculate{ // get characters from NSString NSUInteger len = [_attributedString.string length]; UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len); CFStringGetCharacters((__bridge CFStringRef)_attributedString.string, CFRangeMake(0, [_attributedString.string length]), characters); // allocate glyphs and bounding box arrays for holding the result // assuming that each character is only one glyph, which is wrong CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len); CTFontGetGlyphsForCharacters(_font, characters, glyphs, len); // get bounding boxes for glyphs CTFontGetBoundingRectsForGlyphs(_font, kCTFontDefaultOrientation, glyphs, _characterFrames, len); free(characters); free(glyphs); // Measure how mush specec will be needed for this attributed string // So we can find minimun frame needed CFRange fitRange; CGSize s = CTFramesetterSuggestFrameSizeWithConstraints(_framesetter, rangeAll, NULL, CGSizeMake(W, MAXFLOAT), &fitRange); _frameRect = CGRectMake(0, 0, s.width, s.height); CGPathRef framePath = CGPathCreateWithRect(_frameRect, NULL); _ctFrame = CTFramesetterCreateFrame(_framesetter, rangeAll, framePath, NULL); CGPathRelease(framePath); // Get the lines in our frame NSArray* lines = (NSArray*)CTFrameGetLines(_ctFrame); _lineCount = [lines count]; // Allocate memory to hold line frames information: if (_lineOrigins != NULL)free(_lineOrigins); _lineOrigins = malloc(sizeof(CGPoint) * _lineCount); if (_lineFrames != NULL)free(_lineFrames); _lineFrames = malloc(sizeof(CGRect) * _lineCount); // Get the origin point of each of the lines CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, 0), _lineOrigins); // Solution borrowew from (but simplified): // https://github.com/twitter/twui/blob/master/lib/Support/CoreText%2BAdditions.m // Loop throught the lines for(CFIndex i = 0; i < _lineCount; ++i) { CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:i]; CFRange lineRange = CTLineGetStringRange(line); CFIndex lineStartIndex = lineRange.location; CFIndex lineEndIndex = lineStartIndex + lineRange.length; CGPoint lineOrigin = _lineOrigins[i]; CGFloat ascent, descent, leading; CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); // If we have more than 1 line, we want to find the real height of the line by measuring the distance between the current line and previous line. If it's only 1 line, then we'll guess the line's height. BOOL useRealHeight = i < _lineCount - 1; CGFloat neighborLineY = i > 0 ? _lineOrigins[i - 1].y : (_lineCount - 1 > i ? _lineOrigins[i + 1].y : 0.0f); CGFloat lineHeight = ceil(useRealHeight ? abs(neighborLineY - lineOrigin.y) : ascent + descent + leading); _lineFrames[i].origin = lineOrigin; _lineFrames[i].size = CGSizeMake(lineWidth, lineHeight); for (int ic = lineStartIndex; ic < lineEndIndex; ic++) { CGFloat startOffset = CTLineGetOffsetForStringIndex(line, ic, NULL); _characterFrames[ic].origin = CGPointMake(startOffset, lineOrigin.y); } } } #pragma mark - Rendering Text: -(void)renderInContext:(CGContextRef)context contextSize:(CGSize)size{ CGContextSaveGState(context); // Draw Core Text attributes string: CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, CGRectGetHeight(_frameRect)); CGContextScaleCTM(context, 1.0, -1.0); CTFrameDraw(_ctFrame, context); // Draw line and letter frames: CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.5].CGColor); CGContextSetLineWidth(context, 1.0); CGContextBeginPath(context); CGContextAddRects(context, _lineFrames, _lineCount); CGContextClosePath(context); CGContextStrokePath(context); CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5].CGColor); CGContextBeginPath(context); CGContextAddRects(context, _characterFrames, _attributedString.string.length); CGContextClosePath(context); CGContextStrokePath(context); CGContextRestoreGState(context); } 

你在你的问题上做了大量的工作,并且亲密无间。 你所遇到的问题来自于你为每一帧定位边界框的代码行:

 _characterFrames[ic].origin = CGPointMake(startOffset, lineOrigin.y); 

问题在于你重写了任何抵消帧已经有。

如果您要将该行注释掉,您会发现所有的框架都或多或less地位于相同的位置, 但是您也会看到它们没有放在完全相同的位置。 有些定位更多的左侧或右侧,更多的上下。 这意味着字形的框架有自己的位置。

在这里输入图像说明

解决您的问题的方法是在将帧移动到线上的正确位置时考虑帧的当前位置。 你可以通过分别添加到x和y来完成:

 _characterFrames[ic].origin.x += startOffset; _characterFrames[ic].origin.y += lineOrigin.y; 

或通过抵消矩形:

 _characterFrames[ic] = CGRectOffset(_characterFrames[ic], startOffset, lineOrigin.y); 

现在边框会有正确的位置:

在这里输入图像说明

你应该看到它适用于一些更极端的字体

在这里输入图像说明