CTFramesetterSuggestFrameSizeWithConstraints разрывает строку, даже если на той же строке есть свободное место

Мы пытаемся получить предлагаемый размер кадра с помощью Core Text API CTFramesetterSuggestFrameSizeWithConstraints.

Пусть E = \ uFFFC; W = \ u200B; S = \ u00A0

CTFrameSetter создается со следующей строкой с атрибутами:

WSESWWSESW

Символам S и E присваиваются CTRunDelegates, которые имеют CTRunDelegateGetWidthCallback, которые возвращают 0 и говорят 100 соответственно.

Когда выполняется следующий код:

auto frameOptions = @{ (id)kCTFrameProgressionAttributeName: @(kCTFrameProgressionTopToBottom)) };
auto constraintSize = CGSizeMake(200, CGFLOAT_MAX);
CFRange fitRange = CFRangeMake(0, 0);

auto pathSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter_.get(), CFRangeMake(0, 0),(CFDictionaryRef)frameOptions, constraintSize, &fitRange);

Значение pathSize, возвращаемое API, равно 100, и мы получаем две отдельные строки

Затем мы создаем CTFrameRef, используя следующий фрагмент кода:

auto rect = CGRectMake(0, 0, 200, pathSize.height);
auto path = CGPathCreateWithRect(rect, nil));
auto frame = CTFramesetterCreateFrame(framesetter_.get(),
    CFRangeMake(0, 0), path, (CFDictionaryRef)frameOptions));

Вопрос:

  1. Почему CTFramesetterSuggestFrameSizeWithConstraints возвращает pathSize, который имеет ширину 100, когда очевидно, что ширина ограничения (200) должна быть достаточной для размещения всего текста в одной строке.

  2. Как CoreText решает, какие символы / символы из строки с атрибутами должны быть объединены за один прогон? В приведенном выше примере мы получаем следующие две строки:

Строка 1: W S E SWW (4 CTTextRun)

Строка 2: S E SW (3 CTTextRun)

Может ли кто-нибудь помочь мне с ответами на эти два вопроса, связанных с вышеупомянутым сценарием. Заранее спасибо!

Изменить 1:

Добавление контекста к проблеме и нашему текущему подходу: Ссылка на изображение

Пожалуйста, обратитесь к изображению выше для ссылки на Parent, Child1, Child2.

Родитель представляет прямоугольник / прямоугольник, для которого мы хотим разметить и вычислить размеры. Мы хотим поместить текст в поле Родитель. Child1, Child2 представлены в нашем тексте символами «E».

  1. Сначала мы пытаемся вычислить размер родительского блока.
  2. Мы используем вычисленный размер родительского блока в качестве ограничений для
    CTFramesetterSuggestFrameSizeWithConstraints, а затем используем его возвращаемое значение для создания CTFrame.

Вычисление размера. Вычисление размера родительского блока зависит от нескольких факторов - может быть заданный размер в качестве внешнего параметра, или он может быть получен из его контейнерного блока, или может быть равен размеру (зафиксирован на некоторые min, max) достаточно, чтобы уместить его текстовое содержимое. Таким образом, он не всегда неограничен (одна строка) или, иногда, даже не зависит от текстового содержимого.

Рассмотрим случай, когда вычисление зависит от текстового содержимого:

Итак, мы вычисляем ширину текста без каких-либо ограничений, используя:

CTFramesetterSuggestFrameSizeWithConstraints(framesetter_.get(), CFRangeMake(0, 0),
            (CFDictionaryRef)frameOptions, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), &fitRange); 

Это возвращает значение 100 (child1) + 100 (child2) = ровно 200 пунктов. Допустим, у нас достаточно места, и 200 пунктов - это то, что установлено в качестве размера родительского блока.

Макет текста. На данный момент у нас есть только рассчитанный размер родительского поля и нет информации о том, как он был рассчитан.

Если теперь мы используем ограничения 200 точек с CTFramesetterSuggestFrameSizeWithConstraints, он возвращает 100 точек и разбивает текст на две строки.

Вот где у нас есть проблема - неограниченный размер текста при использовании для создания CTFrame дает неожиданный разрыв строки.


