Getting Font Metadata?

I have a need to get the “metadata” from all of our Fonts. Things like the Family Name, Type, Foundry, Name, and Version.

I tried a hexdump of some font files, but found nothing human-readable. But I know the data exists because Suitcase has it availble.

For example, the font Monaco, available with every Mac. Suitcase tells me the following:

Name: Monaco
Family: Monaco
Foundry: Apple Computer
Type: TrueType
Version: ‘5.0d1e1’

Or Helvetica:

Name: Helvetica
Family: Helvetica
Foundry: Adobe Systems
Type: PostScript pair
Version: '002.000

What I need is a dump of all this data from ALL the fonts. Our fonts, unfortunately are stored all over the place. The System and User libraries, in addition to a Fonts partition (using Suitcase). I’d like a fast way to access this metadata, but right now I have to use Suitcase like this:


tell application "Extensis Suitcase X1"
	set font_count to count Font
	
	repeat with i from 1 to font_count
		set font_family to family of Font i
		set font_type to font type of Font i
		set font_foundry to foundry of Font i
		set font_name to name of Font i
		set font_version to version of Font i
		
		set log_string to font_name & tab & font_family & tab & font_foundry & tab & font_type & tab & "'" & font_version
		my logMe(log_string, 0)
	end repeat
	
end tell

logMe is simply a file-writing routine I use.


--
--Log Entry Generation
--
on logMe(log_string, indent_level)
	if g_debug is "true" then --allows turning the debugger on and off so logMe's can be left in final version
		
		try
			writeEntry(log_string, indent_level, g_log_file_path)
		on error
			tell application "Finder"
				make new file at g_desktop_folder_path with properties {name:g_log_file_name & ".txt"}
			end tell
			writeEntry(log_string, indent_level, g_log_file_path)
		end try
	end if
end logMe

on writeEntry(log_string, indent_level, log_file_path)
	--streamlined code
	repeat indent_level times
		write tab to (g_log_file_path as alias) starting at eof
	end repeat
	write (log_string & return) to (g_log_file_path as alias) starting at eof
end writeEntry

The above method is really slow though. Parsing through 8300+ individual fonts takes 30-45 minutes on a G5–and I can’t do much on the computer while it’s running (Suitcase gets crabby if I try to actually work while this script is doing its thing). I eventually will set this script up to run overnight, but in the meantime…any ideas to speed this up?

Hi Kevin,

your script is slow, because you read the list of Fonts in each loop

I don’t own Suitcase, so I couldn’t test it, but this should be much faster

tell application "Extensis Suitcase X1"
	repeat with i in (get Fonts)
	tell contents of i
	my logMe((name & tab & family & tab & foundry & tab & type & tab & "'" & version), 0)
	end tell	
	end repeat
end tell

It got hung-up on:


repeat with i in (get fonts)

Says “Expected a reference”

I stepped through the script in debugging mode (ScriptDebugger) and I don’t see it load a list into any of the variables or show as a result. Why did you think that was the cause of the speed issue? “font_count” itself is giving me “8800” which is about right and the “parent” data doesn’t seem to show a list either. If it’s loading a list it’s doing it in a way I’ve not seen before (quite possible, mind you).

Reading the list in every loop is much slower than having it in memory and accessing it directly.
Take Nigel Garvey’s lotsa routine from this thread (you also have to install the osax GetMilliSec)
and try it.
You can see in ScriptDebugger’s explorer the right plural spelling of the Font property in Suitcase

Or use something like “get every font”

Still lost. No idea what “list” we’re talking about, what your reference to plural was about, and what exactly this all has to do with the link to that other thread.

Don’t mean that flippantly either, you just went over my head is all. :frowning:

Hi Kevin,

I installed the demo version of Suitcase and figured out some things.
Although there is a Fonts property Suitcase understand only every Font
A script object, which contains the List is the fastest way to parse and get the data.
It takes less than 9 seconds for approx. 750 Fonts on my machine (G5 Dual 2,5 GHz, 10.4.8)

