CIAttributedTextImageGenerator filter - text doesn't fit (NSAttributedString) - swift

I use a Core Image filter CIAttributedTextImageGenerator to generate text as a CIImage. However, sometimes the text just doesn't fit into the resulted CIImage as you can see at the picture:
I tried to play with different key-values of NSAttributedString to make some padding around text but with no success:
func generateImageFromText(_ text: String, style: TextStyle) -> CIImage? {
let font = UIFont.init(name: style.fontName, size: style.fontSize)!
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = style.textAlignment
paragraphStyle.headIndent = 5.0
paragraphStyle.tailIndent = -5.0
paragraphStyle.firstLineHeadIndent = 0
let shadow = NSShadow()
if let shadowStyle = style.shadowStyle {
shadow.shadowColor = shadowStyle.color
shadow.shadowOffset = shadowStyle.offset
shadow.shadowBlurRadius = shadowStyle.blurRadius
}
var strokeColor = UIColor.clear
var strokeWidth: CGFloat = 0.0
if let strokeStyle = style.strokeStyle {
strokeColor = strokeStyle.color
strokeWidth = strokeStyle.width
}
let attributes: [NSAttributedString.Key: Any] = [
.baselineOffset: 50,
.font: font,
.foregroundColor: style.color,
.paragraphStyle: paragraphStyle,
.shadow: shadow,
.strokeColor: strokeColor,
.strokeWidth: strokeWidth
]
let attributedQuote = NSAttributedString(string: text, attributes: attributes)
let textGenerationFilter = CIFilter(name: "CIAttributedTextImageGenerator")!
textGenerationFilter.setValue(attributedQuote, forKey: "inputText")
textGenerationFilter.setValue(NSNumber(value: Double(1.0)), forKey: "inputScaleFactor")
guard let textImage = textGenerationFilter.outputImage else {
return nil
}
return textImage
}
Maybe there are some values of NSAttributedString that I miss which can help to fit in the text?

Related

Text Wrapping not happening as it should in NSTextField Attributed String

