web-dev-qa-db-ja.com

Core TextはiOSでレターフレームを計算します

NSAttributedString(Core Text)の各文字(グリフ)ごとに正確なバウンディングボックスを計算する必要があります。同様の問題(コアテキストの選択など)を解決するために使用されるいくつかのコードをまとめると、結果は非常に良好ですが、適切に計算されているフレーム(赤)はわずかです。

enter image description here

ほとんどのフレームは、水平方向または垂直方向(ごくわずか)の位置がずれています。その原因は何ですか?どうすればこのコードを完成できますか?:

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

}
53
Lukasz

あなたはあなたの質問で印象的な量の仕事をしました、そしてあなた自身でsoでした。発生していた問題は、各フレームの境界ボックスを配置する次のコード行に起因します。

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

それの問題は、あなたがoverridingであるということです。フレームがすでに持っていたオフセットは何でも。

その行をコメントアウトすると、すべてのフレームが同じ場所に配置されていることがわかります多かれ少なかれbutに配置されていないこともわかりますまったく同じ場所。左または右に配置されるものと、上または下に配置されるものがあります。つまり、グリフのフレームには独自の位置があります。

enter image description here

問題の解決策は、フレームをライン上の正しい位置に移動するときに、フレームの現在の位置を考慮することです。 xとyに別々に追加することでそれを行うことができます:

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

または長方形をオフセットすることによって:

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

これで、バウンディングボックスは正しい位置になります。

enter image description here

そして、そこにある極端なフォントのいくつかで機能することがわかります

enter image description here

60