tell application "Extensis Suitcase X1"
	set Fonttext to ""
	script o
		property L : every Font
	end script
	repeat with i in o's L
		tell contents of i
			set Fonttext to Fonttext & (name & tab & family & tab & foundry & tab & font type & tab & "'" & version & return)
		end tell
	end repeat
end tell

write_to_disk from Fonttext into (((path to desktop) as Unicode text) & "FontData.txt")

on write_to_disk from |data| into target
	set ff to open for access file target with write permission
	write |data| to ff
	close access ff
end write_to_disk

Perhaps I’m more conservative, Stefan, but I’d have written it this way:


on write_to_disk from |data| into target
	set ff to open for access file target with write permission
	try
		set eof of ff to 0 -- erase the file
		write |data| to ff
		close access ff
	on error e
		close access ff
		display dialog e
	end try
end write_to_disk

me too ;), this is my short test version.
I hope you like the rest more :slight_smile:

I thought the rest was slick! :cool:

light bulb goes off

I misunderstood…I thought you said I was loading a list, but it looks like from staring at your script that I needed to load a list. I’ve never been good with list handling, but it seems I need to learn…the speed differences are staggering! The script seems to work and VERY fast. Down to 3 minutes!

Rather than just have you guys kindly give me solutions, I like to learn as much as I can as to the “how” and “why” so I can cut down on asking similar questions in the future…so some questions:

I’m a bit confused by |data| and target. Wouldn’t the handler more commonly be done as:


write_to_disk(Fonttext, (((path to desktop) as Unicode text) & "FontData.txt"))

on write_to_disk(|data|, target)
	set ff to open for access file target with write permission
	write |data| to ff
	close access ff
end write_to_disk

Or would that change the functionality?

Also, I was under the impression that piping a phrase was a way to keep from causing confusion with a local term. In other words “|data|” is only used if you’re determined to use “data” as a variable but “data” means something else to the context of the script or handler. If so, I was under the impression it was better just to use another name rather than fussing around with piping the variable name.

Or is “|data|” being used in some way I’m not familiar with?

Thanks bunches!

There are two kinds to pass parameters to a handler:
1. The common way with the parentheses
The parameters must be proceeded by the handler in the same order
as they are within the parentheses

2. The labeled parameters
The parameters are passed with labels (21 prepositions like from, at, into, onto)
instead of the parentheses. One preposition is assigned to one parameter.
The order doesn’t matter, and you can define your own boolean parameters.
An example:

on myhandler from a into b given subtract:subtract
	if subtract is false then
		return a + b
	else
		return a - b
	end if
end myhandler 

myhandler from 5 into 2 with subtract --> 3
myhandler from 5 into 2 without subtract --> 7

The basic functionality is the same with parentheses or labeled parameters.

data is a reserved word in AppleScript.
You can use reserved words as variables only with wrapping them in into pipes.
With the pipes they are normal variables

Any reasons to use one method over another, or are they equally efficient?

Yours is the first script I’ve played with that uses labelled parameters, so I’m very curious. Learning alot here. :wink:

I described the differences above, I use the labeled one, when I need boolean parameters with a certain name
The write_to_disk routine is a part of a larger one, which I use for testing, and there is an append parameter

write_to_disk from "Hello" into (((path to desktop) as Unicode text) & "testFile.txt") with append

on write_to_disk from |data| into target given append:append
	set ff to open for access file target with write permission
	if append is false then set eof of ff to 0
	write |data| to ff starting at eof
	close access ff
end write_to_disk

Okay this is odd…having a problem:

If I run the script manually from ScriptDebugger, it works as expected, writing a proper Excel file quickly, about 3 minutes.

However, saved as an application the runtime leaps-up to 25 minutes and the Font Type column is giving me values like:

«constant ****FTPP»
«constant ****FTTT»