I am having an issue with text not wrapping correctly if there is a single quote, or macOS ASCII Extended Character #213 (shift+opt.+]) in a string.
Apple does not escape the media item title string when it is retrieved through the iTunesLibrary framework.
As you can see in the example below, the first string is exactly how it come from the iTunesLibrary using the framework API call. The second string is is the single quote is escaped, the third string is if I use macOS Extended ASCII Character code 213, and the fourth string is if I use a tilde. The tilde is not the right character to use in this situation, but it is the only one that correctly wraps the text in the cell.
I've been working on this for the past 6-8 hours to figure it out and I'm just throwing it out there to see if someone can help me.
ViewController.swift
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame.size = NSSize(width: 616, height: 184)
// Strings
let string1 = "I Keep Forgettin' (Every Time You're Near)"
let string2 = "I Keep Forgettin\' (Every Time You're Near)"
let string3 = "I Keep Forgettin’ (Every Time You're Near)"
let string4 = "I Keep Forgettin` (Every Time You're Near)"
// Formatting
let foreground = NSColor.purple.cgColor
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.tabStops = .none
paragraphStyle.baseWritingDirection = .leftToRight
guard let font = NSFont(name: "Helvetica", size: 28.0) else { return }
// Labels
let label1 = NSTextField(frame: NSRect(x: 20, y: self.view.frame.minY+20, width: 144, height: 144))
label1.cell = VerticallyCenteredTextFieldCell()
label1.wantsLayer = true
label1.layer?.borderColor = NSColor.purple.cgColor
label1.layer?.borderWidth = 0.5
label1.layer?.backgroundColor = NSColor.lightGray.cgColor
label1.alphaValue = 1
var fontSize = bestFontSize(attributedString: NSAttributedString(string: string1, attributes: [.font: font, .paragraphStyle: paragraphStyle]), size: CGSize(width: 136, height: 136))
label1.attributedStringValue = NSAttributedString(string: string1, attributes: [.font: font.withSize(fontSize), .foregroundColor: foreground, .paragraphStyle: paragraphStyle])
self.view.addSubview(label1)
let label2 = NSTextField(frame: NSRect(x: 164, y: self.view.frame.minY+20, width: 144, height: 144))
label2.cell = VerticallyCenteredTextFieldCell()
label2.wantsLayer = true
label2.layer?.borderColor = NSColor.purple.cgColor
label2.layer?.borderWidth = 0.5
label2.layer?.backgroundColor = NSColor.lightGray.cgColor
label2.alphaValue = 1
fontSize = bestFontSize(attributedString: NSAttributedString(string: string2, attributes: [.font: font, .paragraphStyle: paragraphStyle]), size: CGSize(width: 136, height: 136))
label2.attributedStringValue = NSAttributedString(string: string2, attributes: [.font: font.withSize(fontSize), .foregroundColor: foreground, .paragraphStyle: paragraphStyle])
self.view.addSubview(label2)
let label3 = NSTextField(frame: NSRect(x: 308, y: self.view.frame.minY+20, width: 144, height: 144))
label3.cell = VerticallyCenteredTextFieldCell()
label3.wantsLayer = true
label3.layer?.borderColor = NSColor.purple.cgColor
label3.layer?.borderWidth = 0.5
label3.layer?.backgroundColor = NSColor.lightGray.cgColor
label3.alphaValue = 1
fontSize = bestFontSize(attributedString: NSAttributedString(string: string3, attributes: [.font: font, .paragraphStyle: paragraphStyle]), size: CGSize(width: 136, height: 136))
label3.attributedStringValue = NSAttributedString(string: string3, attributes: [.font: font.withSize(fontSize), .foregroundColor: foreground, .paragraphStyle: paragraphStyle])
self.view.addSubview(label3)
let label4 = NSTextField(frame: NSRect(x: 452, y: self.view.frame.minY+20, width: 144, height: 144))
label4.cell = VerticallyCenteredTextFieldCell()
label4.wantsLayer = true
label4.layer?.borderColor = NSColor.purple.cgColor
label4.layer?.borderWidth = 0.5
label4.layer?.backgroundColor = NSColor.lightGray.cgColor
label4.alphaValue = 1
fontSize = bestFontSize(attributedString: NSAttributedString(string: string4, attributes: [.font: font, .paragraphStyle: paragraphStyle]), size: CGSize(width: 136, height: 136))
label4.attributedStringValue = NSAttributedString(string: string4, attributes: [.font: font.withSize(fontSize), .foregroundColor: foreground, .paragraphStyle: paragraphStyle])
self.view.addSubview(label4)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func bestFontSize(attributedString: NSAttributedString, size: CGSize) -> CGFloat {
// Create a property to hold the font and size
var font: NSFont?
// Get the font information from the string attibutes
attributedString.enumerateAttribute(.font, in: NSRange(0..<attributedString.length)) { value, range, stop in
if let attrFont = value as? NSFont {
font = attrFont
}
}
if font == nil {
return 0
}
// Get any paragraph styling attributes
var paragraphStyle: NSMutableParagraphStyle?
attributedString.enumerateAttribute(.paragraphStyle, in: NSMakeRange(0, attributedString.length)) { value, range, stop in
if let style = value as? NSMutableParagraphStyle {
paragraphStyle = style
}
}
if paragraphStyle == nil {
return 0
}
// Create a sorted list of words from the string in descending order of length (chars) of the word
let fragment = attributedString.string.split(separator: " ").sorted() { $0.count > $1.count }
// Create a bounding box size that will be used to check the width of the largest word in the string
var width = String(fragment[0]).boundingRect(with: CGSize(width: .greatestFiniteMagnitude, height: size.height), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: font!, .paragraphStyle: paragraphStyle!], context: nil).width.rounded(.up)
// Create a bounding box size that will be used to check the height of the string
var height = attributedString.string.boundingRect(with: CGSize(width: size.width, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: font!, .paragraphStyle: paragraphStyle!], context: nil).height.rounded(.up)
while height >= size.height || width >= size.width {
guard let pointSize = font?.pointSize else {
return 0
}
font = font?.withSize(pointSize-0.25)
width = String(fragment[0]).boundingRect(with: CGSize(width: .greatestFiniteMagnitude, height: size.height), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: font!, .paragraphStyle: paragraphStyle!], context: nil).width.rounded(.up)
height = attributedString.string.boundingRect(with: CGSize(width: size.width, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: font!, .paragraphStyle: paragraphStyle!], context: nil).height.rounded(.up)
}
return font!.pointSize
}
}
VerticallyCenteredTextFieldCell.swift
import Cocoa
class VerticallyCenteredTextFieldCell: NSTextFieldCell {
// https://stackoverflow.com/questions/11775128/set-text-vertical-center-in-nstextfield/33788973 - Sayanti Mondal
func adjustedFrame(toVerticallyCenterText rect: NSRect) -> NSRect {
// super would normally draw from the top of the cell
var titleRect = super.titleRect(forBounds: rect)
let minimumHeight = self.cellSize(forBounds: rect).height
titleRect.origin.y += (titleRect.height - minimumHeight) / 2
titleRect.size.height = minimumHeight
return titleRect
}
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
super.drawInterior(withFrame: adjustedFrame(toVerticallyCenterText: cellFrame), in: controlView)
}
}
This is the result I get:
Anyone else get the same result running this?

