Script: Transform pasteboard for pasting into Tinderbox

This script changes various attributes of the RTF(D) clipboard to the values of the frontmost Tinderbox.

Usage

  • Copy, e.g. in Safari

  • Run script

  • Paste

Setup

Properties are independent (except changeCodeFont which is only used if changeFont is true).

  • changeFont: Change font to $TextFont.

  • changeCodeFont: Change mono spaced font to $CodeFont.

  • changeFontSize: Change font size to $TextFontSize.

  • changeParagraphStyle: Change line height to $LineSpacing and paragraph spacing to $ParagraphSpacing.

  • changeColor: Change text color to $TextColor.

  • removeBackgroundColor: Remove background color.

  • ensureDoubleLinebreak: Remove line separators (U+2028). If $ParagraphSpacing = 0 replace multiple linefeeds with double linefeed, else with single linefeed.

  • ensureLinebreakAtEnd: Add linefeed at end (if necessary). Useful for transclusion.

  • removeImages: Use RTF instead of RTFD.

Running it via Alfred’s NSAppleScript action is very fast, so if you can I’d suggest using that.

Here's how to use Alfred's NSAppleScript
  • Contextual menu > Actions > Run NSAppleScript

  • Embed script in Alfred’s NSAppleScript handler


-- use statements and properties
use AppleScript version "2.4"
property changeFont : 
property changeCodeFont: …

-- alfred handler
on alfred_script(q)

	-- script goes here (without use statements, properties and handler) 

end alfred_script


-- handler
on convertFontToFamily …

  • Tick checkbox Cache compiled AppleScript

Installing fonts

Depending on the font you’re using in a Tinderbox it might be necessary to first install the font.

Note: Make sure that you’re allowed to install a given font.
Tinderbox’s default font MercurySSm-Book is not licensed for usage outside of Tinderbox.

  • Find Tinderbox.app in Finder

  • Contextual menu > Show Package Contents

  • Open folder Contents

  • Open folder Resources

  • Open folder fonts

  • Double click font
    (This opens FontBook.app)

  • Confirm FontBook.app’s dialog by clicking Install

Examples

Note: I have $ParagraphSpacing set to 0, thus the script added double linebreaks.

The script is probably not in its final state, I didn’t do much testing and am not sure whether the regexes are ok. Feedback is of course very welcome, if you have ideas I’d like to know them, if you have questions please don’t hesitate to ask.

Script

-- Transform pasteboard for pasting into Tinderbox

use AppleScript version "2.4"
use framework "Foundation"
use framework "AppKit"
use scripting additions

property changeFont : true -- Change font to $TextFont.
property changeCodeFont : true -- Change mono spaced font to $CodeFont.
property changeFontSize : true -- Change font size to $TextFontSize.
property changeParagraphStyle : true -- Change line height to $LineSpacing and paragraph spacing to $ParagraphSpacing.
property changeColor : true -- Change text color to $TextColor.
property removeBackgroundColor : true -- Remove background color.
property ensureDoubleLinebreak : true -- Remove line separators (U+2028) and replace multiple linefeeds with double linefeed.
property ensureLinebreakAtEnd : true -- Add linefeed at end (if necessary). Useful for transclusion.
property removeImages : false -- Use RTF instead of RTFD clipboard.

