PDFPage.attributedString returns the wrong font - ios

I'm reading an existing PDF using PDFKit. I get an attributedString for a page, but the fonts in the string don't match the fonts actually in the PDF:
The fonts in the PDF (according to several different apps) are:
CourierFinalDraft (TypeType Roman) Embedded Subset
CourierFinalDraft-Bold (TrueType Roman) Embedded Subset
CourierFinalDraft-Italic (TrueType Roman) Embedded Subset
My Swift code to get the font is:
guard let page = pdf.page(at: pageNo) else { return }
guard let content = page.attributedString else { return }
content.enumerateAttributes(in: range, options:[]) {(dict: [String:Any], range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let font = dict[NSFontAttributeName] as! UIFont
}
All of the text is returned in a single range. The font returned has the following:
font-family: "Times New Roman"
font-name: "TimesNewRomanPSMT"
So the Bold and Italic text are returned in the same range as the normal text, and I can't distinguish among them, which is what I'm trying to do. As well as the font name I also look at:
font.fontDescriptor.symbolicTraits.contains(.traitItalic)
but of course this is always False as all text is returned in the same range as if it is normal.
This is using the XCode simulator, in case that's relevant. The PDF does render correctly (including bold and italics) on Safari in the Simulator. Unfortunately I can't try it out on a real iPhone.

This question is a few years old, but I found that I had to make a mutable copy of the data in order to interact with it and then use the information available to modify the underlying font and re-display it.
let str = content.mutableCopy() as! NSMutableAttributedString
str.beginEditing()
str.enumerateAttribute(.font,
in: NSRange(location: 0, length: str.length),
options: []) { value, range, _ in
guard let value = value as? UIFont else {
return
}
let isBold = value.fontDescriptor.symbolicTraits.contains(.traitBold)
let font = // Your font logic here based on traits
str.removeAttribute(.font, range: range)
str.addAttribute(.font, value: font, range: range)
str.addAttribute(.paragraphStyle, value: style, range: range)
}
str.endEditing()
I have not gone over performance issues on this yet for larger pdfs, but it does not seem to take up too many resources for pdfs between 10-20 pages. For a higher number of pages, I would most likely create an actual paging mechanism and then adjust for each page on the fly rather than loop the entire pdf file.

Related

Create custom NSAttributedString.Key

I'm trying to build a simple note app. At the moment, I'm focusing on the possibility to set the text with different text styles (e.g. body, headline, bold, italic, etc.). I used a NSAttributedString to set the different text styles. Now, I'd like to detect which style has been applied to the selected text.
I thought a good way to do it would have been to create a custom NSAttributedString.Key, so that I can assign it when setting the attributes (e.g. .textStyle: "headline", and read it when I need to detect it. I tried implementing it as an extension of NSAttributedString.Key but without success. What would be the correct way to do it?
Is there a better alternative?
You can simply create a TextStyle enumeration and set your cases "body, headline, bold, italic, etc" (You can assign any value to them if needed). Then you just need to create a new NSAttributedString key:
enum TextStyle {
case body, headline, bold, italic
}
extension NSAttributedString.Key {
static let textStyle: NSAttributedString.Key = .init("textStyle")
}
Playground Testing
let attributedString = NSMutableAttributedString(string: "Hello Playground")
attributedString.setAttributes([.textStyle: TextStyle.headline], range: NSRange(location: 0, length: 5))
attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attributes, range, stop in
print(attributes, range, stop )
print(attributedString.attributedSubstring(from: range))
}

How to split NSAttributedString across equal bounds views with correct word wrap

I have been grappling with this since a while. There are APIs that give us bounds size for given attributes of NSAttributedString.
But there is no direct way to get string range that would fit within given bounds.
My requirement is to fit very long string across a few paged views (PDF is not an option, neither is scrolling). Hence I have to figure out string size for each view (same bounds).
Upon research I found that CTFramesetterSuggestFrameSizeWithConstraints and its friends in Core Text maybe of help. I tried the approach described here, but the resulting ranges have one ugly problem:
It ignores word breaks (a different problem unrelated to Core Text, but I would really like to see if there was some solution to that as well).
Basically I want paging of text across number of UITextView objects, but not getting the right attributed string splits.
NOTE:
My NSAttributedString attributes are as follows:
let attributes: [NSAttributedString.Key : Any] = [.foregroundColor : textColor, .font : font, .paragraphStyle : titleParagraphStyle]
(titleParagraphStyle has lineBreakMode set to byWordWrapping)
extension UITextView
{
func getStringSplits (fullString: String, attributes: [NSAttributedString.Key:Any]) -> [String]
{
let attributeString = NSAttributedString(string: fullString, attributes: attributes)
let frameSetterRef = CTFramesetterCreateWithAttributedString(attributeString as CFAttributedString)
var initFitRange:CFRange = CFRangeMake(0, 0)
var finalRange:CFRange = CFRangeMake(0, fullString.count)
var ranges: [Int] = []
repeat
{
CTFramesetterSuggestFrameSizeWithConstraints(frameSetterRef, initFitRange, attributes as CFDictionary, CGSize(width: bounds.size.width, height: bounds.size.height), &finalRange)
initFitRange.location += finalRange.length
ranges.append(finalRange.length)
}
while (finalRange.location < attributeString.string.count)
var stringSplits: [String] = []
var startIndex: String.Index = fullString.startIndex
for n in ranges
{
let endIndex = fullString.index(startIndex, offsetBy: n, limitedBy: fullString.endIndex) ?? fullString.endIndex
let theSubString = fullString[startIndex..<endIndex]
stringSplits.append(String(theSubString))
startIndex = endIndex
}
return stringSplits
}
}
My requirement is to fit very long string across a few paged views (PDF is not an option, neither is scrolling). Hence I have to figure out string size for each view (same bounds).
No, you don't have to figure that out. The text kit stack will do it for you.
In fact, UITextView will flow long text from one text view to another automatically. It's just a matter of configuring the text kit stack — one layout manager with multiple text containers.

Change only fontsize of NSAttributedString

I have a NSAttributedString that was loaded from a RTF file, so it already holds several font-attributes for different ranges.
Now I want to adapt the font size to the screensize of the device, but when I add a whole new font attribute with a new size, the other fonts disappear.
Is there a way to change only the font size for the whole string?
If you only want to change the size of any given font found in the attributed string then you can do:
let newStr = someAttributedString.mutableCopy() as! NSMutableAttributedString
newStr.beginEditing()
newStr.enumerateAttribute(.font, in: NSRange(location: 0, length: newStr.string.utf16.count)) { (value, range, stop) in
if let oldFont = value as? UIFont {
let newFont = oldFont.withSize(20) // whatever size you need
newStr.addAttribute(.font, value: newFont, range: range)
}
}
newStr.endEditing()
print(newStr)
This will keep all other attributes in place.
If you want to replace all fonts in a given attributed string with a single font of a given size but keep all other attributes such as bold and italic, see:
NSAttributedString, change the font overall BUT keep all other attributes?

iOS Swift Relative font size

I have a UITextView, and I want to change its font size.
however, I want it to change relatively since it pulls from a file that has multiple font sizes in it and I want it to change accordingly.
for example, I have a word in font size 36 and one in font size 12 and I want to scale them by 0.75% to 27 and 9 respectively.
If I try:
textview.font = UIFont(name: textview.font.fontName, size: 20)
it will only change the entire UITextView font size.
thanks!
You can use this extension:
extension NSAttributedString {
#warn_unused_result
func scaleBy(scale: CGFloat) -> NSAttributedString {
let scaledAttributedString = NSMutableAttributedString(attributedString: self)
scaledAttributedString.enumerateAttribute(NSFontAttributeName, inRange: NSRange(location: 0, length: scaledAttributedString.length), options: NSAttributedStringEnumerationOptions(rawValue: 0)) { (value, range, _) in
if let oldFont = value as? UIFont {
let newFont = oldFont.fontWithSize(oldFont.pointSize * scale)
scaledAttributedString.removeAttribute(NSFontAttributeName, range: range)
scaledAttributedString.addAttribute(NSFontAttributeName, value: newFont, range: range)
}
}
return scaledAttributedString
}
}
Then just call something like:
textField.attributedText = textField.attributedText!.scaleBy(0.5)
Example:
You would have to write (or find) a parser for the rich text format file that could extract the font size data for each text element (I think this would be the \fsN tags in most cases) and then use that number (multiplied by 0.75) to set the size of each word or phrase individually. You could use an attributed string if the differently sized words need to be recombined into a single string, but that wouldn't be necessary if each word or phrase was in a separate label.
Personally, I would disregard the font sizes of the source data and impose a layout within the app that looks nice, if that's an option.

Using MMMarkdown, only system font will show bold or italicized text

I'm using MMMarkdown and when I apply no font to UILabel or UITextView (I have to keep UITextView so TTTAttributedLabel won't do), all of markdown works. When I give the label or textview a font, I only get markdown hyperlinks to work. I tried switching to the AttributedMarkdown library but hyperlinks don't work at all with that one.
In my Markdown Class:
class Markdown: NSObject {
func markdownString(stringForVideoDescription:NSString) -> NSMutableAttributedString {
var options = [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType]
var error:NSError?
var markdown:NSString = stringForVideoDescription
var html:NSString = MMMarkdown.HTMLStringWithMarkdown(markdown, error: &error)
var markdownAttributedString:NSMutableAttributedString = NSMutableAttributedString(data: html.dataUsingEncoding(NSUTF32StringEncoding)!, options: options, documentAttributes: nil, error: &error)!
if let font: UIFont = UIFont(name: "Marion-Regular", size: 14) {
markdownAttributedString.addAttributes([NSUnderlineStyleAttributeName:NSUnderlineStyleAttributeName], range: NSMakeRange(0, markdownAttributedString.length))
}
println(html) // used to see that markdown is in fact working
return markdownAttributedString
}
In my view, I'm actually pulling from json api but I replace to test with:
let bodyText = "\*This* sample \[I'm an inline-style link](https://www.google.com)"
myTextView.attributedText = Markdown().markdownString(bodyText)
Do I need to set up css to specify font for each bold, italicized text? Any help is greatly appreciated as my designers don't want to use the default font of Times New Roman.

Resources