How to set a proper location for CIAttributedTextImageGenerator?

I use "CIAttributedTextImageGenerator" to generate a CIImage from text and then I overlay it on my edited image with "CISourceAtopCompositing" :
// Text to image
let font = UIFont.systemFont(ofSize: 78)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.firstLineHeadIndent = 5.0
let shadow = NSShadow()
shadow.shadowColor = UIColor.red
shadow.shadowBlurRadius = 5
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: UIColor.blue,
.paragraphStyle: paragraphStyle,
.shadow: shadow
]
let attributedQuote = NSAttributedString(string: "General Kenobi", attributes: attributes)
let textGenerationFilter = CIFilter(name: "CIAttributedTextImageGenerator")!
textGenerationFilter.setValue(attributedQuote, forKey: "inputText")
textGenerationFilter.setValue(NSNumber(value: Double(inputSizeFactor)), forKey: "inputScaleFactor")
let textImage = textGenerationFilter.outputImage!.oriented(.right)
finalImage = textImage
.applyingFilter("CISourceAtopCompositing", parameters: [ kCIInputBackgroundImageKey: finalImage])
However, the text image is always in the right bottom corner of my edited image:
How can I set it a custom location? For example, if I want it to be in the bottom right corner, or in the center of the edited image?
You can apply transformations to the image before you composite it over the background:
let transformedText = textImage.transformed(by: CGAffineTransform(translationX: 200, y: 300)
I'm afraid you have to calculate the exact position for centering the image yourself. There's no built-in way to center one image ontop another.

How can I center an image on a textView who is attached to a NSTextAttachment type?

I have a textView (programmatically) and I inserted an image appended to a NSTextAttachment and want it to be aligned to the center of the text view... but I haven't found any solutions yet...I was wondering if that task is possible to be done via code only...
let image = ResizeImage(image: UIImage(named: "logo")!, targetSize: size)
let image1Attachment = NSTextAttachment()
image1Attachment.image = image
let image1Attachments = NSAttributedString(attachment: image1Attachment)
My best approach to the solution on trying to achieve this was this code :
let image = ResizeImage(image: UIImage(named: "telepaint1")!,
targetSize: size)
//let textAttachment = NSTextAttachment()
let textAttachment = NSTextAttachment()
//let imageStyle = NSMutableParagraphStyle()
let imageStyle = NSMutableParagraphStyle()
//imageStyle.alignment = .center
imageStyle.alignment = .center
//textAttachment.image = image
textAttachment.image = image
let imageText = NSAttributedString(attachment:
textAttachment).mutableCopy() as! NSMutableAttributedString
//attempt to center image...
let attributedStringToAppend: NSMutableAttributedString =
NSMutableAttributedString(attributedString:
NSAttributedString(string: "\n\n"))
//attributedStringToAppend.addAttributes(attr, range: range)
let combination2: NSMutableAttributedString =
NSMutableAttributedString(attributedString:
NSAttributedString(string: "\n\n"))
combination2.append(lineBreak)
// combination2.addAttribute(attr, range: NSRange(location: 0,
length: length))
combination2.append(imageText)
//this text will display the "Walktrough" text on image
combination2.append(attributedText)
tv.attributedText = combination2
Answer(worked for me!):
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
attributedText.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: length))
let size = CGSize(width: 100, height: 100)
//Here is an attempt to resize image and center it...
let image = ResizeImage(image: UIImage(named: "logo")!.withRenderingMode(.alwaysTemplate), targetSize: size)
let textAttachment = NSTextAttachment()
let imageStyle = NSMutableParagraphStyle()
imageStyle.alignment = .center
textAttachment.image = image
let imageText = NSAttributedString(attachment: textAttachment).mutableCopy() as! NSMutableAttributedString
let length2 = imageText.length
imageText.addAttribute(NSAttributedString.Key.paragraphStyle, value: imageStyle, range: NSRange(location: 0, length: length2))

