Created
August 11, 2016 06:42
-
-
Save bingxie/ecd4bddaa7935a0be19daab337d165d8 to your computer and use it in GitHub Desktop.
Generate a list of fonts for ImageMagick
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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