CTFramesetterSuggestFrameSizeWithConstraints () основного текста каждый раз возвращает неверный размер - PullRequest
8 голосов
/ 25 апреля 2010

Согласно документам, 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.

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

Основной текст: 133,569336 x 16,592285

Кварц: 135,000000 х 31,000000

Ответы [ 6 ]

14 голосов
/ 18 ноября 2010

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

+(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;
}
5 голосов
/ 05 мая 2010

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

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 голосов
/ 05 апреля 2012

Проблема в том, что вы должны применить стиль абзаца к тексту, прежде чем измерять его. Если вы этого не сделаете, то получите значение по умолчанию 0,0. Я предоставил пример кода для того, как это сделать, в своем ответе на дубликат этого вопроса здесь https://stackoverflow.com/a/10019378/1313863.

0 голосов
/ 21 августа 2018

ответ ing.conti, но в Swift 4:

    var H:CGFloat = 0

    // Create the framesetter with the attributed string.
    let framesetter = CTFramesetterCreateWithAttributedString(attributedString as! CFMutableAttributedString)
    let box:CGRect = CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)

    let startIndex:CFIndex = 0

    let path:CGMutablePath = CGMutablePath()
    path.addRect(box)

    // Create a frame for this column and draw it.
    let frame:CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, nil)
    // Start the next frame at the first character not visible in this frame.
    //CFRange frameRange = CTFrameGetVisibleStringRange(frame);
    //startIndex += frameRange.length;

    let lineArray:CFArray = CTFrameGetLines(frame)
    let lineCount:CFIndex = CFArrayGetCount(lineArray)
    var h:CGFloat = 0
    var ascent:CGFloat = 0
    var descent:CGFloat = 0
    var leading:CGFloat = 0

    for j in 0..<lineCount {
        let currentLine = unsafeBitCast(CFArrayGetValueAtIndex(lineArray, j), to: CTLine.self)
        CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading)
        h = ascent + descent + leading;
        H+=h;
    }
    return H;

Я пытался сохранить его как 1: 1 в коде Objective C, но Swift не так хорош при работе с указателями, поэтому для приведения были необходимы некоторые изменения.

Я также провел несколько тестов, сравнивая этот код (и его аналог ObjC) с другими методами высоты. В качестве хедз-апа я использовал ОГРОМНУЮ и очень сложную атрибутивную строку в качестве входных данных, а также сделал это на симе, поэтому сами времена не имеют смысла, однако относительные скорости верны.

Runtime for 1000 iterations (ms) BoundsForRect: 8909.763097763062
Runtime for 1000 iterations (ms) layoutManager: 7727.7010679244995
Runtime for 1000 iterations (ms) CTFramesetterSuggestFrameSizeWithConstraints: 1968.9229726791382
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame ObjC: 1941.6030206680298
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame-Swift: 1912.694974899292
0 голосов
/ 07 января 2017

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

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

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

UPDATE

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

И даже тогда он по-прежнему не учитывает стили абзацев>: |

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

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

0 голосов
/ 04 сентября 2011

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

...