<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package Font::TTF::Coverage;

=head1 NAME

Font::TTF::Coverage - Opentype coverage and class definition objects

=head1 DESCRIPTION

Coverage tables and class definition objects are virtually identical concepts
in OpenType. Their difference comes purely in their storage. Therefore we can
say that a coverage table is a class definition in which the class definition
for each glyph is the corresponding index in the coverage table. The resulting
data structure is that a Coverage table has the following fields:

=item cover

A boolean to indicate whether this table is a coverage table (TRUE) or a
class definition (FALSE)

=item val

A hash of glyph ids against values (either coverage index or class value)

=item fmt

The storage format used is given here, but is recalculated when the table
is written out.

=item count

A count of the elements in a coverage table for use with add. Each subsequent
addition is added with the current count and increments the count.

=head1 METHODS

=cut

=head2 new($isCover [, vals])

Creates a new coverage table or class definition table, depending upon the
value of $isCover. if $isCover then vals may be a list of glyphs to include in order.
If no $isCover, then vals is a hash of glyphs against class values.

=cut

sub new
{
    my ($class) = shift;
    my ($isCover) = shift;
    my ($self) = {};

    $self-&gt;{'cover'} = $isCover;
    $self-&gt;{'count'} = 0;
    if ($isCover)
    {
        my ($v);
        foreach $v (@_)
        { $self-&gt;{'val'}{$v} = $self-&gt;{'count'}++; }
    }
    else
    {
        $self-&gt;{'val'} = {@_};
        foreach (values %{$self-&gt;{'val'}}) {$self-&gt;{'max'} = $_ if $_ &gt; $self-&gt;{'max'}}
    }
    bless $self, $class;
}


=head2 read($fh)

Reads the coverage/class table from the given file handle

=cut

sub read
{
    my ($self, $fh) = @_;
    my ($dat, $fmt, $num, $i, $c);

    $fh-&gt;read($dat, 4);
    ($fmt, $num) = unpack("n2", $dat);
    $self-&gt;{'fmt'} = $fmt;

    if ($self-&gt;{'cover'})
    {
        if ($fmt == 1)
        {
            $fh-&gt;read($dat, $num &lt;&lt; 1);
            map {$self-&gt;{'val'}{$_} = $i++} unpack("n*", $dat);
        } elsif ($fmt == 2)
        {
            $fh-&gt;read($dat, $num * 6);
            for ($i = 0; $i &lt; $num; $i++)
            {
                ($first, $last, $c) = unpack("n3", substr($dat, $i * 6, 6));
                map {$self-&gt;{'val'}{$_} = $c++} ($first .. $last);
            }
        }
        $self-&gt;{'count'} = $num;
    } elsif ($fmt == 1)
    {
        $fh-&gt;read($dat, 2);
        $first = $num;
        ($num) = unpack("n", $dat);
        $fh-&gt;read($dat, $num &lt;&lt; 1);
        map {$self-&gt;{'val'}{$first++} = $_; $self-&gt;{'max'} = $_ if ($_ &gt; $self-&gt;{'max'})} unpack("n*", $dat);
    } elsif ($fmt == 2)
    {
        $fh-&gt;read($dat, $num * 6);
        for ($i = 0; $i &lt; $num; $i++)
        {
            ($first, $last, $c) = unpack("n3", substr($dat, $i * 6, 6));
            map {$self-&gt;{'val'}{$_} = $c} ($first .. $last);
            $self-&gt;{'max'} = $c if ($c &gt; $self-&gt;{'max'});
        }
    }
    $self;
}


=head2 out($fh, $state)

Writes the coverage/class table to the given file handle. If $state is 1 then
the output string is returned rather than being output to a filehandle.

=cut

sub out
{
    my ($self, $fh, $state) = @_;
    my ($g, $eff, $grp, $out);
    my ($shipout) = ($state ? sub {$out .= $_[0];} : sub {$fh-&gt;print($_[0]);});
    my (@gids) = sort {$a &lt;=&gt; $b} keys %{$self-&gt;{'val'}};

    $fmt = 1; $grp = 1; $eff = 0;
    for ($i = 1; $i &lt;= $#gids; $i++)
    {
        if ($self-&gt;{'val'}{$gids[$i]} &lt; $self-&gt;{'val'}{$gids[$i-1]} &amp;&amp; $self-&gt;{'cover'})
        {
            $fmt = 2;
            last;
        } elsif ($gids[$i] == $gids[$i-1] + 1 &amp;&amp; ($self-&gt;{'cover'} || $self-&gt;{'val'}{$gids[$i]} == $self-&gt;{'val'}{$gids[$i-1]}))
        { $eff++; }
        else
        {
            $grp++;
            $eff += $gids[$i] - $gids[$i-1] if (!$self-&gt;{'cover'});
        }
    }
#    if ($self-&gt;{'cover'})
    { $fmt = 2 if ($eff / $grp &gt; 3); }
#    else
#    { $fmt = 2 if ($grp &gt; 1); }
    
    if ($fmt == 1 &amp;&amp; $self-&gt;{'cover'})
    {
        my ($last) = 0;
        &amp;$shipout(pack('n2', 1, scalar @gids));
        &amp;$shipout(pack('n*', @gids));
    } elsif ($fmt == 1)
    {
        my ($last) = $gids[0];
        &amp;$shipout(pack("n3", 1, $last, $gids[-1] - $last + 1));
        foreach $g (@gids)
        {
            if ($g &gt; $last + 1)
            { &amp;$shipout(pack('n*', (0) x ($g - $last - 1))); }
            &amp;$shipout(pack('n', $self-&gt;{'val'}{$g}));
            $last = $g;
        }
    } else
    {
        my ($start, $end, $ind, $numloc, $endloc, $num);
        &amp;$shipout(pack("n2", 2, 0));
        $numloc = $fh-&gt;tell() - 2 unless $state;

        $start = 0; $end = 0; $num = 0;
        while ($end &lt; $#gids)
        {
            if ($gids[$end + 1] == $gids[$end] + 1
                &amp;&amp; $self-&gt;{'val'}{$gids[$end + 1]}
                        == $self-&gt;{'val'}{$gids[$end]}
                           + ($self-&gt;{'cover'} ? 1 : 0))
            {
                $end++;
                next;
            }

            &amp;$shipout(pack("n3", $gids[$start], $gids[$end],
                    $self-&gt;{'val'}{$gids[$start]}));
            $start = $end + 1;
            $end++;
            $num++;
        }
        &amp;$shipout(pack("n3", $gids[$start], $gids[$end],
                $self-&gt;{'val'}{$gids[$start]}));
        $num++;
        if ($state)
        { substr($out, 2, 2) = pack('n', $num); }
        else
        {
            $endloc = $fh-&gt;tell();
            $fh-&gt;seek($numloc, 0);
            $fh-&gt;print(pack("n", $num));
            $fh-&gt;seek($endloc, 0);
        }
    }
    return ($state ? $out : $self);
}


=head2 $c-&gt;add($glyphid[, $class])

Adds a glyph id to the coverage table incrementing the count so that each subsequent addition
has the next sequential number. Returns the index number of the glyphid added

=cut

sub add
{
    my ($self, $gid, $class) = @_;
    
    return $self-&gt;{'val'}{$gid} if (defined $self-&gt;{'val'}{$gid});
    if ($self-&gt;{'cover'})
    {
        $self-&gt;{'val'}{$gid} = $self-&gt;{'count'};
        return $self-&gt;{'count'}++;
    }
    else
    {
        $self-&gt;{'val'}{$gid} = $class || '0';
        $self-&gt;{'max'} = $class if ($class &gt; $self-&gt;{'max'});
        return $class;
    }
}


=head2 $c-&gt;signature

Returns a vector of all the glyph ids covered by this coverage table or class

=cut

sub signature
{
    my ($self) = @_;
    my ($vec, $range, $size);

if (0)
{
    if ($self-&gt;{'cover'})
    { $range = 1; $size = 1; }
    else
    {
        $range = $self-&gt;{'max'};
        $size = 1;
        while ($range &gt; 1)
        {
            $size = $size &lt;&lt; 1;
            $range = $range &gt;&gt; 1;
        }
        $range = $self-&gt;{'max'} + 1;
    }
    foreach (keys %{$self-&gt;{'val'}})
    { vec($vec, $_, $size) = $self-&gt;{'val'}{$_} &gt; $range ? $range : $self-&gt;{'val'}{$_}; }
    length($vec) . ":" . $vec;
}
    $vec = join(";", map{"$_,$self-&gt;{'val'}{$_}"} keys %{$self-&gt;{'val'}});
}

=head2 @map=$c-&gt;sort

Sorts the coverage table so that indexes are in ascending order of glyphid.
Returns a map such that $map[$new_index]=$old_index.

=cut

sub sort
{
    my ($self) = @_;
    my (@res, $i);

    foreach (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{'val'}})
    {
        push(@res, $self-&gt;{'val'}{$_});
        $self-&gt;{'val'}{$_} = $i++;
    }
    @res;
}

=head2 $c-&gt;out_xml($context)

Outputs this coverage/class in XML

=cut

sub out_xml
{
    my ($self, $context, $depth) = @_;
    my ($fh) = $context-&gt;{'fh'};

    $fh-&gt;print("$depth&lt;" . ($self-&gt;{'cover'} ? 'coverage' : 'class') . "&gt;\n");
    foreach $gid (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{'val'}})
    {
        $fh-&gt;printf("$depth$context-&gt;{'indent'}&lt;gref glyph='%s' val='%s'/&gt;\n", $gid, $self-&gt;{'val'}{$gid});
    }
    $fh-&gt;print("$depth&lt;/" . ($self-&gt;{'cover'} ? 'coverage' : 'class') . "&gt;\n");
    $self;
}

sub release
{ }


=head1 AUTHOR

Martin Hosken Martin_Hosken@sil.org. See L&lt;Font::TTF::Font&gt; for copyright and
licensing.

=cut

1;

</pre></body></html>