#!/usr/bin/perl -w =head1 NAME makeunidef.pl - Generates Unicode data files for B<ucs.sty>. =head1 SYSNOPSIS makeunidef.pl [B<--database>=I<file>] [B<--targetdir>=I<dir>] [B<--verbose>] [B<--help>] [B<--comments>] [B<--nocomments>] [B<--compress>] [B<--nocompress>] [B<--exclude>=I<option>] I<configfiles> =head1 DESCRIPTION Generate the F<uni-....def>-files for use by B<ucs.sty>. These are generated out of one or more config files (with suffix C<.gz> if gzipped), whose format is described below (see L<"CONFIG FILES">). For some characters there will be autogenerated code, if none is supplied by the config files, this code will be associated with the Unicode option C<autogenerated>. =head1 OPTIONS =over 4 =item B<--comments>, B<--nocomments> Enable or disable the automatic generation of comments. Defaults to B<--comments>. =item B<--compress>, B<--nocompress> Enable or disable the compression of uninames.dat. Defaults to B<--compress>. =item B<-d>, B<--db>, B<--database>=I<file> Specify the Unicode database, as provided by the Unicode Consortium. Defaults to F<UnicodeData.txt>. The file must have the suffix C<.gz> if it is gzipped. =item B<--dir> See B<--target>. =item B<--exclude>, B<--ex>=I<option> Do not include the characters associated with option I<option>. This is e.g. useful for saving space by not including all CJK characters. When an excluded option is used in documents, an error is yielded. =item B<-h>, B<--help> Shows help. =item B<--nocomments> See B<--comments>. =item B<--nocompress> See B<--compress>. =item B<-t>, B<--dir>, B<--target>, B<--targetdir>=I<dir> Sets the target directory for the generated files to I<dir>. The default is the current directory. =item B<-v>, B<--verbose> Be verbose. =back =head1 CONFIG FILES The config files (which may be gzipped) are to be written in a line oriented format. C<#> starts a comment which ends at the end of the actual line. The C<#> must be preceded by a whitespace, except for lines consisting only of a comment. An empty line or a line containing only a comment is ignored. A line can be a command or a character definition. For possible commands see L<"CONFIG COMMANDS">. A character definition has the following syntax: I<code> I<macro> where I<code> is the code position and I<macro> a LaTeX-macro to render the glyph. I<macro> is stripped of surrounding whitespaces, and I<code> has one of the following formats (all case insensitive): =over 4 =item U+I<num>, 0xI<num>, $I<num> I<num> being the hexadecimal representation of the code position. =item I<num> I<num> being the decimal representation of the code position, whereby I<num> must not start with 0. =item 0I<num> I<num> being the octal representation of the code position. =back In all the above formats, I<num> can have any number of digits. =head2 CONFIG COMMANDS A command line consists of an command name and optional space separated command arguments. The following commands are defined: =over 4 =item B<AUTOOPTION> Same as B<OPTION>, by the second argument specifies a LaTeX package. If this package is loaded, the option is set automatically. This option is deprecated. =item B<BEGIN>, B<{> This starts a group. All options set after this are only valid up to the matching B<END> or B<}>. Config files, which set options, should be enclosed in a block, so that they can't have side effects on other parts of the config file. Each config file is contained in an implicit block. Blocks can be nested. =item B<END>, B<}> See B<BEGIN>. =item B<ENVELOPE> All macros are wrapped inside the envelope given by this command. The original macro is inserted into the envelope instead of every occurrence of C<@@@> and the decimal character code is inserted for each occurrence of C<$$$>. B<ENVELOPE> will override B<ENVELOPE> commands executed before. =item B<GLOBAL> Inserts some code into F<uni-global.def>. This file is loaded with F<ucs.sty> in the preamble. F<uni-global.def> is not executed in a group, so defining globally is not necessary. See also B<PROVIDE>. =item B<OPTION> This command takes one argument. It is the name of the Unicode option to associate with the macros defined after this command. An option set by B<OPTION> is valid until it is overridden by another B<OPTION> command or until the block ends. =item B<PROVIDE> Associates a piece of code with characters defined afterwards (until the block ends). The code is inserted into every B<uni-....def>-file containing one of these characters, so the code is guaranteed to have been executed before the corresponding characters are executed. No assumption should be made whether the code is executed in a group or at top level, i.e. declarations should be made globally, but no garbage should be defined or catcodes modified without restoring them afterwards. The code may be executed several times. See also B<GLOBAL>. =back =head1 KNOWN BUGS AND PROBLEMS There should be a better way to quote whitespaces in command arguments and to insert C<#>s. Perhaps I will add this in future, thereby trying to preserve backward compatibility. =head1 REPORTING BUGS Send bug reports to Wolfgang Jeltsch <I<wolfgang@cs.ioc.ee>>, the mails containing the words B<bug report: makeunidef.pl> in the subject. =head1 AUTHOR Dominique Unruh <I<unruh@ut.ee>>. =head1 MAINTAINER Wolfgang Jeltsch <I<wolfgang@cs.ioc.ee>>. =head1 FILES =over 4 =item F<uni-I<N>.def>, F<uni-global.def>, F<uninames.dat> Unicode definitions for B<ucs.sty>, created by this program, I<N> stands for different decimal numbers. =item F<UnicodeData.txt> Unicode database by the Unicode Consortium, read only. This file can be found at F<http://www.unicode.org/Public/UNIDATA/UnicodeData.txt> =back =head1 SEE ALSO The LaTeX package B<ucs.sty>. =cut #'; # for emacs use IO::Handle; use IO::File; use Data::Dumper; use Getopt::Long; use Carp; use strict; # prototypes sub dumphuffman($$$); #{} use vars qw/$createcomments $targetdir @configfiles @characters %files @unidata $unidata $messagebreak $dump_unidata $verbose $generate_uninames $tables_to_dump $tabledir $autogen %knownoptions $loadunidata %stats_charsperoption %providehandlers $providehandlerseq %provides $huffman_decoder %file_attribs $compressnames %excludedoptions %uninames_abbrev @ranges $onlyfile $fileswritten $devnull /; $messagebreak = '\MessageBreak '; $autogen = 'autogenerated by makeunidef.pl'; %uninames_abbrev = ( # "\000" => '{Character available with following options:'. # '\MessageBreak\space\space\space}', "\001" => '{Unicode character \number\uc@got\uc@spc = U+\uc@temp@a:'. '\MessageBreak}', # "\002" => '{No name found}', "\003" => '{Character available with following excluded options:'. '\MessageBreak\space\space\space}', "\n" => '\MessageBreak', " " => '{ }', ); sub globalcode ($) { my $code = shift; return unless $dump_unidata; my $filename = "$targetdir/uni-global.def"; my $file = openfile($filename, "%%% unicode global data for ucs.sty, $autogen", "Unicode global data"); print $file "$code\n"; } sub getprovidehandler($) { my $code = shift; return undef unless defined $code; my $handler = $providehandlers{$code}; unless (defined $handler) { # print "NEW HANDLER: '$code'\n"; $handler = $providehandlerseq++; $providehandlers{$code} = $handler; } $provides{$handler} = { code => $code }; return $handler; } sub openreadfile($) { my $filename = shift; $filename .= ".gz" if (!-e $filename && -e "$filename.gz"); if ($filename =~ /\.gz/) { die "Could not read file $filename" unless -r $filename; return new IO::File("gzip -cdf ''\Q$filename\E |"); } else { return new IO::File($filename,"r"); } } sub optionname($) { my $o = shift; return 'default' unless define($o) ne ''; return $o; } sub loadconfig ($) { my ($filename) = @_; my $fh = openreadfile $filename or die "Could not open configfile $filename: $!"; my %options; my @optionstack; while (my $line = <$fh>) { chomp($line); my $linepos = "$filename:$."; $line =~ s/(^|\s)\#.*$//; $line =~ s/^\s*//; $line =~ s/\s*$//; next if $line eq ''; if ($line =~ /^[0-9]/ || $line =~ /^u\+/i) { my ($num,$command) = split ' ',$line,2; $num = oct($num) if $num =~ /^0/; $num = hex($num) if $num =~ s/^\$//; $num = hex($num) if $num =~ s/^u\+//i; #print "Line: $line, Num: $num, Command: $command\n"; my %o = %options; $o{definedat} = $linepos; $command = '' if lc($command) eq '<empty>'; unless (defined ($command)) { warn "$linepos: Use <empty> to declare an empty ". "glyph macro.\n"; next; }; $o{combining} = 1 if (($command =~ /(^|[^\#])\#1/) || (defined $o{envelope} && $o{envelope} =~ /(^|[^\#])\#1/)); if (isprivate($num) && optionname($o{onoption}) !~ /^(private|local)./) { warn sprintf "%s: Character U+%04X is in private area, ". "but has option '%s' (not 'private...' or 'local...').\n", $linepos,$num,optionname($o{onoption}); } push @{$characters[$num]}, [ $command, \%o ]; my $range = findrange2($num); if (defined $range) { ${$$range{options}}{optionname($o{onoption})} =1}; if ($unidata[$num]) { $ {$ {$unidata[$num]}{options}}{optionname($o{onoption})} =1}; $stats_charsperoption{define($o{onoption})}++; } else { my @cmd = split ' ',$line; if ($cmd[0] eq 'BEGIN' || $cmd[0] eq '{') { my %t = %options; push @optionstack, \%t; } elsif ($cmd[0] eq 'END' || $cmd[0] eq '}') { if (@optionstack) { my $t = pop @optionstack; %options = %$t } else { warn "$linepos: '$cmd[0]' outside a block.\n"; } } elsif ($cmd[0] eq 'OPTION') { $options{onoption} = $cmd[1]; unless (defined $knownoptions{$cmd[1]}) { $knownoptions{$cmd[1]} = []; } } elsif ($cmd[0] eq 'AUTOOPTION') { $options{onoption} = $cmd[1]; unless (defined $knownoptions{$cmd[1]}) { $knownoptions{$cmd[1]} = [ $cmd[2] ]; } else { # ************ DOUBLED push @{$knownoptions{$cmd[1]}}, $cmd[2]; } } elsif ($cmd[0] eq 'ENVELOPE') { $options{envelope} = join ' ',@cmd[1..$#cmd]; # } elsif ($cmd[0] eq 'GLOBAL') { # globalcode(join ' ',@cmd[1..$#cmd]); } elsif (($cmd[0] eq 'PROVIDE') || ($cmd[0] eq 'GLOBAL')) { my $prov = ''; if ($cmd[1] eq 'MULTILINE') { my ($e,$l) = (0); my $xlinepos = $linepos; while (!$e && defined($l = <$fh>)) { if ($l =~ /^\s*END\s*$/) { $e = 1 } else { $prov .= $l; } } unless ($e) { warn "$xlinepos: Unfinished PROVIDE MULTILINE.\n"; $prov = undef; } } else { $prov = join ' ',@cmd[1..$#cmd]; } chomp $prov; $prov =~ s/%$//s; if ($cmd[0] eq 'PROVIDE') { $options{provide} = getprovidehandler($prov); } else { globalcode($prov); } } elsif ($cmd[0] eq 'FONTENC') { $options{fontenc} = $cmd[1]; } elsif ($cmd[0] eq 'FONTFAMILY') { $options{fontfamily} = $cmd[1]; } elsif ($cmd[0] eq 'RIGHTLEFT') { $options{rightleft} = 1; } elsif ($cmd[0] eq 'TABLECODE') { $options{tablecode} = join ' ',@cmd[1..$#cmd]; } elsif ($cmd[0] eq 'CTRLGLYPH') { $options{ctrlglyph} = 1; } elsif ($cmd[0] eq 'COMBINECHAR') { if ($cmd[1] =~ /^U\+[0-9A-F]+$/i) { my ($n) = ($cmd[1] =~ /^U\+([0-9A-F]+)$/i); $options{combinechar} = hex($n); delete $options{combineglyph}; delete $options{combineoption}; } elsif ($cmd[1] =~ /^[a-z]+\/U\+[0-9A-F]+$/i) { my ($o,$n) = ($cmd[1] =~ /^([a-z]+)\/U\+([0-9A-F]+)$/i); $options{combinechar} = hex($n); $options{combineoption} = $o; delete $options{combineglyph}; } else { $options{combineglyph} = $cmd[1]; delete $options{combinechar}; delete $options{combineoption}; } } elsif ($cmd[0] eq 'LOADFONTENC') { $options{loadfontenc} = $cmd[1]; } elsif ($cmd[0] eq 'PACKAGE') { $options{package} = $cmd[1]; } elsif ($cmd[0] eq 'TABLEGLYPH') { $options{tableglyph} = join ' ',@cmd[1..$#cmd]; } elsif ($cmd[0] eq 'TABLEENVELOPE') { $options{tableenvelope} = join ' ',@cmd[1..$#cmd]; } else { warn "$linepos: Malformed line $line"; } } }; close $fh or die "Something went wrong when closing $filename: $!"; }; sub openfile ($$$%) { my ($filename,$header,$description,%attribs) = @_; my $file = $files{$filename}; unless (defined $file) { my $nowrite = 0; $nowrite = 1 if defined($onlyfile) && $filename !~ m@(^|/)$onlyfile$@; $fileswritten ++ unless $nowrite; $attribs{nowrite} = 1 if $nowrite; if (!$nowrite) { $file = new IO::File($filename,O_CREAT|O_WRONLY|O_EXCL) or die "Could not open $filename for writing: $!"; } else { print "Not writing file $filename\n" if $verbose; $devnull = new IO::File('/dev/null',O_WRONLY) unless ($devnull); $file = $devnull; } print $file "$header\n"; my @date = localtime; my $date = sprintf "%04d/%02d/%02d", $date[5]+1900, $date[4]+1, $date[3]; my $basename = $filename; $basename =~ s@.*/@@; print $file "\\ifx\\ProvidesFile\\undefined\\else \\ProvidesFile{$basename}[$date UCS: $description]% \\fi "; flush $file; $files{$filename} = $file; $file_attribs{$filename} = \%attribs; } return $file; } sub dumpcharacters() { for (my $i=0; $i<=$#characters; $i++) { next unless defined $characters[$i]; my %seen = (); for my $j (@{$characters[$i]}) { my $val = $$j[0]; my $options = $$j[1]; my $envelope = $$options{envelope}; if (define($envelope) ne '') { my $t = $val; $val = $envelope; $val =~ s/\$\$\$/$i/g; $val =~ s/\@\@\@/$t/g; } $val = "\\uc\@cmb$val" if ($$options{combining}); my $comment = define($$options{comment}); my $onoption = $$options{onoption}; next if $excludedoptions{(defined $onoption)?$onoption:'default'}; my $page = int($i/256); my $filename = "$targetdir/uni-$page.def"; #print "Character $i ($filename):\n"; my $range = sprintf("U+%04X..U+%04X", $page*256,$page*256+255); my $provide = $$options{provide}; my $header = "%%% Unicode to TeX mapping, file uni-$page.def, ". "$range, autogenerated by makeunidef.pl"; my $file = openfile($filename,$header,"Unicode data $range"); if (defined $provide) { $provide = $provides{$provide}; unless (defined $$provide{"done:$filename"}) { $$provide{"done:$filename"} = 1; print $file "$$provide{code}%\n"; #print "Providing $$provide{code} to $filename\n"; } } my $texcmd = ''; if (defined $val) { $texcmd = "\\uc\@dclc{$i}{". (defined $onoption?$onoption:'default'). "}{$val}"; } $texcmd .= "% $comment"; $texcmd .= " (missing)" unless defined $val; $texcmd =~ s/\s+$//; print $file "$texcmd\n"; my $tag = define($onoption); if (defined $seen{$tag}) { warn sprintf "%s: Character U+%04X(%s) redefined. ". "(First definition in %s).\n", $$options{definedat},$i,optionname($onoption),$seen{$tag}; } else { $seen{$tag} = define($$options{definedat}); } #print "$texcmd\n"; } } flushfiles(); } sub loadunidata($) { my ($filename) = @_; my $f = openreadfile $unidata or die "Could not open $unidata for reading: $!"; my $inrange = undef; my $rangestart; while (my $line = <$f>) { chomp($line); my @line = split ';', $line; my %line = (); $line{num} = hex($line[0]); $line{name} = $line[1]; $line{category} = $line[2]; $line{combining} = $line[3]; $line{flags} = $line[4]; my @compose = split ' ',$line[5]; my $compflag = undef; if (defined $compose[0] && $compose[0] =~ /^</) { $compflag = shift @compose; $compflag =~ s/^<//; $compflag =~ s/>$//; } $line{compose} = \@compose; $line{compflag} = $compflag; $line{decdigit} = $line[6]; $line{digit} = $line[7]; $line{numerical} = $line[8]; $line{bracket} = $line[9]; $line{alias} = $line[10]; $line{comment} = $line[11]; $line{upcase} = $line[12]; $line{downcase} = $line[13]; $line{titlecase} = $line[14]; $line{case3} = $line[15]; my ($rangename,$rangedir) = ($line{name} =~ /^\<(.*), (First|Last)\>$/); if (defined $rangename) { if ($rangedir eq 'First') { if (defined $inrange) { warn "$filename:$.: Range '$rangename' started while ". "in range '$inrange'.\n"; } #warn "Starting: '$rangename'"; $inrange = $rangename; $rangestart = $line{num}; next; } elsif ($rangedir eq 'Last') { if (defined $inrange && $rangename eq $inrange) { #warn "Ending: '$inrange'"; $line{rangestart} = $rangestart; $line{rangeend} = $line{num}; # $line{num} = $line{rangestart}; $line{name} = "Contained in range '$rangename'"; #printf "Range found: U+%04X..U+%04X '%s'\n", #$rangestart, $line{num}, $rangename if $verbose; } elsif (defined $inrange) { warn "$filename:$.: ". "Range '$inrange' ended by '$rangename'"; } $inrange = undef; } else { die "Internal error"; } } else { if (defined $inrange) { warn "$filename:$.: ". "Range '$inrange' not ended on next line.\n"; $inrange = undef; } } $unidata[$line{num}] = \%line; push @ranges, \%line if $line{rangestart}; } close $f or die "Something went wrong when closing $filename: $!"; } sub generate_globals() { while (my ($option,$pkgs) = each %knownoptions) { if (!$excludedoptions{$option}) { if (@$pkgs == 1) { my $pkg = $$pkgs[0]; globalcode("\\DeclareUnicodeOption[$pkg]{$option}\%"); } else { globalcode("\\DeclareUnicodeOption{$option}\%"); for my $pkg (@$pkgs) { globalcode("\\LinkUnicodeOptionToPkg{$option}{$pkg}\%"); } } } else { globalcode ("\\XDeclareUnicodeOption{}{$option}{}{". "\\PackageError{ucs}{Option $option has not been generated}". "{run makeunidef.pl without --exclude $option.}}{}"); } } flushfiles(); } sub define($) { my $str = shift; return $str if defined $str; return ''; } sub define0($) { my $str = shift; return $str if defined $str; return 0; } sub findrange($) { my $i = shift; for my $r (@ranges) { if ($i>=$$r{rangestart} && $i<=$$r{rangeend}) { return $$r{name}; } } return undef; } sub findrange2($) { my $i = shift; for my $r (@ranges) { if ($i>=$$r{rangestart} && $i<=$$r{rangeend}) { return $r; } } return undef; } sub generate() { for (my $i=0; $i<=$#unidata; $i++) { my $char = $unidata[$i]; my $tex = undef; next unless defined $char; next unless %$char; next if (defined $characters[$i]); if (@{$$char{compose}}) { my $success = 1; for my $comp (@{$$char{compose}}) { $success = 0 unless defined $characters[hex($comp)]; } if ($success) { my $compflag = $$char{compflag}; my $compmode = 'none'; if (define($compflag) eq '') { $compmode = 'normal' } elsif ($compflag eq 'fraction') { $compmode = 'normal' } elsif ($compflag eq 'compat') { $compmode = 'normal' } elsif ($compflag eq 'noBreak') { $compmode = 'normal' } elsif ($compflag eq 'super') { $compmode = 'normal.super' } elsif ($compflag eq 'sub') { $compmode = 'normal.sub' } elsif ($compflag eq 'font') { $compmode = 'normal' } elsif ($compflag eq 'circle') { $compmode = 'normal.circle' } elsif ($compflag eq 'square') { $compmode = 'normal.square' } elsif ($compflag eq 'wide') { $compmode = 'normal.wide' } elsif ($compflag eq 'narrow') { $compmode = 'normal.narrow' } elsif ($compflag eq 'vertical') { $compmode = 'normal.vertical' } elsif ($compflag eq 'small') { $compmode = 'normal.small' } else { warn "Unknown composition flag \<$compflag\> ". "in character ".sprintf("%04X",$$char{num}); }; if ($compmode =~ /^normal/) { $tex = join '', map { my $a = hex($_); "\\unichar{$a}" } @{$$char{compose}}; if ($compmode eq 'normal.super') { $tex = "\\unicodesuper{$tex}"; } elsif ($compmode eq 'normal.sub') { $tex = "\\unicodesub{$tex}"; } elsif ($compmode eq 'normal.square') { $tex = "\\unicodesquare{$tex}"; } elsif ($compmode eq 'normal.circle') { $tex = "\\unicodecircle{$tex}"; } elsif ($compmode eq 'normal.wide') { $tex = "\\unicodewide{$tex}"; } elsif ($compmode eq 'normal.narrow') { $tex = "\\unicodenarrow{$tex}"; } elsif ($compmode eq 'normal.vertical') { $tex = "\\unicodevertical{$tex}"; } elsif ($compmode eq 'normal.small') { $tex = "\\unicodesmall{$tex}"; } } elsif ($compmode eq 'none') { } else { die "Unknown composition mode $compmode. INTERNAL ERROR"; } } if (!$success && $$char{numerical} ne '') { #print "NUMERICAL $i\n"; $tex = $$char{numerical}; } #print "C: $$char{compflag}\n" if defined $$char{compflag}; if ($tex) { push @{$characters[$i]}, [ "$tex", #"\\dirtyunicode{$i}{$tex}" { dirty => 1, onoption => 'autogenerated' } ]; unless (defined $knownoptions{autogenerated}) { $knownoptions{autogenerated} = []; } } } } } sub initcharacters() { # for (my $i=0; $i<128; $i++) { # push @{$characters[$i]}, [ "\\char$i\\relax", { comment => 'ASCII' } ]; # } } sub makecomments() { for (my $i=0; $i<$#unidata; $i++) { next unless defined $unidata[$i]; push @{$characters[$i]}, [] unless defined $characters[$i]; for my $char (@{$characters[$i]}) { my $options = $$char[1]; next if defined $$options{comment}; my $onoption = $$options{onoption}; #print "CHAR: $char\n"; #print "CHAR2: $$char[1]\n"; my $utf8 = utf16toutf8($i); my $comment = ''; $comment .= "OPTION: $onoption, " if ($onoption); $comment .= sprintf "0x%04X = %d (%s) - %s", $i ,$i, $utf8, $ {$unidata[$i]}{name}; $$options{comment} = $comment; $$char[1] = $options; } } } sub utf16toutf8($) { my $char = shift; if ($char<0x80) { return $char; } if ($char<0x0800) { return chr(0xc0+int($char/64)).chr(0x80+($char%64)); } return chr(0xe0+int($char/0x1000)). chr(0x80+int(($char%0x1000)/64)). chr(0x80+($char%64)); } sub flushfiles() { for my $file (values %files) { flush $file; } } sub closefiles() { for my $filename (keys %files) { my $file = $files{$filename}; my $attrib = $file_attribs{$filename}; next if $$attrib{nowrite}; my $cs = 'utf-8'; $cs = $$attrib{coding} if defined $$attrib{coding}; print $file <<EOT; %%% Local Variables: %%% mode: latex %%% coding: $cs %%% End: EOT close $file or die "Something went wrong when closing $filename: $!"; } if (define0($fileswritten)==0) { warn "No files written"; } } sub showhelp() { print <<EOT; Generates uni-....def files for ucs.sty Options: --database <file> Set unicode database (default: UnicodeData.txt; short -d) --nocomments Disable comments in generated files --targetdir <dir> Set target directory (default: .; short: -t) --verbose Be verbose (short: -v) --help This page (short: -h) <file(s)> Use this configfile(s) Configfiles consists of lines, each like this <unicode number> <LaTeX-Code> where <unicode number> may be prefixed by 0x or 0 to denote hex or oct. Lines beginning with # denote a comment. EOT } sub optioneq($$) { my ($a,$b) = @_; $a = 'default' if define($a) eq ''; $b = 'default' if define($b) eq ''; return $a eq $b; } sub getchar($$) { my ($c,$option) = @_; #print "getchar($c,$option)\n"; return undef unless $characters[$c]; for my $char (@{$characters[$c]}) { my $options = $$char[1]; return $char if define($$options{onoption}) eq $option; return $char if define($$options{onoption}) eq '' && $option eq 'default'; } return undef; } sub expand_tablespec($); sub expand_tablespec($) { my $tablesize = 256; my $spec = shift; my @specs = (); $spec = '*:*' if $spec eq '*'; my @spec = ($spec =~ /^([a-z0-9]+|\*):(.*)$/i); my $option = $spec[0]; my $range = $spec[1]; if (define($option) eq '') { warn "Invalid table specifier '$spec'. Ignoring"; return (); } if ($option eq '*') { push @specs, expand_tablespec("default:$range"); for my $o (sort keys %knownoptions) { push @specs, expand_tablespec("$o:$range") unless $o eq 'default'; } } elsif ($range eq '*') { for (my $i=0; $i<=$#characters; $i+=$tablesize) { for (my $j=0; $j<$tablesize; $j++) { if (my $c = getchar($i+$j,$option)) { my $options = $$c[1]; #next if $$options{dirty}; push @specs, expand_tablespec (sprintf "%s:%04X-%04X", $option,$i,$i+$tablesize-1); last; } } } } else { warn "Unknown option '$option' in table spec '$spec'. Ignoring" unless $option eq 'default' || defined $knownoptions{$option}; my @range = map { hex } split '-', $range, 2; push @specs, { option => $option, start => $range[0], end => $range[1], format => 'table', }; } return @specs; } sub dump_charactertables() { my @tables = map { expand_tablespec($_) } map { s/\s+$//; s/^\s+//; $_ } split /,/, $tables_to_dump; for my $spec (@tables) { dump_charactertable($spec); } flushfiles(); } sub default_entrydata($) { my $i = shift; return ( 'code' => $i, 'hexcode' => sprintf("%04X", $i), 'hexcodebeforelast' => sprintf("%1X", int($i%256/16)), ); } sub make_entrydata($$$$); sub make_entrydata($$$$) { my ($option,$i,$options,$hasctrlglyph) = @_; my %entrydata = default_entrydata($i); if (defined $$options{tableglyph}) { warn sprintf "%s: Character %s/U+%04X has ". "TABLEGLYPH specification.\n", $$options{definedat},$option,$i; } if ($$options{ctrlglyph}) { if ($hasctrlglyph) { %entrydata = %{make_entrydata('ctrlglyphs',$i,$hasctrlglyph,0)}; $entrydata{ctrlglyph} = 1; $entrydata{option} = 'ctrlglyphs'; return \%entrydata; } else { warn sprintf "%s: Char U+%04X has no control ". "glyph.\n",$$options{definedat},$i; $entrydata{tableglyph} = sprintf('{\tiny %04X}',$i); } } $entrydata{dirty} = ($$options{dirty})?1:0; $entrydata{rightleft} = ($$options{rightleft})?1:0; $entrydata{combining} = ($$options{combining})?1:0; $entrydata{fontfamily} = $$options{fontfamily} if defined $$options{fontfamily}; $entrydata{combinechar} = $$options{combinechar} if defined $$options{combinechar}; $entrydata{combineglyph} = $$options{combineglyph} if defined $$options{combineglyph}; $entrydata{combineoption} = $$options{combineoption} if defined $$options{combineoption}; $entrydata{fontenc} = $$options{fontenc} if defined $$options{fontenc}; if (defined $$options{tableglyph}) { warn sprintf "Add to ctrlglyphs.ucf:\nU+%04X\t%s\n", $i,$$options{tableglyph} unless $hasctrlglyph; $entrydata{tableglyph} = $$options{tableglyph}; } if (defined $$options{tableenvelope}) { warn sprintf "%s: Character %s/u+%04x has ". "TABLEENVELOPE specification.\n", $$options{definedat},$option,$i; $entrydata{tableenvelope} = $$options{tableenvelope}; $entrydata{tableenvelope} =~ s/\@\@\@/\#1/g; $entrydata{"tableenvelope.optspec"} = '#1'; } # $entrydata{fontenc} = 'T1' # unless defined $entrydata{fontenc}; for my $x (qw/loadfontenc package tablecode/) { $entrydata{"$x.option"} = $$options{$x}; } return \%entrydata; } sub dump_charactertable($$$$) { my $spec = shift; my $option = $$spec{option}; my $start = $$spec{start}; my $end = $$spec{end}; my $format = $$spec{format}; my $hstart = sprintf "%04X", $start; my $hend = sprintf "%04X", $end; my $filename = "$tabledir/table-$option-$hstart-$hend.tex"; print "Generating $filename\n" if $verbose; my $file = openfile("$tabledir/table-$option-$hstart-$hend.tex", "%%% table for option $option, U+$hstart..U+$hend, $autogen", "Unicode chart U+$hstart..U+$hend"); print $file <<EOT; \\input{tables.inc} \\def\\gformat{$format}% \\def\\goption{$option}% \\def\\ghstart{$hstart}% \\def\\ghend{$hend}% \\def\\gstart{$start}% \\def\\gend{$end}% EOT my $table = "\\tstart%\n"; my %fontencs = (); my %packages = (); my %tablecode = (); for (my $i=$start; $i<=$end; $i++) { my @chars = (); @chars = @{$characters[$i]} if defined @{$characters[$i]}; my $entry = ($unidata[$i])?"\\noglyph":"\\unassigned"; my $found = 0; my $hasctrlglyph = undef; for my $char (@chars) { my $options = $$char[1]; if (define($$char[0]) ne '' || define($$options{envelope}) ne '') { if (optioneq($$options{onoption},'ctrlglyphs')) { $hasctrlglyph = $options; } } } my $entrydata = {default_entrydata($i)}; for my $char (@chars) { my $options = $$char[1]; if (define($$char[0]) ne '' || define($$options{envelope}) ne '') { if (optioneq($$options{onoption},$option)) { $entry = '\glyph'; $entrydata = make_entrydata($option,$i,$options, $hasctrlglyph); $fontencs{$$entrydata{fontenc}} = 1 if defined $$entrydata{fontenc} && $$entrydata{fontenc} ne 'T1'; $fontencs{$$entrydata{'loadfontenc.option'}} = 1 if defined $$entrydata{'loadfontenc.option'}; if (defined $$entrydata{"package.option"}) { for my $p (split ';', $$entrydata{'package.option'}) { $packages{$p} = 1; }} $tablecode{$$entrydata{"tablecode.option"}} = 1 if defined $$entrydata{"tablecode.option"}; $found = 1; } elsif (!$found) { if (!$$options{dirty} || $entry eq '') { $entry = '\withoption'; } } } } for my $i (keys %$entrydata) { next if $i =~ /\./; my $os = define($$entrydata{"$i.optspec"}); $table .= "\\gdef\\a$i$os\{$$entrydata{$i}\}%\n"; } my $comment = sprintf "%04X %s %s", $i, utf16toutf8($i), define($unidata[$i]->{name}); $table .= "$entry\% $comment\n"; } $table .= "\\tend%\n"; print $file "\\def\\gfontencs{",join(',',keys %fontencs,'T1'),"}%\n"; for my $p (keys %packages) { $p = "{$p}" unless $p =~ /\}$/; print $file "\\usepackage$p%\n"; } for my $c (keys %tablecode) { print $file "$c%\n"; } print $file $table; flush $file; } sub parseargs() { #Getopt::Long::Configure(qw/bundling/); my %opt = (); unless (GetOptions(\%opt,qw/config|configfile|c=s@ exclude|ex=s@ database|db|d=s comments! names! data! loadunidata! tables=s tabledir=s targetdir|dir|t|target=s help|h verbose|v compress! onlyfile=s /)) { die "Bad command line options, try --help"; } if ($opt{help}) { showhelp(); exit; } unless ($opt{forceasterisk}) { @ARGV = grep { if (/\*/ && !-e $_) { print "Configfile $_ seems to be unmatched wildcard. Ignoring.\n" if $opt{verbose}; 0; } else { 1; } } @ARGV; } push @{$opt{config}}, @ARGV if @ARGV; die "You must supply at least one config file" unless defined $opt{config}; $opt{exclude} = [] unless defined $opt{exclude}; @{$opt{exclude}} = split /,/,join ',',@{$opt{exclude}}; $opt{database} = 'UnicodeData.txt' unless defined $opt{database}; $opt{comments} = 1 unless defined $opt{comments}; $opt{names} = 1 unless defined $opt{names}; $opt{data} = 1 unless defined $opt{data}; $opt{targetdir} = '.' unless defined $opt{targetdir}; $opt{loadunidata} = 1 unless defined $opt{loadunidata}; $opt{tables} = '*' if $opt{tabledir} && !defined $opt{tables}; $opt{tabledir} = '.' unless defined $opt{tabledir}; $opt{compress} = 1 unless defined $opt{compress}; @configfiles = @{$opt{config}}; $unidata = $opt{database}; $createcomments = $opt{comments}; $targetdir = $opt{targetdir}; $verbose = $opt{verbose}; $generate_uninames = $opt{names}; $tabledir = $opt{tabledir}; $tables_to_dump = $opt{tables}; $dump_unidata = $opt{data}; $loadunidata = $opt{loadunidata}; $compressnames = $opt{compress}; $onlyfile = $opt{onlyfile}; %excludedoptions = map { $_ => 1 } @{$opt{exclude}}; } sub untaint($) { my $str = shift; my ($unt) = ($str =~ /^(.*)$/); return $unt; } sub unlinkfile($) { my $fullname = shift; return 1 if defined($onlyfile) && $fullname !~ m@(^|/)$onlyfile$@; return 1 unless -e $fullname; my $f = new IO::File($fullname,O_RDONLY) or die "Could not open $fullname for reading: $!"; my $line = <$f>; close $f; unless ($line =~ /^\%\%\%.*autogenerated by makeunidef.pl/) { die "File $fullname was not generated by me, ". "will not overwrite it"; } unlink untaint($fullname) or die "Could not remove $fullname: $!"; #print "FILE: $file\n"; } sub unlinkfiles($$) { my ($pattern,$dir) = @_; my $d = new IO::Handle; opendir $d, $dir or die "Could not open directory $targetdir: $!"; while (my $file = readdir $d) { #print "F: $file\n"; next unless $file =~ /$pattern/x; #print "UF: $file\n"; unlinkfile("$dir/$file") or die "Could not unlink $dir/$file"; } } $huffman_decoder = '\count255=128 \loop\ifnum\count255<256\relax \catcode\count255=13 \advance\count255by1\relax \repeat \catcode`\G=13 \catcode`\H=13 \catcode`\I=13 \def\uncompress{% \def^^80{\0\0\0\0\0\0\0}% \def^^81{\0\0\0\0\0\0\1}% \def^^82{\0\0\0\0\0\1\0}% \def^^83{\0\0\0\0\0\1\1}% \def^^84{\0\0\0\0\1\0\0}% \def^^85{\0\0\0\0\1\0\1}% \def^^86{\0\0\0\0\1\1\0}% \def^^87{\0\0\0\0\1\1\1}% \def^^88{\0\0\0\1\0\0\0}% \def^^89{\0\0\0\1\0\0\1}% \def^^8a{\0\0\0\1\0\1\0}% \def^^8b{\0\0\0\1\0\1\1}% \def^^8c{\0\0\0\1\1\0\0}% \def^^8d{\0\0\0\1\1\0\1}% \def^^8e{\0\0\0\1\1\1\0}% \def^^8f{\0\0\0\1\1\1\1}% \def^^90{\0\0\1\0\0\0\0}% \def^^91{\0\0\1\0\0\0\1}% \def^^92{\0\0\1\0\0\1\0}% \def^^93{\0\0\1\0\0\1\1}% \def^^94{\0\0\1\0\1\0\0}% \def^^95{\0\0\1\0\1\0\1}% \def^^96{\0\0\1\0\1\1\0}% \def^^97{\0\0\1\0\1\1\1}% \def^^98{\0\0\1\1\0\0\0}% \def^^99{\0\0\1\1\0\0\1}% \def^^9a{\0\0\1\1\0\1\0}% \def^^9b{\0\0\1\1\0\1\1}% \def^^9c{\0\0\1\1\1\0\0}% \def^^9d{\0\0\1\1\1\0\1}% \def^^9e{\0\0\1\1\1\1\0}% \def^^9f{\0\0\1\1\1\1\1}% \def^^a0{\0\1\0\0\0\0\0}% \def^^a1{\0\1\0\0\0\0\1}% \def^^a2{\0\1\0\0\0\1\0}% \def^^a3{\0\1\0\0\0\1\1}% \def^^a4{\0\1\0\0\1\0\0}% \def^^a5{\0\1\0\0\1\0\1}% \def^^a6{\0\1\0\0\1\1\0}% \def^^a7{\0\1\0\0\1\1\1}% \def^^a8{\0\1\0\1\0\0\0}% \def^^a9{\0\1\0\1\0\0\1}% \def^^aa{\0\1\0\1\0\1\0}% \def^^ab{\0\1\0\1\0\1\1}% \def^^ac{\0\1\0\1\1\0\0}% \def^^ad{\0\1\0\1\1\0\1}% \def^^ae{\0\1\0\1\1\1\0}% \def^^af{\0\1\0\1\1\1\1}% \def^^b0{\0\1\1\0\0\0\0}% \def^^b1{\0\1\1\0\0\0\1}% \def^^b2{\0\1\1\0\0\1\0}% \def^^b3{\0\1\1\0\0\1\1}% \def^^b4{\0\1\1\0\1\0\0}% \def^^b5{\0\1\1\0\1\0\1}% \def^^b6{\0\1\1\0\1\1\0}% \def^^b7{\0\1\1\0\1\1\1}% \def^^b8{\0\1\1\1\0\0\0}% \def^^b9{\0\1\1\1\0\0\1}% \def^^ba{\0\1\1\1\0\1\0}% \def^^bb{\0\1\1\1\0\1\1}% \def^^bc{\0\1\1\1\1\0\0}% \def^^bd{\0\1\1\1\1\0\1}% \def^^be{\0\1\1\1\1\1\0}% \def^^bf{\0\1\1\1\1\1\1}% \def^^c0{\1\0\0\0\0\0\0}% \def^^c1{\1\0\0\0\0\0\1}% \def^^c2{\1\0\0\0\0\1\0}% \def^^c3{\1\0\0\0\0\1\1}% \def^^c4{\1\0\0\0\1\0\0}% \def^^c5{\1\0\0\0\1\0\1}% \def^^c6{\1\0\0\0\1\1\0}% \def^^c7{\1\0\0\0\1\1\1}% \def^^c8{\1\0\0\1\0\0\0}% \def^^c9{\1\0\0\1\0\0\1}% \def^^ca{\1\0\0\1\0\1\0}% \def^^cb{\1\0\0\1\0\1\1}% \def^^cc{\1\0\0\1\1\0\0}% \def^^cd{\1\0\0\1\1\0\1}% \def^^ce{\1\0\0\1\1\1\0}% \def^^cf{\1\0\0\1\1\1\1}% \def^^d0{\1\0\1\0\0\0\0}% \def^^d1{\1\0\1\0\0\0\1}% \def^^d2{\1\0\1\0\0\1\0}% \def^^d3{\1\0\1\0\0\1\1}% \def^^d4{\1\0\1\0\1\0\0}% \def^^d5{\1\0\1\0\1\0\1}% \def^^d6{\1\0\1\0\1\1\0}% \def^^d7{\1\0\1\0\1\1\1}% \def^^d8{\1\0\1\1\0\0\0}% \def^^d9{\1\0\1\1\0\0\1}% \def^^da{\1\0\1\1\0\1\0}% \def^^db{\1\0\1\1\0\1\1}% \def^^dc{\1\0\1\1\1\0\0}% \def^^dd{\1\0\1\1\1\0\1}% \def^^de{\1\0\1\1\1\1\0}% \def^^df{\1\0\1\1\1\1\1}% \def^^e0{\1\1\0\0\0\0\0}% \def^^e1{\1\1\0\0\0\0\1}% \def^^e2{\1\1\0\0\0\1\0}% \def^^e3{\1\1\0\0\0\1\1}% \def^^e4{\1\1\0\0\1\0\0}% \def^^e5{\1\1\0\0\1\0\1}% \def^^e6{\1\1\0\0\1\1\0}% \def^^e7{\1\1\0\0\1\1\1}% \def^^e8{\1\1\0\1\0\0\0}% \def^^e9{\1\1\0\1\0\0\1}% \def^^ea{\1\1\0\1\0\1\0}% \def^^eb{\1\1\0\1\0\1\1}% \def^^ec{\1\1\0\1\1\0\0}% \def^^ed{\1\1\0\1\1\0\1}% \def^^ee{\1\1\0\1\1\1\0}% \def^^ef{\1\1\0\1\1\1\1}% \def^^f0{\1\1\1\0\0\0\0}% \def^^f1{\1\1\1\0\0\0\1}% \def^^f2{\1\1\1\0\0\1\0}% \def^^f3{\1\1\1\0\0\1\1}% \def^^f4{\1\1\1\0\1\0\0}% \def^^f5{\1\1\1\0\1\0\1}% \def^^f6{\1\1\1\0\1\1\0}% \def^^f7{\1\1\1\0\1\1\1}% \def^^f8{\1\1\1\1\0\0\0}% \def^^f9{\1\1\1\1\0\0\1}% \def^^fa{\1\1\1\1\0\1\0}% \def^^fb{\1\1\1\1\0\1\1}% \def^^fc{\1\1\1\1\1\0\0}% \def^^fd{\1\1\1\1\1\0\1}% \def^^fe{\1\1\1\1\1\1\0}% \def^^ff{\1\1\1\1\1\1\1}% \readline}% \@tempcnta=0 \def\readline#1 {\toks255{}\toks254{}\relax #1% \let\uc@temp@c\uc@temp@a \unicode@numtohex\uc@temp@a\uc@got4% \edef\uc@temp@b{\the\toks254}% \edef\uc@temp@b{\uc@temp@b}% \global\let\uc@temp@a\uc@temp@c \expandafter\info\expandafter{\uc@temp@b}% \endinput}% \def\skipcodes#1G{\advance\@tempcnta by"#1\relax\checkline}% \def\skipline#1 {}% \def\add#1{\toks254\expandafter{\the\toks254 #1}}% \def\checkline{\advance\@tempcnta by1\relax \ifnum\@tempcnta=\uc@got \expandafter\uncompress\else\expandafter\skipline\fi}% \def\checkrange#1I#2G{% \advance\@tempcnta by"#1\relax \@tempcntb\@tempcnta\advance\@tempcntb by-"#2\relax \ifnum\@tempcntb>\uc@got\let\uc@temp@c\skipline\else \ifnum\@tempcnta<\uc@got\let\uc@temp@c\skipline\else \let\uc@temp@c\uncompress \fi\fi \uc@temp@c}% \letG\checkline \letH\skipcodes \letI\checkrange \def\1{\toks255\expandafter{\the\toks255 b}\2}% \def\0{\toks255\expandafter{\the\toks255 a}\2}% \def\2{\expandafter\ifx\csname hc@\the\toks255\endcsname\relax \else\csname hc@\the\toks255\endcsname\toks255{}\fi}%'; sub getname($) { my $i = shift; my $fullname; my $rangeend; my $xoptions; if (defined $unidata[$i]) { my $name = $ {$unidata[$i]}{name}; my $alias = $ {$unidata[$i]}{alias}; my $comment = $ {$unidata[$i]}{comment}; $rangeend = $ {$unidata[$i]}{rangeend}; $fullname = $name; $fullname .= "\n$alias" if $alias; $fullname .= "\n$comment" if $comment; $xoptions = join ', ', grep { $excludedoptions{$_} } keys %{$ {$unidata[$i]}{options}}; $xoptions = undef if $xoptions eq ''; } # if ($characters[$i]) { # $options = grep { defined $_ } join ', ', map { # my $o = $$_[1]; $o = $$o{onoption}; # $o = 'default' if (!defined $o) || ($o eq ''); $o; # $o = undef unless $excludedoptions{$o}; $o; # } @{$characters[$i]}; # if (defined $rangeend) { # $options2 = $options; $options = undef; # $fullname2 = findrange($i); # } else { # $fullname = findrange($i) unless defined $fullname; # } # } return undef unless defined $fullname; my $str = "\001"; if (defined $fullname) { $str .= "$fullname\n" } else { $str .= "\002\n" }; if (defined $xoptions) { $str .= "\003$xoptions\n" }; chomp $str; # my $str2 = undef; # if (defined $options2) { # $str2 = "\001"; # if (defined $fullname2) { $str2 .= "$fullname\n" } # else { $str2 .= "\002\n" }; # { $str2 .= "\000$options2\n" }; # chomp $str2; # } return ($str); } sub dumpnames_compressed() { my $file = openfile("$targetdir/uninames.dat", "%%% unicode name hash for ucs.sty, $autogen", "Unicode character names, compressed", coding => 'no-conversion'); print "Creating Huffman code\n" if $verbose; my (@weight, @tree, %lookup); my $nexthuffman = 0; my $upto = $#unidata; $upto = $#characters if $#characters > $upto; for (my $i=1; $i<=$upto; $i++) { for my $str (getname $i) { next unless defined $str; for my $c (split '',$str) { my $n = $lookup{$c}; unless (defined $n) { $n = $nexthuffman++; $lookup{$c} = $n }; $weight[$n]++; $tree[$n] = $c; } } } my $highnum = 9999999; while (1) { my ($smallest, $smallest2); my $smallestval = $highnum; my $smallest2val = $highnum; for (my $i=0; $i<$nexthuffman; $i++) { my $val = $weight[$i]; my $j = $i; next unless defined $val; # print "0: $j\n"; if ($val < $smallestval) { my $tmp = $val; $val = $smallestval; $smallestval = $tmp; $tmp = $j; $j = $smallest; $smallest = $tmp; } if ($val < $smallest2val) { $smallest2val = $val; $smallest2 = $j; } # print "A: $smallest, $smallest2\n"; } last if ($smallest2val==$highnum); $weight[$smallest] = undef; $weight[$smallest2] = undef; $tree[$nexthuffman] = [$tree[$smallest],$tree[$smallest2]]; $weight[$nexthuffman] = $smallestval+$smallest2val; # print "$smallest + $smallest2 => $nexthuffman\n"; # print Dumper($tree[$nexthuffman]); $nexthuffman++; } dumphuffman($tree[$nexthuffman-1],'',\%lookup); my $padding = undef; for my $i (values %lookup) { if (length($i)>7) { $padding = $i; last; } } unless (defined $padding) { warn "There is no huffman bit sequence longer than 7 bit. ". "Padding with spaces"; $padding = $lookup{' '} x 8; } print "Compressing\n" if $verbose; for my $c (keys %lookup) { my $v = $lookup{$c}; $v =~ s/1/b/g; $v =~ s/0/a/g; $c = $uninames_abbrev{$c} if defined $uninames_abbrev{$c}; print $file "\\def\\hc\@$v\{\\add $c\}\%\n"; } print $file "$huffman_decoder\n"; my $cp = 0; for (my $i=1; $i<=$#unidata; $i++) { for my $str (getname $i) { next unless defined $str; my $rangestart; if ($unidata[$i]) { $rangestart = $ {$unidata[$i]}{rangestart}; }; $str =~ s/./ if (!defined $lookup{$&}) { print "L: '$&'\n"; }; $lookup{$&}; /egs; # $str = join '', map { if ($_) { # pack "B*", sprintf "3%-7s", $_; # } } split /(.{1,7})/, $str; $str =~ s/.{1,7}/ sprintf "3%s%s",$&,substr($padding,0,7-length($&)); /eg; $str = pack("B*",$str); $cp++; if (defined $rangestart) { # printf $file "I%XI%X", $i-$cp+1, $rangeend-$i; # $cp=$rangeend; printf $file "I%XI%X", $i-$cp+1, $i-$rangestart; $cp=$i; } else { if ($i>$cp) { printf $file "H%X", $i-$cp; $cp=$i}; } print $file "G$str\n"; } } flushfiles(); } sub dumphuffman($$$) { my ($tree,$prefix,$lookup) = @_; # print Dumper([$tree]); return; if (ref $tree) { dumphuffman($$tree[0],$prefix.'0',$lookup); dumphuffman($$tree[1],$prefix.'1',$lookup); } else { # print "HUFFMAN: '$tree' => '$prefix'\n"; $$lookup{$tree} = $prefix; } } sub isprivate($) { my $cp = shift; return 1 if ($cp>=0xe000 && $cp<=0xf8ff); return 1 if ($cp>=0xf0000 && $cp<=0xffffd); return 1 if ($cp>=0x100000 && $cp<=0x10fffd); return 0; } sub dumpnames_uncompressed() { my $file = openfile("$targetdir/uninames.dat", "%%% unicode name hash for ucs.sty, $autogen", "Unicode character names"); warn "Dumping uncompressed unicode data, ". "this is no longer maintained and may give unpredicted results.\n"; dump_ranges($file); for (my $i=1; $i<=$#unidata; $i++) { next unless defined $unidata[$i]; my $name = $ {$unidata[$i]}{name}; my $alias = $ {$unidata[$i]}{alias}; my $comment = $ {$unidata[$i]}{comment}; my $str = sprintf "Unicode character %d = 0x%04x:${messagebreak}%s", $i, $i, $name; $str .= "${messagebreak}$alias" if $alias; $str .= "${messagebreak}$comment" if $comment; print $file "\\info{$str}\%\n"; } flushfiles(); } parseargs(); print "PASS: initcharacters\n" if $verbose; initcharacters(); if ($dump_unidata) { print "PASS: unlink unidata files\n" if $verbose; unlinkfiles(' ^uni-[0-9]+\.def$| ^uninames.dat$| ^uni-global\.def$ ',$targetdir); } if ($tables_to_dump) { print "PASS: unlink tables\n" if $verbose; unlinkfiles(' ^table-.*-.*-.*\.tex$ ',$tabledir); } if ($loadunidata) { print "PASS: loadunidata($unidata)\n" if $verbose; loadunidata($unidata); } for my $configfile (@configfiles) { print "PASS: loadconfig($configfile)\n" if $verbose; loadconfig($configfile); } print "PASS: generate\n" if $verbose; generate(); if ($dump_unidata) { print "PASS: generate globals\n" if $verbose; generate_globals(); } if ($createcomments) { print "PASS: makecomments\n" if $verbose; makecomments(); } if ($dump_unidata) { print "PASS: dumpcharacters\n" if $verbose; dumpcharacters(); } if ($tables_to_dump) { print "PASS: dumping character tables to directory $tabledir/\n" if $verbose; dump_charactertables(); } if ($generate_uninames && $dump_unidata && $loadunidata) { if ($compressnames) { print "PASS: dumpnames (compressed)\n" if $verbose; dumpnames_compressed(); } else { print "PASS: dumpnames (uncompressed)\n" if $verbose; dumpnames_uncompressed(); }} print "PASS: closefiles\n" if $verbose; closefiles(); ### LocalVariables: ### mode: perl ### End: ### Local IspellDict: british # LocalWords: uni def makeunidef pl de ucs sty AUTOOPTION num configfiles dir # LocalWords: nocomments SYSNOPSIS targetdir db UnicodeData txt uninames dat # LocalWords: UNIDATA org www http unicode