person Harnish    schedule 28.01.2021    source источник
comment
Было бы полезно показать фактический код установки и результаты. В частности, как вы применяете свои атрибуты. То, что вы описываете, не совпадает с тем, как разбиваются пробежки. И ваш вывод включает A и B, которые вы не определяете. Кроме того, CTFramesetter иногда дает небольшую передышку (часто 1 пт), поэтому я не обязательно ожидал, что два символа по 100 пт уместятся ровно в 200 пт. Я бы поэкспериментировал с увеличением ширины прямоугольника, пока вы не увидите, что он умещается на одной линии, и посмотрел, сколько еще вам нужно. (Но это также могут быть ошибки в вашей атрибутивной строке.)   -  person Rob Napier    schedule 29.01.2021
comment
Обновил вывод. Спасибо, @RobNapier за предложения. Я попытался увеличить ширину прямоугольника - увеличив ее на 1 пункт, текст уместился в одну строку, и проблема была решена. У вас есть идеи относительно того, в каких сценариях CTFrameSetter нуждается в этой передышке? А также, от каких факторов может зависеть величина этого необходимого дополнительного пространства? Выделение таких сценариев поможет нам соответственно расширить ограничения.   -  person Harnish    schedule 29.01.2021
comment
Хотя я обычно обнаружил, что это добавляет дополнительный пункт, это не задокументировано и не обещает быть стабильным в разных выпусках или конфигурациях. Это может зависеть от шрифта, конкретных глифов, разрешения экрана или общего размера. (С новыми шрифтами пользовательского интерфейса Apple, казалось, были внесены некоторые тонкие изменения в макет.) Если вы хотите знать, насколько большой CTFramesetter собирается делать что-то, вам нужно сделать его и спросить. Просто сделайте ваше горизонтальное пространство неограниченным (см. greatestFiniteMagnitude, или просто используйте 10 000). Если вы можете увеличить ограничения, они не были настоящими ограничениями.   -  person Rob Napier    schedule 29.01.2021
comment
Кроме того, если вы хотите, чтобы все умещалось в одну линию, вам, вероятно, вообще не нужен фреймсеттер. Вы можете использовать CTLine напрямую. Возможно, вам понадобится CTLineGetBoundsWithOptions или CTLineGetTypographicBounds, в зависимости от потребностей вашего макета. Некоторые глифы выводятся за пределы своих окон em, а некоторые глифы выстраиваются лучше, когда они немного смещены внутри фрагмента строки, поэтому вы не можете делать много предположений. Вам просто нужно позволить системе сделать макет и запросить у нее результаты.   -  person Rob Napier    schedule 29.01.2021


Ответы (1)


К вашему первому вопросу, как обсуждалось в комментариях, потому что CTFramesetter имеет различные правила, которые могут добавлять отступы или вносить корректировки глифов. Это зависит от множества вещей (и не обещает быть последовательным с течением времени), поэтому вы не можете догадаться. Просто спросите CTFramesetter (или CTLine), каков размер, и соответствующим образом настройте макет. Или, если у вас есть жесткое ограничение (оно должно соответствовать 100 точкам), установите его, и оно будет перенесено. Но если вы скажете «хорошо», 101 балл тоже было бы неплохо, тогда 100 баллов не были бы ограничением. Сделайте ваши ограничения фактическим ограничением или оставьте его без ограничений (в этом случае вам, вероятно, понадобится CTLine, а не CTFramesetter).

Что касается второго вопроса, CTRun - это диапазон, к которому прикреплены все одинаковые значения атрибутов. NSAttributedString умно в этом отношении и может объединять вместе эквивалентные соседние области, даже если вы устанавливаете атрибуты перекрывающимися способами. Однако это не обещание, что это будет выполняться всегда. Для последовательных объектов CTRun законно иметь одинаковые атрибуты. Это может произойти, когда атрибуты применяются сложным образом или когда строки с атрибутами объединяются.

Если вы проверите атрибуты для своей NSAttributedString, вы обнаружите, что, вероятно, существует некоторая разница между первым и вторым символами, вторым и третьим символами, а затем символы 4-6 имеют точно такие же атрибуты.

person Rob Napier    schedule 29.01.2021
comment
Отредактировал сообщение, чтобы добавить больше контекста к первой проблеме. Я понимаю вашу точку зрения о расширении ограничений - мы точно знаем ограничения - и использовали то же самое. Мы просто изучали, является ли это «дополнительное пространство» достаточно маленьким (и предсказуемым), чтобы иметь меньшее визуальное воздействие, и может ли оно быть добавлено к ограничениям, чтобы получить правильное поведение при взломе (сейчас это просто хак) - person Harnish; 01.02.2021
comment
Пространство непредсказуемо. Для заданного набора входных данных, включая версию ОС и оборудование, он всегда будет одним и тем же; он не меняется случайным образом. Но изменение любого фактора может изменить результат. На практике изменения ОС на этом уровне происходят очень редко, поэтому вы можете бросить кости и рискнуть. Но если вы хотите получить правильный ответ, вы получите его, сделав макет и спросив. Нет документации по алгоритмам, и они не обещаны. - person Rob Napier; 01.02.2021
comment
В вашем конкретном случае вам нужно либо просто добавить дополнительную точку (которая обычно кажется достаточной, но не обещается), и попробовать макет, либо вам нужно будет перебирать назад и вперед между дочерним элементом и parent, пока вы не получите стабильный макет. Я делал это несколько раз в проектах. Это нетривиально, но это можно сделать. Или вы можете избавиться от CTFramesetter и выполнить большую часть макета вручную, чтобы вы точно знали, что происходит, и, возможно, их настраиваете (CTRun по-прежнему вам очень поможет). Я очень редко использую CTFramesetter. Я в основном использую исключительно CTLine и CTRun. - person Rob Napier; 01.02.2021
comment
Или переместитесь вверх по стеку и используйте TextKit, а не Core Text. NSLayoutManager довольно мощный, особенно на Mac. (В iOS я обнаружил, что если вы используете его неожиданным образом, он часто может дать сбой, а документация не объясняет ожидаемые способы хорошо.) Или сделайте свой макет с помощью Autolayout, который уже обрабатывает многие из этих родительских и ребенку необходимо согласовать свои размеры и найти устойчивое решение. - person Rob Napier; 01.02.2021