#!/usr/bin/perl -w # # 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) # Open Type Fonts (otf) # Ghostscript Adobe Fonts (afm) # # 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 magick identify the various fonts. # # On Linux system the scritp uses the "locate" command to find the fonts. # If you recently added fonts you should run "updatedb" first. # # On MacOSX only the fonts stored in /Library/Fonts are looked for. # That is the script is equivelent to doing... # # find /Library/Fonts -type f -name '*.*' | \ # imagick_type_gen -f - > type.xml # # When the "type.xml" font definitions file has been generated and # installed, should then see a list of the fonts found with... # magick -list font # # And can use the fonts, by name, with commands like... # magick -font Candice -pointsize 72 label:Anthony x: # # Instead of having to specifying TTF font file directly... # magick -font ~/lib/font/truetype/favoriate/candice.ttf \ # -pointsize 72 label:Anthony x: # # 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 Feburary 2017 # ### # # Example use, seperating system from personal fonts so the later # overrides the former (of the same font exists) # # For example... # # # Find personal fonts # find $HOME/fonts -type f -name '*.ttf' | \ # imagick_type_gen -f - > ~/.magick/type-myfonts.xml # # # Find System Fonts - then remove personal fonts from that list # sudo updatedb # imagick_type_gen > ~/.magick/type-system.xml # perl -00 -i -ne "m%glyphs=\"$HOME/% || print" ~/.magick/type-system.xml # # You can then include both of these files into a "~/.magick/type.xml" # so that the person # # # # # # # 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, though that is unlikely. # ### # Internal Notes and History # # Primary Source # http://www.imagemagick.org/Usage/scripts/imagick_type_gen # # Originally the script used an external tool to read TTF fonts, but now # that is built-in thanks to Peter N Lewis # # 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" # # The original version of this script was found on # http://studio.imagemagick.org/pipermail/magick-users/2003-March/001703.html # by raptor , presumaibly around March 2002 # # Re-Write by Anthony Thyssen , August 2002 # May 2003 Update with TTF family names # Oct 2005 Update to use "getttinfo" if available # Jan 2009 updated # Feb 2017 merge bug report from Kazuyoshi Tlacaelel # # 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. # ### use strict; use FindBin; my $PROGNAME = $FindBin::Script; 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 my $FILE = 0; # read a font list from a file (maybe stdin) # ====================================================================== # Font Handling... # ====================================================================== # # True Type fonts Handling # my $ttf_template = herefile( q{ | }); my $ttf_template_full = herefile( q{ | }); sub ttf_file_parse { # # Method for Parsing TTF files curtesy of # Peter N Lewis # 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; } #--------------------------- # # 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{ | }); 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; } #--------------------------- # # Adobe Type fonts # # Get font name from the AFM file my $afm_template_full = herefile( q{ | }); sub afm_name { my $file = shift; my( $name, $fullname, $family ) = ('','',''); if ( open AFM, $file ) { while( ) { 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/\bDemi\s*[Oo]blique\b/Do/g; $name =~ s/\bCondensed\b/C/g; $name =~ s/\bBold\b/B/g; $name =~ s/\bItalic\b/I/g; $name =~ s/\bOblique\b/Ob/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; $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; } } # ====================================================================== # Option Handling... # ====================================================================== sub Usage { print STDERR @_, "\n" if @_; @ARGV = ( "$FindBin::Bin/$PROGNAME" ); while( <> ) { next if 1 .. 2; last if /^###/; last unless /^#/; s/^#$//; s/^# //; last if /^$/; print STDERR $. == 3 ? "Usage: $_" : " $_"; } print STDERR "For full manual use --help\n"; exit 10; } sub Help { @ARGV = ( "$FindBin::Bin/$PROGNAME" ); while( <> ) { next if $. == 1; last if /^###/; last unless /^#/; s/^#$//; s/^# //; print STDERR; } exit 10; } sub do_font { local $_ = shift; if ( /\.ttf$/i ) { do_ttf_font($_) } elsif ( $_ =~ /\.otf$/i ) { do_otf_font($_) } else { print STDERR "$PROGNAME: \"$_\" skipped, unknown suffix\n"; } } sub locate { # Locate font files with the given suffix my $suffix = shift; if ( -d "/Library/Fonts" ) { # We must be on MacOSX! # Use a 'find' to discover fonts in that directory only # Find method from Kazuyoshi Tlacaelel - Feb 2017 return grep { /\.$suffix$/i && -f $_ } split( "\n", `find /Library/Fonts -type f -name '*.*'` ); # map { glob "$_" } # Use glob to expand '?' in locate output # split "\n", `locate -i '.$_[0]'`; # alternative } # All linux system run updatedb and locate - so ask it return split('\0', `locate -0ier '\\.$suffix\$'`); #return grep { /\.$_[0]$/i && -f $_ } # map { glob "$_" } # split "\n", `find /Library/Fonts -name '*.*'`; } 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; } OPTION: # Multi-switch option handling while( @ARGV && $ARGV[0] =~ s/^-(?=.)// ) { $_ = shift; { m/^$/ && do { next }; # Next option m/^-$/ && do { last }; # End of options '--' m/^\?/ && do { Help }; # Usage Help '-?' m/^-?(help|doc|man|manual)$/ && Help; # Print help manual comments s/^d// && do { $DEBUG++; redo }; # \ s/^v// && do { $VERBOSE++; redo }; # / s/^f// && do { $FILE++; redo }; # / Usage( "$PROGNAME: Unknown Option \"-$_\"" ); } continue { next OPTION }; last OPTION; } print herefile( q{ | | }); if ( $FILE ) { while( <> ) { s/#.*$//; # ignore comments s/\s+$//; # remove end of line spaces next if /^$/; # skip blank lines do_font($_); } } elsif ( @ARGV ) { # 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; for ( locate('ttf') ) { do_ttf_font($_); } print STDERR "Doing OTF fonts\n" if $VERBOSE; for ( locate('otf') ) { do_otf_font($_); } print STDERR "Doing ATM fonts\n" if $VERBOSE; do_afm_fonts(); } print "\n"; # ----------------------------------------------------------------------------