Text highlighting via script-function

Hi everybody,

hi everybody I would love to use the MyClippings.txt file from my Kindle to add highlights to the corresponding PDFs in Devonthink. Is there a script function or a script you know of which takes a text string and finds the correponding document for this string in DT and finally highlights the text string in the correponding PDF in DT.

Any help is appreciated

Best regards
Andy

A search didn’t yield something like that, so the answer is probably no.

Could you post a MyClippings.txt file? Doesn’t need to be a real one but if you use dummy data it’s necessary that it got the same structure. If possible a real world example would be the better option.

Thanks for your reply the format of the MyClippings.txt file looks like the following

Lee's method is similar to Warren Buffett’s 25-5 Rule, which requires you to focus on just 5 critical tasks and ignore everything else.
==========
Microchips Needed in Your Car, PlayStation and Phone Are In Short Supply (bloomberg.com)
- Ihre Markierung bei Position 102-104 | Hinzugefügt am Sonntag, 4. April 2021 22:26:51

Basically I will have to iterate the MyClipplings file line by line while the sentence I want to highlight in DevonThink is above the line mark and the document name is below the line mark.

Is there any functionality to add highlights to files in DevonThink via script?

Best regards
Andy

No. But it might be possible another way. I‘ll see whether that would work.

So the record name in DEVONthink is Microchips Needed in Your Car, PlayStation and Phone Are In Short Supply (bloomberg.com).pdf, right?

Unfortunately your example is suboptimal as it doesn’t show the MyClipping.txt‘s structure but only one highlight.

How are highlights separated in the MyClippings file? By a blank line?

1 Like

Hi pete31,

thanks a lot for your help. You are right, the various excerpts in the MyClippings.txt are separated by a blank line. A more comprehensive example of the MyClippings.txt looks like this:

Prioritize those six items in order of their true importance.
==========
The Daily Routine Experts Recommend for Peak Productivity (getpocket.com)
- Ihre Markierung bei Position 17-17 | Hinzugefügt am Sonntag, 4. April 2021 11:07:50

only on the first task.
==========
The Daily Routine Experts Recommend for Peak Productivity (getpocket.com)
- Ihre Markierung bei Position 36-37 | Hinzugefügt am Sonntag, 4. April 2021 11:10:33

Lee's method is similar to Warren Buffett’s 25-5 Rule, which requires you to focus on just 5 critical tasks and ignore everything else.
==========
Microchips Needed in Your Car, PlayStation and Phone Are In Short Supply (bloomberg.com)
- Ihre Markierung bei Position 102-104 | Hinzugefügt am Sonntag, 4. April 2021 22:26:51

200 millimeter wafers, from which lower-end chips are made. Those include power management chips and display ICs (or integrated circuits), required in a wide range of sectors from automotive to consumer electronics, but are in a short supply at the moment.
==========
Microchips Needed in Your Car, PlayStation and Phone Are In Short Supply (bloomberg.com)
- Ihre Markierung bei Position 119-120 | Hinzugefügt am Sonntag, 4. April 2021 22:29:26

Furthermore you are right. The the corresponding PDF in DevonThink has the same name as in the excerpt, e.g. “Microchips Needed in Your Car, PlayStation and Phone Are In Short Supply (bloomberg.com)”.

Best regards
Andy

This script adds highlight annotations from a selected Kindle MyClippings record.

Note: It does not check whether annotations exist. Run it only one time.

Properties

  • property theHighlightColorIndex: Index of the highlight color.
    See Preferences > Colors > Highlighting
  • property theDelimiter: Start of a clipping’s last line. Change this to your language

To try the script:

  • create a test database
  • duplicate your MyClippings record and the PDFs
  • close the database that contains the original PDFs
  • select duplicated MyClippings record
  • run script one time
-- Add PDF highlight annotations from Kindle MyClippings

-- Note: This script adds annotations. It does not check whether annotations exist. Run it only one time.

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

property theHighlightColorIndex : 4 -- set a color,  Preferences > Colors > Highlighting
property theDelimiter : "- Ihre Markierung bei Position" -- change to your language

set theArray to current application's NSMutableArray's new()

