Core Text CTFramesetterSuggestFrameSizeWithConstraints() возвращает неправильный размер каждый раз

В соответствии с документами CTFramesetterSuggestFrameSizeWithConstraints () "определяет размер кадра, необходимый для диапазона строк".

К сожалению, размер, возвращаемый этой функцией, никогда не бывает точным. Вот что я делаю:

    NSAttributedString *string = [[[NSAttributedString alloc] initWithString:@"lorem ipsum" attributes:nil] autorelease];
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(rect.size.width, CGFLOAT_MAX), NULL);

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

Это правильный способ использования этого метода?

Есть ли другой способ компоновки основного текста?

Кажется, я не единственный, кто сталкивается с проблемами этого метода. См. https://devforums.apple.com/message/181450.

Изменить: Я измерил ту же строку с кварцем, используя sizeWithFont:, снабдив тот же шрифт как атрибутной строке, так и Quartz. Вот полученные мной измерения:

Основной текст: 133.569336 x 16.592285

Кварц: 135.000000 x 31.000000

Ответы

Ответ 1

попробуйте это.. кажется, работают:

+(CGFloat)heightForAttributedString:(NSAttributedString *)attrString forWidth:(CGFloat)inWidth
{
    CGFloat H = 0;

    // Create the framesetter with the attributed string.
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attrString); 

    CGRect box = CGRectMake(0,0, inWidth, CGFLOAT_MAX);

    CFIndex startIndex = 0;

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, box);

    // Create a frame for this column and draw it.
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, NULL);

    // Start the next frame at the first character not visible in this frame.
    //CFRange frameRange = CTFrameGetVisibleStringRange(frame);
    //startIndex += frameRange.length;

    CFArrayRef lineArray = CTFrameGetLines(frame);
    CFIndex j = 0, lineCount = CFArrayGetCount(lineArray);
    CGFloat h, ascent, descent, leading;

    for (j=0; j < lineCount; j++)
    {
        CTLineRef currentLine = (CTLineRef)CFArrayGetValueAtIndex(lineArray, j);
        CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading);
        h = ascent + descent + leading;
        NSLog(@"%f", h);
        H+=h;
    }

    CFRelease(frame);
    CFRelease(path);
    CFRelease(framesetter);


    return H;
}

Ответ 2

Для одиночного линейного кадра попробуйте следующее:

line = CTLineCreateWithAttributedString((CFAttributedStringRef) string);
CGFloat ascent;
CGFloat descent;
CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
CGFloat height = ascent+descent;
CGSize textSize = CGSizeMake(width,height);

Для многострочных кадров вам также необходимо добавить строку (см. пример кода в Руководство по программированию основного текста)

По какой-то причине CTFramesetterSuggestFrameSizeWithConstraints() использует разницу в восхождении и спуске для расчета высоты:

CGFloat wrongHeight = ascent-descent;
CGSize textSize = CGSizeMake(width, wrongHeight);

Это может быть ошибка?

У меня возникают другие проблемы с шириной рамки; Стоит проверить, как это видно только в особых случаях. Подробнее см. этот вопрос.

Ответ 3

Проблема заключается в том, что перед измерением необходимо применить стиль абзаца к тексту. Если вы этого не сделаете, вы получите значение по умолчанию 0.0. Я представил образец кода для того, как это сделать в моем ответе на дубликат этого вопроса здесь fooobar.com/questions/7763/....

Ответ 4

Это может показаться странным, но я обнаружил, что если вы сначала используете функцию ceil, а затем добавьте +1 к высоте, она всегда будет работать. Многие сторонние API используют этот трюк.

Ответ 5

Воскрешение.

Когда изначально определяется, где строки должны быть помещены в рамку, Core Text, кажется, массирует восхождение + спуск для целей расчета происхождения линии. В частности, похоже на 0.2 * (восхождение + спуск) добавляется к восхождению, а затем и спуск, и результирующее восхождение изменяются на floor(x + 0.5), а затем базовые позиции рассчитываются на основе этих скорректированных восхождений и спусков. На оба этих шага влияют определенные условия, природа которых я не уверен, и я также забыл, в какой момент стили абзаца учитываются, несмотря на то, что они смотрели только несколько дней назад.

Я уже смирился с тем, что рассматривал линию, начинающуюся с ее базовой линии, и не пыталась выяснить, на что идут реальные линии. К сожалению, это все еще не кажется достаточным: стили абзаца не отражены в CTLineGetTypographicBounds(), а некоторые шрифты, такие как Klee, которые имеют отличные от нуля строки, завершают пересечение пути! Не уверен, что делать с этим... возможно, для другого вопроса.

UPDATE

Кажется, что CTLineGetBoundsWithOptions(line, 0) получает правильные границы линий, но не совсем полностью: есть разрыв между строками и с некоторыми шрифтами (снова Klee), разрыв отрицательный, а линии перекрываются... Не уверен, что делать об этом.: | По крайней мере, мы немного ближе.

И даже тогда он все еще не учитывает стили абзацa > : |

CTLineGetBoundsWithOptions() не указан на сайте документации Apple, возможно, из-за ошибки в текущей версии их генератора документации. Это полностью документированный API, однако - вы найдете его в файлах заголовков, и это было подробно обсуждено на сессии 226 WWDC 2012.

Ни один из вариантов не относится к нам: они уменьшают границы rect, принимая во внимание определенные варианты дизайна шрифтов (или увеличивают границы в случайном порядке, в случае нового kCTLineBoundsIncludeLanguageExtents). Однако полезной опцией является kCTLineBoundsUseGlyphPathBounds, что эквивалентно CTLineGetImageBounds(), но без необходимости указывать a CGContext (и, таким образом, не подвергаться существующей текстовой матрице или CTM).