Непоследовательное поведение кернинга в NSAttributedString - PullRequest
0 голосов
/ 01 марта 2019

Мне нужно использовать атрибут Kern из NSAttributedString.Как я вижу в документации, значение этого атрибута по умолчанию равно 0.0.Но я столкнулся со странным поведением для фразы Hello, world (для фразы "Hello" все нормально):

NSDictionary<NSString*, id>* attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:12]};
NSString* text = @"Hello, World";
NSAttributedString* string = [[NSAttributedString alloc] initWithString:text attributes:attributes];
CGSize size1 = [string size];

NSMutableDictionary<NSString*, id>* attributesWithKernel = [attributes mutableCopy];
attributesWithKernel[NSKernAttributeName] = @(0.0);
NSAttributedString* stringWithKern = [[NSAttributedString alloc] initWithString:text attributes:attributesWithKernel];
CGSize size2 = [stringWithKern size];   
XCTAssertTrue(CGSizeEqualToSize(size1, size2)); //here test falls
//size1 = size1 = (width = 68.8125, height = 14.3203125)
//size2 = (width = 69.515625, height = 14.3203125)

Чтобы сделать равными size1 и size2, кернинг должен быть равен -7.105427357601002e-15, я знаю, что этоочень близко к 0.0, но это странно, потому что это меняет ширину почти на пиксель.
NSAttributedString имеет такое же поведение в Objective-C и в Swift, например для swift:

    let text = "Hello, World"
    let attributes : [NSAttributedString.Key : Any] = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: UIFont.systemFontSize)]
    let str = NSAttributedString(string: text, attributes: attributes)
    let size = str.size()

    var attributesWithKern = attributes
    attributesWithKern[NSAttributedString.Key.kern] = NSNumber(value: 0.0)
    let strWithKern = NSAttributedString(string: text, attributes: attributesWithKern)
    let sizeWithKern = strWithKern.size()

    XCTAssertTrue(size == sizeWithKern)

Как я могу исправить это поведение?

PS Теперь я просто удаляю NSKernAttributeKey из строки атрибута, если ключ равен 0.0, но я не думаю, что это хорошее решение.

Ответы [ 2 ]

0 голосов
/ 02 марта 2019

Я считаю, что документы здесь неправильные, и стоит открыть радар по этому поводу.Когда значение не установлено, оно интерпретируется как «нормальный кернинг».Когда 0 установлен, это интерпретируется как «отключить кернинг», поэтому ширина немного шире (кернинг обычно немного отрицателен, в результате чего в этом шрифте появляются символы пары керн, такие как «W» и «o», немногоближе).Я не думаю, что есть какой-либо способ явно запросить «кернинг по умолчанию» без удаления атрибута.

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

Причина, по которой ваше крошечное отрицательное значение работает, заключается в том, что он не равен нулю, поэтому он не отключает кернинг, но он настолько мал, что поведение очень, очень близко к значению по умолчанию,и вы работаете с точностью Double в промежуточных вычислениях (или, возможно, с точностью Float, в зависимости от того, как она реализована внутри).Вы должны обнаружить, что ваш тест проходит для любого значения, меньшего (ближе к нулю), чем это, а не только это значение.В моих тестах положительный 7e-15 также работает, например.

0 голосов
/ 02 марта 2019

Я провел несколько тестов на основе вашего кода и подтверждаю это поведение, которое выглядит как ошибка.Дело в том, что для некоторых букв строки равны, для некоторых нет.Например, строки с «Hello Mo» или «Hello Oo» равны, но, например, «Hello WoWoWo» сильно отличаются.Таким образом, мы видим, что кернинг добавляется для буквы «W» без какой-либо причины.Это также может зависеть от выбранного шрифта, хотя я не проверял его.Я вижу здесь единственное решение, которое вы использовали - удаление NSKernAttributeKey, если оно равно 0.

enter image description here

...