tell application id "DNtp"
	try
		set theRecords to selected records
		if theRecords = {} then return
		set theRecord to item 1 of theRecords
		set theRecord_Text to plain text of theRecord
		set theClippings to my tid(theRecord_Text, {linefeed & linefeed})
		set theNames to {}
		
		repeat with thisClipping in theClippings
			try
				set thisClipping to thisClipping as string
				set theTextItems to my tid(thisClipping, {linefeed & "==========" & linefeed, linefeed & theDelimiter})
				set thisSearchTerm to (characters 1 thru -2 in (item 1 of theTextItems)) as string
				set thisName to item 2 of theTextItems
				set theArray to my addSearchTerm(thisName, thisSearchTerm, theArray)
				if thisName is not in theNames then set end of theNames to thisName
			on error
				error "Error: Parsing"
			end try
		end repeat
		
		show progress indicator "Adding highlight annotations... " steps (count theNames) with cancel button
		
		repeat with thisName in theNames
			set thisName to thisName as string
			set theResults to search "kind:pdf {any: name==" & thisName & space & "name==" & thisName & ".pdf}"
			if theResults ≠ {} then
				step progress indicator "... " & thisName
				set thisResult_Path to path of item 1 of theResults
				my addHighlightAnnotations(thisResult_Path, thisName, theArray)
			else
				log message "Script \"Add PDF highlight annotations from Kindle MyClippings\": No record with name \"" & thisName & "\""
			end if
		end repeat
		
		hide progress indicator
	on error error_message number error_number
		hide progress indicator
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on addHighlightAnnotations(thePath, theNameWithoutExtension, theArray)
	try
		set thePDF to current application's PDFDocument's alloc()'s initWithURL:(current application's |NSURL|'s fileURLWithPath:thePath)
		set theSearchTerms to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.NameWithoutExtension = " & quoted form of theNameWithoutExtension)))'s valueForKey:"SearchTerm"
		set theSearchTerms to (current application's NSOrderedSet's orderedSetWithArray:theSearchTerms)'s array()
		
		set theSelectionsArray to current application's NSMutableArray's new()
		
		repeat with i from 0 to ((theSearchTerms's |count|()) - 1)
			set thisSearchTerm to (theSearchTerms's objectAtIndex:i)
			set theResultSelections to (thePDF's findString:thisSearchTerm withOptions:0)
			if theResultSelections's |count|() ≠ 0 then
				set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm, theResultSelections, theSelectionsArray)
			else
				set thisSearchTerm_Components to (thisSearchTerm's componentsSeparatedByString:space)
				repeat with j from 0 to ((thisSearchTerm_Components's |count|()) - 1)
					set theseSearchTerm_Components_1 to (thisSearchTerm_Components's subarrayWithRange:{0, (thisSearchTerm_Components's |count|()) - j})
					set thisSearchTerm_Part_1 to (theseSearchTerm_Components_1's componentsJoinedByString:space)
					set theResultSelections to (thePDF's findString:thisSearchTerm_Part_1 withOptions:0)
					if theResultSelections's |count|() ≠ 0 then
						set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm_Part_1, theResultSelections, theSelectionsArray)
						set thisLocation to (theseSearchTerm_Components_1's |count|())
						set thisLength to (thisSearchTerm_Components's |count|()) - thisLocation
						set theseSearchTerm_Components_2 to (thisSearchTerm_Components's subarrayWithRange:{thisLocation, thisLength})
						set thisSearchTerm_Part_2 to (theseSearchTerm_Components_2's componentsJoinedByString:space)
						set theResultSelections to (thePDF's findString:thisSearchTerm_Part_2 withOptions:0)
						if theResultSelections's |count|() ≠ 0 then
							set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm_Part_2, theResultSelections, theSelectionsArray)
						end if
						exit repeat
					end if
				end repeat
			end if
		end repeat
		
		set theDeeperLookArray to current application's NSMutableArray's new()
		
		repeat with i from 0 to ((theSearchTerms's |count|()) - 1)
			set thisSearchTerm to (theSearchTerms's objectAtIndex:i)
			repeat with j from 0 to ((theSearchTerms's |count|()) - 1)
				set thatSearchTerm to (theSearchTerms's objectAtIndex:j)
				if (thisSearchTerm's containsString:thatSearchTerm) and not (thisSearchTerm's isEqualTo:thatSearchTerm) then
					(theDeeperLookArray's addObject:thatSearchTerm)
				end if
			end repeat
		end repeat
		
		if theHighlightColorIndex < 1 or theHighlightColorIndex > 7 then error "No valid HighlightColor index. Valid: 1-7"
		if current application's id = "com.devon-technologies.think3" then
			set theDefaults to current application's NSUserDefaults's standardUserDefaults()
		else
			set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.devon-technologies.think3"
		end if
		set theDictionary to (theDefaults's dictionaryRepresentation())'s dictionaryWithValuesForKeys:{"HighlightColor-0", "HighlightColor-1", "HighlightColor-2", "HighlightColor-3", "HighlightColor-4", "HighlightColor-5", "HighlightColor-6"}
		set theColorDictionary to theDictionary's objectForKey:("HighlightColor-" & ((theHighlightColorIndex - 1) as string))
		set theRed to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"red"))
		set theGreen to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"green"))
		set theBlue to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"blue"))
		set theAlpha to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"alpha"))
		set theHighlightColor to current application's NSColor's colorWithCalibratedRed:theRed green:theGreen blue:theBlue alpha:theAlpha
		set theDocumentAuthor to (theDefaults's dictionaryRepresentation())'s stringForKey:"DocumentAuthor"
		set theAnnotationSubtype to current application's PDFAnnotationSubtypeHighlight
		set theAnnotationProperties to current application's NSMutableDictionary's new()
		(theAnnotationProperties's setObject:theHighlightColor forKey:(current application's PDFAnnotationKeyColor))
		
		repeat with i from 0 to ((theSelectionsArray's |count|()) - 1)
			set thisItem to (theSelectionsArray's objectAtIndex:i)
			set thisSearchTerm to (thisItem's valueForKey:"SearchTerm")
			set thisResultSelection_BoundsForPage to (thisItem's valueForKey:"Selection_BoundsForPage")
			set thisResultSelection_Lines_QuadPointsArray to (thisItem's valueForKey:"Selection_Lines_QuadPoints")
			set createAnnotation to true
			
			if (theDeeperLookArray's containsObject:thisSearchTerm) then
				set thisResultSelection_Page_Label to (thisItem's valueForKey:"Selection_Page_Label")
				set thisSelectionsArray_filtered to (theSelectionsArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.Selection_Page_Label = " & quoted form of (thisResultSelection_Page_Label as string) & " AND " & "self.SearchTerm CONTAINS " & quoted form of (thisSearchTerm as string) & " AND " & "!self.SearchTerm = " & quoted form of (thisSearchTerm as string))))
				repeat with j from 0 to ((thisSelectionsArray_filtered's |count|()) - 1)
					set thatItem to (thisSelectionsArray_filtered's objectAtIndex:j)
					set thatResultSelection_BoundsForPage to (thatItem's valueForKey:"Selection_BoundsForPage")
					set thatResultSelection_Lines_QuadPointsArray to (thatItem's valueForKey:"Selection_Lines_QuadPoints")
					set intersectsBoundsForPage to current application's NSIntersectsRect(thisResultSelection_BoundsForPage, thatResultSelection_BoundsForPage)
					if intersectsBoundsForPage then
						repeat with k from 0 to ((thisResultSelection_Lines_QuadPointsArray's |count|()) - 1) by 4
							set thisResultSelection_Line_QuadPoints to (thisResultSelection_Lines_QuadPointsArray's subarrayWithRange:{k, 4})
							set thisResultSelection_Line_Bounds to my makeRect(thisResultSelection_Line_QuadPoints)
							repeat with l from 0 to ((thatResultSelection_Lines_QuadPointsArray's |count|()) - 1) by 4
								set thatResultSelection_Line_QuadPoints to (thatResultSelection_Lines_QuadPointsArray's subarrayWithRange:{l, 4})
								set thatResultSelection_Line_Bounds to my makeRect(thatResultSelection_Line_QuadPoints)
								set intersectsBoundsForLine to current application's NSIntersectsRect(thisResultSelection_Line_Bounds, thatResultSelection_Line_Bounds)
								if intersectsBoundsForLine then
									set createAnnotation to false
									exit repeat
								end if
							end repeat
							if intersectsBoundsForLine then exit repeat
						end repeat
						if intersectsBoundsForLine then exit repeat
					end if
				end repeat
			end if
			
			if createAnnotation then
				set thisDate to (current application's NSDate's |date|())
				(theAnnotationProperties's setObject:thisDate forKey:(current application's PDFAnnotationKeyDate))
				set thisAnnotation to (current application's PDFAnnotation's alloc()'s initWithBounds:(thisResultSelection_BoundsForPage) forType:theAnnotationSubtype withProperties:theAnnotationProperties)
				set thisAnnotation_QuadPoints to current application's NSMutableArray's new()
				repeat with i from 0 to ((thisResultSelection_Lines_QuadPointsArray's |count|()) - 1)
					(thisAnnotation_QuadPoints's addObject:(current application's NSValue's valueWithPoint:(thisResultSelection_Lines_QuadPointsArray's objectAtIndex:i)))
				end repeat
				(thisAnnotation's setValue:(thisAnnotation_QuadPoints) forAnnotationKey:(current application's PDFAnnotationKeyQuadPoints))
				if not (theDocumentAuthor's isEqualTo:"") then (thisAnnotation's setUserName:theDocumentAuthor)
				set thisResultSelection_Page to (thisItem's valueForKey:"Selection_Page")
				(thisResultSelection_Page's addAnnotation:thisAnnotation)
			end if
		end repeat
		
		thePDF's writeToFile:thePath
		
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addHighlightAnnotations\"" message error_message as warning
		error number -128
	end try
end addHighlightAnnotations

on addSelectionToSelectionsArray(thisSearchTerm, theResultSelections, theSelectionsArray)
	try
		repeat with i from 0 to ((theResultSelections's |count|()) - 1)
			set thisResultSelection to (theResultSelections's objectAtIndex:i)
			set thisResultSelection_Pages to thisResultSelection's pages()
			repeat with j from 0 to ((thisResultSelection_Pages's |count|()) - 1)
				set thisResultSelection_Page to (thisResultSelection_Pages's objectAtIndex:j)
				set thisResultSelection_BoundsForPage to (thisResultSelection's boundsForPage:thisResultSelection_Page)
				set thisResultSelection_Lines to thisResultSelection's selectionsByLine
				set thisResultSelection_Lines_QuadPointsArray to current application's NSMutableArray's new()
				repeat with k from 0 to ((thisResultSelection_Lines's |count|()) - 1)
					set thisResultSelection_Line to (thisResultSelection_Lines's objectAtIndex:k)
					set thisResultSelection_Line_Page to (thisResultSelection_Line's pages())'s firstObject()
					if (thisResultSelection_Line_Page's isEqualTo:thisResultSelection_Page) then
						set thisResultSelection_Line_BoundsForPage to (thisResultSelection_Line's boundsForPage:thisResultSelection_Line_Page)
						set thisResultSelection_Line_BoundsForPage to current application's NSRect's NSInsetRect(thisResultSelection_Line_BoundsForPage, -1, -1) -- DEVONthink recognizes text more reliably
						set MinX to current application's NSRect's NSMinX(thisResultSelection_Line_BoundsForPage)
						set MinY to current application's NSRect's NSMinY(thisResultSelection_Line_BoundsForPage)
						set MaxX to current application's NSRect's NSMaxX(thisResultSelection_Line_BoundsForPage)
						set MaxY to current application's NSRect's NSMaxY(thisResultSelection_Line_BoundsForPage)
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MinX, MaxY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MaxX, MaxY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MinX, MinY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MaxX, MinY})
					end if
				end repeat
				set thisResultSelection_Page_Label to thisResultSelection_Page's label()
				(theSelectionsArray's addObject:{Selection_Page:thisResultSelection_Page, Selection_Page_Label:thisResultSelection_Page_Label, Selection_BoundsForPage:thisResultSelection_BoundsForPage, Selection_Lines_QuadPoints:thisResultSelection_Lines_QuadPointsArray, SearchTerm:thisSearchTerm})
			end repeat
		end repeat
		return theSelectionsArray
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addSelectionToSelectionsArray\"" message error_message as warning
		error number -128
	end try
end addSelectionToSelectionsArray

on makeRect(theSelection_QuadPoints)
	try
		set theSelection_QuadPoints to (theSelection_QuadPoints as list)
		set MinX to theSelection_QuadPoints's item 1's item 1
		set MinY to theSelection_QuadPoints's item 3's item 2
		set theWidth to (theSelection_QuadPoints's item 2's item 1) - MinX
		set theHeight to (theSelection_QuadPoints's item 1's item 2) - MinY
		set theRect to current application's NSRect's NSMakeRect(MinX, MinY, theWidth, theHeight)
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"makeRect\"" message error_message as warning
		error number -128
	end try
end makeRect

on addSearchTerm(theNameWithoutExtension, theSearchTerm, theArray)
	try
		(theArray's addObject:{NameWithoutExtension:theNameWithoutExtension, SearchTerm:theSearchTerm})
		return theArray
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addSearchTerm\"" message error_message as warning
		error number -128
	end try
end addSearchTerm

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

4 Likes

Hi Pete,

this is amazing! Thank you so much for you reply and this amazing script. Please DM me your paypal I would like to give you a tip.

Glad it’s working! I plan to add annotations as part of another script so had to figure this out anyway. But thanks for the offer, it’s appreciated :slight_smile:

Just wanted to give a shoutout to @pete31 for creating this script. I’ve previously looked into Skim to add annotations to PDFs, but wasn’t aware it could also be done via ObjC / Quartz. Thanks for writing this Pete!

2 Likes

@pete31 I’m slowly working towards integrating this script in my workflow. I’m trying to ‘reverse’ a Sheet (TSV) back into a PDF. Basically the output from “Summarize higlights as sheet”. Would it be possible to integrate the Note that goes with a highlight back into the PDF as well (I’m assuming it’s possible, but this PDF and Quartz magic is a bit above my paygrade :wink: )

It’s actually not that complicated, but the AS-ObjC-bridge makes the script very hard to read, in my opinion All these current application's something are eating up so much space and brain power, it’s difficult to get to the bone of it.

const annotationProperties = $.NSMutableDictionary.init;
if (createAnnotation) {
  const date = $.NSDate.date;
  annotationProperties.setObjectForKey(date, $.PDFAnnotationKeyDate);
  const annotation = $.PDFAnnotation.alloc.initWithBoundsForTypeWithProperties(...)
  const quadPoints = $.NSMutableArray.init;
...

Using shorter variable names also might help to clarify the overall structure (yes, I know that these neverendingveryinformativestringsareusedeveninJava, but I never found them particularly helpful – camel case or not). The more I have to read, the more difficult it becomes to understand. That is true for normal text as well as for code. And it is of course by no means a criticism of @pete31’s script, of course.

Yes, using the script from this thread it’s

(thisAnnotation's setValue:("Hello World") forAnnotationKey:(current application's PDFAnnotationKeyContents))

This adds “Hello World” to every annotation’s content, of course this is just a demo.

Demo script
-- This is a DEMO script. Do NOT use it to add annotations from Kindle MyClippings

-- Add PDF highlight annotations from Kindle MyClippings including "Hello World"

-- Note: This script adds annotations. It does not check whether annotations exist. Run it only one time.

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

property theHighlightColorIndex : 4 -- set a color,  Preferences > Colors > Highlighting
property theDelimiter : "- Ihre Markierung bei Position" -- change to your language

set theArray to current application's NSMutableArray's new()

tell application id "DNtp"
	try
		set theRecords to selected records
		if theRecords = {} then return
		set theRecord to item 1 of theRecords
		set theRecord_Text to plain text of theRecord
		set theClippings to my tid(theRecord_Text, {linefeed & linefeed})
		set theNames to {}
		
		repeat with thisClipping in theClippings
			try
				set thisClipping to thisClipping as string
				set theTextItems to my tid(thisClipping, {linefeed & "==========" & linefeed, linefeed & theDelimiter})
				set thisSearchTerm to (characters 1 thru -2 in (item 1 of theTextItems)) as string
				set thisName to item 2 of theTextItems
				set theArray to my addSearchTerm(thisName, thisSearchTerm, theArray)
				if thisName is not in theNames then set end of theNames to thisName
			on error
				error "Error: Parsing"
			end try
		end repeat
		
		show progress indicator "Adding highlight annotations... " steps (count theNames) with cancel button
		
		repeat with thisName in theNames
			set thisName to thisName as string
			set theResults to search "kind:pdf {any: name==" & thisName & space & "name==" & thisName & ".pdf}"
			if theResults ≠ {} then
				step progress indicator "... " & thisName
				set thisResult_Path to path of item 1 of theResults
				my addHighlightAnnotations(thisResult_Path, thisName, theArray)
			else
				log message "Script \"Add PDF highlight annotations from Kindle MyClippings\": No record with name \"" & thisName & "\""
			end if
		end repeat
		
		hide progress indicator
	on error error_message number error_number
		hide progress indicator
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on addHighlightAnnotations(thePath, theNameWithoutExtension, theArray)
	try
		set thePDF to current application's PDFDocument's alloc()'s initWithURL:(current application's |NSURL|'s fileURLWithPath:thePath)
		set theSearchTerms to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.NameWithoutExtension = " & quoted form of theNameWithoutExtension)))'s valueForKey:"SearchTerm"
		set theSearchTerms to (current application's NSOrderedSet's orderedSetWithArray:theSearchTerms)'s array()
		
		set theSelectionsArray to current application's NSMutableArray's new()
		
		repeat with i from 0 to ((theSearchTerms's |count|()) - 1)
			set thisSearchTerm to (theSearchTerms's objectAtIndex:i)
			set theResultSelections to (thePDF's findString:thisSearchTerm withOptions:0)
			if theResultSelections's |count|() ≠ 0 then
				set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm, theResultSelections, theSelectionsArray)
			else
				set thisSearchTerm_Components to (thisSearchTerm's componentsSeparatedByString:space)
				repeat with j from 0 to ((thisSearchTerm_Components's |count|()) - 1)
					set theseSearchTerm_Components_1 to (thisSearchTerm_Components's subarrayWithRange:{0, (thisSearchTerm_Components's |count|()) - j})
					set thisSearchTerm_Part_1 to (theseSearchTerm_Components_1's componentsJoinedByString:space)
					set theResultSelections to (thePDF's findString:thisSearchTerm_Part_1 withOptions:0)
					if theResultSelections's |count|() ≠ 0 then
						set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm_Part_1, theResultSelections, theSelectionsArray)
						set thisLocation to (theseSearchTerm_Components_1's |count|())
						set thisLength to (thisSearchTerm_Components's |count|()) - thisLocation
						set theseSearchTerm_Components_2 to (thisSearchTerm_Components's subarrayWithRange:{thisLocation, thisLength})
						set thisSearchTerm_Part_2 to (theseSearchTerm_Components_2's componentsJoinedByString:space)
						set theResultSelections to (thePDF's findString:thisSearchTerm_Part_2 withOptions:0)
						if theResultSelections's |count|() ≠ 0 then
							set theSelectionsArray to my addSelectionToSelectionsArray(thisSearchTerm_Part_2, theResultSelections, theSelectionsArray)
						end if
						exit repeat
					end if
				end repeat
			end if
		end repeat
		
		set theDeeperLookArray to current application's NSMutableArray's new()
		
		repeat with i from 0 to ((theSearchTerms's |count|()) - 1)
			set thisSearchTerm to (theSearchTerms's objectAtIndex:i)
			repeat with j from 0 to ((theSearchTerms's |count|()) - 1)
				set thatSearchTerm to (theSearchTerms's objectAtIndex:j)
				if (thisSearchTerm's containsString:thatSearchTerm) and not (thisSearchTerm's isEqualTo:thatSearchTerm) then
					(theDeeperLookArray's addObject:thatSearchTerm)
				end if
			end repeat
		end repeat
		
		if theHighlightColorIndex < 1 or theHighlightColorIndex > 7 then error "No valid HighlightColor index. Valid: 1-7"
		if current application's id = "com.devon-technologies.think3" then
			set theDefaults to current application's NSUserDefaults's standardUserDefaults()
		else
			set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.devon-technologies.think3"
		end if
		set theDictionary to (theDefaults's dictionaryRepresentation())'s dictionaryWithValuesForKeys:{"HighlightColor-0", "HighlightColor-1", "HighlightColor-2", "HighlightColor-3", "HighlightColor-4", "HighlightColor-5", "HighlightColor-6"}
		set theColorDictionary to theDictionary's objectForKey:("HighlightColor-" & ((theHighlightColorIndex - 1) as string))
		set theRed to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"red"))
		set theGreen to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"green"))
		set theBlue to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"blue"))
		set theAlpha to current application's NSNumber's numberWithDouble:((theColorDictionary's valueForKey:"alpha"))
		set theHighlightColor to current application's NSColor's colorWithCalibratedRed:theRed green:theGreen blue:theBlue alpha:theAlpha
		set theDocumentAuthor to (theDefaults's dictionaryRepresentation())'s stringForKey:"DocumentAuthor"
		set theAnnotationSubtype to current application's PDFAnnotationSubtypeHighlight
		set theAnnotationProperties to current application's NSMutableDictionary's new()
		(theAnnotationProperties's setObject:theHighlightColor forKey:(current application's PDFAnnotationKeyColor))
		
		repeat with i from 0 to ((theSelectionsArray's |count|()) - 1)
			set thisItem to (theSelectionsArray's objectAtIndex:i)
			set thisSearchTerm to (thisItem's valueForKey:"SearchTerm")
			set thisResultSelection_BoundsForPage to (thisItem's valueForKey:"Selection_BoundsForPage")
			set thisResultSelection_Lines_QuadPointsArray to (thisItem's valueForKey:"Selection_Lines_QuadPoints")
			set createAnnotation to true
			
			if (theDeeperLookArray's containsObject:thisSearchTerm) then
				set thisResultSelection_Page_Label to (thisItem's valueForKey:"Selection_Page_Label")
				set thisSelectionsArray_filtered to (theSelectionsArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.Selection_Page_Label = " & quoted form of (thisResultSelection_Page_Label as string) & " AND " & "self.SearchTerm CONTAINS " & quoted form of (thisSearchTerm as string) & " AND " & "!self.SearchTerm = " & quoted form of (thisSearchTerm as string))))
				repeat with j from 0 to ((thisSelectionsArray_filtered's |count|()) - 1)
					set thatItem to (thisSelectionsArray_filtered's objectAtIndex:j)
					set thatResultSelection_BoundsForPage to (thatItem's valueForKey:"Selection_BoundsForPage")
					set thatResultSelection_Lines_QuadPointsArray to (thatItem's valueForKey:"Selection_Lines_QuadPoints")
					set intersectsBoundsForPage to current application's NSIntersectsRect(thisResultSelection_BoundsForPage, thatResultSelection_BoundsForPage)
					if intersectsBoundsForPage then
						repeat with k from 0 to ((thisResultSelection_Lines_QuadPointsArray's |count|()) - 1) by 4
							set thisResultSelection_Line_QuadPoints to (thisResultSelection_Lines_QuadPointsArray's subarrayWithRange:{k, 4})
							set thisResultSelection_Line_Bounds to my makeRect(thisResultSelection_Line_QuadPoints)
							repeat with l from 0 to ((thatResultSelection_Lines_QuadPointsArray's |count|()) - 1) by 4
								set thatResultSelection_Line_QuadPoints to (thatResultSelection_Lines_QuadPointsArray's subarrayWithRange:{l, 4})
								set thatResultSelection_Line_Bounds to my makeRect(thatResultSelection_Line_QuadPoints)
								set intersectsBoundsForLine to current application's NSIntersectsRect(thisResultSelection_Line_Bounds, thatResultSelection_Line_Bounds)
								if intersectsBoundsForLine then
									set createAnnotation to false
									exit repeat
								end if
							end repeat
							if intersectsBoundsForLine then exit repeat
						end repeat
						if intersectsBoundsForLine then exit repeat
					end if
				end repeat
			end if
			
			if createAnnotation then
				set thisDate to (current application's NSDate's |date|())
				(theAnnotationProperties's setObject:thisDate forKey:(current application's PDFAnnotationKeyDate))
				set thisAnnotation to (current application's PDFAnnotation's alloc()'s initWithBounds:(thisResultSelection_BoundsForPage) forType:theAnnotationSubtype withProperties:theAnnotationProperties)
				set thisAnnotation_QuadPoints to current application's NSMutableArray's new()
				repeat with i from 0 to ((thisResultSelection_Lines_QuadPointsArray's |count|()) - 1)
					(thisAnnotation_QuadPoints's addObject:(current application's NSValue's valueWithPoint:(thisResultSelection_Lines_QuadPointsArray's objectAtIndex:i)))
				end repeat
				(thisAnnotation's setValue:(thisAnnotation_QuadPoints) forAnnotationKey:(current application's PDFAnnotationKeyQuadPoints))
				
				--------------------------------------------------------------------- Add Contents ----------------------------------------------------------------------
				
				(thisAnnotation's setValue:("Hello World") forAnnotationKey:(current application's PDFAnnotationKeyContents))
				
				-------------------------------------------------------------------------------------------------------------------------------------------------
				
				if not (theDocumentAuthor's isEqualTo:"") then (thisAnnotation's setUserName:theDocumentAuthor)
				set thisResultSelection_Page to (thisItem's valueForKey:"Selection_Page")
				(thisResultSelection_Page's addAnnotation:thisAnnotation)
			end if
		end repeat
		
		thePDF's writeToFile:thePath
		
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addHighlightAnnotations\"" message error_message as warning
		error number -128
	end try
end addHighlightAnnotations

on addSelectionToSelectionsArray(thisSearchTerm, theResultSelections, theSelectionsArray)
	try
		repeat with i from 0 to ((theResultSelections's |count|()) - 1)
			set thisResultSelection to (theResultSelections's objectAtIndex:i)
			set thisResultSelection_Pages to thisResultSelection's pages()
			repeat with j from 0 to ((thisResultSelection_Pages's |count|()) - 1)
				set thisResultSelection_Page to (thisResultSelection_Pages's objectAtIndex:j)
				set thisResultSelection_BoundsForPage to (thisResultSelection's boundsForPage:thisResultSelection_Page)
				set thisResultSelection_Lines to thisResultSelection's selectionsByLine
				set thisResultSelection_Lines_QuadPointsArray to current application's NSMutableArray's new()
				repeat with k from 0 to ((thisResultSelection_Lines's |count|()) - 1)
					set thisResultSelection_Line to (thisResultSelection_Lines's objectAtIndex:k)
					set thisResultSelection_Line_Page to (thisResultSelection_Line's pages())'s firstObject()
					if (thisResultSelection_Line_Page's isEqualTo:thisResultSelection_Page) then
						set thisResultSelection_Line_BoundsForPage to (thisResultSelection_Line's boundsForPage:thisResultSelection_Line_Page)
						set thisResultSelection_Line_BoundsForPage to current application's NSRect's NSInsetRect(thisResultSelection_Line_BoundsForPage, -1, -1) -- DEVONthink recognizes text more reliably
						set MinX to current application's NSRect's NSMinX(thisResultSelection_Line_BoundsForPage)
						set MinY to current application's NSRect's NSMinY(thisResultSelection_Line_BoundsForPage)
						set MaxX to current application's NSRect's NSMaxX(thisResultSelection_Line_BoundsForPage)
						set MaxY to current application's NSRect's NSMaxY(thisResultSelection_Line_BoundsForPage)
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MinX, MaxY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MaxX, MaxY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MinX, MinY})
						(thisResultSelection_Lines_QuadPointsArray's addObject:{MaxX, MinY})
					end if
				end repeat
				set thisResultSelection_Page_Label to thisResultSelection_Page's label()
				(theSelectionsArray's addObject:{Selection_Page:thisResultSelection_Page, Selection_Page_Label:thisResultSelection_Page_Label, Selection_BoundsForPage:thisResultSelection_BoundsForPage, Selection_Lines_QuadPoints:thisResultSelection_Lines_QuadPointsArray, SearchTerm:thisSearchTerm})
			end repeat
		end repeat
		return theSelectionsArray
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addSelectionToSelectionsArray\"" message error_message as warning
		error number -128
	end try
end addSelectionToSelectionsArray

on makeRect(theSelection_QuadPoints)
	try
		set theSelection_QuadPoints to (theSelection_QuadPoints as list)
		set MinX to theSelection_QuadPoints's item 1's item 1
		set MinY to theSelection_QuadPoints's item 3's item 2
		set theWidth to (theSelection_QuadPoints's item 2's item 1) - MinX
		set theHeight to (theSelection_QuadPoints's item 1's item 2) - MinY
		set theRect to current application's NSRect's NSMakeRect(MinX, MinY, theWidth, theHeight)
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"makeRect\"" message error_message as warning
		error number -128
	end try
end makeRect

on addSearchTerm(theNameWithoutExtension, theSearchTerm, theArray)
	try
		(theArray's addObject:{NameWithoutExtension:theNameWithoutExtension, SearchTerm:theSearchTerm})
		return theArray
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"addSearchTerm\"" message error_message as warning
		error number -128
	end try
end addSearchTerm

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

Thanks Pete. I can see that the note is added when I run “Summarize higlights” again (and also because “Remove note” is shown when right-clicking the highlight). There’s no visual marker for the note like when I add it manually (the little yellow block). Is there a way to create that too?

Agreed. One improvement could be to do something like this on top of the script

property ca : current application
property NSUserDefault: ca's NSUserDefault
...

Where do you see this? Couldn’t find that in the contextual menus.

Not sure what you mean. Here’s what I see after running the demo script:


@cgrunenberg a selected annotation’s contents column is hard to read in dark mode (DEVONthink 3.8.3, macOS 10.14.6)

Thanks, already fixed in the latest internal builds.

1 Like

When I add a note to a highlight there’s a small yellow marker that appears:

2022-05-09 DEVONthink 3 Resources — Roxane van Iperen- 'De Zuidas zit vol slimme schapen' 13.12.49

It seems this might be a text annotation icon defined by PDFTextAnnotationIconType
(Apple Developer Documentation) but I’m struggling a bit to find the right ASObjC syntax to try and add it.

Ah, you want to add another annotation instead of adding text to the highlight annotation.

That should be possible, but where should it be added? You placed the ones you added manually where you saw that there’s free space (i.e. no text in the PDF), so the script would need to check that in order to don’t overlap with another line. That should be possible but I can’t do that now.

I’m slowly starting to understand more PDF :wink: It seems it’s a “popup” type note with a highlight. I have not created these manually but did the following:

  • Highlight text in PDF
  • Right-click “Add note”
  • Add text to note

DT does the (auto)placement of the ‘squares’ - they’re not manual.

I guess these are popup annotations, is this correct @cgrunenberg?