#!/usr/bin/env perl # updmap: utility to maintain map files for outline fonts. # # # Copyright 2011 Norbert Preining # This file is licensed under the GNU General Public License version 2 # or any later version. # # History: # Original shell script (C) 2002 Thomas Esser # first perl variant (C) Fabrice Popineau # the original version where licensed under the following agreement: # Anyone may freely use, modify, and/or distribute this file, without # limitation. # my ($TEXMFROOT, $TEXMFMAIN, $TEXMFVAR); BEGIN { $^W=1; $TEXMFROOT = `kpsewhich -var-value=TEXMFROOT`; if ($?) { print STDERR "updmap: Cannot find TEXMFROOT, aborting!\n"; exit 1; } chomp($TEXMFROOT); chomp($TEXMFMAIN =`kpsewhich --var-value=TEXMFMAIN`); chomp($TEXMFVAR = `kpsewhich -var-value=TEXMFVAR`); chomp($TEXMFCONFIG = `kpsewhich -var-value=TEXMFCONFIG`); unshift (@INC, "$TEXMFROOT/tlpkg"); } my $version = '$Id$'; use strict; use TeXLive::TLUtils qw(mkdirhier mktexupd win32 log); use Getopt::Long qw(:config no_autoabbrev ignore_case_always); use Pod::Usage; my $prg = TeXLive::TLUtils::basename($0); # used for testing only use Data::Dumper; my %opts = ( quiet => 0, nohash => 0, nomkmap => 0 ); my @cmdline_options = ( "cnffile=s@", "copy", "disable=s@", "dvipdfmoutputdir=s", "dvipsoutputdir=s", "enable=s{1,2}", "edit", "force", "listavailablemaps", "listmaps|l", "nohash", "nomkmap", "dry-run|n", "outputdir=s", "pdftexoutputdir=s", "quiet|silent|q", # the following is a correct specification of an option according # to the manual, but it does not work! # we will treat that option by itself in processOptions #"setoption=s@{1,2}", "showoptions=s@", "syncwithtrees", "version", "help|h" ); my %settings = ( dvipsPreferOutline => [ qw/true false/ ], LW35 => [ qw/URW URWkb ADOBE ADOBEkb/ ], dvipsDownloadBase35 => [ qw/true false/ ], pdftexDownloadBase14 => [ qw/true false/ ], dvipdfmDownloadBase14 => [ qw/true false/ ], ); &main(); ############### sub main { processOptions(); help() if $opts{'help'}; if ($opts{'showoptions'}) { showOptions(@{$opts{'showoptions'}}); exit 0; } # determine which config files shold be used # replaces the former "setupCfgFile" if ($opts{'cnffile'}) { for my $f (@{$opts{'cnffile'}}) { if (! -f $f) { die "$prg: Config file \"$f\" not found."; } } } else { @{$opts{'cnffile'}} = `kpsewhich -all updmap.cfg`; chomp(@{$opts{'cnffile'}}); } if ($opts{'listmaps'}) { listMaps(); exit 0; } if ($opts{'syncwithtrees'}) { warning("NOT IMPLEMENTED syncwithtrees!\n"); exit 1; } # we do changes always in the used config file with the highest # priority my ($top_config_file) = @{$opts{'cnffile'}}; my $bakFile = $top_config_file; $bakFile =~ s/\.cfg$/.bak/; ©File($top_config_file, $bakFile); my $cmd; my $data = read_updmap_files(@{$opts{'cnffile'}}); if ($opts{'edit'}) { # it's not a good idea to edit updmap.cfg manually these days, # but for compatibility we'll silently keep the option. $cmd = 'edit'; my $editor = $ENV{'VISUAL'} || $ENV{'EDITOR'}; $editor ||= (&win32 ? "notepad" : "vi"); system($editor, $top_config_file); } elsif ($opts{'setoption'}) { $cmd = 'setOption'; warning("NOT IMPLEMENTED setoption!\n"); exit 1; } elsif ($opts{'enable'}) { $cmd = 'enableMap'; warning("NOT IMPLEMENTED enable!\n"); exit 1; } elsif ($opts{'disable'}) { $cmd = 'disableMap'; warning("NOT IMPLEMENTED disable!\n"); exit 1; } if ($cmd && !$opts{'force'} && files_are_equal($bakFile, $top_config_file)) { print "$top_config_file unchanged. Map files not recreated.\n" if !$opts{'quiet'}; } else { if (!$opts{'nomkmap'}) { setupOutputDir("dvips"); setupOutputDir("pdftex"); mkMaps($data); } unlink ($bakFile); } my $updLSR = &mktexupd(); $updLSR->{mustexist}(0); if (!$opts{'nohash'}) { print "$prg: Updating ls-R files.\n" if !$opts{'quiet'}; $updLSR->{exec}(); } } sub mkMaps { warning("NOT IMPLEMENTED mkMaps ... dumping data structure for now!\n"); $Data::Dumper::Indent = 1; my $data = read_updmap_files(@{$opts{'cnffile'}}); $data = read_map_files($data); $data = merge_data($data); print Data::Dumper->Dump([$data], [qw(mapdata)]); } sub processOptions { # first process the stupid setoption= s@{1,2} which is not accepted my $oldconfig = Getopt::Long::Configure(qw(pass_through)); my @setoptions; GetOptions("setoption=s{1,2}" => \@setoptions) or die "Try \"$prg --help\" for more information.\n"; Getopt::Long::Configure($oldconfig); # now revamp the @setoptions that they have the proper style my @realset; while (@setoptions) { my $t = shift @setoptions; if ($t =~ m/=/) { push @realset, $t; } else { my $tt = shift @setoptions; die "Try \"$prg --help\" for more information.\n" if !defined($tt); push @realset, "$t=$tt"; } } if (@realset) { @{$opts{'setoption'}} = @realset; } # now continue with normal option handling GetOptions(\%opts, @cmdline_options) or die "Try \"$prg --help\" for more information.\n"; } # determines the output dir for driver from cmd line, or if not given # from TEXMFVAR sub setupOutputDir { my $driver = shift; if (!$opts{$driver . "outputdir"}) { if ($opts{'outputdir'}) { $opts{$driver . "outputdir"} = $opts{'outputdir'}; } else { $opts{$driver . "outputdir"} = "$TEXMFVAR/fonts/map/$driver/updmap"; } } my $od = $opts{$driver . "outputdir"}; &mkdirhier($od); if (! -w $od) { die "$prg: Directory \"$od\" isn't writable: $!"; } print "$driver output dir: \"$od\"\n" if !$opts{'quiet'}; return $od; } ############################################################################# # showOptions(item) # show Options for an item # sub showOptions { foreach my $item (@_) { if ($item eq "LW35") { print "URWkb URW ADOBE ADOBEkb\n"; } elsif ($item =~ m/(dvipsPreferOutline|(dvipdfm|pdftex)DownloadBase14|dvipsDownloadBase35)/) { print "true false\n"; } else { print "Unknown item \"$item\"; should be one of LW35, dvipsPreferOutline,\n" . " dvipsDownloadBase35, or pdftexDownloadBase14\n"; } } exit 0 } # # listMaps # # list all maps in the format # Maptype Mapname 'enabled'|'disabled' 'in' updmap_cfg_filename # sub listMaps { my $data = read_updmap_files(@{$opts{'cnffile'}}); for my $m (keys %{$data->{'maps'}}) { my $origin = $data->{'maps'}{$m}{'origin'}; print $data->{'updmap'}{$origin}{'maps'}{$m}{'type'}, " $m ", $data->{'updmap'}{$origin}{'maps'}{$m}{'status'}, " in $origin\n"; } } ############################################################################### # copyFile() # copy file $src to $dst, sets $dst creation and mod time # sub copyFile { my ($src, $dst) = @_; my $dir; ($dir=$dst)=~s/(.*)\/.*/$1/; mkdirhier $dir; $src eq $dst && return "can't copy $src to itself!\n"; open IN, "<$src" or die "$0: can't open source file $src for copying: $!"; open OUT, ">$dst"; binmode(IN); binmode(OUT); print OUT ; close(OUT); close(IN); my @t = stat($src); utime($t[8], $t[9], $dst); } ################################################################# # # reading updmap-cfg files and the actual map files # # the following hash saves *all* the information and is passed around # we do not fill everything from the very beginning to make sure that # we only read what is necessary (speed!) # # initialized by read_updmap_files # $data->{'order'} = [ list of updmap in decreasing priority ] # $data->{'updmap'}{$full_path_name_of_updmap}{'setting'}{$key} = $val # $data->{'updmap'}{$full_path_name_of_updmap}{'maps'}{$mapname}{'type'} # = 'Map'|'MixedMap'|'disabled' # $data->{'updmap'}{$full_path_name_of_updmap}{'maps'}{$mapname}{'status'} # = 'enabled'|'disabled' # $data->{'maps'}{$m}{'origin'} = $updmap_path_name # # initialized by read_map_files # $data->{'maps'}{$m}{'fonts'}{$font} = $rest # # initialized by merge_data # $data->{'merged'}{'setting'}{$key} = $val # $data->{'merged'}{'allMaps'}{'fonts'}{$fontdef} = $rest # $data->{'merged'}{'noMixedMaps'}{'fonts'}{$fontdef} = $rest # sub read_updmap_files { my (@l) = @_; my %data; for my $l (@l) { my $updmap = read_updmap_file($l); if (defined($updmap->{'setting'})) { for my $k (keys %{$updmap->{'setting'}}) { $data{'updmap'}{$l}{'setting'}{$k} = $updmap->{'setting'}{$k}; } } if (defined($updmap->{'maps'})) { for my $k (keys %{$updmap->{'maps'}}) { $data{'updmap'}{$l}{'maps'}{$k}{'type'} = $updmap->{'maps'}{$k}{'type'}; $data{'updmap'}{$l}{'maps'}{$k}{'status'} = $updmap->{'maps'}{$k}{'status'}; } } } $data{'order'} = \@l; # first round determine which maps should be used and which type, as # different updmap.cfg files might specify different types of maps # (MixedMap or Map). # Again, we have to do that in reverse order for my $l (reverse @l) { if (defined($data{'updmap'}{$l}{'maps'})) { for my $m (keys %{$data{'updmap'}{$l}{'maps'}}) { $data{'maps'}{$m}{'origin'} = $l; } } } return \%data; } sub read_updmap_file { my $fn = shift; if (!open(FN,"<$fn")) { log("Cannot read $fn: $!"); return; } my $i = 0; my %data; while () { $i++; chomp; next if /^\s*$/; next if /^\s*#$/; next if /^\s*#[^!]/; next if /^#![^ ]/; # allow for commands on the line itself s/([^#].*)#.*$/$1/; my ($a, $b, @rest) = split ' '; if ($a eq "#!") { if ($b eq "Map" || $b eq "MixedMap") { my $c = shift @rest; if (!defined($c)) { warning("updmap: apparently not a real disable line, ignored: $_\n"); } else { if (defined($data{'maps'}{$c})) { warning("updmap: double mentioning of $c in $fn\n"); } $data{'maps'}{$c}{'status'} = 'disabled'; $data{'maps'}{$c}{'type'} = $b; } } next; } if (@rest) { warning("updmap: line $i in $fn contains a syntax error, more than two words!\n"); } if (defined($settings{$a})) { my @possible_values = @{$settings{$a}}; if (scalar(grep($_ eq $b, @possible_values))) { $data{'setting'}{$a} = $b; } else { warning("updmap: unknown setting for $a: $b, ignored!\n"); } } elsif ($a eq "Map" || $a eq "MixedMap") { if (defined($data{'maps'}{$b}) && $data{'maps'}{$b}{'type'} ne $a) { warning("updmap: double mentioning of $b with conflicting types in $fn\n"); } else { $data{'maps'}{$b}{'type'} = $a; $data{'maps'}{$b}{'status'} = 'enabled'; } } else { warning("updmap: unrecognized line $i in $fn: $_\n"); } } close(FN) || warn("Cannot close $fn: $!"); return \%data; } sub read_map_files { my $data = shift; if (!defined($data->{'updmap'})) { return $data; } for my $f (keys %{$data->{'updmap'}}) { next if !defined($data->{'updmap'}{$f}{'maps'}); for my $m (keys %{$data->{'updmap'}{$f}{'maps'}}) { # we do not read a map file multiple times, if $data{'maps'}{$m} is # defined we expect that it was read and do skip it next if defined($data->{'maps'}{$m}{'fonts'}); my $ret = read_map_file($m); if (defined($ret)) { for my $font (keys %$ret) { $data->{'maps'}{$m}{'fonts'}{$font} = $ret->{$font}; } } } } return $data; } sub read_map_file { my $mn = shift; my $fn = `kpsewhich $mn`; chomp($fn); return if (!$fn); open(MF,"<$fn") || return; my %data; while () { chomp; next if /^\s*$/; next if /^\s*%/; my ($a, $b) = split(' ', $_, 2); $data{$a} = $b; } return \%data; } # # merging the various font definitions # sub merge_data { my $data = shift; my @l = @{$data->{'order'}}; # # for security clean out everything that was there %{$data->{'merged'}} = (); # # first read in the settings # we read it in *reverse* order and simple fill up the combined data # thus if there are multiple definitions/settings, the one coming from # the first in the original list will win! for my $l (reverse @l) { # merge settings if (defined($data->{'updmap'}{$l}{'setting'})) { for my $k (keys %{$data->{'updmap'}{$l}{'setting'}}) { $data->{'merged'}{'setting'}{$k} = $data->{'updmap'}{$l}{'setting'}{$k}; } } } # # now merge the data # for my $m (keys %{$data->{'maps'}}) { my $origin = $data->{'maps'}{$m}{'origin'}; next if ($data->{'updmap'}{$origin}{'maps'}{$m}{'status'} eq "disabled"); for my $f (keys %{$data->{'maps'}{$m}{'fonts'}}) { $data->{'merged'}{'allMaps'}{'fonts'}{$f} = $data->{'maps'}{$m}{'fonts'}{$f}; $data->{'merged'}{'noMixedMaps'}{'fonts'}{$f} = $data->{'maps'}{$m}{'fonts'}{$f} if ($data->{'updmap'}{$origin}{'maps'}{$m}{'type'} eq "Map"); } } return $data; } sub warning { print STDERR @_; } # # help, version etc etc # sub version { my $ret = sprintf "%s (TeX Live, multi) version %s\n", $prg, $version; return $ret; } sub help { my $usage = <<"EOF"; Usage: $prg [OPTION] ... [COMMAND] or: $prg-sys [OPTION] ... [COMMAND] Update the default font map files used by pdftex, dvips, and dvipdfm(x), as determined by the configuration file updmap.cfg (the one returned by running "kpsewhich updmap.cfg"). Among other things, these map files are used to determine which fonts should be used as bitmaps and which as outlines, and to determine which fonts are included in the output. By default, the TeX filename database (ls-R) is also updated. Options: --cnffile FILE read FILE for the updmap configuration --dvipsoutputdir DIR specify output directory (dvips syntax) --pdftexoutputdir DIR specify output directory (pdftex syntax) --outputdir DIR specify output directory (for all files) --copy cp generic files rather than using symlinks --force recreate files even if config hasn't changed --nomkmap do not recreate map files --nohash do not run texhash -n, --dry-run only show the configuration, no output --quiet, --silent reduce verbosity Commands: --help show this message and exit --version show version information and exit --showoptions ITEM show alternatives for options --setoption OPTION VALUE set option, where OPTION is one of: LW35, dvipsPreferOutline, dvipsDownloadBase35, or pdftexDownloadBase14 --setoption OPTION=VALUE as above, just different syntax --enable MAPTYPE MAPFILE add "MAPTYPE MAPFILE" to updmap.cfg, where MAPTYPE is either Map or MixedMap --enable Map=MAPFILE add \"Map MAPFILE\" to updmap.cfg --enable MixedMap=MAPFILE add \"MixedMap MAPFILE\" to updmap.cfg --disable MAPFILE disable MAPFILE, whether Map or MixedMap --listmaps list all active and inactive maps --listavailablemaps same as --listmaps, but without unavailable map files --syncwithtrees disable unavailable map files in updmap.cfg Explanation of the map types: the (only) difference between Map and MixedMap is that MixedMap entries are not added to psfonts_pk.map. The purpose is to help users with printers that render Type 1 outline fonts worse than mode-tuned Type 1 bitmap fonts. So, MixedMap is used for fonts that are available as both Type 1 and Metafont. Explanation of the --setoption possibilities: dvipsPreferOutline true|false (default true) Whether dvips uses bitmaps or outlines, when both are available. dvipsDownloadBase35 true|false (default false) Whether dvips includes the standard 35 PostScript fonts in its output. pdftexDownloadBase14 true|false (default true) Whether pdftex includes the standard 14 PDF fonts in its output. LW35 URWkb|URW|ADOBEkb|ADOBE (default URWkb) Adapt the font and file names of the standard 35 PostScript fonts. URWkb URW fonts with "berry" filenames (e.g. uhvbo8ac.pfb) URW URW fonts with "vendor" filenames (e.g. n019064l.pfb) ADOBEkb Adobe fonts with "berry" filenames (e.g. phvbo8an.pfb) ADOBE Adobe fonts with "vendor" filenames (e.g. hvnbo___.pfb) These options are only read and acted on by updmap; dvips, pdftex, etc., do not know anything about them. They work by changing the default map file which the programs read, so they can be overridden by specifying command-line options or configuration files to the programs, as explained at the beginning of updmap.cfg. Explanation of trees and files normally used: updmap both reads and writes TEXMFCONFIG/web2c/updmap.cfg, according to the actions specified. updmap writes the map files for dvips (psfonts.map) and pdftex (pdftex.map) to the TEXMFVAR/fonts/map/updmap/{dvips,pdftex}/ directories. The log file is written to TEXMFVAR/web2c/updmap.log. When updmap-sys is run, TEXMFSYSCONFIG and TEXMFSYSVAR are used instead. This is the only difference between updmap-sys and updmap. Other locations can be used if overridden on the command line, or these trees don't exist, or you are not using the original TeX Live. To see the precise locations of the various files that will be read and written, give the -n option (or read the man page). For step-by-step instructions on making new fonts known to TeX, read http://tug.org/fonts/fontinstall.html. For even more terse instructions, read the beginning of updmap.cfg. Report bugs to: tex-k\@tug.org TeX Live home page: EOF ; print &version(); print $usage; exit 0; } ### Local Variables: ### perl-indent-level: 2 ### tab-width: 2 ### indent-tabs-mode: nil ### End: # vim:set tabstop=2 expandtab: #