iOS 13:如何在 UIKit/SwiftUI 中调整自定义字体的前导/下降/行高

mar*_*ius 5 fonts uikit uifont ios swift

我正在使用自定义字体,并且渲染以某种方式搞砸了行高,可能是因为配置错误descentleading(?),因此 g 和 j 在渲染文本的最后一行被切断。我认为这可能是这种特定字体的问题,因为 Sketch 也暴露了相关字体的类似问题,但我觉得我对排版测量或字体的了解还不够。我发现Typographic Concepts上的这个 Apple 文档页面非常有见地。

渲染标签暴露切断

我用 FontLab 的测试版本查看了字体本身,顺便说一句,这是我第一次使用的 - 所以我几乎不知道我在看什么。看起来 g 确实低于配置的descent,这似乎是最后一行的内容。(?)(参见:FontLab 中的字符视图,显示 g 的下降部分

通过lineSpacing我可以调整线条本身之间的距离以在前几行中解决这个问题。我知道iOS的14会带来一个办法修改leadingText在SwiftUI。但我需要针对 iOS 13,所以这无济于事。

我也试过 SwiftUI 的Text,一个普通的UILabel.textUILabel.attributedText自定义的段落样式,但我在那里调整的任何东西似乎都没有缓解这个问题。视图甚至没有剪辑。只是向框架添加填充根本没有帮助。它增加了距离,但 g 和 j 仍然被切断。

我能做什么?当最后一行有 ag 和 j 时,子类UILabel并覆盖 theintrinsicContentSize以添加一些额外的空间?感觉 a) 脏 b) 考虑到填充没有帮助,它可能无法解决问题?

字体本身是这里的问题吗?我可以以某种方式修补字体而不会使它变得更糟吗?

当我使用较低级别的 API 时,有什么方法可以修改字体的前导或下降高度?似乎我可以像CTFontCreateCopyWithAttributes(_:_:_:_:)候选人一样进入 CoreText,如果我可以通过属性修改前导、行空间或下降?可以或猴子补丁/ swizzle 东西而不会射中自己的膝盖吗?我应该向雷达提交反馈吗?

agi*_*007 1

我知道你在问什么,因为我在自定义字体方面也遇到过同样的问题。我将提供两种解决方案。在我自己的项目中,我遵循了您的建议,重写了intrinsicContentSize并添加了高度和宽度的填充乘数。就我而言,字体是面向用户的,因此我有一个包含所有相关信息的结构。仅供参考 Chalkduster 在系统和剪辑中。我也相信这都是字体文件本身造成的。

解决方案1:示例:

struct UserFont{
    var name : String
    var displayName : String
    var widthMultiplier : CGFloat
    var heightMultiplier : CGFloat
}   
Run Code Online (Sandbox Code Playgroud)

然后在我的 UILabel 中,我将其子类化以使用这两个指标

@IBDesignable
class MultiplierUILabel: UILabel {
    @IBInspectable var widthPaddingMultiplier : CGFloat = 1
    @IBInspectable var heightPaddingMultiplier : CGFloat = 1
    override var intrinsicContentSize: CGSize{
       return CGSize(width: super.intrinsicContentSize.width * widthPaddingMultiplier, height: super.intrinsicContentSize.height * heightPaddingMultiplier)
    }
}
Run Code Online (Sandbox Code Playgroud)

这对我来说是最简单的实现,因为我相应地找到了字体和乘数比例。

解决方案 2:
您可以通过测量字形边界并调整原点 y 来使绘制发生得稍高一些。例如,这修复了系统中包含的 Chalkduster 字体的剪裁问题。

@IBDesignable
class PaddingUILabel: UILabel {
    override func drawText(in rect:CGRect) {
        //hello
        guard let labelText = text else {  return super.drawText(in: rect) }
        //just some breathing room
        let info = boundsForAttrString(str: labelText, font: self.font!, kern: .leastNormalMagnitude)
        let glyph = info.glyph
        var newRect = rect
        let glyphPadding = -(glyph.origin.y)
        if glyphPadding - info.descent > 1 && info.descent != 0{
            newRect.origin.y -= glyphPadding/2
        }else{
            if info.descent != 0{
                newRect.origin.y += (info.descent - glyphPadding)/2
            }
        }
        super.drawText(in: newRect)
    }
    
    func boundsForAttrString(str:String,font:UIFont,kern:CGFloat)->(glyph:CGRect,descent:CGFloat){
        let attr = NSAttributedString(string: str, attributes: [.font:font,.kern:kern])
        let line = CTLineCreateWithAttributedString(attr)
        var ascent : CGFloat = 0
        var descent : CGFloat = 0
        var leading : CGFloat = 0
        
        CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
        let glyph = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds).integral
        return (glyph,leading != 0 ? descent : 0)
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方案2的结果:系统 系统

使用字形边界的 PaddingUILabel 填充UI标签