I would like to apply a single solid line stroke to text. This is easily obtained using NSAttributedString
specifying the .strokeWidth
. However I am finding it to be tricky to determine what the strokeWidth
should be given a UIFont
to be rendered at any given pointSize
. I can easily say, okay at a point size of 50 a stroke width of 1 looks great. I intuitively assumed if you double the font size you should double the stroke width, thus as the font scales the stroke will scale proportionally and result in the stroke thickness appearing consistent with the original "base" font size. However this is not what occurs. As the font size and stroke width increase proportionally, the stroke width becomes much too thick.
The screenshot here shows the first line is a font size of 50 and stroke width of 1. Next line is doubled so font size 100 stroke width 2, and this is repeated until the last line which is 350 vs 7.
I believe this is occurring because the stroke is rendered both inward and outward. Its center is at the edge of the character, then it expands in both directions. You can see this if you compare these two images, this one without a stroke applied.
So as the font size increases, the stroke width should not increase proportionally, it needs to increase at a slower rate to ensure the thickness is consistent across all the sizes. I am trying to determine the correct way to calculate this value.
So given a base configuration that looks desirable (let's just say 50pt font size and 1pt stroke width) and a new pointSize
(for example 350pt), how do you calculate the correct strokeWidth
? Or perhaps I should utilize a different value not pointSize
?
My current algorithm that proportionally scales it is:
let strokeWidth = font.pointSize / 50
(simply solving for x in 1/50 = x/pointSize
)
Here is the code I'm using to draw this text:
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)