I’m saving this script as an application mostly because I used Cron to set-off the scripts overnight and have had problems running .scpt files this way (they crash and/or behave oddly).

I believe FTPP and FTTT are file types (PostScript and TrueType respectively), which is odd.

Any ideas?

Model: Dual 2.7 GHz G5
AppleScript: (default Tiger 10.6.4)
Browser: Firefox 1.5.0.4
Operating System: Mac OS X (10.4)

StefanK made some suggestions offline, but they didn’t fix the problem.

There are currently two issues with the script (current version below):

–Run from Script Debugger it takes 3 minutes…run as an Application takes 25 minutes…WTH?

–Run from Script Debugger, the Font Type comes out properly, run as an App and the Font Type returns wierd values (per previous post)

Here’s what I’ve got:


--
-- Font Metadata Extract via Suitcase
-- by Kevin Quosig
--
--
-- Uses Suitcase to pull basic font metadata about all fonts
-- that it has access to and save to an Excel spreadsheet
-- that can be provided to vendor partners so they can but
-- any CompanyName fonts they need to do work for us
--



--
-- INITIALIZE VARIABLES
--
global g_debug
set g_debug to "true"

global g_file_name
set g_file_name to ("CompanyName_Fonts" & "_" & dateStamp()) as text

global g_desktop_folder_path
set g_desktop_folder_path to path to desktop folder

global g_temp_file_path
set g_temp_file_path to (g_desktop_folder_path & g_file_name & ".txt") as text

global g_xls_save_path
set g_xls_save_path to "Diamond Design:Process & Technology:Process & Technology General:Data Extracts:Font Metadata:"

global g_xls_file_path
set g_xls_file_path to (g_xls_save_path & g_file_name & ".xls") as text


--
--SUBROUTINES, DEBUGGING
--
set targetLibrary to "Data: Automation:Script Library:logMe.scpt" as string
load script file targetLibrary
set logMeLibrary to result


--
-- UTILITY HANDLERS
--

-- Built with help from MacScripter. For details see:
-- http://bbs.applescript.net/viewtopic.php?pid=76721
--
on write_to_disk(write_me, write_where)
	set file_reference to open for access file write_where with write permission
	try
		set eof of file_reference to 0 -- erase the file
		write write_me to file_reference
		close access file_reference
	on error error_info
		close access file_reference
		display dialog error_info
	end try
end write_to_disk

--
--YYMMDD datestamp generator
--
on dateStamp()
	-- Load date components from system
	tell (current date) to set dayStamp to day
	tell (current date) to set monthStamp to (its month as integer)
	tell (current date) to set yearStamp to year
	
	--Coerce components to two-digit form
	set dayStamp to (text -2 thru -1 of ("0" & dayStamp as string))
	set monthStamp to (text -2 thru -1 of ("0" & monthStamp as string))
	set yearStamp to (text 3 thru 4 of (yearStamp as string))
	
	--Assemble datestamp
	set finishedDateStamp to yearStamp & monthStamp & dayStamp as text
	
	return finishedDateStamp
end dateStamp


--
-- MAIN HANDLER
--

tell logMeLibrary
	logMe("---", 1, "LOG--Overnight Automation.txt")
	logMe("Font Metadata Extract via Suitecase Start--" & (current date), 1, "LOG--Overnight Automation.txt")
end tell

tell application "Extensis Suitcase X1"
	
	-- Built with help from MacScripter. For details see:
	-- http://bbs.applescript.net/viewtopic.php?pid=76721
	--
	--get list of all fonts and their metadata
	set data_extract to ""
	
	script font_property
		property all_fonts : {}
	end script
	
	set font_property's all_fonts to (get every Font)
	
	(* previous to StefanK's suggestion to Font Type returning wierd values as an application
	script get_fonts
		property all_fonts : every Font
	end script
	*)
	
	--loop through list and load variable
	repeat with current_font in font_property's all_fonts
		tell contents of current_font
			set data_extract to data_extract & (name & tab & family & tab & foundry & tab & font type & tab & "'" & version & return)
		end tell
	end repeat
	
