#!/usr/bin/perl =begin COPYRIGHT ------------------------------------------------------------ cmap2enc - convert glyph indices in a TrueType-flavored OpenType font to Adobe glyph names Copyright (c) 2005, 2006 Marc Penninga. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA =end ------------------------------------------------------------------------ =cut use Getopt::Std; use integer; use warnings; no warnings qw(uninitialized); %friendly_name = ( aalt => "Access All Alternates", afrc => "Alternate Fractions", alts => "Alternate Forms", calt => "Contextual Alternates", case => "Case-Sensitive Forms", clig => "Contextual Ligatures", cpsp => "Capital Spacing", cswh => "Contextual Swash", c2sc => "Small Capitals From Capitals", dlig => "Discretionary Ligatures", dnom => "Denominators", expt => "Expert Forms", fina => "Terminal Forms", frac => "Fractions", hist => "Historical Forms", hlig => "Historical Ligatures", init => "Initial Forms", liga => "Standard Ligatures", lnum => "Lining Figures", numr => "Numerators", onum => "Oldstyle Figures", ordn => "Ordinals", ornm => "Ornaments", pnum => "Proportional Figures", rlig => "Required Ligatures", salt => "Stylistic Alternates", sinf => "Scientific Inferiors", smcp => "Small Caps", subs => "Subscripts", sups => "Superscripts", swsh => "Swash", titl => "Titling", tnum => "Tabular Figures", zero => "Slashed Zero", ); sub getu($) { my $arg = shift; my $n = length $arg; my $r = 0; for my $i (0 .. $n - 1) { $r = ($r << 8) + unpack("C", substr($arg, $i, 1)); } return $r; } sub getcov($) { my $Coverage = shift; my $CoverageFormat = getu(substr $Coverage, 0, 2); my @r = (); if ($CoverageFormat == 1) { my $GlyphCount = getu(substr $Coverage, 2, 2); for my $i (0 .. $GlyphCount - 1) { push @r, getu(substr $Coverage, 4 + 2 * $i, 2); } } elsif ($CoverageFormat == 2) { my $RangeCount = getu(substr $Coverage, 2, 2); for my $i (0 .. $RangeCount - 1) { my $RangeRecord = substr $Coverage, 4 + 6 * $i, 6; my $Start = getu(substr $RangeRecord, 0, 2); my $End = getu(substr $RangeRecord, 2, 2); for my $j ($Start .. $End) { push @r, $j; } } } else { warn "Warning: unknown CoverageFormat `$CoverageFormat'\n"; } return @r; } ############################################################################## getopts "f:l:ps:x:", \%options; if ($options{f}) { for (split ",", $options{f}) { $feature_selected{$_} = 1; } } $suffix = $feature_selected{ pnum } ? '-' : '-t'; $suffix .= $feature_selected{ onum } ? 'osf' : 'lf'; $suffix .= $feature_selected{ smcp } ? '-sc' : ''; $suffix .= $feature_selected{ swsh } ? '-swash' : ''; $suffix .= $feature_selected{ titl } ? '-titling' : ''; if ( $feature_selected{ sups } ) { $suffix = '-sup' } if ( $feature_selected{ ornm } ) { $suffix = '-orn' } $options{s} ||= "latn"; $options{l} ||= "DFLT"; $0 =~ s!.*/!!; die "Usage: $0 [options] fontfile encfile\n" unless ($options{p} and @ARGV == 1) or @ARGV == 2; chop(@glyph = ); for (@glyph) { next if /^\s*\#/; /(.+);([\dA-Fa-f]{4})/ and $code{$1} = $2; } if ((chop($fn = `kpsewhich $ARGV[0] 2>&1`) and -e $fn) or (chop($fn = `findtexmf $ARGV[0] 2>&1`) and -e $fn)) { open FONT, "<$fn" or die "Error: can't open `$fn' - $!\n"; } else { open FONT, "<$ARGV[0]" or die "Error: can't open `$ARGV[0]' - $!\n"; } binmode FONT; { local $/; $font = ; } $OffsetTable = substr $font, 0, 12; $numTables = getu(substr $OffsetTable, 4, 2); $TableDirectory = substr $font, 12, 16 * $numTables; for $i (0 .. $numTables - 1) { $entry = substr $TableDirectory, 16 * $i, 16; $tag = substr $entry, 0, 4; $offset{$tag} = getu(substr $entry, 8, 4); $length{$tag} = getu(substr $entry, 12, 4); } if (exists $offset{GSUB}) { $GSUB = substr $font, $offset{GSUB}, $length{GSUB}; $ScriptList = substr $GSUB, getu(substr $GSUB, 4, 2); $FeatureList = substr $GSUB, getu(substr $GSUB, 6, 2); $LookupList = substr $GSUB, getu(substr $GSUB, 8, 2); $LookupCount = getu(substr $LookupList, 0, 2); undef @Lookup; undef @LookupType; for $i (0 .. $LookupCount - 1) { $Lookup = substr $LookupList, getu(substr $LookupList, 2 + 2 * $i, 2); $LookupType[$i] = getu(substr $Lookup, 0, 2); next unless $LookupType[$i] == 1; $SubTableCount = getu(substr $Lookup, 4, 2); for $j (0 .. $SubTableCount - 1) { $SubTable = substr $Lookup, getu(substr $Lookup, 6 + 2 * $j, 2); $SubstFormat = getu(substr $SubTable, 0, 2); if ($SubstFormat == 1) { $Coverage = substr $SubTable, getu(substr $SubTable, 2, 2); @Coverage = getcov($Coverage); $DeltaGlyphID = getu(substr $SubTable, 4, 2); for $k (0 .. $#Coverage) { $Lookup[$i]{$Coverage[$k]} = $Coverage[$k] + $DeltaGlyphID; } } elsif ($SubstFormat == 2) { $Coverage = substr $SubTable, getu(substr $SubTable, 2, 2); @Coverage = getcov($Coverage); $GlyphCount = getu(substr $SubTable, 4, 2); if ($GlyphCount != scalar(@Coverage)) { warn "Error: SingleSubstFormat2 subtable - " . "GlyphCount wrong\n"; next; } for $k (0 .. $#Coverage) { $Lookup[$i]{$Coverage[$k]} = getu(substr $SubTable, 6 + 2 * $k, 2); } } else { warn "Error: unknown SingleSubstFormat `$SubstFormat'\n"; next; } } } undef @FeatureTag; undef @FeatureLookupListIndex; $FeatureCount = getu(substr $FeatureList, 0, 2); for $i (0 .. $FeatureCount - 1) { $FeatureRecord = substr $FeatureList, 2 + 6 * $i, 6; $FeatureTag[$i] = substr $FeatureRecord, 0, 4; $Feature = substr $FeatureList, getu(substr $FeatureRecord, 4, 2); $LookupCount = getu(substr $Feature, 2, 2); $feature_supported{$FeatureTag[$i]} = 1; for $j (0 .. $LookupCount - 1) { $FeatureLookupListIndex[$i][$j] = getu(substr $Feature, 4 + 2 * $j, 2); if ($LookupType[$FeatureLookupListIndex[$i][$j]] != 1) { $feature_supported{$FeatureTag[$i]} = 0; } } } undef %Script; undef %FeatureIndex; $ScriptCount = getu(substr $ScriptList, 0, 2); for $i (0 .. $ScriptCount - 1) { $ScriptRecord = substr $ScriptList, 2 + 6 * $i, 6; $ScriptTag = substr $ScriptRecord, 0, 4; $Script = substr $ScriptList, getu(substr $ScriptRecord, 4, 2); if (substr($Script, 0, 2)) { $LangSysTag = "DFLT"; push @{$Script{$ScriptTag}}, $LangSysTag; $LangSys = substr $Script, getu(substr $Script, 0, 2); $FeatureCount = getu(substr $LangSys, 4, 2); for $k (0 .. $FeatureCount - 1) { push @{$FeatureIndex{$ScriptTag}{$LangSysTag}}, getu(substr $LangSys, 6 + 2 * $k, 2); } } $LangSysCount = getu(substr $Script, 2, 2); for $j (0 .. $LangSysCount - 1) { $LangSysRecord = substr $Script, 4 + 6 * $j, 6; $LangSysTag = substr $LangSysRecord, 0, 4; push @{$Script{$ScriptTag}}, $LangSysTag; $LangSys = substr $Script, getu(substr $LangSysRecord, 4, 2); $FeatureCount = getu(substr $LangSys, 4, 2); for $k (0 .. $FeatureCount - 1) { push @{$FeatureIndex{$ScriptTag}{$LangSysTag}}, getu(substr $LangSys, 6 + 2 * $k, 2); } } } } if (exists $offset{GPOS}) { $GPOS = substr $font, $offset{GPOS}, $length{GPOS}; $FeatureList = substr $GPOS, getu(substr $GPOS, 6, 2); $FeatureCount = getu(substr $FeatureList, 0, 2); for $i (0 .. $FeatureCount - 1) { $FeatureRecord = substr $FeatureList, 2 + 6 * $i, 6; $FeatureTag = substr $FeatureRecord, 0, 4; $feature_supported{$FeatureTag} = 0; } } if ($options{p}) { if (exists $offset{name}) { $name = substr $font, $offset{name}, $length{name}; $count = getu(substr $name, 2, 2); $stringOffset = getu(substr $name, 4, 2); for $i (0 .. $count - 1) { $nameRecord = substr $name, 6 + 12 * $i, 12; $platformID = getu(substr $nameRecord, 0, 2); $encodingID = getu(substr $nameRecord, 2, 2); $languageID = getu(substr $nameRecord, 4, 2); $nameID = getu(substr $nameRecord, 6, 2); $length = getu(substr $nameRecord, 8, 2); $offset = getu(substr $nameRecord, 10, 2); $string = substr $name, $stringOffset + $offset, $length; $string{"$platformID,$encodingID,$languageID,$nameID"} = $string; } print "\n", "-" x 80, "\n\n"; for $i ((6,10,0)) { if (exists $string{"0,3,0,$i"}) { print $string{"0,3,0,$i"}, "\n\n"; } elsif (exists $string{"0,4,0,$i"}) { print $string{"0,4,0,$i"}, "\n\n"; } elsif (exists $string{"3,1,1033,$i"}) { print $string{"3,1,1033,$i"}, "\n\n"; } elsif (exists $string{"3,10,1033,$i"}) { print $string{"3,10,1033,$i"}, "\n\n"; } } print "-" x 80, "\n\n"; } print "SCRIPT\tLANG\tFEATURES\n\n"; for $script (keys %Script) { print "$script"; for $lang (@{$Script{$script}}) { print "\t$lang\t"; for (sort {$a <=> $b} @{$FeatureIndex{$script}{$lang}}) { print "$FeatureTag[$_]", exists $friendly_name{$FeatureTag[$_]} ? " - $friendly_name{$FeatureTag[$_]}" : " - Unknown Feature", $feature_supported{$FeatureTag[$_]} ? "" : " (not supported by $0)", "\n\t\t"; } print "\n"; } } print "-" x 80, "\n\n"; } undef @subst; for $i (0 .. 0xFFFF) { $subst[$i] = $i; } undef %lookup_selected; for $feature (sort {$a <=> $b} @{$FeatureIndex{$options{s}}{$options{l}}}) { if (!$feature_selected{$FeatureTag[$feature]}) { next; } if (!$feature_supported{$FeatureTag[$feature]}) { warn "Warning: feature `$FeatureTag[$feature]' not supported\n"; next; } for (@{$FeatureLookupListIndex[$feature]}) { $lookup_selected{$_} = 1; } } for $lookup (sort {$a <=> $b} keys %lookup_selected) { for (sort {$a <=> $b} keys %{$Lookup[$lookup]}) { $subst[$_] = $Lookup[$lookup]{$_}; } } if ($options{x}) { open SUB, "<$options{x}" or warn "Warning: can't open `$options{x}' - $!\n\tfile skipped\n"; for () { if (/^\s*\#/) { next; } elsif (/([0-9A-Fa-f]{4});([0-9A-Fa-f]{4})/) { $subst[hex($1)] = hex($2); $subst[hex($2)] = hex($2); } elsif (/(\w+);([0-9A-Fa-f]{4})/) { $glyphName{$1} ||= sprintf hex($2) < 256 ? "index0x%02X" : "index0x%04X", hex $2; delete $code{$1}; $subst[hex($2)] = hex($2); } } } $cmap = substr $font, $offset{cmap}, $length{cmap}; $numTables = getu(substr $cmap, 2, 2); for $i (0 .. $numTables - 1) { $EncodingRecord = substr $cmap, 4 + $i * 8, 8; $platformID = getu(substr $EncodingRecord, 0, 2); $encodingID = getu(substr $EncodingRecord, 2, 2); $offset = getu(substr $EncodingRecord, 4, 4); $format = getu(substr $cmap, $offset, 2); $length = getu(substr $cmap, $offset + 2, 2); $subtable = substr $cmap, $offset, $length; next unless ($platformID == 0 and ($encodingID == 3 or $encodingID == 4)) or ($platformID == 3 and ($encodingID == 1 or $encodingID == 10)); if ($format == 4) { $segCountX2 = getu(substr $subtable, 6, 2); $segCount = $segCountX2 / 2; $endCount = substr $subtable, 14, $segCountX2; $startCount = substr $subtable, 16 + $segCountX2, $segCountX2; $idDelta = substr $subtable, 16 + 2 * $segCountX2, $segCountX2; $idRangeOffset = substr $subtable, 16 + 3 * $segCountX2, $segCountX2; for $j (0 .. $segCount - 1) { $endCount[$j] = getu(substr $endCount, 2 * $j, 2); $startCount[$j] = getu(substr $startCount, 2 * $j, 2); $idDelta[$j] = getu(substr $idDelta, 2 * $j, 2); $idRangeOffset[$j] = getu(substr $idRangeOffset, 2 * $j, 2); next if $startCount[$j] == 0xFFFF; for $characterCode ($startCount[$j] .. $endCount[$j]) { if ($idRangeOffset[$j] != 0) { $ptr = $idRangeOffset[$j] / 2 + $characterCode - $startCount[$j] + 8 + 3 * $segCount + $j; $tmp = getu(substr $subtable, 2 * $ptr, 2); if ($tmp) { $glyphIndex = ($tmp + $idDelta[$j]) % 65536; } else { $glyphIndex = 0; } } else { $glyphIndex = ($characterCode + $idDelta[$j]) % 65536; } $glyphIndex = $subst[$glyphIndex]; $uni = sprintf "%04X", $characterCode; $glyphName{$uni} ||= sprintf $glyphIndex < 256 ? "index0x%02X" : "index0x%04X", $glyphIndex; } } last; } else { warn "Warning: unsupported format `$format'\n"; } } if (!keys %glyphName) { die "Error: no supported platformID/encodingID found in `cmap' table\n"; } if (!$options{p}) { if ((chop($fn = `kpsewhich $ARGV[1] 2>&1`) and -e $fn) or (chop($fn = `findtexmf $ARGV[1] 2>&1`) and -e $fn)) { open ENC, "<$fn" or die "Error: can't open `$fn' - $!\n"; } else { open ENC, "<$ARGV[1]" or die "Error: can't open `$ARGV[1]' - $!\n"; } chop(@lines = ); map s/%.+//, @lines; $_ = join "", @lines; ($name, $_) = /\/([\w-]+)\s*\[(.*)\]\s*def/; @vector = /([\w.]+)/g; $ARGV[0] =~ /(.+)\.(?:ttf|otf)/ and $fontname = $1; $ARGV[1] =~ /(.+)\.enc/ and $encname = $1; open ENC, ">${encname}${suffix}-${fontname}.enc" or die "Error: can't open `${encname}${suffix}-${fontname}.enc' - $!\n"; print ENC "/${name}${suffix}-${fontname} [\n"; for ($i = 0; $i < 256; $i += 16) { printf ENC "%% 0x%04x\n", $i; for ($j = 0; $j < 16; $j += 4) { for $k (0 .. 3) { print ENC " /", $glyphName{$code{$vector[$i + $j + $k]}} || $glyphName{$vector[$i + $j + $k]} || $vector[$i + $j + $k]; } print ENC "\n"; } } print ENC "] def\n"; open MTX, ">reset${fontname}-${encname}${suffix}.mtx" or die "Error: can't open " . "'reset${fontname}-${encname}${suffix}.mtx' - $!\n"; print MTX < I =head1 DESCRIPTION Many TrueType-flavored OpenType fonts contain no glyph names. When creating an F file with F, this causes all glyphs to be named something like C, which makes the F file almost useless for use with F or F. B uses the font's `cmap' table to map glyph indices to Unicode values, and Adobe's F file to map these to glyph names. B creates a new encoding file (for downloading with the font file) and a F file (for use with F). The encoding file contains the same glyphs as the original one, but has all glyph names replaced with the corresponding glyph indices; the F file maps these glyph indices back to the original glyph names (it consists of many commands of the form C<\renameGlyph{index0x01E7}{Euro}>, plus a definition of that C<\renameGlyph> command). Some of the glyph substitution features of OpenType fonts are supported; e.g. when the `smcp' (Small Caps) feature is specified, the glyph names of the lowercase glyphs will be mapped to the glyph indices of the small capitals. A list of features supported by the font can be generated using the B<-p> option, see below. =head1 EXAMPLES NOTE: the F subdirectory in the I distribution contains several complete examples of the use of B and other tools from the I collection. =over 2 =item B<-> To display all scripts, languages and features supported by Linotype Palatino: cmap2enc -p pala.ttf =item B<-> To install Linotype Palatino in T1 encoding, with oldstyle figures: =over 3 =item B<1.> Generate an encoding file F and a metrics file F: cmap2enc -f onum pala.ttf t1.enc =item B<2.> Create an F file for the font; this is done best using I (also in the I collection): font2afm pala.ttf =item B<3.> Reencode the F file using the F encoding to create a `raw' font I, with either I (also in the I collection) or I (but be warned that I might take a long time, and can even crash if the F is really big): afm2afm -e t1-tosf-pala.enc -o rlplrj9d.afm pala.afm or \transformfont{rlplrj9d}{\reencodefont{t1-tosf-pala}{\fromafm{pala}}} =item B<4.> Create a `value-added' F file from this raw font by adding ligatures, faking missing glyphs etc. with I: \installfont{lplrj9d}{rlplrj9d,resetpala-t1-tosf,newlatin} {t1}{T1}{lplj}{m}{n}{} =back =back =head1 OPTIONS B supports the following command-line options: =over 4 =item B<-p> Print some info about the font, plus a list of all scripts, languages and features contained in this font's GSUB table; then exit. (When using this option, the I need not be specified.) =item B<-f> I[,I]* Use the specified features when matching glyph indices to glyph names. Multiple features can be specified as a comma-separated list, B spaces between the items. =item B<-x> I Use the user-specified substitutions from I (see below). =item B<-l> I Select the specified language. The default is `DFLT' (surprise!). =item B<-s> I