2 years ago

#116550

test-img

Peter W.

NSFont autoscaling via NSStringDrawingContext does not work

I'm building a small test app that draws text into rectangles on images. Said text might sometimes be too long to be drawn into said rectangles, but I thought I would simply use NSStringDrawingContext and NSAttributedString.boundingRect(with:options:context:) in order to generate a scale factor that I'd use to scale the font down to make it fit.

The problem I'm now facing is that this straight up is not working, as actualScaleFactor is always 1.0, even though it should be less than that. To demonstrate, I've prepared a minimal reproducible example.

The image used as input variable image can be found here: https://i.imgur.com/VNd2y8r.png. The text target rectangle rect is marked by the black rectangle in the image.

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
paragraphStyle.lineBreakMode = .byWordWrapping

let textAttributes: [NSAttributedString.Key: Any] = [
    .font: NSFont(name: "Futura-Bold", size: 100)!,
    .foregroundColor: NSColor.white,
    .paragraphStyle: paragraphStyle,
]
let text = "A very long string that'll be auto-wrapped when it gets drawn"
let attrStr = NSAttributedString(string: text, attributes: textAttributes)

let drawingOptions: NSString.DrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let context = NSStringDrawingContext()

// counter-intuitively, 1.0 is the "maximum" minimum scale factor
// see also https://developer.apple.com/documentation/uikit/nsstringdrawingcontext/1534020-minimumscalefactor
context.minimumScaleFactor = 1.0 

let position = NSPoint(x: 80, y: 140)
let size = NSSize(width: 640, height: 200)
let rect = NSRect(origin: position, size: size)

let newImage = NSImage(size: image.size, flipped: true) { imageRect in
    image.draw(in: imageRect)

    // let NSStringDrawingContext do its thing
    attrStr.boundingRect(with: size, options: drawingOptions, context: context)
    print("Actual Scale Factor: \(context.actualScaleFactor)") // is always 1.0

    attrStr.setAttributes([.font: font.withSize(font.pointSize * context.actualScaleFactor)], range: NSRangeFromString(text))
    attrStr.draw(with: rect, options: drawingOptions, context: context)

    return true
}

At this point, newImage should be an image that looks something like this:

successful font shrinkage (recreated in an image editing tool)

Instead, this is what I get: no font shrinkage at all

This is obviously wrong.

While I was looking for a solution, I found this related SO question from 2013 with a conclusion of "it's broken", but surely this has to be possible somehow by now. iOS and macOS label controls both have this feature, SwiftUI even makes it available as a modifier entitled simply minimumScaleFactor. Is there a replacement API I'm not aware of?

swift

nsattributedstring

appkit

core-text

0 Answers

Your Answer

Accepted video resources