Подтвердить что ты не робот

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 only 1 line, then we'll guess the line 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);

}
4b9b3361

Ответ 1

Вы сделали впечатляющий объем работы в своем вопросе и были настолько близки по своему усмотрению. Проблема, с которой вы столкнулись, исходит из этой строки кода, где вы устанавливаете ограничивающие поля для каждого кадра:

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

Проблема заключается в том, что вы переопределяете любое смещение, которое уже имело фрейм.

Если вы хотите прокомментировать эту строку, вы увидите, что все кадры были расположены более или менее в одном месте, но вы также увидите, что они не расположены в одном и том же месте. Некоторые из них расположены больше влево или вправо, а некоторые вверх или вниз. Это означает, что рамки для глифов имеют собственную позицию.

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