try
	---------------------------------------------------------- Get Tinderbox Attributes -----------------------------------------------------------
	
	tell application id "com.eastgate.Tinderbox-9"
		try
			if not (exists front document) then error "Please open a Tinderbox"
			tell front document
				set theFontName to value of attribute "TextFont"
				set theFontSize to value of attribute "TextFontSize"
				set theMonoFontName to value of attribute "CodeFont"
				set theTextColor_Tinderbox to value of attribute "TextColor"
				set theLineSpacing_Tinderbox to value of attribute "LineSpacing"
				set theParagraphSpacing_Tinderbox to value of attribute "ParagraphSpacing"
			end tell
		on error error_message number error_number
			if the error_number is not -128 then display alert "Tinderbox" message error_message as warning
			error number -128
		end try
	end tell
	
	set theFontSize to theFontSize as real
	set theFont to current application's NSFont's fontWithName:(theFontName) |size|:theFontSize
	if theFont = missing value then error "Font \"" & theFontName & "\" is not installed on your mac." & linefeed & linefeed & "See https://forum.eastgate.com/t/script-transform-pasteboard-for-pasting-into-tinderbox/4986"
	set theFont_FamilyName to theFont's familyName()
	set theMonoFont to current application's NSFont's fontWithName:(theMonoFontName) |size|:theFontSize
	if (theMonoFont = missing value) or not ((theMonoFont's isFixedPitch()) as boolean) then error "Font \"" & theMonoFontName & "\" is not installed on your mac." & linefeed & linefeed & "See https://forum.eastgate.com/t/script-transform-pasteboard-for-pasting-into-tinderbox/4986"
	set theMonoFont_FamilyName to theMonoFont's familyName()
	set theLineSpacing to ((theLineSpacing_Tinderbox as integer) / 100) as real
	set theParagraphSpacing to theParagraphSpacing_Tinderbox as integer
	
	--------------------------------------------------------------- Get Pasteboard ----------------------------------------------------------------
	
	set theAttributedString to missing value
	set theNSPasteboardTypeRTFD to current application's NSPasteboardTypeRTFD
	set theNSPasteboardTypeRTF to current application's NSPasteboardTypeRTF
	set thePasteboard to current application's NSPasteboard's generalPasteboard()
	set thePasteboardItems to thePasteboard's pasteboardItems()
	set thePasteboardDataArray to current application's NSMutableArray's new()
	repeat with i from 0 to ((thePasteboardItems's |count|()) - 1)
		set thePasteboardItem to (thePasteboardItems's objectAtIndex:i)
		set thePasteboardItem_Types to thePasteboardItem's |types|()
		if not removeImages and (thePasteboardItem_Types's containsObject:theNSPasteboardTypeRTFD) then
			set thePasteboardItem_Data to (thePasteboardItem's dataForType:theNSPasteboardTypeRTFD)
			set theAttributedString to (current application's NSAttributedString's alloc()'s initWithData:thePasteboardItem_Data options:(missing value) documentAttributes:(missing value) |error|:(missing value))
		else if (thePasteboardItem_Types's containsObject:theNSPasteboardTypeRTF) then
			set thePasteboardItem_Data to (thePasteboardItem's dataForType:theNSPasteboardTypeRTF)
			set theAttributedString to (current application's NSAttributedString's alloc()'s initWithData:thePasteboardItem_Data options:(missing value) documentAttributes:(missing value) |error|:(missing value))
		end if
		if (thePasteboardItem_Types's containsObject:"com.apple.webarchive") then
			(thePasteboardDataArray's addObject:({PasteboardType:"com.apple.webarchive", PasteboardData:(thePasteboardItem's dataForType:"com.apple.webarchive")}))
		end if
		if theAttributedString ≠ missing value then
			exit repeat
		end if
	end repeat
	if theAttributedString = missing value then error "No RTFD or RTF in Clipboard"
	set theMutableAttributedString to theAttributedString's mutableCopy()
	
	------------------------------------------- Ensure Double Linebreak: Remove Line Separators --------------------------------------------
	
	if ensureDoubleLinebreak then
		set theMutableAttributedString to my regexReplaceAttributedString(theMutableAttributedString, {location:0, |length|:theMutableAttributedString's |length|()}, "(?m:(\\p{Zl}+))", "")
	end if
	
	-------------------------------------------------------- Change Font And Font Size ---------------------------------------------------------
	
	set theMutableAttributedString_Length to theMutableAttributedString's |length|()
	
	if changeFont or changeFontSize then
		set theLocation to 0
		set theAttribute to current application's NSFontAttributeName
		set theFontManager to current application's NSFontManager's sharedFontManager()
		if ensureDoubleLinebreak then set theAttributesArray to current application's NSMutableArray's new()
		repeat while (theLocation < theMutableAttributedString_Length)
			set {thisFont, thisFontRange} to theMutableAttributedString's |attribute|:theAttribute atIndex:(theLocation) longestEffectiveRange:(reference) inRange:{location:theLocation, |length|:(theMutableAttributedString_Length - theLocation)}
			if changeFont and changeFontSize then
				set thisNewFont to my convertFontToFamily(theFontManager, thisFont, theFontSize, theFont_FamilyName, theMonoFont_FamilyName)
			else if changeFont then
				set thisNewFont to my convertFontToFamily(theFontManager, thisFont, (thisFont's |pointSize|()), theFont_FamilyName, theMonoFont_FamilyName)
			else if changeFontSize then
				set thisNewFont to (theFontManager's convertFont:(thisFont) toSize:theFontSize)
			end if
			(theMutableAttributedString's removeAttribute:theAttribute range:thisFontRange)
			(theMutableAttributedString's addAttribute:theAttribute value:thisNewFont range:thisFontRange)
			if ensureDoubleLinebreak then theAttributesArray's addObject:{|Font|:thisFont, FontRange:thisFontRange, FontIsFixedPitch:(thisFont's isFixedPitch())}
			set theLocation to theLocation + (thisFontRange's |length|())
		end repeat
	else
		if ensureDoubleLinebreak then
			set theLocation to 0
			set theAttribute to current application's NSFontAttributeName
			set theAttributesArray to current application's NSMutableArray's new()
			repeat while (theLocation < theMutableAttributedString_Length)
				set {thisFont, thisFontRange} to theAttributedString's |attribute|:theAttribute atIndex:(theLocation) longestEffectiveRange:(reference) inRange:{location:theLocation, |length|:(theMutableAttributedString_Length - theLocation)}
				theAttributesArray's addObject:{|Font|:thisFont, FontRange:thisFontRange, FontIsFixedPitch:(thisFont's isFixedPitch())}
				set theLocation to theLocation + (thisFontRange's |length|())
			end repeat
		end if
	end if
	
	-------------------------------- Ensure Double Linebreak: Replace Linefeeds In Non-Mono-Spaced Font ---------------------------------
	
	if ensureDoubleLinebreak then
		set theFontRangesArray to (theAttributesArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontIsFixedPitch = false")))'s valueForKey:"FontRange"
		set theFontRangesArray_Count to theFontRangesArray's |count|()
		set theFontRangesUnitedArray to current application's NSMutableArray's new()
		set i to 0
		repeat while i ≤ (theFontRangesArray_Count - 1)
			set thisRange to (theFontRangesArray's objectAtIndex:i)
			set theRange to thisRange
			if i < (theFontRangesArray_Count - 1) then
				repeat with j from (i + 1) to (theFontRangesArray_Count - 1)
					set thisNextRange to (theFontRangesArray's objectAtIndex:j)
					set isLocationInRange to current application's NSRange's NSLocationInRange((current application's NSRange's NSMaxRange(theRange)), thisNextRange)
					if isLocationInRange then
						set theRange to current application's NSRange's NSUnionRange(theRange, thisNextRange)
						set i to i + 1
					else
						exit repeat
					end if
				end repeat
			end if
			theFontRangesUnitedArray's insertObject:(theRange) atIndex:0
			set i to i + 1
		end repeat
		set thePattern to "(?m:(((\\t+)?\\n)+))"
		repeat with i from 0 to ((theFontRangesUnitedArray's |count|()) - 1)
			set thisRange to (theFontRangesUnitedArray's objectAtIndex:i)
			set thisRange_Location to (thisRange's location) as integer
			if thisRange_Location > 0 then
				set thisRange_modified to {location:thisRange_Location - 1, |length|:((thisRange's |length|) as integer) + 1}
			else
				set thisRange_modified to thisRange
			end if
			if theParagraphSpacing > 0 then
				set theMutableAttributedString to my regexReplaceAttributedString(theMutableAttributedString, thisRange_modified, thePattern, linefeed)
			else
				set theMutableAttributedString to my regexReplaceAttributedString(theMutableAttributedString, thisRange_modified, thePattern, linefeed & linefeed)
			end if
		end repeat
	end if
	
	set theMutableAttributedString_Length to theMutableAttributedString's |length|()
	
	--------------------------------------------------- Change Line And Paragraph Spacing ----------------------------------------------------
	
	if changeParagraphStyle then
		set theLocation to 0
		set theAttribute to current application's NSParagraphStyleAttributeName
		repeat while (theLocation < theMutableAttributedString_Length)
			set {thisParagraphStyle, thisParagraphStyleRange} to theMutableAttributedString's |attribute|:theAttribute atIndex:(theLocation) longestEffectiveRange:(reference) inRange:{location:theLocation, |length|:(theMutableAttributedString_Length - theLocation)}
			set thisMutableParagraphStyle to thisParagraphStyle's mutableCopy()
			thisMutableParagraphStyle's setParagraphSpacing:theParagraphSpacing
			thisMutableParagraphStyle's setLineHeightMultiple:theLineSpacing
			thisMutableParagraphStyle's setMinimumLineHeight:0
			thisMutableParagraphStyle's setMaximumLineHeight:0
			(theMutableAttributedString's removeAttribute:theAttribute range:thisParagraphStyleRange)
			(theMutableAttributedString's addAttribute:theAttribute value:thisMutableParagraphStyle range:thisParagraphStyleRange)
			set theLocation to theLocation + (thisParagraphStyleRange's |length|())
		end repeat
	end if
	
	---------------------------------------------------------------- Change Color -----------------------------------------------------------------
	
	if changeColor then
		set {R, G, B} to my getNSRGBFromHex(theTextColor_Tinderbox)
		set theTextColor to current application's NSColor's colorWithRed:(R) green:(G) blue:(B) alpha:(1)
		theMutableAttributedString's removeAttribute:(current application's NSForegroundColorAttributeName) range:{location:0, |length|:theMutableAttributedString_Length}
		theMutableAttributedString's addAttribute:(current application's NSForegroundColorAttributeName) value:theTextColor range:{location:0, |length|:theMutableAttributedString_Length}
	end if
	
	--------------------------------------------------------- Remove Background Color ----------------------------------------------------------
	
	if removeBackgroundColor then
		theMutableAttributedString's removeAttribute:(current application's NSBackgroundColorAttributeName) range:{location:0, |length|:theMutableAttributedString_Length}
	end if
	
	---------------------------------------------------------- Ensure Linebreak At End -----------------------------------------------------------
	
	if ensureLinebreakAtEnd then
		if not ((theMutableAttributedString's |string|())'s hasSuffix:linefeed) then
			set theParagraphStyleAttribute to (theMutableAttributedString's |attribute|:(current application's NSParagraphStyleAttributeName) atIndex:((theMutableAttributedString's |length|()) - 1) effectiveRange:(missing value))
			theMutableAttributedString's appendAttributedString:(current application's NSAttributedString's alloc()'s initWithString:linefeed attributes:{NSParagraphStyle:theParagraphStyleAttribute})
		end if
	end if
	
	--------------------------------------------------------------- Set Pasteboard ----------------------------------------------------------------
	
	set theMutableAttributedString_Range to {location:0, |length|:theMutableAttributedString's |length|()}
	if (theMutableAttributedString's containsAttachmentsInRange:theMutableAttributedString_Range) then
		set theDocumentAttributes to current application's NSDictionary's dictionaryWithDictionary:{NSDocumentTypeDocumentAttribute:(current application's NSRTFDTextDocumentType)}
		set theData to theMutableAttributedString's RTFDFromRange:(theMutableAttributedString_Range) documentAttributes:theDocumentAttributes
		set theType to theNSPasteboardTypeRTFD
	else
		set theDocumentAttributes to current application's NSDictionary's dictionaryWithDictionary:{NSDocumentTypeDocumentAttribute:(current application's NSRTFTextDocumentType)}
		set theData to theMutableAttributedString's RTFFromRange:(theMutableAttributedString_Range) documentAttributes:theDocumentAttributes
		set theType to theNSPasteboardTypeRTF
	end if
	
	(thePasteboardDataArray's addObject:({PasteboardData:theData, PasteboardType:theType}))
	
	thePasteboard's clearContents()
	
	repeat with i from 0 to (thePasteboardDataArray's |count|()) - 1
		(thePasteboard's setData:((thePasteboardDataArray's objectAtIndex:i)'s valueForKey:"PasteboardData") forType:((thePasteboardDataArray's objectAtIndex:i)'s valueForKey:"PasteboardType"))
	end repeat
	
	display notification "Formatted" with title "Transform Pasteboard"
	
on error error_message number error_number
	activate
	if the error_number is not -128 then display alert "Error: \"Transform Pasteboard: Tinderbox\"" message error_message as warning
	error number -128
end try

on convertFontToFamily(theFontManager, theFont, theFontSize, theFont_FamilyName, theMonoFont_FamilyName)
	try
		set theNewFont to (theFontManager's convertFont:(theFont) toSize:theFontSize)
		if not ((theFont's isFixedPitch()) as boolean) then
			set theNewFont to (theFontManager's convertFont:theNewFont toFamily:theFont_FamilyName)
		else
			if changeCodeFont then
				set theNewFont to (theFontManager's convertFont:theNewFont toFamily:theMonoFont_FamilyName)
			end if
		end if
		return theNewFont
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"convertFontToFamily\"" message error_message as warning
		error number -128
	end try
end convertFontToFamily

on regexReplaceAttributedString(theMutableAttributedString, theRange, thePattern, theReplacementPattern)
	try
		set {theRegex, theError} to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(reference)
		if theRegex = missing value then error theError's localizedDescription() as string
		set theMutableAttributedString_String to theMutableAttributedString's |string|()
		set theMatches to (theRegex's matchesInString:theMutableAttributedString_String options:0 range:theRange) as list
		set theMatches to reverse of theMatches
		repeat with thisMatch in theMatches
			(theMutableAttributedString's replaceCharactersInRange:(thisMatch's range()) withString:theReplacementPattern)
		end repeat
		return theMutableAttributedString
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"regexReplaceAttributedString\"" message error_message as warning
		error number -128
	end try
end regexReplaceAttributedString

on getNSRGBFromHex(theHTMLColor) -- #xxxxxx (based on https://macscripter.net/viewtopic.php?pid=95048#p95048)
	try
		set theHexList to "0123456789ABCDEF"
		set theNSRGBList to {}
		repeat with i from 2 to 6 by 2
			set end of theNSRGBList to ((((offset of theHTMLColor's character i in theHexList) - 1) * 16) + (offset of theHTMLColor's character (i + 1) in theHexList) - 1) / 255
		end repeat
		return theNSRGBList
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"getNSRGBFromHex\"" message error_message as warning
		error number -128
	end try
end getNSRGBFromHex

3 Likes

Hey there. Love this!

I must not be understanding all the instructions. Does one need to create a bunch of attributes? I’m getting the following errror:
image

1 Like

Ahh. This is because your Tinderbox’s font is not installed on your mac (I didn’t test with Tinderbox’s default font so I missed that possibe error).

For now you could try another font. There might be a solution to fonts not being installed but I’m not sure yet whether it’s allowed.

I’ve found a very silly mistake. Please copy the script again.

Got it. Do you know if there would be anyway to rt. mouse click on an image and save it to the HD?

Not sure I understand. On macOS you can save any image by using the contextual menu, I think.

I tried mouse-clicking on the image in the $Text and could not find a save image option.

You’re right, seems RTF doesn’t allow this. Didn’t know that.

If you use Yoink.app you could right click, Share > Yoink, then drag from Yoink to where you want to save the image.

1 Like

Updated script.

  • Replaced property removeColor with changeColor.
    changeColor sets the text color to $TextColor.

  • Replaced misleading error message.
    New error message makes aware of necessary fonts not being installed.
    Please see Installing fonts.

1 Like

BTW, Yoink is part of SetApp suite. SetApp every day is getting more and more utility and value for me.

thanks for sharing information.

1 Like