给定具有任何 pointSize 的 UIFont,如何计算适当的笔画宽度?

How to calculate appropriate stroke width given a UIFont with any pointSize?

我想对文本应用单实线描边。这很容易使用 NSAttributedString 指定 .strokeWidth 获得。然而,我发现确定 strokeWidth 应该在任何给定的 pointSize 处呈现什么 UIFont 是很棘手的。我可以很容易地说,好吧,在 50 的磅值下,1 的笔画宽度看起来很棒。我凭直觉假设,如果将字体大小加倍,则应该将笔画宽度加倍,因此随着字体缩放,笔画将按比例缩放,并导致笔画粗细与原始 "base" 字体大小一致。然而,事实并非如此。随着字体大小和笔画宽度按比例增加,笔画宽度变得太粗了。

此处的屏幕截图显示第一行的字体大小为 50,笔画宽度为 1。下一行加倍,因此字体大小为 100,笔画宽度为 2,重复此操作直到最后一行为 350 vs 7。

我相信这是因为笔画既向内又向外呈现。它的中心在角色的边缘,然后向两个方向扩展。如果你比较这两张图片,你可以看到这一点,这张没有应用笔触。

因此随着字体大小的增加,笔划宽度不应按比例增加,需要以较慢的速度增加以确保粗细在所有大小上保持一致。我正在尝试确定计算此值的正确方法。

所以给定一个看起来很理想的基本配置(假设 50pt 字体大小和 1pt 笔画宽度)和一个新的 pointSize(例如 350pt),你如何计算正确的 strokeWidth ?或者我应该使用不同的值而不是 pointSize?

我当前按比例缩放的算法是:
let strokeWidth = font.pointSize / 50(简单地求解 1/50 = x/pointSize 中的 x)

这是我用来绘制文本的代码:

    let text = "hello"
    let imageRect = CGRect(x: 0, y: 0, width: 343 * 3, height: 500 * 3)

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let alphaInfo = CGImageAlphaInfo.premultipliedLast.rawValue

    let bitmapContext = CGContext(data: nil, width: Int(imageRect.width), height: Int(imageRect.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: alphaInfo)!
    bitmapContext.setAlpha(1)
    bitmapContext.setTextDrawingMode(CGTextDrawingMode.fill)

    //1
    bitmapContext.textPosition = CGPoint(x: 40, y: 1080)
    let displayLineText1 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 50), .strokeColor: UIColor.red, .strokeWidth: 1]))
    CTLineDraw(displayLineText1, bitmapContext)

    //2
    bitmapContext.textPosition = CGPoint(x: 40, y: 1000)
    let displayLineText2 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 100), .strokeColor: UIColor.red, .strokeWidth: 2]))
    CTLineDraw(displayLineText2, bitmapContext)

    //3
    bitmapContext.textPosition = CGPoint(x: 40, y: 875)
    let displayLineText3 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 150), .strokeColor: UIColor.red, .strokeWidth: 3]))
    CTLineDraw(displayLineText3, bitmapContext)

    //4
    bitmapContext.textPosition = CGPoint(x: 40, y: 725)
    let displayLineText4 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 200), .strokeColor: UIColor.red, .strokeWidth: 4]))
    CTLineDraw(displayLineText4, bitmapContext)

    //5
    bitmapContext.textPosition = CGPoint(x: 40, y: 540)
    let displayLineText5 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 250), .strokeColor: UIColor.red, .strokeWidth: 5]))
    CTLineDraw(displayLineText5, bitmapContext)

    //6
    bitmapContext.textPosition = CGPoint(x: 40, y: 310)
    let displayLineText6 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 300), .strokeColor: UIColor.red, .strokeWidth: 6]))
    CTLineDraw(displayLineText6, bitmapContext)

    //7
    bitmapContext.textPosition = CGPoint(x: 40, y: 40)
    let displayLineText7 = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.foregroundColor: UIColor.black, .font: UIFont.systemFont(ofSize: 350), .strokeColor: UIColor.red, .strokeWidth: 7]))
    CTLineDraw(displayLineText7, bitmapContext)

    let textCGImage = bitmapContext.makeImage()!
    let textImage = CIImage(cgImage: textCGImage)

来自 the documentation for .strokeWidth(已强调):

The value of this attribute is an NSNumber object containing a floating-point value. This value represents the amount to change the stroke width and is specified as a percentage of the font point size. Specify 0 (the default) for no additional changes. Specify positive values to change the stroke width alone. Specify negative values to stroke and fill the text. For example, a typical value for outlined text would be 3.0.

因此,该值已经缩放到字体大小。你也不应该自己扩展它。选择一个值,在任何给定的字体大小下提供您喜欢的相对笔画宽度,并将其用于所有字体大小。