SKLabelNode + NSMutableAttributedString = no shadow

let paragraph = NSMutableParagraphStyle()
paragraph.alignment = NSTextAlignment.right
paragraph.tailIndent = -3
let shadow = NSShadow()
shadow.shadowBlurRadius = 5
shadow.shadowColor = UIColor.gray
shadow.shadowOffset = CGSize(width: 2, height: -2)
let text = NSMutableAttributedString(string: "MY LABEL", attributes:
[NSShadowAttributeName : shadow,
NSStrokeColorAttributeName : UIColor.black,
NSStrokeWidthAttributeName : -3,
NSForegroundColorAttributeName: UIColor.white,
NSFontAttributeName : UIFont(name: "MyFont", size: 24) as Any,
NSParagraphStyleAttributeName : paragraph])
let node = SKLabelNode()
addChild(node)
node.attributedText = attributedString
It works but there is NO shadow. I can use the same NSMutableAttributedString on well-known ASAttributedLabel and it works nice and there IS a shadow, but i want to use SKLabelNode to achieve better performance on IOS11. ASAttributedLabel may lag on dynamic scenes :(

CATextLayer is ignoring NSMutableParagraphStyle

I want to use NSMutableAttributedString to draw the text on the CATextLayer with the text. I try set attributes to the like font and fontcolor of the NSMutableAttributedString through the [attrString setAttributes:attributes range:range]; but all attributes in the NSMutableParagraphStyle are ignored.
let str = "I want to use NSMutableAttributedString to draw the text on the CATextLayer with the text. I try set attributes to the like font and fontcolor of the NSMutableAttributedString through the [attrString setAttributes:attributes range:range]; but all attributes in the NSMutableParagraphStyle are ignored."
let att = NSMutableAttributedString(string: str)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 12
att.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, str.characters.count))
let contentLayer = initTextLayer("", andFontSize: 11)
contentLayer.string = att
contentLayer.frame = CGRect(x: 30 * AUTOSIZESCALEX, y: CGRectGetMaxY(titleLayer.frame) + 15 * AUTOSIZESCALEY, width: SCREEN_WIDTH - 60 * AUTOSIZESCALEX, height: 200 * AUTOSIZESCALEY)
self.contentView.layer.addSublayer(contentLayer)
func initTextLayer(title: String, andFontSize size: CGFloat) -> CATextLayer{
let textLayer = CATextLayer()
textLayer.foregroundColor = UIColor(hexString: "333333").CGColor
textLayer.string = title
textLayer.alignmentMode = kCAAlignmentLeft
textLayer.contentsScale = UIScreen.mainScreen().scale
textLayer.shouldRasterize = false
textLayer.wrapped = true
textLayer.fontSize = size * AUTOSIZESCALEY
return textLayer
}
is there something I'm doing wrong?
I had a similar problem and used this:
// Set text alignment. Apparently CATextLayer doesn't use value in NSAttributedString.
if let style = text.attribute(NSParagraphStyleAttributeName, atIndex: 0, effectiveRange: nil) as? NSParagraphStyle {
switch style.alignment {
case .Center:
textLayer.alignmentMode = kCAAlignmentCenter
case .Left:
textLayer.alignmentMode = kCAAlignmentLeft
case .Right:
textLayer.alignmentMode = kCAAlignmentRight
default:
break
}
}