Creating styled text with NSMutableAttributedString

Here is a snippet of code demonstrating how to construct styled text using NSMutableAttributedString, the editable version of NSAttributedString.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

-- classes, constants, and enums used
property NSFont : a reference to current application's NSFont
property NSFontManager : a reference to current application's NSFontManager
property NSColor : a reference to current application's NSColor
property NSMutableAttributedString : a reference to current application's NSMutableAttributedString
property NSDictionary : a reference to current application's NSDictionary
property NSFontAttributeName : a reference to current application's NSFontAttributeName
property NSForegroundColorAttributeName : a reference to current application's NSForegroundColorAttributeName
property NSBoldFontMask : a reference to current application's NSBoldFontMask

set theString to "The quick brown fox jumped over the lazy dog"
set theBoldWords to {"fox", "dog"}
set theRedWords to {"jumped"}
set theGreenWords to {"quick", "lazy"}

set theBlackColor to NSColor's blackColor()
set theRedColor to NSColor's redColor()
set theGreenColor to NSColor's greenColor()
set theFont to NSFont's fontWithName:"Helvetica" |size|:30
set theBoldFont to NSFontManager's sharedFontManager()'s convertFont:theFont toHaveTrait:NSBoldFontMask
set theDict to NSDictionary's dictionaryWithObjects:{theBlackColor, theFont} forKeys:{NSForegroundColorAttributeName, NSFontAttributeName}
set theATS to NSMutableAttributedString's alloc()'s initWithString:theString attributes:theDict

--	Add Bolding
repeat with aWord in theBoldWords
	set range to (theATS's |string|()'s rangeOfString:aWord)
	
	(theATS's addAttribute:NSFontAttributeName value:theBoldFont range:range)
end repeat

--	Add Colors
repeat with aWord in theRedWords
	set range to (theATS's |string|()'s rangeOfString:aWord)
	
	(theATS's addAttribute:NSForegroundColorAttributeName value:theRedColor range:range)
end repeat

repeat with aWord in theGreenWords
	set range to (theATS's |string|()'s rangeOfString:aWord)
	
	(theATS's addAttribute:NSForegroundColorAttributeName value:theGreenColor range:range)
end repeat

theATS -- lets look at it

This results in the following:

NSAttributedString

And then, if you wanted to write this out to an RTF file, you could add this code:

--	write the attributed string out as an RTF file
set theFile to choose file name
set rtfData to theATS's RTFFromRange:{0, theATS's |length|()} documentAttributes:{DocumentType:current application's NSRTFTextDocumentType}

rtfData's writeToFile:(theFile's POSIX path) atomically:true
1 Like

Thanks Mark. Cool script.

How can we set the clipboard to the RTF?

I tried, without success, both of these:

set rtfData to theATS's RTFFromRange:{0, theATS's |length|()} documentAttributes:{NSRTFTextDocumentType:(its NSDocumentTypeDocumentAttribute)}

set the clipboard to rtfData

--- and also --

set the clipboard to theATS

You need to do this to write to the pasteboard in ASObjC-land:

set rtfData to theATS's RTFFromRange:{0, theATS's |length|()} documentAttributes:{NSRTFTextDocumentType:(its NSDocumentTypeDocumentAttribute)}
set pb to current application's NSPasteboard's generalPasteboard() -- get pasteboard
pb's declareTypes:{current application's NSPasteboardTypeRTF} owner:(missing value)
pb's setData:rtfData forType:(current application's NSPasteboardTypeRTF)
1 Like

You’re old-school :wink:

set pb to current application's NSPasteboard's generalPasteboard() -- get pasteboard
pb's clearContents()
pb's writeObjects:{theATS}
1 Like

It’s all new school to me. LOL

One little correction. That should be:
set theClip to current application's NSPasteboard's generalPasteboard() -- get pasteboard

pd should be theClip

Thanks — I’ve amended the script.

And to anticipate the next question, you can get it off the clipboard like this:

set pb to current application's NSPasteboard's generalPasteboard()
-- get any attributed strings off clipboard
set allATS to (pb's readObjectsForClasses:{current application's NSAttributedString} options:(missing value))
if allATS's |count|() = 0 then error "No rich text found on the clipboard"
-- make an editable copy of the first item of the array so we can modify it
set theATS to allATS's firstObject()'s mutableCopy()

Thanks.

Need a little help understanding.
I don’t see how that limits the get to rich text.
When I tested with plain text on the CB, it retrieved it.

UPDATE: Even though I wrote plain text using AppleScript, it also created a RTF version as well:
set the clipboard to "some plain text from AppleScript"

IAC, what if RTF is not the first object on the CB?

The clipboard tries to offer something for everyone.

In this context, the various flavors — public.rtf, public.utf16-plain-text, etc, – refer to the same object. We ask for the firstObject because the clipboard can hold more than one object (think about when you select several files in the Finder and copy, for example).