end tell

my write_to_disk(data_extract, g_temp_file_path)

tell application "Microsoft Excel"
	--open text dump
	Open g_temp_file_path
	
	--sort data
	Sort Selection Key1 Range "R1C1" Order1 xlAscending Header xlGuess OrderCustom 1 Orientation xlTopToBottom without MatchCase
	
	--add policy
	Select Range "R1"
	Insert Selection Shift xlDown
	Insert Selection Shift xlDown
	Insert Selection Shift xlDown
	Insert Selection Shift xlDown
	Insert Selection Shift xlDown
	Select Range "R1C1"
	set FormulaR1C1 of ActiveCell to "CompanyName is prohibited by font license agreements to give any of its fonts to vendor partners,"
	Select Range "R2C1"
	set FormulaR1C1 of ActiveCell to "even when doing work on our behalf. The fonts listed below are ones currently in use at CompanyName and"
	Select Range "R3C1"
	set FormulaR1C1 of ActiveCell to "provided so that our partners may purchase the same fonts in compliance with the law."
	
	--add title
	Select Range "R1"
	Insert Selection Shift xlDown
	Insert Selection Shift xlDown
	Select Range "R1C1"
	set Bold of Font of Selection to true
	set Size of Font of Selection to 18.0
	set FormulaR1C1 of ActiveCell to "CompanyName Fonts Metadata List"
	
	--add column headers
	Select Range "R7C1:R7C5"
	set ColorIndex of Font of Selection to 2.0
	set ColorIndex of Interior of Selection to 1
	set Pattern of Interior of Selection to xlSolid
	set Bold of Font of Selection to true
	Select Range "R7C1"
	set FormulaR1C1 of ActiveCell to "NAME"
	Select Range "R7C2"
	set FormulaR1C1 of ActiveCell to "FAMILY"
	Select Range "R7C3"
	set FormulaR1C1 of ActiveCell to "FOUNDRY"
	Select Range "R7C4"
	set FormulaR1C1 of ActiveCell to "TYPE"
	Select Range "R7C5"
	set FormulaR1C1 of ActiveCell to "VERSION"
	Select Range "R7C6"
	
	--size columns
	set ColumnWidth of Range "C1" to 33
	set ColumnWidth of Range "C2" to 33
	set ColumnWidth of Range "C3" to 33
	set ColumnWidth of Range "C4" to 14
	set ColumnWidth of Range "C5" to 12
	
	--set page setup
	set LeftMargin of PageSetup of ActiveSheet to 36.0
	set RightMargin of PageSetup of ActiveSheet to 36.0
	set TopMargin of PageSetup of ActiveSheet to 36.0
	set BottomMargin of PageSetup of ActiveSheet to 36.0
	set Zoom of PageSetup of ActiveSheet to 50
	
	--close file
	Save ActiveWorkbook In (g_xls_file_path) As xlNormal
	Close ActiveWorkbook
end tell

tell application "Finder"
	delete file g_temp_file_path
end tell

tell logMeLibrary
	logMe("Font Metadata Extract via Suitecase End--" & (current date), 1, "LOG--Overnight Automation.txt")
	logMe("---", 1, "LOG--Overnight Automation.txt")
end tell

Any idea what’s up with the speed issue and/or the wierd value returns on Font Type?

Well as much as I’d like to figure out why the script-saved-as-app is behaving differently than the script-run-from-editor, and why it’s taking longer as an app, I really need to get this fixed or go back to my old script.

So if I know «constant ****FTPP» is supposed to be PostScript pair, given the above script, how could I do a search-n-replace in-place of the “font type” property data, or maybe some sort of if-then check as it writes the data?

Any help you can provide would be most appreicated.