我需要计算NSAttributed字符串(核心文本)中每个字符(字形)的精确边界框.

在此处输入图像描述

大多数帧在水平或垂直方向上都是错位的(一点点).原因是什么?我怎样才能完善这个代码

-(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);

}

推荐答案

你在问题中做了大量的工作,而且你自己的成绩接近so分.您遇到的问题来自这行代码,其中您 for each 帧定位边界框:

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

问题是,不管帧已经有多少偏移量,你都是overriding.

如果你要注释掉那一行,你会看到所有的框架都被放置在同一个位置but,你也会看到它们没有被放置在完全相同的位置.有些更偏向左边或右边,有些则向上或向下.这意味着图示符的帧有自己的位置.

在此处输入图像描述

问题的解决方案是,在将帧移动到线上的正确位置时,考虑帧的当前位置.可以通过分别添加到x和y来实现:

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

或者通过偏移矩形:

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

现在,边界框将具有正确的位置:

在此处输入图像描述

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

在此处输入图像描述

Objective-c相关问答推荐

为何在Objective-C中,使用self.propName赋值会导致崩溃而使用_ivarName则不会?

UITableViewCell:如何防止蓝色 Select ​​背景不带 isSelected 属性?

UITextField secureTextEntry 带有自定义字体的项目符号?

如何创建圆形按钮?

禁用手势识别器

iOS performSelectorOnMainThread 有多个参数

如何在目标 C 中创建单例类

从我在 iPhone 上的应用程序调用官方 *Settings* 应用程序

将 NSArray 转换为 NSDictionary

未找到 RKObjectMapping.h

判断 NSString 是否只是由空格组成

无法在 Interface Builder (Xcode 5) 中创建与子视图的出口连接

将 HTTP 标头添加到 NSURLRequest

NSManagedObjectContext:异常断点在 save: 方法处停止,但没有日志(log)/崩溃/错误

MKAnnotation 图像偏移与自定义引脚图像

使用 NSLog 打印 NSData

Xcode:无法判断应用程序包

在 Objective-C 中计算两个日期之间的天数

找出用户是否按下了 uinavigationcontroller 中的后退按钮?

如何在 iOS 中创建自定义 UIActivity?