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).