Skip to content

Instantly share code, notes, and snippets.

@bingxie
Created August 11, 2016 06:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bingxie/ecd4bddaa7935a0be19daab337d165d8 to your computer and use it in GitHub Desktop.
Save bingxie/ecd4bddaa7935a0be19daab337d165d8 to your computer and use it in GitHub Desktop.
Generate a list of fonts for ImageMagick
#!/usr/bin/perl -w
#
(my $prog = $0) =~ s/^.*\///;
sub Usage {
die @_, &herefile( qq{
| Usage: $prog > ~/.magick/type.xml
| $prog [-d] font1.ttf font2.ttf ... > type.xml
| $prog -f ttf_font_file_list > type.xml
|
| Generate an ImageMagick font list "type.xml" file for ALL fonts
| currently on the local linux system. This includes
| True Type Fonts (ttf)
| Ghostscript Fonts (afm)
| Open Type Fonts (otf)
|
| The output can be saved into files in the ".magick" sub-directory of
| your home, to be referenced by, or replacing the "type.xml" file.
|
| This file informs ImageMagick of the fonts location, font type, name and
| family. It also trys its best to clean up the name to provide a 'nicer'
| one for you to identify the various fonts.
|
| By default the fonts are found using the linux "locate" command, so run
| "updatedb" if you only just added new fonts to your computer.
|
| However you can use a "-f" option to read the font filenames from a
| file, or from STANDARD INPUT (using a "-" for a file name).
| For example...
|
| find /home/user/my/font/dir/ -name '*.ttf' | \
| imagick_type_gen -f - > ~/.magick/type-myfonts.xml
|
| You can then include that into your own personal "~/.magick/type.xml"
| file or the system-wide "type.xml" which has a form like...
| <typemap>
| <include file="type-system.xml" />
| <include file="type-myfonts.xml" />
| </typemap>
|
| Note that later defintions will override earlier ones. As such "myfonts"
| will override any "system" font. However any fonts that the IM system
| configures in /usr/lib/ImageMagick-*/config/type*.xml will override the
| both the above definitions, if the same font name is used (unlikely).
|
| When the "type.xml" font definitions file has been generated and
| installed, should then see a list of the fonts found with...
| convert -list font
|
| And can use the fonts, by name, with commands like...
| convert -font Candice -pointsize 72 label:Anthony x:
|
| Instead of having to specifying TTF font file directly...
| convert -font ~/lib/font/truetype/favoriate/candice.ttf \
| -pointsize 72 label:Anthony x:
|
| NOTE before IM v6.1.2-3 the font list file was called "type.mgk" and
| not "type.xml". And you would use "-list type" instead of "-list font"
|
| Also see the script "show_fonts" which displays a sample image either
| a IM defined font, or the given font files. The "graphics_utf" script
| may also be useful to look at specific UTF character sections of a
| specific font, such as Math symbols.
|
| Anthony Thyssen May 2003 Updated January 2009
});
}
# Internal working notes...
#
# Originally the script used an external tool to read TTF fonts, but now
# that is built-in thanks to Peter N Lewis <peter@stairways.com.au>
#
# WARNING: Input arguments are NOT tested for correctness.
# This script represents a security risk if used ONLINE.
# I accept no responsiblity for misuse. Use at own risk.
#
# The original version of this script was found on
# http://studio.imagemagick.org/pipermail/magick-users/2003-March/001703.html
# by raptor <raptor@unacs.bg>, presumaibly around March 2002
#
# Re-Write by Anthony Thyssen <anthony@cit.griffith.edu.au>, August 2002
# May 2003 Update with TTF family names
# Oct 2005 Update to use "getttinfo" if available
#
use strict;
use Fcntl qw( O_RDONLY SEEK_SET );
binmode(STDOUT, ":utf8");
binmode(STDERR, ":utf8");
my $VERBOSE = 0; # verbose output of fonts found
my $DEBUG = 0; # debug TTF file decoding
# ======================================================================
# Subroutines...
# ======================================================================
#
# True Type fonts Handling
#
my $ttf_template = herefile( q{
| <type
| format="ttf"
| name="%s"
| glyphs="%s"
| />
});
my $ttf_template_full = herefile( q{
| <type
| format="ttf"
| name="%s"
| fullname="%s"
| family="%s"
| glyphs="%s"
| />
});
sub ttf_file_parse {
#
# Method for Parsing TTF files curtesy of
# Peter N Lewis <peter@stairways.com.au>
#
my $file = $_[0];
my ( $font_family, $font_fullname, $font_psname ) = ( '','','','' );
my ( $fh, $len );
unless ( sysopen( $fh, $file, O_RDONLY ) ) {
warn "Cannot open $file: $!\n";
return;
}
my $header;
unless ( sysread( $fh, $header, 12 ) ) {
warn "Cant read header: $file";
close($fh);
return;
}
my ( $sfnt_version, $numTables, $searchRange, $entrySelector, $rangeShift
) = unpack( 'Nnnnn', $header );
my $sfnt_version_code = unpack( 'A4', $header );
unless ( $sfnt_version == 0x00010000
|| $sfnt_version_code eq 'true'
|| $sfnt_version_code eq 'typ1' ) {
warn "TTF Version mismatch, not a basic TrueType font file: $file";
close($fh);
return;
}
print STDERR "TTF Table count: $numTables\n" if $DEBUG>=2;
foreach ( 1..$numTables ) {
my $table_entry;
unless ( sysread( $fh, $table_entry, 16 ) ) {
warn "Cant read master table $_ from $file";
last;
}
my ( $table_tag, $table_checkSum, $table_offset, $table_length
) = unpack( 'A4NNN', $table_entry );
print STDERR "Table: $table_tag\n" if $DEBUG>=2;
$table_tag eq 'name' or next;
my $table_header;
sysseek( $fh, $table_offset, SEEK_SET ) or die "Can't seek: $file";
sysread( $fh, $table_header, 6 );
my ( $table_format, $table_count, $table_stringOffset
) = unpack( 'nnn', $table_header );
print STDERR "Name Table Entries: $table_count\n" if $DEBUG>=2;
my $table_base = $table_offset + 6;
my $storage_base = $table_base + $table_count * 12;
foreach my $index ( 1..$table_count ) {
my $entry;
sysseek( $fh, $table_base + ($index-1)*12, SEEK_SET )
or die "Cant seek: $file";
sysread( $fh, $entry, 12 );
my ( $name_platformID, $name_encodingID, $name_languageID,
$name_id, $name_length, $name_offset
) = unpack( 'nnnnnn', $entry );
print STDERR "Index[$index]: ", join ( ", ",
$name_platformID, $name_encodingID, $name_languageID,
$name_id, $name_length, $name_offset ), "\n" if $DEBUG>=2;
#
# ID meanings : figured out from getttinfo
#
# Platform: 0=Apple 1=macintosh 3=microsoft
# Encoding: 0=unicode(8) 1=unicode(16)
# Language: 0=english 1033=English-US 1041=Japanese 2052=Chinese
#
next unless $name_languageID == 0
|| $name_languageID == 1033
;
my $name;
sysseek( $fh, $storage_base + $name_offset, SEEK_SET )
or die "Cant seek: $file";
sysread( $fh, $name, $name_length );
# Decode UTF-16 to UTF-8 if nessary
$name = pack("U*",unpack("n*", $name)) if $name_encodingID == 1;
$name =~ s/\0//g; # clean fonts use UTF-16 when it should be UTF-8
print STDERR "$name\n" if $DEBUG>=2;
$font_family = $name if $name_id == 1;
#font_subfamily = $name if $name_id == 2; # (EG: Regular)
#font_identifier = $name if $name_id == 3; # Unique Name
$font_fullname = $name if $name_id == 4;
#font_version = $name if $name_id == 5;
$font_psname = $name if $name_id == 6; # Postscipt Name
#font_trademark = $name if $name_id == 7;
#font_manufacturer = $name if $name_id == 8;
#font_designer = $name if $name_id == 9;
}
last; # found "name" table -- skip any other tables as irrelevent
}
close( $fh );
return ( $font_family, $font_fullname, $font_psname );
}
sub ttf_name {
my $file = shift;
my ( $family, $fullname, $psname ) = &ttf_file_parse( $file );
print STDERR "$file\n\t==> $family -- $fullname -- $psname\n" if $DEBUG;
$fullname =~ s/[^\s\w-]//g; # Check: Pepsi.ttf
$fullname =~ s/^\s+//;
$fullname =~ s/\s+$//;
$fullname =~ s/(^|\s)-/$1/g;
$fullname =~ s/-(\s|$)/$1/g;
$family =~ s/[^\s\w-]//g; # Check: Pepsi.ttf
$family =~ s/^\s*//;
$family =~ s/\s*$//;
$family =~ s/\s*(MS|ITC)$//; # font factory ititials
$family =~ s/^(MS|ITC)\s*//;
$family =~ s/\s*(FB|MT)\s*/ /; # Check: MaturaScriptCapitals
$family =~ s/^Monotype\s*//; # Check: Corsiva
$family =~ s/^AR PL\s*//; # Check: gkai00mp.ttf
$family =~ s/\sBV$//; # Check: CandyStore.ttf
# Determine simple font name
# Junk/abbr decriptive strings, foundaries, etc
# Test with the fonts given
my $name = ($fullname);
$name =~ s/-/ /g;
$name =~ s/\s*(MS|ITC)$//; # font factory ititials
$name =~ s/^(MS|ITC)\s*//;
$name =~ s/\s*(FB|MT)\s*/ /; # Check: MaturaScriptCapitals
$name =~ s/^Monotype\s*//; # Check: Corsiva
$name =~ s/^AR PL\s*//; # Check: gkai00mp.ttf
$name =~ s/^TTF_//; # Check: TattoEF.tff
$name =~ s/^HE_//; # Check: Terminal.tff
$name =~ s/^KR\s//; # Check: SimpleFleur*.ttf
$name =~ s/\sBT$//; # Check: Amazone.ttf
$name =~ s/\sBV$//; # Check: CandyStore.ttf
$name =~ s/\sFM$//; # Check: CactusSandwich.ttf
$name =~ s/\sNFI$//; # Check: Zreaks.ttf
$name =~ s/SSK$//; # Check: BravoScript.ttf
$name =~ s/Regular//g; # Check: Gecko
$name =~ s/\bPlain\b//g; # Check: LittleGidding
$name =~ s/\bReg\b//g; # Check: agencyr.ttf
$name =~ s/\bNormal\b//g;
#$name =~ s/\bSans\b//g;
$name =~ s/\bDemi\s*[Bb]old\b/Db/g;
$name =~ s/\bCondensed\b/C/g;
$name =~ s/\bBold\b/B/g;
$name =~ s/\bItalic\b/I/g;
$name =~ s/\bExtra[Bb]old\b/Xb/g;
$name =~ s/\bBlack\b/Bk/g;
$name =~ s/\bHeavy\b/H/g;
$name =~ s/\bMedium\b/M/g; # Check: gkai00mp.ttf
$name =~ s/\bLight\b/L/g;
$name =~ s/\bOblique\b/Ob/g;
$name =~ s/\bUnregistered\b//g; # Check: CandyCane.ttf
$name =~ s/\s+//g; # Remove all spaces
# Special Case Renaming
$name = "Dot" if $name eq "NationalFirstFontDotted";
$fullname =~ s/\s+/ /g;
$fullname =~ s/\s$//;
$fullname =~ s/^\s//;
# Failed to parse TTF file?
return( ( $file =~ m/^.*\/(.*?).ttf$/ )[0] ) unless $name;
return ($name, $fullname, $family); # return the name if found!
}
sub do_ttf_font {
my $file = shift;
my (@ttf) = ttf_name($file);
print STDERR join( ' - ', @ttf), "\n" if $VERBOSE;
printf $ttf_template, @ttf, $file if @ttf == 1;
printf $ttf_template_full, @ttf, $file if @ttf == 3;
}
sub do_ttf_fonts {
for ( locate('ttf') ) {
do_ttf_font($_);
}
}
#---------------------------
#
# Open Type fonts
#
# I do not know how to parse OTF files (yet)
# so we are stuck with just the filebame
#
my $otf_template = herefile( q{
| <type
| format="otf"
| name="%s"
| glyphs="%s"
| />
});
sub do_otf_font {
my $file = shift;
my $name = $file;
$name =~ s/^.*\///;
$name =~ s/\.otf$//;
$name =~ s/-?Regular//g;
$name =~ s/-?Bold?/B/g;
$name =~ s/-?Italic/I/g;
$name =~ s/-?Ita?/I/g;
$name =~ s/-?Oblique/Ob/g;
print STDERR join( ' - ', $name ), "\n" if $VERBOSE;
printf $otf_template, $name, $file;
}
sub do_otf_fonts {
for ( locate('otf') ) {
do_otf_font($_);
}
}
#---------------------------
#
# Adobe Type fonts
#
# Get font name from the AFM file
my $afm_template_full = herefile( q{
| <type
| format="type1"
| name="%s"
| fullname="%s"
| family="%s"
| glyphs="%s"
| metrics="%s"
| />
});
sub afm_name {
my $file = shift;
my( $name, $fullname, $family ) = ('','','');
if ( open AFM, $file ) {
while( <AFM> ) {
chop; last if /^StartCharMetrics/;
#$name = $1 if /^FontName (.*)/;
$fullname = $1 if /^FullName (.*)/;
$family = $1 if /^FamilyName (.*)/;
}
close AFM;
$family =~ s/\s*L$//; # just the stupid 'L'
$fullname =~ s/\bL\b//;
$name = $fullname;
$name =~ s/\bRegular\b//; # Junk/abbr decriptive strings
$name =~ s/\bDemi\s*[Bb]old\b/Db/g;
$name =~ s/\bCondensed\b/C/g;
$name =~ s/\bBold\b/B/g;
$name =~ s/\bItalic\b/I/g;
$name =~ s/\bExtra[Bb]old\b/Xb/g;
$name =~ s/\bBlack\b/Bk/g;
$name =~ s/\bHeavy\b/H/g;
$name =~ s/\bLight\b/L/g;
$name =~ s/[-\s]+//g;
$fullname =~ s/\s+/ /g;
$fullname =~ s/\s$//g;
$fullname =~ s/^\s//g;
} else {
warn "Cannot open $file";
}
return ($name, $fullname, $family ) if $name && $fullname && $family;
}
sub do_afm_fonts {
my %atf;
# locate abode font files
map { my ($k) = m/^(.*?).pfb*$/i; $atf{lc($k)}{pfb} = $_ } locate('pfb');
map { my ($k) = m/^(.*?).afm*$/i; $atf{lc($k)}{afm} = $_ } locate('afm');
# for each Abode font where BOTH files were found.
for my $key (keys %atf) {
next unless $atf{$key}{pfb} && $atf{$key}{afm};
my (@afm) = afm_name($atf{$key}{afm});
#print STDERR join( ' - ', @afm), "\n" if $VERBOSE;
printf $afm_template_full, @afm, $atf{$key}{pfb}, $atf{$key}{afm}
if @afm == 3;
}
}
# -----------------------------
#
# Miscellanous functions
#
sub locate {
# This fails under MacOSX
#return split('\0', `locate -0ier '\\.$_[0]\$'`);
# Use perl to 'glob' expand '?' in locate output on MacOSX
return grep { /\.$_[0]$/i && -f $_ }
map { glob "$_" }
split "\n", `locate -i '.$_[0]'`;
}
sub herefile { # Handle a multi-line quoted indented string
my $string = shift;
$string =~ s/^\s*//; # remove start spaces
$string =~ s/^\s*\| ?//gm; # remove line starts
$string =~ s/\s*$/\n/g; # remove end spaces
return $string;
}
sub do_font {
local $_ = shift;
if ( /\.ttf$/i ) {
do_ttf_font($_)
}
elsif ( $_ =~ /\.otf$/i ) {
do_otf_font($_)
}
else {
print STDERR "$prog: \"$_\" skipped, unknown suffix\n";
}
}
# ======================================================================
# Main Function
# ======================================================================
print herefile( q{
| <?xml version="1.0"?>
| <typemap>
});
# Read TTF font filenames from a file or stdin
$DEBUG=1,shift if @ARGV && $ARGV[0] eq '-d';
if ( @ARGV ) {
if ( $ARGV[0] eq '-?' || $ARGV[0] eq '-h' || $ARGV[0] eq '--help' ) {
Usage;
}
if ( $ARGV[0] eq '-f' ) {
shift;
while( <> ) {
s/#.*$//; # ignore comments
s/\s+$//; # remove end of line spaces
next if /^$/; # skip blank lines
do_font($_);
}
}
else {
# TTF font filenames as arguments
for ( @ARGV ) {
do_font($_);
}
}
} else {
# Generate the "type.xml" file using "locate"
print STDERR "Doing TTF fonts\n" if $VERBOSE;
do_ttf_fonts();
print STDERR "Doing OTF fonts\n" if $VERBOSE;
do_otf_fonts($_);
print STDERR "Doing ATM fonts\n" if $VERBOSE;
do_afm_fonts();
}
print "</typemap>\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment