Blob Blame History Raw
package Font::TTF::OS_2;

=head1 NAME

Font::TTF::OS_2 - the OS/2 table in a TTF font

=head1 DESCRIPTION

The OS/2 table has two versions and forms, one an extension of the other. This
module supports both forms and the switching between them.

=head1 INSTANCE VARIABLES

No other variables than those in table and those in the standard:

    Version
    xAvgCharWidth
    usWeightClass
    usWidthClass
    fsType
    ySubscriptXSize
    ySubScriptYSize
    ySubscriptXOffset
    ySubscriptYOffset
    ySuperscriptXSize
    ySuperscriptYSize
    ySuperscriptXOffset
    ySuperscriptYOffset
    yStrikeoutSize
    yStrikeoutPosition
    sFamilyClass
    bFamilyType
    bSerifStyle
    bWeight
    bProportion
    bContrast
    bStrokeVariation
    bArmStyle
    bLetterform
    bMidline
    bXheight
    ulUnicodeRange1
    ulUnicodeRange2
    ulUnicodeRange3
    ulUnicodeRange4
    achVendID
    fsSelection
    usFirstCharIndex
    usLastCharIndex
    sTypoAscender
    sTypoDescender
    sTypoLineGap
    usWinAscent
    usWinDescent
    ulCodePageRange1
    ulCodePageRange2
    xHeight
    CapHeight
    defaultChar
    breakChar
    maxLookups

Notice that versions 0, 1, 2 & 3 of the table are supported. Notice also that the
Panose variable has been broken down into its elements.

=head1 METHODS

=cut

use strict;
use vars qw(@ISA @fields @lens @field_info @weights @ranges @codepages);
use Font::TTF::Table;

@ISA = qw(Font::TTF::Table);
@field_info = (
    'xAvgCharWidth' => 's',
    'usWeightClass' => 'S',
    'usWidthClass' => 'S',
    'fsType' => 's',
    'ySubscriptXSize' => 's',
    'ySubScriptYSize' => 's',
    'ySubscriptXOffset' => 's',
    'ySubscriptYOffset' => 's',
    'ySuperscriptXSize' => 's',
    'ySuperscriptYSize' => 's',
    'ySuperscriptXOffset' => 's',
    'ySuperscriptYOffset' => 's',
    'yStrikeoutSize' => 's',
    'yStrikeoutPosition' => 's',
    'sFamilyClass' => 's',
    'bFamilyType' => 'C',
    'bSerifStyle' => 'C',
    'bWeight' => 'C',
    'bProportion' => 'C',
    'bContrast' => 'C',
    'bStrokeVariation' => 'C',
    'bArmStyle' => 'C',
    'bLetterform' => 'C',
    'bMidline' => 'C',
    'bXheight' => 'C',
    'ulUnicodeRange1' => 'L',
    'ulUnicodeRange2' => 'L',
    'ulUnicodeRange3' => 'L',
    'ulUnicodeRange4' => 'L',
    'achVendID' => 'L',
    'fsSelection' => 'S',
    'usFirstCharIndex' => 'S',
    'usLastCharIndex' => 'S',
    'sTypoAscender' => 'S',
    'sTypoDescender' => 's',
    'sTypoLineGap' => 'S',
    'usWinAscent' => 'S',
    'usWinDescent' => 'S',
    '' => '',
    'ulCodePageRange1' => 'L',
    'ulCodePageRange2' => 'L',
    '' => '',
    'xHeight' => 's',
    'CapHeight' => 's',
    'defaultChar' => 'S',
    'breakChar' => 'S',
    'maxLookups' => 's',
    '' => '',            # i.e. v3 is basically same as v2
    '' => ''             # i.e. v4 is structurally identical to v3
    );

@weights = qw(64 14 27 35 100 20 14 42 63 3 6 35 20 56 56 17 4 49 56 71 31 10 18 3 18 2 166);

use Font::TTF::Utils;

sub init
{
    my ($k, $v, $c, $n, $i, $t, $j);

    $n = 0;
    @lens = (76, 84, 94, 94, 94);
    for ($j = 0; $j < $#field_info; $j += 2)
    {
        if ($field_info[$j] eq '')
        {
            $n++;
            next;
        }
        ($k, $v, $c) = TTF_Init_Fields($field_info[$j], $c, $field_info[$j+1]);
        next unless defined $k && $k ne "";
        for ($i = $n; $i < 5; $i++)
        { $fields[$i]{$k} = $v; }
    }
}


=head2 $t->read

Reads in the various values from disk (see details of OS/2 table)

=cut

sub read
{
    my ($self) = @_;
    my ($dat, $ver);

    $self->SUPER::read or return $self;

    init unless defined $fields[2]{'xAvgCharWidth'};
    $self->{' INFILE'}->read($dat, 2);
    $ver = unpack("n", $dat);
    $self->{'Version'} = $ver;
    if ($ver < 5)
    {
        $self->{' INFILE'}->read($dat, $lens[$ver]);
        TTF_Read_Fields($self, $dat, $fields[$ver]);
    }
    $self;
}


=head2 $t->out($fh)

Writes the table to a file either from memory or by copying.

=cut

sub out
{
    my ($self, $fh) = @_;
    my ($ver);

    return $self->SUPER::out($fh) unless $self->{' read'};

    $ver = $self->{'Version'};
    $fh->print(pack("n", $ver));
    $fh->print(TTF_Out_Fields($self, $fields[$ver], $lens[$ver]));
    $self;
}


=head2 $t->XML_element($context, $depth, $key, $value)

Tidies up the hex values to output them in hex

=cut

sub XML_element
{
    my ($self) = shift;
    my ($context, $depth, $key, $value) = @_;
    my ($fh) = $context->{'fh'};

    if ($key =~ m/^ul(?:Unicode|CodePage)Range\d$/o)
    { $fh->printf("%s<%s>%08X</%s>\n", $depth, $key, $value, $key); }
    elsif ($key eq 'achVendID')
    { $fh->printf("%s<%s name='%s'/>\n", $depth, $key, pack('N', $value)); }
    else
    { return $self->SUPER::XML_element(@_); }
    $self;
}


=head2 $t->XML_end($context, $tag, %attrs)

Now handle them on the way back in

=cut

sub XML_end
{
    my ($self) = shift;
    my ($context, $tag, %attrs) = @_;

    if ($tag =~ m/^ul(?:Unicode|CodePage)Range\d$/o)
    { return hex($context->{'text'}); }
    elsif ($tag eq 'achVendID')
    { return unpack('N', $attrs{'name'}); }
    else
    { return $self->SUPER::XML_end(@_); }
}

=head2 $t->minsize()

Returns the minimum size this table can be. If it is smaller than this, then the table
must be bad and should be deleted or whatever.

=cut

sub minsize
{
    return 78;
}

=head2 $t->update

Updates the OS/2 table by getting information from other sources:

Updates the C<firstChar> and C<lastChar> values based on the MS table in the
cmap.

Updates the sTypoAscender, sTypoDescender & sTypoLineGap to be the same values
as Ascender, Descender and Linegap from the hhea table (assuming it is dirty)
and also sets usWinAscent to be the sum of Ascender+Linegap and usWinDescent to
be the negative of Descender.

=cut

sub update
{
    my ($self) = @_;
    my ($map, @keys, $table, $i, $avg, $hmtx);

    return undef unless ($self->SUPER::update);

    $self->{' PARENT'}{'cmap'}->update;
    $map = $self->{' PARENT'}{'cmap'}->find_ms || return undef;
    $hmtx = $self->{' PARENT'}{'hmtx'}->read;

    @keys = sort {$a <=> $b} grep {$_ < 0x10000} keys %{$map->{'val'}};

    $self->{'usFirstCharIndex'} = $keys[0];
    $self->{'usLastCharIndex'} = $keys[-1];

    $table = $self->{' PARENT'}{'hhea'}->read;
    
    # try any way we can to get some real numbers passed around!
    if (($self->{'fsSelection'} & 128) != 0)
    {
        # assume the user knows what they are doing and has sensible values already
    }
    elsif ($table->{'Ascender'} != 0 || $table->{'Descender'} != 0)
    {
        $self->{'sTypoAscender'} = $table->{'Ascender'};
        $self->{'sTypoDescender'} = $table->{'Descender'};
        $self->{'sTypoLineGap'} = $table->{'LineGap'};
        $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'};
        $self->{'usWinDescent'} = -$self->{'sTypoDescender'};
    }
    elsif ($self->{'sTypoAscender'} != 0 || $self->{'sTypoDescender'} != 0)
    {
        $table->{'Ascender'} = $self->{'sTypoAscender'};
        $table->{'Descender'} = $self->{'sTypoDescender'};
        $table->{'LineGap'} = $self->{'sTypoLineGap'};
        $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'};
        $self->{'usWinDescent'} = -$self->{'sTypoDescender'};
    } 
    elsif ($self->{'usWinAscent'} != 0 || $self->{'usWinDescent'} != 0)
    {
        $self->{'sTypoAscender'} = $table->{'Ascender'} = $self->{'usWinAscent'};
        $self->{'sTypoDescender'} = $table->{'Descender'} = -$self->{'usWinDescent'};
        $self->{'sTypoLineGap'} = $table->{'LineGap'} = 0;
    }

    if ($self->{'Version'} < 3)
    {
        for ($i = 0; $i < 26; $i++)
        { $avg += $hmtx->{'advance'}[$map->{'val'}{$i + 0x0061}] * $weights[$i]; }
        $avg += $hmtx->{'advance'}[$map->{'val'}{0x0020}] * $weights[-1];
        $self->{'xAvgCharWidth'} = $avg / 1000;
    }
    elsif ($self->{'Version'} > 2)
    {
        $i = 0; $avg = 0;
        foreach (@{$hmtx->{'advance'}})
        {
            next unless ($_);
            $i++;
            $avg += $_;
        }
        $avg /= $i if ($i);
        $self->{'xAvgCharWidth'} = $avg;
    }

    $self->{'ulUnicodeRange2'} &= ~0x2000000;
    foreach $i (keys %{$map->{'val'}})
    {
        if ($i >= 0x10000)
        {
            $self->{'ulUnicodeRange2'} |= 0x2000000;
            last;
        }
    }

    $self->{'Version'} = 1 if (defined $self->{'ulCodePageRange1'} && $self->{'Version'} < 1);
    $self->{'Version'} = 2 if (defined $self->{'maxLookups'} && $self->{'Version'} < 2);
    
    if ((exists $self->{' PARENT'}{'GPOS'} && $self->{' PARENT'}{'GPOS'}{' read'}) ||
        (exists $self->{' PARENT'}{'GSUB'} && $self->{' PARENT'}{'GSUB'}{' read'}))
    {
        # one or both of GPOS & GSUB exist and have been read or modified; so update usMaxContexts
        my ($lp, $ls);
        $lp = $self->{' PARENT'}{'GPOS'}->maxContext if exists $self->{' PARENT'}{'GPOS'};
        $ls = $self->{' PARENT'}{'GSUB'}->maxContext if exists $self->{' PARENT'}{'GSUB'};
        $self->{'maxLookups'} = $lp > $ls ? $lp : $ls;
    }
    
    $self;
}


=head2 $t->guessRangeBits (\%map, [$cp_threshold, [%u_threshold]])

Set the ulCodePageRange and ulUnicodeRange fields based on characters actually present in the font.

%map is a hash keyed by USV returning non-zero for characters present (e.g. use {'val'} 
a from Unicode cmap).

The two optional parameters are percentage of characters within the codepage or unicode range that need
to be present to constitute coverage. A threshold of 0 causes corresponding range bits to 
be set if any characters are present at all, while a negative value causes the corresponding
range bits to be unchanged. Defaults are 50 and 0, respectively.

For codepage bits, the threshold is percentage of characters between 0xC0 and 0xFF that need
to be present to constitute coverage). For codepages other than 1252, 
characters (e.g., punctuation) that are defined identically to cp1252 are ignored
for the purposes of this percentage calculation. Looks only for SBCS codepages, not DBCS.

For Unicode range bits that represent multiple ranges, e.g., bit 29 represents:

  Latin Extended Additional  1E00-1EFF
  Latin Extended-C           2C60-2C7F
  Latin Extended-D           A720-A7FF

the bit will be set if any of these ranges meet the threshold requirement.

=cut

sub guessRangeBits
{
    my ($self, $ucmap, $cp_threshold, $u_threshold) = @_; 
    $cp_threshold = 50 unless defined ($cp_threshold); 
    $u_threshold  =  0 unless defined ($u_threshold);
    my $j;  # index into codepages or ranges
    my $u;  # USV

    unless (ref($codepages[0]))
    {
        # One-time work to convert range data
        
        # unpack codepages. But first make sure we have Zlib
        eval {require Compress::Zlib} || croak("Cannot unpack codepage data Compress::Zlib not available");

        for ($j = 0; $j <= $#codepages; $j++)
        {
            next unless $codepages[$j];
            $codepages[$j] = [unpack("n*", Compress::Zlib::uncompress(unpack("u", $codepages[$j])))];
            warn ("Got nothing for codepage # $j\n") if $#{$codepages[$j]} < 128;
        }
        
        # convert Unicode array data to hash
        my @newRanges;
        for ($j = 0; $j <= $#ranges; $j++)
        {
            next unless $ranges[$j][2] =~ m/^([0-9a-f]{4,6})-([0-9a-f]{4,6})$/oi;
            my $s = hex ($1);
            my $e = hex ($2);
            next unless $e > $s;
            push @newRanges, {start => $s, end => $e, bit => $ranges[$j][0], name => $ranges[$j][1]};
        }
        @ranges = sort {$a->{'end'} <=> $b->{'end'}} (@newRanges);
    }

    if ($cp_threshold >= 0)
    {
        my $cpr;    # codepage range vector
        $cp_threshold = 100 if $cp_threshold > 100;
        vec($cpr, 63 ,1) = 0;   # Get all 64 bits into the vector
        for $j (0 .. $#codepages)   # For each codepage
        {
            # Count the number of chars from @range part of codepage that are present in the font
            my $present = 0;
            my $total = 0;
            foreach (0x20 .. 0xFF)
            {
                $u = $codepages[$j][$_];
                next if $u == 0xFFFD;   # Ignore undefined things in codepage
                # For codepage 1252, ignore upper ansi altogether
                # For other codepages, ignore characters that are same as in 1252.
                next if $j > 0 ? $u == $codepages[0][$_] : $u > 0x007F;
                $total++;
                $present++ if exists $ucmap->{$u} && $ucmap->{$u} > 0;
            }
            #printf STDERR "DBG: Got $present / $total (%0.3f%%) in codepage bit $j\n", $present * 100 / $total;
            #print STDERR "DBG: setting bit $j\n" if $cp_threshold == 0 ? ($present > 0) : (($present * 100 / $total) >= $cp_threshold);
            vec($cpr, $j, 1) = 1 if $cp_threshold == 0 ? ($present > 0) : (($present * 100 / $total) >= $cp_threshold);
            #print STDERR "\n";
        }
        ($self->{'ulCodePageRange1'}, $self->{'ulCodePageRange2'}) = unpack ('V2', $cpr);
    } 

    if ($u_threshold >= 0)
    {
        my $ucr;    # Unicode range vector
        my @count;  # Count of chars present in each range
        $u_threshold  = 100 if $u_threshold  > 100;
        vec($ucr, 127,1) = 0;   # Get all 128 bits into the vector
        $j = 0;  
CHAR:   for $u (sort {$a <=> $b} keys %{$ucmap})
        {
            while ($u > $ranges[$j]{'end'})
            {
                last CHAR if ++$j > $#ranges;
            }
            next if $u < $ranges[$j]{'start'};
            $count[$j]++ if $ucmap->{$u};
        }
        foreach $j (0 .. $#ranges)
        {
            vec($ucr, $ranges[$j]{'bit'}, 1) = 1 if $u_threshold == 0 ? 
                ($count[$j] > 0) :
                (($count[$j] * 100 / ($ranges[$j]{'end'} - $ranges[$j]{'start'} + 1)) >= $u_threshold);
        }
        ($self->{'ulUnicodeRange1'},$self->{'ulUnicodeRange2'},$self->{'ulUnicodeRange3'},$self->{'ulUnicodeRange4'}) =
            unpack ('V4', $ucr);
    }
}

BEGIN {

@codepages = (
<<'EOT',    # Bit 0: cp1252
M>)P-R>-B%E```-#S+1O+O%NU;-MVRURV[7?H"3*7;2_;M6S;K=7Y>Q`1)8VT
MTDDO@XPRR2R+K++)+H><<LDM6AYYY9-?`0454E@111537!`C5@DEE1*GM#+*
M*J>\"BJJI+(JJJJFNAIJJJ6V.NJJI[X&&FJDL2:::J:Y%EIJI;4VVFJGO0XZ
MZJ2S+KKJ)EYW/?342V]]]-5/?P,,-$B"P888:ICA1AAIE-'&&&N<\2:8:)+)
MIIAJFNEFF&F6V>:8:Y[Y%E@8$E-30J'(HE`LQ(408J*20I5(0J@;Z9::$IF?
MFO)_"X2"H4@H&F)#=,@3E1P3&QD<ZD7B__^"R"R++;'4,LNML-(JJZVQUCJ)
MUMM@HTTVVV*K;;;;8:===MMCKWWV.^"@0PX[XJACCDMRPDFGG';&6>><=\%%
MEUQVQ5777'?#3;?<=D>RN^ZY[X&''GGLB:>>>>Z%EUYY[8VWWGGO@X\^^>R+
3K[[Y[H>??OGMCQ1_I?X#Q@N!>@``
EOT

<<'EOT',    # Bit 1: cp1250
M>)P-Q^=+U'$`Q_'WY^>LW%K=G:/OG6:VM4R[*RU'.7.EY2BW5E:.MOM?Z8%8
MXB#!,O"1$8A$/2G:$D40$?6D2)`.NP=O>+T!8>&'/P$$$D0P&]C()D(()8QP
M(H@DBFABV,P6MF+#CH-8XH@G@6T8G+A()(GM)+.#%':RB]WL82_[V$\J:1S@
M(.D<(H-,#N/&PQ&.DD4VQSA.#KGDD<\)3E)`(4444T(IIRBCG`HJJ>(TU=1P
MAK/44D<]#9SC/(TTT4P+K;313@>=7.`BE^CB,E>X2C<]]-+'-:YS@YO<XC9W
MZ&>`08889H11,[7N-0Y?"2;9&./T*57-QJUZM6M8_;ZW&;N),_'&9:)-S+K7
MZ5*+\:A!'1K1`'>M)>NM<AB3/^/<XSX3:F22*::9T2`/F+4^*)<Y'O*(>1XK
M0$TL*,M:4;:&5,TB3V3Q5&X%LJ00EF7CF1P\YX7"%*$\%?"25ZK@-6]4JV[>
MJY>/K*B53ZKA,U_DQU=Y%,0WA?)==GXHEI_\4K@BE:]"?O-'E?QE577J84U]
+_,.K-NO=?_3I:`(`
EOT

<<'EOT',    # Bit 2: cp1251
M>)P%P=52%5``0-$-;!KISG,OW=W=#=(IV-W=_H+^@0^.CB,Z^J`^^&V.K@7$
M$$L<$D\"B2213`JII'&*=#+()(ML<L@ECWP***2(8DHHI8QR*@A$B%))%=74
M4$L=]3302!/-M-!*&^UTT$D7W?302Q_]###($,.,,,H8XTPPR133S##+'/,L
ML,@2RZRPRFG66&>#3;;89H==]MCG@$/.<,0Q9SG'>2YPD4M<Y@I7N<9U;G"3
M6]SF#G>YQWT>\)!'/.8)3WG&<U[PDE>\YHVQQH5B-T)YJ`DA1,)):#,Y])MB
MFJEFN!X*0U$H#64A&G)"[K^_D:A[8<!]#SWPF/>F>V02'WS+1SX9PV?E"R=\
MY9N)?.>'"6[[CI_\XK=KD7PW^>.N\6ZY8Z999IMCKGGF6V"A119;8JEEEEMA
M,&+42JNLML9:ZZRWP4:;;+;%5MMLM\-.N^RVQU[[['?`08<<=L11QQQWPDFG
3G';&6>><=\%%EUQVQ=7_+3E$L0``
EOT

<<'EOT',    # Bit 3: cp1253
M>)PUB><WEG$`AB_=[9+,$/J];])&2QE-39+VWKN4T2(-7^A4I]-`DI:.G=<.
M12)-H_ZCYSR]7YS[7!^NZP8\&(48S1C&,H[Q3&`BDYB,)U/P8BK>^."+'_X$
M,(U`@@AF.B&$$L8,#`Z<S"2<640PFSG,91[S6<!"(HDBFD4L9@E+648,RUE!
M+''$D\!*5K&:-:QE'8FL9P,;V<1FMI!$,EM)81NI;&<'.]G%;O:PEWWLYP`'
M.<1ACG"48QSG!"<YQ6G.<)9SG.<"%TGC$I=))X-,LKC"5:YQG1MDD\--<KG%
M;>YPESSCLBT3[%%HPDR$,<;AMF@WL;8U,A-H@DR("35.XVO\;,OA=+>XD9=R
M%>@>%512134UU%+GKO6X:*#1^--,"ZVT*9]V.NC4?3W00[KUB!X]UA,]U3,5
MJDC%>JX2O5"I7JI,K_1:;_16[U2N][:E"E6J2M6J4:WJ]$'U<JE!C6I2LUK4
MJC9]5+LZU*E/^JPN=>N+>O15O>K3-_7KNW[HIW[IM_YH0(,:TK#^ZI]M_0=K
#AH5\
EOT

<<'EOT',    # Bit 4: cp1254
M>)PER6-ST'$``.#GOVPLM]9OJY9KR\NV[99M^SOT";)MV[S,NVQ=]JWKNN?E
M@TB,##+*)+,LLLHFNQQRRB6W//+*)[]8!1142&%%%%5,<7%*B%=2D"!1*:65
MD:2L<LJKH*)**JNBJF0IJJFNAIIJJ:V.5'754U\##3726!---=-<"RVUTEH;
M;;737@<===)9%UUUTUT//?726Q]]]=/?``,-DF:P(88:9K@11AIEM#'&&F>\
M"2::9+(IIIIFNAEFFF6V.>::9[X%%H;UZ7]"L6A1B`])(82$F),A.4H+J5'7
M]#__A2*A:(@+)4)BB`T%8NXE)$:#0]VHV[^+9EELB:6666Z%E599;8VUUEEO
M@XTVV6R+K;;9;H>==MEMC[WVV>^`@PXY[(BCCCGNA)-..>V,L\XY[X*++D7Q
M+KOBJFNNN^&F6VZ[XZY[47(TT'T///3(8T\\]<QS+[STRFMOO/7.^ZBD#S[Z
6Y+,OOOKFNQ]^^N5WE!(-DOX7C-.`8```
EOT

<<'EOT',    # Bit 5: cp1255
M>)Q%C]=7#@`<AI\^7F04"FV_[T-#1G9EE%U66UG9>X;L\3_X"Z)3*4E[G)9Q
MCFLR(MDK71'GN/K.)\[I.,_=\]X\+^"%@T$,1@QA*,/P9C@C&,DH?/!E-&,8
MBQ_^C&,\$P@@D""""2&4,"9B.'$QB<E,(9P((HEB*M%,8SHSF$D,LYC-'.8R
MC_DL()8XXEG((A:SA`026<HREK."E:QB-4DDLX:UK&,]&T@AE332R2"3+#:2
M30Z;V,P6MK*-7+:S@YWL8C=[V,L^]G.`@QSB,$<XRC&.<X(\3G**T^1SAK.<
MXSP7N,@E+G.%JU;N<5N0US4+LW`S<SH>6DR_B?.X![``"[00"S67^9F_H]OI
MZG?Q`RL%7.<&A59&$<64<)-2.KE%.;>IX`Z55%%-#;7444\#C33QFV9::*6-
M=E6J2M6J4:WJ5*\&-:K)XU:S6M2J-K7KKN[IOA[HNWZH3S_UZW_97_1(C]6A
M)WJJ9WJN3KW02W7IE;KU6F_T5N_T7A_T49_T65_T53WZIMY_OWS,U^/^`\"V
"EBL`
EOT

<<'EOT',    # Bit 6: cp1256
M>)P%P;=/%``8A^'W$'\J'*`"2N>[`XX.1^?.1N_UL``*=A'L8$,4-^.J$R,1
M&Q$%%+N2N/@'N+@:%UN"BXDA./@\@(,@5A',:L0:UK*.$$)Q$D8X$:QG`QN)
M)(IH-K&9&&*)(YX$$DDB&<.%FQ122<-#.AEDDD4V.>221SY>"BBDB&)***6,
M<GSXV<)6MK&='5102175U%!+'?4TT$@3S;302AOM=-!)@"YVLHO=[*&;'GK9
MRS[ZZ&<_!SC((0YSA*,<8X#C##+$"4YRBM.<X2SG&&:$\US@(I>XS"A7&.,J
MUQCGNLUHW.(<MRW)/&;F"OIH7HV:SQ'0#4WHIF8MQF(MP1+-;9$6I6F76[?,
M[^@RIX7I#9-R<H<I[G*/^SS@(=-:Y!$S/.8)L\PQSU.>L<!S7O"25[Q6/&]Y
MQWL6E:P/<LFM%*4J31ZE*T.9RE*V<I2K/.7+JP(5JDC%*E$IGU6F<OGD5X4J
M5:5JOJB&KZI5G>K5P#>^\X.?_%*CFECBMYK5HE:U\4?MZN"O.EE6@!7^6;A%
'Z--_?PY;L```
EOT

<<'EOT',    # Bit 7: cp1257
M>)P-B4=/%&``1-^L%`M86(1=FM\N106E]R)2;6`7%"P@%CI([_X5HT8E8N]1
M.5GBV2@22;P("6<C>G"S?H<W\S(#"`>K"""0(()9S1K6LHX00EG/!C:RB3"<
MA+.9"")QX2:*:&*()8XM&#QXB2>!1)+8RC:VDTP*.]A)*FFDDT$F66230RYY
MY%-`(4444\(N2ME-&>544$D5U>QA+_O8SP%JJ.4@ASC,$8YRC..<H(YZ3G**
M!AHYS1G.<HXFFCE/"Q>XR"4NTTH;[73021?=]-#+%?KH9X!!AAAFA%'&&&>"
M2::X:F;\/A-EB3-)QAB/M71+@=_''<='7EIW&;>),;'&:YPFW._S>.U6:/\'
MCN\VKUFN<X.;MF]QFV],JXZ[S'"/^WS@(8]XS!.>\HSGO.`OKU3/:][PEEF6
M%*!4H2#>\5XNA2F$3QI5A+S*5;**U*0*5?%9-7SA*W/J4YD:U,Z"QC7)#P4J
M35(P/UF46TZ%LJPQ12I>>4I1L9I5J6I^J9;?K/!'_2I7HSKXIPE-.>;_`YNL
";XP`
EOT

<<'EOT',    # Bit 8: cp1258
M>)PER>5.EF$`@.'KY;,[,%#1!U3L[NYNL+N[$_L</`*PN[MUBC5U3F>R&;.=
M.J>S&>K<_>^Z$8D3DT=>^>170$&%%%9$4<445T))I906KXRRRBDO004559*H
MLBJ"),FJJJ:Z%#745$MM==153WT---1(8TTTU4QS+;342FMMM-5.>QUTU$EG
M773537<]]-1+;WWTU4]_`PPT2*HT@PTQU###C3#2**.-,=8XXTTPT22333'5
M--/-,-,LL\TQUSSS+;#0(HLML=0RZ99;8:555EMC;=B9FQ,J1.M"Y9`20DB*
MRPH-_TJK*#4WYW^A?$@(E4)B2`ZE0WQ<=E+R7VL=I?U[4;H,F=;;8*---MMB
MJVVVVV&G77;;8Z]]]CO@H$,..^*H8XX[X:133COCK'/.1W$NN"C+)9==<=6U
M&-?=<#,JX5:LH-ON1!GNNN>^!QYZ)#O:$XMY[(FGGD4QS[WPTBNOO?'6NUCD
@O0\^1B5]BE7UV9<HTU???/?#3[_\CO:&'7+_`."G?R``
EOT

undef,      # Bit 9: Reserved
undef,      # Bit 10: Reserved
undef,      # Bit 11: Reserved
undef,      # Bit 12: Reserved
undef,      # Bit 13: Reserved
undef,      # Bit 14: Reserved
undef,      # Bit 15: Reserved

<<'EOT',    # Bit 16: cp874
M>)Q=C=52&U``!1?W11(@@00N`8*[4\'=W>KN[OHK_8#^8J=]Z.0!9E_.S,Z<
M!;+()H=<\LBG@$****:$4LJ0<BJHI(H(4:JIH988<>JH)T&2!AH)-)&BF19:
M2=-&.QUTTD4W/?321S\###+$,".,,L8X$TQRB<M<X2I33#/#+'/,L\`B2RRS
MPBIKK+/!)EMLL\,N>^QSP"%'''/"*6=<XSHWN,DM;G.'N]SC/@]XR",>\X2G
M/.,Y+WC)*U[SAK>\XST?^,@G/O.%KWSC.S_X&7[__?.?D,ZL\X18B(=$2(94
MB(3H1<LOL\PVQUSSS+?`0HLLML12R]1R*ZRTRHA1JZVQUIAQZZPW8=(&&PTV
MF;+9%EM-VV:['7;:9;<]]MIGOP,..N2P(XXZYK@33F;J3CGMC+/..>^"BRZY
@[(JKKKGNAIMNN>V.N^ZY[X&''GGLB:>>91[^`:-3=+0`
EOT

undef,      # Bit 17: Reserved
undef,      # Bit 18: Reserved
undef,      # Bit 19: Reserved
undef,      # Bit 20: Reserved
undef,      # Bit 21: Reserved
undef,      # Bit 22: Reserved
undef,      # Bit 23: Reserved
undef,      # Bit 24: Reserved
undef,      # Bit 25: Reserved
undef,      # Bit 26: Reserved
undef,      # Bit 27: Reserved
undef,      # Bit 28: Reserved
undef,      # Bit 29: Reserved
undef,      # Bit 30: Reserved
undef,      # Bit 31: Reserved
undef,      # Bit 32: Reserved
undef,      # Bit 33: Reserved
undef,      # Bit 34: Reserved
undef,      # Bit 35: Reserved
undef,      # Bit 36: Reserved
undef,      # Bit 37: Reserved
undef,      # Bit 38: Reserved
undef,      # Bit 39: Reserved
undef,      # Bit 40: Reserved
undef,      # Bit 41: Reserved
undef,      # Bit 42: Reserved
undef,      # Bit 43: Reserved
undef,      # Bit 44: Reserved
undef,      # Bit 45: Reserved
undef,      # Bit 46: Reserved
undef,      # Bit 47: Reserved

<<'EOT',    # Bit 48: cp869
M>)Q%R>=7#0`<Q^&/OLB(0G%3N.K^LK*S0U;#+$5$1$141E967I03IZ-QB13=
M3EHJ&@C)WN(_ZN0<;YSGY0,,P@LQF"$,Q9MA#&<$(_%A%*/QQ8\QC&4<_@0P
MG@DX"&0B000SB<E,P<E40@C%A1'&-*8S@YG,(IS9S&$N\YC/`A82P2(6LX2E
M+&,Y*XAD):M8311K6,LZUK.!:&*()8Z-;&(S6]C*-N))8#N))+&#G22SB]VD
ML(>]I+*/_:1Q@(.D<XC#9'"$HV1RC.-DD4T.)SC)*4Z3RQG.<H[S7""/BUSB
M,E>X2OY`_W^Z/M!/-ZTT.!W.0!4Y`W1#-_58Q?_VEEIH5@F==*F5.K7IB9[J
MITK5I]\J4[G<NJT[JM!=>G5/E;308V56;F[S,I?NJTK5>F#IEF#)EJJ'JC$_
M\[<("[=@PR+E4:VE6))E6H:E6;QEJ4Z/5*\&-:I)S>I0I[K,83Y69`5ZIN=V
M32_4K9=ZI=?JT1OUZJW>Z;T^Z),^ZK,*:*-#7_15WVC4=Q723I-^Z)?:]<<\
'>/X"GB5Z;@``
EOT

<<'EOT',    # Bit 49: cp866
M>)P%P45#$P``AN$7_!0#<=088]381XYN&-W=#:,;N]N#!P\>C#_`P1_@S;_G
M\P`II'(#<9-;I'&;.]SE'NG<)X,'!,@DBVQRR"5('B'R"5-`A$****:$4J*4
M$<.44T$E55130YQ:ZJBG@4:::*:%5MIHIX-.NN@F00^]]-'/`(,,,<P(HXPQ
MS@233#'-#+/,,<\"BRRQS`JKK+'.!IMLL<T.27;98Y\##CGBF!-..>.<"RZY
MXB&/>,P3GO*,Y[S@):]XS1O>\H[W?.`CG_BL@#*5I6SE*%=!Y2FD?(55H(@*
M5:1BE:A4494I)JM<%:I4E:I5H[AJ5:=Z-:A136I6BUK5IG9UJ%-=ZE9"/>I5
MGW_ZEW\[U3'O^\"K7O&A%[SF'6][RP'GN,5Q1XP33GK7FU[VN4^\YWE?^=1G
M/O*Q-[SN12_YTA<..=W?_-7?_<-?U*\!#6I(PQK1J,8TK@E-:DK3FM&LYI2B
:!4G+2M.:,I3D;S2??]%P:9`_ON;Z/\R_26T`
EOT

<<'EOT',    # Bit 50: cp865
M>)P%P3=,%&``AN$7?FR(>BH@W>,_/A%%;-BQ*V*G]W9P1[=W:0X.#@P(BR,&
M([$,8B0F1J.)B'%D,0XD*K$W@AHIDNCS`#[X8O!C$I.9PE2FX<]T`IC!3&;A
M8#9SF$L@000SCQ!""2.<""*)8CY.HK&XB$$L():%Q+&(Q<2SA`26LHSEK&`E
MB:QB-6M8RSK6LX$D-K*)S6QA*]O8S@YVDLPN4MC-'O:RC_T<X""II)%.!IED
MD4T.N>213P&%%%%,":6X*:,<#UXJJ*2*:FJHI8Y#'.8(1SG&<4YPDE.<Y@QG
M.<=YZFF@D2::><X$GQGD'6]XST>^\)5/#/&#;_3RC!=\H(_?_.$G?QGC'R\9
M8)1.7CF[?-IXRW=^,<XP_=SD`8]=#F[SB(=<Y1;7=%EM:I>O8N16F;*5I7*E
M*4=%*E2!'`I4HN(5(92D8I4H7YFJ5H5*E:HZ5:I*'GF5IURE*T.UJE&(`G1)
M%]6B5ETPW;PV[>:)Z31/Z3&]YKJY8FZ8>S;*])D>&V?==%NO];B<KFA&;`IW
0;"CW;9BSF;OJH.,_G\AQ]P``
EOT

<<'EOT',    # Bit 51: cp864
M>)P=R'EHSW$<Q_&G_7B[?AB&7;;YVL<Y]]S#S+&YS7W;?;GO<VY_"2%)2\8<
M:18C+<V2/R@W24N2)$G(%5/\7C_E\><#J$<(/NK3`*,AC6A,$YKBIQG-:4$H
M+6E%:\)H0UO:$4X$D40137MBB"6.#GAT)-Z*Z$1GNM"5;G0G@1[TI!>]Z4-?
M^I%(?P8PD$$,9@A#26(8PQE!,B-)812C&<-84DEC'..9P$0F,9DI3"6=:4QG
M!C.9Q6SF,)=YS&<!"UG$8C+()(ML<L@ECWP***2()2QE&<M9P4I6L9HUK&4=
MZ]G`1C:QF2ULI9AM;.<R55Z$%^D..UR(2W+Q+L%%NT07ZOPNS(7[KG@QOCM4
M4L,-+XURJE6GW\%`,*`_^AL,4$J%=E'&&>WYOP=T4$=UW/PZH5,Z:QF6:5F6
M;3F6:WF6;P56J*<6I4I=TW6+Y;1V:*=V:Z_N:9_VZY".Z)A*=%)E.J\+*E>%
M+NFJJE2MF[JEV[JOQYSC(G74ZJXEZYEJ]5*O]5;O]5&?]44_5*,'>J2'>J-B
<2]<[?=`G?=5W/=%S_=0OO=(+?7.EP<`_%"[`E0``
EOT

<<'EOT',    # Bit 52: cp863
M>)P%P4E0#7``Q_%O_9&E>!(M4J__ZQ<I%-E#MA9K^[Z]>J_=4I;L.3@X=$A=
M'!N9:#+3HF+&,@W&.L/%Q3##N%3,<&#&6`X^'\`'7PS3F,X,_)C)+&8S!W\"
MF,L\',PGD`4$L9!%!!-"*&$L)IPE1!")DR@L+J(1,2QE&;$L)XYX5K"25220
MR&K6D,1:UK&>#6QD$YM)9@M;V48*V]G!3G:QFU322">#/>QE'_LYP$$RR2*;
M''+)(Y\""BFBF!)**:.<"BIQ4T4U'KS44$L=]3302!.'.,P1CM),"\<XS@E.
MTLHI3G.&LYSC/!=HXQG_F.(SC_C$&!-\X2N3?.>;,YAQ;O""Y[SD)Z]XPU]^
M<YVW?.`:/;SCO4\GO8SP@S_<Y"ZW&>"UR\$M'G"?A_1S3U?4J2[Y*EIN52E?
M>:I6E@I4IE*5R*$@)2E>X4+)*E>%BI6K>M6H4IEJ4JWJY)%712I4MG+4J`:%
MR%^7=4GMZM!%,\1'TV7&38]YS*AY8GK-5=-G1FR$>6I&;:QU,V2]UN-RNJ+X
39=,9M*'<L6'.-H;53?=_2B5O-```
EOT

<<'EOT',    # Bit 53: cp862
M>)P%P3=,U'$<QN$/_/Q:44]%!40X_L>KHMBQ8V_8Z;T=W-'MO<+@X,"@L#BB
M1(UE$",QL403C;/8.V+7Q,$XF#CY/$`$D3CZ8?1G``,9Q&"&$,50AC$<'R,8
MR2BB&<T8QA)#+'&,(Y[Q))"(GR0\`B0C)C"12:0PF2FD,I5I3&<&,YG%;-*8
MPUSF,9\%+&01Z2QF"4M9QG)6L))5K&8-:\E@'>O9P$8VL9DM9))%-CGDDD<^
M!1121#$EE%)&.154$J2*:D*$J:&6.NIIH)$FMK*-[>Q@)[O8S1[VLH_]'.`@
MASC,$8YRC&9[9#WVV)[84WMFS^V%O;17]MK>V%M[9^^MUSY8GWVT3_;9OMA7
M^V;?[8?]Y"R=G/-?B&BCCU_\X1^_Z>$2-[D;\'&%.]SF#)>YI5-J4[LBE:R@
MJI2O/%4K2P4J4ZE*Y%.TTI2J>*%TE:M"Q<I5O6I4J4PUJ59U"BFL(A4J6SEJ
M5(-B%*43.JY6G52+ZZ+7M;M[KM/=I]L]<.?=:7?17?<2W$/7[:5X0;J\L!<*
8^`-)_/4RN.K%<L.+\S=S31UT_`?UB7$X
EOT

<<'EOT',    # Bit 54: cp861
M>)P%P4=(%7``Q_&O_FU;O4K+W?/__%6F;=NVRZRTW'L]?<_=WD/MT*&#!]-+
M1T,H)`\928<,C4SI$`2!1)@-VB($185F]/D`/OAB\&,2DYG"5*8QG1GX,Y-9
MS,;!'.8RCP`"F<\"@@@FA%#""">"A3B)Q.(B"K&(Q2PAFJ7$$,LREK."E:QB
M-6N(8RWK6,\&-K*)S<2SA:UL8SL[V,DN=K.'!/:2R#[V<X`DDCG((5)()8UT
M,L@DBVQRR"6/?`HHI(AB2G!32AD>O)1302555%-#+8<YPE&.<9P3G.04ISG#
M6<YQG@M<Y!)UU-/``'_YRGL^\):/?.8;(WSA.=\9IH\G/.43_?SD%_\8YS43
M##+$']IXZ;SET\P[1OG!&(]XQ@M>T>-RT,%#NKG!;1[HFIK5(E]%R:U292E3
M94I5M@I5H'PY%*`XQ2I,*%Y%*E:>,E2E<I4H1;6J4*4\\BI7.4I3NFI4K2#Y
MZZJNJ%%-NFPZ>6-:3*]I,X_I,GWFIKENVLT]&V'Z39>-MFXZK==Z7$Y7)+]M
2(G=L,/=MB+.!NVJE]3^&>7'F
EOT

<<'EOT',    # Bit 55: cp860
M>)P%P4E0#7``Q_&O_G8A*MKD]7_]4F2/0F07*NW[]NJ]=DOVO0X=.CB@BV,T
MPU@.931FJ"DR6:8.%0?&#.I@&0[(&,;!YP-,P`O#1"8QF2E,91K3F8$W,YG%
M;'R8PUQ\\<.?><PG@$"""":$!82R$`=A6)R$(R)81"11+&8)T2QE&<M9P4I6
ML9H8UK"66.)8QWHV$,]&-I'`9K:PE6UL9P<[V44BN]G#7I)()H5]I))&.AED
MDD4V.>221SX%%%)$,264XJ*,<MQXJ*"2*JJIH98Z]G.`@QRBGL,<X2C'.,X)
M3G**TYSA+.<X3P-/^<=G1AGC/8_XR!=>\(E!7O*5/A[SG%Z>,<XO?O"&/PSP
MBK=<HXW7CAN,\(%O_.0OWQGB%@_H89@[=-/%56[S4)=T62WR4KA<*E.VLE2N
M-.6H2(4JD(_\%*-HA0C%JU@ERE>FJE6A4J6J3I6JDEL>Y2E7Z<I0K6H4(&\U
MJTD7=%&-IH-WIL7TFC;31Z=Y8JZ;*^:FN6=#3;_IM%'618?U6+?3X0SCMTVD
1W09RWP8Y&KBK5EK_`R9#<9@`
EOT

<<'EOT',    # Bit 56: cp857
M>)P-RE-W%W```-#[7[:6E_O%Y67;7BUKM5HMVSWTT&OU!;)MVSR9YV3K9)_:
MZST7$5'22"N=]#+(*)/,LL@JF^QRR"F7W/*(EE<^^1504"&%Q2BBJ&***Z&D
M4DH+RBBKG/(JJ"A6)955454UU=40IZ9::JNCKGKJ:Z"A1AIKHJEFFFNAI59:
M:Z.M=MKKH*-..NNBJVZZB]=#3PEZZ:V/OOKI;X"!!AELB$1##9-DN!&2C31*
MBM'&&&N<\2:8:)+)IIAJFNEFF&F6V>:8Z[0_7GGLJ8>>>>&U-U[ZX'VDFA-.
M.NNY4[[XYI/??D:JNNF^'Y:['1D0&>B1=S[[Y:,KD:*18@[;9(.##EAJO?UA
M85@4%H>H4-I1QQRQ-B2&KB$A]+?,RI`S1(>X$!MB@M#`$\=#GQ`?DD-2&!2Z
MA!0K[+/..>>=^??719=<#@5"UK`@S+?*A3#/-0]<=]57-^Q,/;?<=<\=;_VS
<V0X;;4W50W99[;L]MEACM[VVVQ:66/(?I8M\J@``
EOT

<<'EOT',    # Bit 57: cp855
M>)P%P<52$``40-$+7$`%*>D&>3327=)("4A(HU@H=K<+%V[5'V#AC`O'+_#G
M/`=(()$D))D44CG#6<Z11CKGR2"3+++)X0*YY)%/`84444P)I9113@655%'-
M16H(:JFCG@8:::*9%B[12AOM=-!)%]WTT$L?_0PPR!##C##*9<889X))IIAF
MAEFN,,<\"RRRQ%6666&5:ZRQS@:;7&>+;7;898]]#CCD!C<YXA:WN<-=[G',
M?1YPPD,>\9@G/.49SWG!2U[QFC>\Y1WO^<!'/O'951.]9I(K)KBFKIOLABEN
MFNIUS[CE6;<]YXYI[IKNOAD>F.F2EYRST3:S;#?;:6OM,M=N\YRTQDXO\(=_
M\3U^Q,](C!JG#/LLC*-8B<W8L]^BR(K<Z(KF*`UBR`&+8SO6XCCNQ&$LQPF_
M'+3$(4L=MLP1RQV-PDB/;_'5"J_&%UN];*5C5CENM1->M,=\.\QQP>:J?/XZ
8;Y.]%CAKO8NV>,4&9ZSC=YQR^A\'$$.V
EOT

<<'EOT',    # Bit 58: cp852
M>)P%P=M/S@$<Q_'WY]?)X:F>%)Z.1%^BG',HG8M2#L^C)]63ZJFG(M+!N;,+
M%]WB'^C"QH:MBRYLKIBMF7'#*H=FS&9FN6%LIJW7"Q`.88030211+&,Y*UB)
MBVABB,5-'*N()X'5K&$M'A))(ID44DEC'>M)9P,;R<#8Q&8RV<)6LLAF&]O9
MP4YVL9L]Y+"7?>SG`+GD<9!\"BBDB&)***6,<@YQF`HJ.4(5U1SE&,<Y@1<?
M)ZG!3RVGJ*.>!@(T<IHFFFDA2"MMA&BG@T[.<)8NSG&>;B[00R]]]'.12USF
M"E>YQG4&&&2(848898QI_O.=+WQ5KZ+XIE)^R"L?/S7`<T7R0KG*XS=_5*!"
M!=3(#!_5K@Z5,*MH/K/`+_XI7!$:T:@\2N2A!N52"P]X8K?LMMTQQS)XRC,E
MJ=E"YK,Z:]*0ALUM"99CV99B6+X<A5G`_-9EG18TKW5S5W%R*X:7BE4%KWBM
M9/.8R\;MIMK48S=XPR?>JDSEJE10K?+S7K7J8Y%YA9CBD3/O?'"FG3GN\9?'
13'+?>:=^U:O!)IA8`NR$89P`
EOT

<<'EOT',    # Bit 59: cp775
M>)P%P<U3S`$<Q_'W)SUA4RJURY:?K6^HE(<46@\][")%D3:;V$3TX*D\]=RA
M0P<']`\T:C*&#!J,\7#"C+,)!S,NF'$VZ.#@]0)$%`N()H98XHAG(8M8C(L$
MEI!($DM))H54EI%&.FX\+&<%7C+(9"4.J_"1139&#JM9PUIRR2.?=110R'HV
ML)%-%+&98DK8PE:V48J?[>Q@)[LHHYP**@D09#=[V$L5^ZBFAOT<H)8Z#G*(
M>@[30(A&CA"FB:,T<XSC1&CA!*V<Y!1MG.8,[73021=G.<=Y+G"1;GJXQ&6N
M<)5K]-)'/P,,,L2P8OG'3XEORN*[XE2N%#4HI#SU\H:WO.<'[U3#'_FXK;":
M^,@7YIGD,Y^8$LKEE_HUH#XG@VGN\H#[O.*ERKC'"[MIMVS<HBQ;T7+)K31K
MM3H+6;,*%+$D2[4BRS>O87YUJ\/"5F_MUF81J[4N#2I&"?(H785J48\Z-61N
M<]F8C=IUNV$C?."KJE7!;^9XHDH5JT2E\BNH9`4<#S,\<KR\YBEW^.MD\M#G
.X1G/F>6Q33#Q'V@>75\`
EOT

<<'EOT',    # Bit 60: cp737
M>)P%P65W#@``AN';'FT8AFV&>>V1T]/3-;WN[M*=\\%QQMG!-IVOH]LFIKOK
M[_CJNH`.A"`ZTHG.=*$KW>A.#T+I22]Z$T8?^M*/</HS@(%$$$D4@XAF,$,8
M2@S#"#"<6,P(1C**T8QA+'&,8SP3F,@D)C.%>*8RC>G,8":SF$T"<YC+/.:S
M@(4L8C%+6,HR$EG."E:RBM6L82U)))-"*FFDDT$F66230RYYY%-`(4444T(I
M991302555%-#+76L8ST;V,@F-K.%K6QC.SO8R2YVLX>][&,_]6I2LUIT0B=U
M2J=U1F=U3N=U01=U29<5U!5=U35=UPW=U"W=5JO:]%A/]%3/U*[G>J&7>J77
M>J.W>J?W^J!/^JC/^J*O^J;O;G*S6QSB6!>[Q!E.=ZF3G>E\YSG780YWO.,<
M;9S@`A<ZQVFN=H6+G.0Z5[K*92YWMK.<XE37NL81#G6##[K1QWU`/W1/]_5`
M/_50O_5'O_17A]2@PSJB1AW5,5H#Y8$RW=%=_@42>12(I#T0%5-/FX,$_P-K
#(&Q0
EOT

undef,      # Bit 61: cp708 (unknown)

<<'EOT',    # Bit 62: cp850
M>)P%P5-C$&```,#;<BW;VI>UC&5[JV6WW++=0P^]5G\@V[9MV[;M.T2(E$QR
M*:242FIII)5.E/0RR"B3S++(*IOL<L@IE]SRR"N?_`HHJ)#"BB@J6E!,<264
M5$II99153GD5Q*BHDLJJJ*J:ZFJHJ998M=515SWU-=!0(XTUT50SS;704BNM
MM=%6G'CMM)>@@XXZZ:R+KKKIKH>>>NFMC[X2]=/?``,-,M@00R499K@11AIE
MM#'&&F>\"2::9+(IIIIFNAF.^>V%AQZ[[XEG7GKEN7?>>NVP(TYXZJC/OOKH
MEQ_^N>JV[Q:Y[EK$7`^\\<E/'URPTD[[K+7:'KLML,JN,"?,#?-"9(AVP$'[
MK0B)(3YT"CTLM"1D"ME"Y5`FY`M"K$<.A:XA(0P.`T+O$!>2+/;>>2>=<CPB
MQAEGG0NY0E28'699ZG28Z9)[+KOHBRNV^.NNFVZYX8\[UMELC0V%<MIKJV6^
2V6Z]Y;;989.-8;[Y_P%EF7O'
EOT

<<'EOT',    # Bit 63: cp437
M>)P%P3=,%&``AN$7?FR(>BH@(.#Q'Y^*(C;LV!LJ*+VW@SNZO1?`P<&!`6%Q
M1`D8RR!&8H(03$2,HXN3B4#LC5@B`4GT>0`??#'X,8G)3&$JT_!G.@',8":S
M<#";.<PED"""F4<(H80QGW`BB&0!3J*PN(A&+&01BXEA"4N)91EQ+&<%*UG%
M:N)9PUK6L9X-;&03"6QF"UO9QG9VL)-=[&8/>TED'_LY0!+)'.00*:221CH9
M9))%-CGDDD<^!1121#$EN"FE#`]>RJF@DBJJJ:&6PQSA*,<XS@E.<HK3G.$L
MYSC/!2YRB3KJ:>`Y$WQBF+<,\HX/?.8+'QGA.U_IYQDO>,\`O_G#3_XRQC]>
M\9J;M-'NO.73S!#?^,4X/WC)';KI<SFX1R\]W.`NCW5-S6J1KZ+E5JFRE*DR
MI2I;A2I0OAP*5+QB%2Z4H"(5*T\9JE*Y2I2B6E6H4AYYE:L<I2E=-:I6B`)T
M55?4J"9=-IV\,2WFB6DS3^DR_:;#7#>WS4,;:09,EXVQ;CJMUWI<3E<4HS:1
1^S:41S;,V<`#M=+Z'T-Z<84`
EOT

); 

# The following taken directly from OT Spec:

@ranges = (
    [ 0,    "Basic Latin",  "0000-007F" ],
    [ 1,    "Latin-1 Supplement",   "0080-00FF" ],
    [ 2,    "Latin Extended-A",     "0100-017F" ],
    [ 3,    "Latin Extended-B",     "0180-024F" ],
    [ 4,    "IPA Extensions",   "0250-02AF" ],
    [ 4,    "Phonetic Extensions",  "1D00-1D7F" ],
    [ 4,    "Phonetic Extensions Supplement",   "1D80-1DBF" ],
    [ 5,    "Spacing Modifier Letters",     "02B0-02FF" ],
    [ 5,    "Modifier Tone Letters",    "A700-A71F" ],
    [ 6,    "Combining Diacritical Marks",  "0300-036F" ],
    [ 6,    "Combining Diacritical Marks Supplement",   "1DC0-1DFF" ],
    [ 7,    "Greek and Coptic",     "0370-03FF" ],
    [ 8,    "Coptic",   "2C80-2CFF" ],
    [ 9,    "Cyrillic",     "0400-04FF" ],
    [ 9,    "Cyrillic Supplement",  "0500-052F" ],
    [ 9,    "Cyrillic Extended-A",  "2DE0-2DFF" ],
    [ 9,    "Cyrillic Extended-B",  "A640-A69F" ],
    [ 10,   "Armenian",     "0530-058F" ],
    [ 11,   "Hebrew",   "0590-05FF" ],
    [ 12,   "Vai",  "A500-A63F" ],
    [ 13,   "Arabic",   "0600-06FF" ],
    [ 13,   "Arabic Supplement",    "0750-077F" ],
    [ 14,   "NKo",  "07C0-07FF" ],
    [ 15,   "Devanagari",   "0900-097F" ],
    [ 16,   "Bengali",  "0980-09FF" ],
    [ 17,   "Gurmukhi",     "0A00-0A7F" ],
    [ 18,   "Gujarati",     "0A80-0AFF" ],
    [ 19,   "Oriya",    "0B00-0B7F" ],
    [ 20,   "Tamil",    "0B80-0BFF" ],
    [ 21,   "Telugu",   "0C00-0C7F" ],
    [ 22,   "Kannada",  "0C80-0CFF" ],
    [ 23,   "Malayalam",    "0D00-0D7F" ],
    [ 24,   "Thai",     "0E00-0E7F" ],
    [ 25,   "Lao",  "0E80-0EFF" ],
    [ 26,   "Georgian",     "10A0-10FF" ],
    [ 26,   "Georgian Supplement",  "2D00-2D2F" ],
    [ 27,   "Balinese",     "1B00-1B7F" ],
    [ 28,   "Hangul Jamo",  "1100-11FF" ],
    [ 29,   "Latin Extended Additional",    "1E00-1EFF" ],
    [ 29,   "Latin Extended-C",     "2C60-2C7F" ],
    [ 29,   "Latin Extended-D",     "A720-A7FF" ],
    [ 30,   "Greek Extended",   "1F00-1FFF" ],
    [ 31,   "General Punctuation",  "2000-206F" ],
    [ 31,   "Supplemental Punctuation",     "2E00-2E7F" ],
    [ 32,   "Superscripts And Subscripts",  "2070-209F" ],
    [ 33,   "Currency Symbols",     "20A0-20CF" ],
    [ 34,   "Combining Diacritical Marks For Symbols",  "20D0-20FF" ],
    [ 35,   "Letterlike Symbols",   "2100-214F" ],
    [ 36,   "Number Forms",     "2150-218F" ],
    [ 37,   "Arrows",   "2190-21FF" ],
    [ 37,   "Supplemental Arrows-A",    "27F0-27FF" ],
    [ 37,   "Supplemental Arrows-B",    "2900-297F" ],
    [ 37,   "Miscellaneous Symbols and Arrows",     "2B00-2BFF" ],
    [ 38,   "Mathematical Operators",   "2200-22FF" ],
    [ 38,   "Supplemental Mathematical Operators",  "2A00-2AFF" ],
    [ 38,   "Miscellaneous Mathematical Symbols-A",     "27C0-27EF" ],
    [ 38,   "Miscellaneous Mathematical Symbols-B",     "2980-29FF" ],
    [ 39,   "Miscellaneous Technical",  "2300-23FF" ],
    [ 40,   "Control Pictures",     "2400-243F" ],
    [ 41,   "Optical Character Recognition",    "2440-245F" ],
    [ 42,   "Enclosed Alphanumerics",   "2460-24FF" ],
    [ 43,   "Box Drawing",  "2500-257F" ],
    [ 44,   "Block Elements",   "2580-259F" ],
    [ 45,   "Geometric Shapes",     "25A0-25FF" ],
    [ 46,   "Miscellaneous Symbols",    "2600-26FF" ],
    [ 47,   "Dingbats",     "2700-27BF" ],
    [ 48,   "CJK Symbols And Punctuation",  "3000-303F" ],
    [ 49,   "Hiragana",     "3040-309F" ],
    [ 50,   "Katakana",     "30A0-30FF" ],
    [ 50,   "Katakana Phonetic Extensions",     "31F0-31FF" ],
    [ 51,   "Bopomofo",     "3100-312F" ],
    [ 51,   "Bopomofo Extended",    "31A0-31BF" ],
    [ 52,   "Hangul Compatibility Jamo",    "3130-318F" ],
    [ 53,   "Phags-pa",     "A840-A87F" ],
    [ 54,   "Enclosed CJK Letters And Months",  "3200-32FF" ],
    [ 55,   "CJK Compatibility",    "3300-33FF" ],
    [ 56,   "Hangul Syllables",     "AC00-D7AF" ],
    [ 57,   "Non-Plane 0 * ",   "D800-DFFF" ],
    [ 58,   "Phoenician",   "10900-1091F" ],
    [ 59,   "CJK Unified Ideographs",   "4E00-9FFF" ],
    [ 59,   "CJK Radicals Supplement",  "2E80-2EFF" ],
    [ 59,   "Kangxi Radicals",  "2F00-2FDF" ],
    [ 59,   "Ideographic Description Characters",   "2FF0-2FFF" ],
    [ 59,   "CJK Unified Ideographs Extension A",   "3400-4DBF" ],
    [ 59,   "CJK Unified Ideographs Extension B",   "20000-2A6DF" ],
    [ 59,   "Kanbun",   "3190-319F" ],
    [ 60,   "Private Use Area (plane 0)",   "E000-F8FF" ],
    [ 61,   "CJK Strokes",  "31C0-31EF" ],
    [ 61,   "CJK Compatibility Ideographs",     "F900-FAFF" ],
    [ 61,   "CJK Compatibility Ideographs Supplement",  "2F800-2FA1F" ],
    [ 62,   "Alphabetic Presentation Forms",    "FB00-FB4F" ],
    [ 63,   "Arabic Presentation Forms-A",  "FB50-FDFF" ],
    [ 64,   "Combining Half Marks",     "FE20-FE2F" ],
    [ 65,   "Vertical Forms",   "FE10-FE1F" ],
    [ 65,   "CJK Compatibility Forms",  "FE30-FE4F" ],
    [ 66,   "Small Form Variants",  "FE50-FE6F" ],
    [ 67,   "Arabic Presentation Forms-B",  "FE70-FEFF" ],
    [ 68,   "Halfwidth And Fullwidth Forms",    "FF00-FFEF" ],
    [ 69,   "Specials",     "FFF0-FFFF" ],
    [ 70,   "Tibetan",  "0F00-0FFF" ],
    [ 71,   "Syriac",   "0700-074F" ],
    [ 72,   "Thaana",   "0780-07BF" ],
    [ 73,   "Sinhala",  "0D80-0DFF" ],
    [ 74,   "Myanmar",  "1000-109F" ],
    [ 75,   "Ethiopic",     "1200-137F" ],
    [ 75,   "Ethiopic Supplement",  "1380-139F" ],
    [ 75,   "Ethiopic Extended",    "2D80-2DDF" ],
    [ 76,   "Cherokee",     "13A0-13FF" ],
    [ 77,   "Unified Canadian Aboriginal Syllabics",    "1400-167F" ],
    [ 78,   "Ogham",    "1680-169F" ],
    [ 79,   "Runic",    "16A0-16FF" ],
    [ 80,   "Khmer",    "1780-17FF" ],
    [ 80,   "Khmer Symbols",    "19E0-19FF" ],
    [ 81,   "Mongolian",    "1800-18AF" ],
    [ 82,   "Braille Patterns",     "2800-28FF" ],
    [ 83,   "Yi Syllables",     "A000-A48F" ],
    [ 83,   "Yi Radicals",  "A490-A4CF" ],
    [ 84,   "Tagalog",  "1700-171F" ],
    [ 85,   "Hanunoo",  "1720-173F" ],
    [ 85,   "Buhid",    "1740-175F" ],
    [ 85,   "Tagbanwa",     "1760-177F" ],
    [ 85,   "Old Italic",   "10300-1032F" ],
    [ 86,   "Gothic",   "10330-1034F" ],
    [ 87,   "Deseret",  "10400-1044F" ],
    [ 88,   "Byzantine Musical Symbols",    "1D000-1D0FF" ],
    [ 88,   "Musical Symbols",  "1D100-1D1FF" ],
    [ 88,   "Ancient Greek Musical Notation",   "1D200-1D24F" ],
    [ 89,   "Mathematical Alphanumeric Symbols",    "1D400-1D7FF" ],
    [ 90,   "Private Use (plane 15)",   "FF000-FFFFD" ],
    [ 90,   "Private Use (plane 16)",   "100000-10FFFD" ],
    [ 91,   "Variation Selectors",  "FE00-FE0F" ],
    [ 91,   "Variation Selectors Supplement",   "E0100-E01EF" ],
    [ 92,   "Tags",     "E0000-E007F" ],
    [ 93,   "Limbu",    "1900-194F" ],
    [ 94,   "Tai Le",   "1950-197F" ],
    [ 95,   "New Tai Lue",  "1980-19DF" ],
    [ 96,   "Buginese",     "1A00-1A1F" ],
    [ 97,   "Glagolitic",   "2C00-2C5F" ],
    [ 98,   "Tifinagh",     "2D30-2D7F" ],
    [ 99,   "Yijing Hexagram Symbols",  "4DC0-4DFF" ],
    [ 100,  "Syloti Nagri",     "A800-A82F" ],
    [ 101,  "Linear B Syllabary",   "10000-1007F" ],
    [ 101,  "Linear B Ideograms",   "10080-100FF" ],
    [ 101,  "Aegean Numbers",   "10100-1013F" ],
    [ 102,  "Ancient Greek Numbers",    "10140-1018F" ],
    [ 103,  "Ugaritic",     "10380-1039F" ],
    [ 104,  "Old Persian",  "103A0-103DF" ],
    [ 105,  "Shavian",  "10450-1047F" ],
    [ 106,  "Osmanya",  "10480-104AF" ],
    [ 107,  "Cypriot Syllabary",    "10800-1083F" ],
    [ 108,  "Kharoshthi",   "10A00-10A5F" ],
    [ 109,  "Tai Xuan Jing Symbols",    "1D300-1D35F" ],
    [ 110,  "Cuneiform",    "12000-123FF" ],
    [ 110,  "Cuneiform Numbers and Punctuation",    "12400-1247F" ],
    [ 111,  "Counting Rod Numerals",    "1D360-1D37F" ],
    [ 112,  "Sundanese",    "1B80-1BBF" ],
    [ 113,  "Lepcha",   "1C00-1C4F" ],
    [ 114,  "Ol Chiki",     "1C50-1C7F" ],
    [ 115,  "Saurashtra",   "A880-A8DF" ],
    [ 116,  "Kayah Li",     "A900-A92F" ],
    [ 117,  "Rejang",   "A930-A95F" ],
    [ 118,  "Cham",     "AA00-AA5F" ],
    [ 119,  "Ancient Symbols",  "10190-101CF" ],
    [ 120,  "Phaistos Disc",    "101D0-101FF" ],
    [ 121,  "Carian",   "102A0-102DF" ],
    [ 121,  "Lycian",   "10280-1029F" ],
    [ 121,  "Lydian",   "10920-1093F" ],
    [ 122,  "Domino Tiles",     "1F030-1F09F" ],
    [ 122,  "Mahjong Tiles",    "1F000-1F02F" ],
    [ 123-127,  "Reserved",     "" ],
);

}


1;

=head1 BUGS

None known

=head1 AUTHOR

Martin Hosken L<http://scripts.sil.org/FontUtils>. 


=head1 LICENSING

Copyright (c) 1998-2016, SIL International (http://www.sil.org) 

This module is released under the terms of the Artistic License 2.0. 
For details, see the full text of the license in the file LICENSE.



=cut