Installed Fonts Info as List of Records

I wrote following script to get Installed Fonts Info as List of Records. It works, but I think, parsing the XML response from shell command may be faster. As is, my script takes about 37 seconds.
 

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

set theXML to do shell script "system_profiler -xml SPFontsDataType"
set {theXMLDoc, theError} to my (NSXMLDocument's alloc()'s initWithXMLString:theXML ¬
	options:(my NSXMLDocumentTidyHTML) |error|:(reference))
set {theMatches, theError} to (theXMLDoc's nodesForXPath:"//array/dict/array/dict/array/dict" |error|:(reference))

set fontsInfo to {}
repeat with aMatch in theMatches
	set childrenElements to (aMatch's children())'s stringValue
	set theKeys to {}
	set theValues to {}
	repeat with i from 1 to (count childrenElements) - 1 by 2
		set end of theKeys to item i of childrenElements
		set end of theValues to item (i + 1) of childrenElements
	end repeat
	set end of fontsInfo to (current application's NSDictionary's dictionaryWithObjects:theValues forKeys:theKeys) as record
end repeat
return fontsInfo

 
Is there another faster way to parse the XML string returned by do shell script “system_profiler -xml SPFontsDataType”?

Your script took 9.06 seconds for me with Mac minI M1 + macOS 13.4beta + 2411 fonts.

Thanks for testing. It only means that your Mac is about 6 times stronger than mine. I would like to improve the speed of the script several times. If it is possible of course.

So far, I have figured out how to speed up the script by 3 times. It’s better to work with a list of Strings instead of an array of NStrings for this (see EDITED code line):
 

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

set theXML to do shell script "system_profiler -xml SPFontsDataType"
set {theXMLDoc, theError} to my (NSXMLDocument's alloc()'s initWithXMLString:theXML ¬
	options:(my NSXMLDocumentTidyHTML) |error|:(reference))
set {theMatches, theError} to (theXMLDoc's nodesForXPath:"//array/dict/array/dict/array/dict" |error|:(reference))

set fontsInfo to {}
repeat with aMatch in theMatches
	set childrenElements to ((aMatch's children())'s stringValue) as list -- EDITED
	set theKeys to {}
	set theValues to {}
	repeat with i from 1 to (count childrenElements) - 1 by 2
		set end of theKeys to item i of childrenElements
		set end of theValues to item (i + 1) of childrenElements
	end repeat
	set end of fontsInfo to (current application's NSDictionary's dictionaryWithObjects:theValues forKeys:theKeys) as record
end repeat
return fontsInfo

 

You’re not saying which infos you want to get.
Assuming the vendor name and other exotic data are not critical for you, here is a script I use.
It takes the fonts that are currently activated, ignoring UI fonts (those whose name starts with a dot).

use framework "Foundation"
use framework "AppKit"
use scripting additions

set fontManager to current application's NSFontManager's sharedFontManager()
set fontArray to (fontManager's availableFonts())
set thePred to current application's NSPredicate's predicateWithFormat:("NOT self MATCHES %@") argumentArray:{"\\..+"}
set fontArray to fontArray's filteredArrayUsingPredicate:thePred

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

repeat with aLoop in fontArray
	set theFont to (current application's NSFont's fontWithName:aLoop |size|:0)
	
	set aDisplay to theFont's displayName()
	set aFamily to theFont's familyName()
	set aName to theFont's fontName()
	set aStyle to (fontManager's traitsOfFont:theFont)
	set aWeight to (fontManager's weightOfFont:theFont)
	set aFixed to theFont's isFixedPitch()
	set aVertical to theFont's isVertical()
	set theRec to ({colName:aName, colFamily:aFamily, colDisplay:aDisplay, colStyle:aStyle, colWeight:aWeight, colFixed:aFixed, colVertical:aVertical})
	
	(fontInfos's addObject:theRec)
end repeat

return fontInfos
1 Like

even faster if you convert to AppleScript record

use framework "Foundation"
use framework "AppKit"
use scripting additions

set fontManager to current application's NSFontManager's sharedFontManager()
set fontArray to (fontManager's availableFonts())
set thePred to current application's NSPredicate's predicateWithFormat:("NOT self MATCHES %@") argumentArray:{"\\..+"}
set fontArray to fontArray's filteredArrayUsingPredicate:thePred

set fontInfos to {}

repeat with aLoop in fontArray
	set theFont to (current application's NSFont's fontWithName:aLoop |size|:0)
	
	set aDisplay to theFont's displayName() as text
	set aFamily to theFont's familyName() as text
	set aName to theFont's fontName() as text
	set aStyle to (fontManager's traitsOfFont:theFont)
	set aWeight to (fontManager's weightOfFont:theFont)
	set aFixed to theFont's isFixedPitch()
	set aVertical to theFont's isVertical()
	set theRec to ({colName:aName, colFamily:aFamily, colDisplay:aDisplay, colStyle:aStyle, colWeight:aWeight, colFixed:aFixed, colVertical:aVertical})
	set end of fontInfos to (theRec as record)
	--(fontInfos's addObject:theRec)
end repeat

return fontInfos
1 Like

Thank you and @robertfern for attempt to help me.

I need full info for every typeface (not family), which gives the system_profiler. It lists 1148 typefaces installed on my Mac. Your AsObjC examples return only 910 typefaces from them (after applying the predicate), and only some keys of every typeface + returns this 7-member records slowly than my last script. Every typeface in my script gives 13-17 key-value pairs as record.

I trust system_profiler reporting implicitly because it’s a system tool for sending a system report to Apple. It must give the most accurate report.

I need fonts. As I know, evey certain font is typeface, not family or collection.

May be, the correct Predicate and record building in AsObjC examples will give the same result with system_profiler & faster.

I have found a much better way to get complete information about installed fonts as a record. Through trial and error, I came to the conclusion that the JSON approach would be much better.

Note that the fontFamilies variable of the following script already contains the full info as a record. So getting a list of typefaces is optional.

Also, this script gives more freedom to manipulate the record, and even a little faster than the previous one (XML version). For example, you can get (easy) list of POSIX paths of font families.
 

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

set jsonString to do shell script "system_profiler -json SPFontsDataType"
set jsonString to my (NSString's stringWithString:jsonString)
set jsonData to jsonString's dataUsingEncoding:(my NSUTF8StringEncoding)
set fontsInfo to (my (NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value))) as record
set fontFamilies to SPFontsDataType of fontsInfo

set allTypeFaces to a reference to {}
repeat with fontFamily in my fontFamilies
	repeat with anItem in (typeFaces of fontFamily)
		set end of allTypeFaces to contents of anItem
	end repeat
end repeat
return allTypeFaces

 

I wrote a lot of AppleScript about getting font information. And I’m very satisfied with them.
I don’t understand why you want to use system_profiler.
Some people says “Word is faster than NSFont” and deeply disapponted that they don’t care of booting time and the existence of font cache.

You can compare the result of my script and the result of NSFontManager’s availableFonts. First, as I see it, the result lists do not match.

Secondly, NSFont provides little ability to analyze the characteristics of an individual font. How would you make sure that a font’s copyright prohibits commercial use? And much more.

So far, I haven’t seen any AsObjC or Objective-C code that returns the families, typefaces of installed (that is, registered by system & FontBook) fonts, and all the characteristics of a single typeface. Or I didn’t search well.

I have already said why I rely more on the system_profiler report. Because this is what Apple uses to provide technical support.

I think you’d better report to Apple not to me.
And write them to recover scriptability with FontBook.

@Takaaki,

Indeed, Apple did a lot of damage with fonts scripting. Here you asked me a question and I tried to answer and that’s it. I love AsObjC and your site as much as you do. For wide opportunities in tasks where plain AppleScript is powerless or slow.

I wrote a lot of bug reports to them. M1 Mac + macOS 11 caused Cocoa Scripting slow-down.
So, I reported them with detailed data and report and the nice words “Your brand-new machine seems x20 slower than 10 years old MacBook Air”.

They fixed it in macOS 12. So, M1 Mac + macOS 12 and 13 have very fast Cocoa scripting execution.
If we don’t write report, the world does not change.

2 Likes