Core Text 在 iOS 中计算字母框架 [英] Core Text calculate letter frame in iOS

查看:23
本文介绍了Core Text 在 iOS 中计算字母框架的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要为 NSAttributedString(核心文本)中的每个字符(字形)计算精确的边界框.将一些用于解决类似问题(核心文本选择等)的代码放在一起后,结果相当不错,但只有少数帧(红色)正确计算:

I need to calculate exact bounding box for every each character (glyph) in NSAttributedString (Core Text). After putting together some code used to solve similar problems (Core Text selection, etc..), the result is quite good, but only few frames (red) are being calculated properly:

大多数框架在水平或垂直方向上都错位了(一点点).那是什么原因呢?我该如何完善这段代码?:

Most of the frames are misplaces either horizontally or vertically (by tiny bit). What is the cause of that? How can I perfect this code?:

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

}

推荐答案

你在你的问题上做了大量的工作,并且非常你自己.您遇到的问题来自这行代码,您在其中为每一帧定位边界框:

You did an impressive amount of work in your question and were so close on your own. The problem you were having comes from this line of code where you position the bounding boxes for each frame:

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

它的问题在于您覆盖框架已有的任何偏移量.

The problem with it is that you are overriding whatever offset the frame already had.

如果您注释掉该行,您会看到所有框架或多或少位于同一位置但是您还会看到它们不是定位在完全相同的位置.有些位置更靠左或向右,有些位置更靠上或靠下.这意味着字形的帧有自己的位置.

If you were to comment out that line you would see that all the frames were positioned more or less in the same place but you would also see that they are not positioned at the exact same place. Some are positioned more to the left or right and some more up or down. This means that the frames for the glyphs have a position of their own.

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

The solution to your problem is to take the current position of the frames into account when you move them into their correct place on the lines. You can either do it by adding to x and y separately:

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

或通过偏移矩形:

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

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

Now the bounding boxes will have their correct positions:

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

and you should see that it works for some of the more extreme fonts out there

这篇关于Core Text 在 iOS 中计算字母框架的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