#!/usr/bin/perl

use strict;
use Font::TTF::Scripts::Volt;
use Data::Dumper;
use Pod::Usage;
use Getopt::Std;

# don't forget Font::TTF::GDEF

our $DEBUG = 0;
my %opts;
my $VERSION;
our $CHAIN_CALL;
our ($if, $of);

$VERSION = 0.01;    # MJPH      11-OCT-2007     First release

unless ($CHAIN_CALL)
{
    getopts('a:d:hit:x:', \%opts);

    unless (defined $ARGV[1] || defined $opts{h})
    {
        pod2usage(1);
        exit;
    }

    if ($opts{h})
    {
        pod2usage( -verbose => 2, -noperldoc => 1);
        exit;
    }

    $if = Font::TTF::Scripts::Volt->read_font($ARGV[0], $opts{a}) || die "Can't read font $ARGV[0]";
    foreach my $t (qw(GPOS GSUB GDEF))
    { delete $if->{'font'}{$t}; }
}

Font::TTF::Scripts::Volt::main($if, %opts);

unless ($CHAIN_CALL)
{ $if->{'font'}->update->out($ARGV[1]) || die "Can't write to font file $ARGV[1]. Do you have it installed?" unless ($DEBUG > 1);}

if ($opts{'d'} || $DEBUG)
{
    foreach (qw(GSUB GPOS))
    {
        delete $if->{'font'}{$_}{' PARENT'};
        print Dumper($if->{'font'}{$_});
    }
    print Dumper($if->{'voltdat'});
}

die $if->{'WARNINGS'} if $if->{'cWARNINGS'};

package Font::TTF::Scripts::Volt;

use Font::TTF::Coverage;
use Font::TTF::GSUB;
use Font::TTF::GPOS;
use Font::TTF::GDEF;
use IO::File;

my $TYPE2;      # To do: resolve when to generate FMT1 or FMT2 pair adjustments

sub main
{
    my ($fv, %opts) = @_;
    
    my ($volt_text, $font);
    
    $font = $fv->{'font'};
    
    if ($opts{'t'})
    {
        my ($inf) = IO::File->new("< $opts{'t'}") || die "Can't open file $opts{'t'}";
        while (<$inf>)
        { $volt_text .= $_; }
        $inf->close;
    }
    elsif (defined $font->{'TSIV'})
    { $volt_text = $font->{'TSIV'}->read->{' dat'}; }
    else
    { die "No VOLT table in the font, nothing to do"; }
    delete $font->{'TSIV'};        # remove the volt source

    if ($opts{'d'})
    {
        $::RD_HINT = 1;
        $::RD_TRACE = $opts{'d'} if ($opts{'d'} > 1);
    }

    $fv->{'voltdat'} = $fv->parse_volt($volt_text);

    $font->{'GSUB'} = Font::TTF::GSUB->new(PARENT => $font, read => 1) unless (defined $font->{'GSUB'});
    $font->{'GSUB'}{'Version'} = 1;
    $font->{'GPOS'} = Font::TTF::GPOS->new(PARENT => $font, read => 1) unless (defined $font->{'GPOS'});
    $font->{'GPOS'}{'Version'} = 1;
    $fv->features_ttf(%opts);
    foreach my $t (qw(GSUB GPOS))
    {
        $font->{$t}{'FEATURES'}{'FEAT_TAGS'} = [sort {$fv->{'featmap'}{$a} cmp $fv->{'featmap'}{$b}} @{$font->{$t}{'FEATURES'}{'FEAT_TAGS'}}];
        $fv->lookups_ttf($t);
        $font->{$t}->dirty;
    }
    $fv->add_gdef();
#    $font->{'OS/2'}->read->{'maxLookups'} = $fv->{'maxcontext'};
#    print STDERR "maxcontext=$fv->{'maxcontext'}\n";
    return unless ($opts{'x'});
    
    my ($xfh) = IO::File->new("> $opts{'x'}") || die "Can't open $opts{'x'} for writing";
    my (%feattags, %multis);
    $xfh->print("<?xml version='1.0' encoding='UTF-8'?>\n");
    $xfh->print("<aliases>\n");
    foreach my $t(qw(GSUB GPOS))
    {
        foreach my $f (@{$font->{$t}{'FEATURES'}{'FEAT_TAGS'}})
        {
            my ($val) = $fv->{'featmap'}{$f};
            if (defined $feattags{$val})
            {
                if ($feattags{$val} eq "")
                { $feattags{$val} = "0"; }
                else
                { $feattags{$val}++; }
                $val = "$val _$feattags{$val}";
            }
            else
            { $feattags{$val} = ""; }
            $xfh->print("    <alias name='$f' value='$val'/>\n");
        }
    }
    foreach my $l (@{$fv->{'voltdat'}{'lookups'}})
    {
        my ($id) = $l->{'id'};
        next unless ($id);
        my ($multi);
        if ($id =~ s/\\.*$//o)
        {
            if ($multis{$id})
            { next; }
            else
            { $multis{$id} = 1; }
        }
        $xfh->print("    <alias name='$id' value='$l->{' index'}'/>\n");
    }
    $xfh->print("</aliases>\n");
    $xfh->close;
}

sub features_ttf
{
    my ($self, %opts) = @_;
    my ($dat) = $self->{'voltdat'};
    my ($font) = $self->{'font'};
    my (%multis);
    my ($t, $s, $l, $ft, $k);    # Script tag, script struct, lang struct, feature tag, lookup name.

    # Lookups don't actually have tags (they are just numbered). Volt users give lookups names, however,
    # and these names link features to lookups, so we have to be able to find lookups by name.
    # So first time through this we create a hash to map lookup names to lookup:
    $dat->{'lookuptags'} = {map {$_->{'id'} => $_} @{$dat->{'lookups'}}} unless (defined $dat->{'lookuptags'});
    
    if ($opts{'i'})
    {
        foreach (@{$dat->{'lookups'}})
        { $_->{' include'} = 1; }
    }
    else
    {
        # There can be unused lookups in the VOLT source, i.e., lookups which aren't tied to a feature.
        # Mark the needed lookups by setting ' include' on the lookup.
        foreach $t (sort keys %{$dat->{'scripts'}}) # For each script name in VOLT source (in alpha order)
        {
            my ($s) = $dat->{'scripts'}{$t};
            foreach $l (@{$s->{'langs'}})    # For each VOLT lang within this script
            {
                foreach $ft (sort keys %{$l->{'features'}}) # For each VOLT feature tag within this lang
                {
                    foreach $k (@{$l->{'features'}{$ft}{'lookups'}}) # For each VOLT lookup name within this feature
                    { $dat->{'lookuptags'}{$k}{' include'} = 1; }
                }
            }
        }
    }

    # Calculate lookup indicies, assign to ' index'.
    # GSUB and GPOS lookups are numbered independently, starting at 0.
    # VOLT lookups can be grouped, e.g. lookup\0, lookup\1, lookup\2, to create sub-tables of a single OT lookup. 
    # If a lookup group is detected, set the ' sub' field on all but first of group.
    foreach $l (@{$dat->{'lookups'}})
    {
        next unless ($l->{' include'});
        my ($multi);
        if ($l->{'id'} =~ m/^([^\\]+)\\/o)
        { $multi = $1; }
        if (defined $multi && defined $multis{$multi})
        {
            $l->{' index'} = $multis{$multi};
            $l->{' sub'} = 1;
        }
        else
        {
            if ($l->{'lookup'}[0] eq 'sub')
            { $l->{' index'} = $dat->{'GSUB_counters'}++; }
            else
            { $l->{' index'} = $dat->{'GPOS_counters'}++; }
            $multis{$multi} = $l->{' index'} if (defined $multi);
        }
    }
    
    
    # Initialize OT script/lang/feature structure
    foreach $t (sort keys %{$dat->{'scripts'}})
    {
        my ($s) = $dat->{'scripts'}{$t};
        foreach $l (@{$s->{'langs'}})
        {
            foreach $ft (sort keys %{$l->{'features'}})
            {
                my ($type);
                foreach $k (@{$l->{'features'}{$ft}{'lookups'}})
                {
                    next if ($dat->{'lookuptags'}{$k}{' sub'});
                    if ($dat->{'lookuptags'}{$k}{'lookup'}[0] eq 'sub')
                    { $type = 'GSUB'; }
                    else
                    { $type = 'GPOS'; }
                    $self->append_feat($font, $s->{'tag'}, $l->{'tag'}, $ft, $dat->{'lookuptags'}{$k}{' index'}, $type);
                }
            }
        }
    }
}

sub append_feat
{
    my ($self, $font, $script, $lang, $feat, $lindex, $type) = @_;
    my ($fname) = "${feat}_${script}_${lang}";

    $self->{'featmap'}{$fname} = $feat;
    
    if ($lang eq 'dflt')
    { $font->{$type}{'SCRIPTS'}{$script}{'DEFAULT'} = {' REFTAG' => 'dflt'}; }

    unless (defined $font->{$type}{'SCRIPTS'}{$script}{$lang})
    {
        push (@{$font->{$type}{'SCRIPTS'}{$script}{'LANG_TAGS'}}, $lang);
    }

    unless (defined $font->{$type}{'FEATURES'}{$fname})
    {
        push (@{$font->{$type}{'FEATURES'}{'FEAT_TAGS'}}, $fname);
    }

    unless (grep {$_ eq $fname} @{$font->{$type}{'SCRIPTS'}{$script}{$lang}{'FEATURES'}})
    {
        push (@{$font->{$type}{'SCRIPTS'}{$script}{$lang}{'FEATURES'}}, $fname);
    }
    
    push (@{$font->{$type}{'FEATURES'}{$fname}{'LOOKUPS'}}, $lindex);
}

sub lookups_ttf
{
    my ($self, $type) = @_;
    my ($dat) = $self->{'voltdat'};
    my ($font) = $self->{'font'};
    my ($ltype) = lc($type);
    my ($clr, @clr);
    my ($l, $c, $lk, $g, $i, $e, $index);
    
    $ltype =~ s/^g//o;

    foreach $l (sort {$a->{' index'} <=> $b->{' index'}}
            grep {$_->{'lookup'}[0] eq $ltype && defined $_->{' index'} && $_->{' include'}}
            @{$dat->{'lookups'}})
    {
        my ($flags);
        $flags |= 1 if ($l->{'dir'} =~ /RTL/oi);
        $flags |= 2 if (!$l->{'base'});
        $flags |= 8 if ($l->{'marks'} =~ m/SKIP/oi);

        if ($l->{'all'} and $l->{'all'} ne 'ALL')
        {
            if (!defined $self->{'alls'}{$l->{'all'}})
            {
                $self->{'alls'}{$l->{'all'}} = ++$self->{'max_all'};
                foreach $g (@{$self->or_context_glyphs(@{$dat->{'groups'}{$l->{'all'}}})})
                { $dat->{'glyphs'}[$g]{'mark_class'} = $self->{'alls'}{$l->{'all'}}; }
            }
            $flags |= $self->{'alls'}{$l->{'all'}} << 8;
        }

        $font->{$type}{'LOOKUP'}[$l->{' index'}]{'FLAG'} = $flags;
        $font->{$type}{'LOOKUP'}[$l->{' index'}]{' index'} = $l->{' index'};
        if (defined $l->{'contexts'}[0][1])
        {
            $font->{$type}{'LOOKUP'}[$l->{' index'}]{'TYPE'} = $type eq 'GSUB' ? 6 : 8;
# use a context chaining subrule and then add a new lookup for the action
# always use format 3, it's simpler (same as VOLT does)
            if ($type eq 'GSUB')
            {
                $clr = {'MATCH' => [],
                        'ACTION' => [[0, $dat->{"${type}_counters"}]]};
                @clr = ({%$clr});
                foreach $lk (@{$l->{'lookup'}[1]})
                {
                    for ($i = 0; $i < @{$lk->[0]}; $i++)
                    {
                        my ($glyphs) = $self->scon_glyphs($lk->[0][$i]);
                        if (!defined $clr->{'MATCH'}[$i])
                        { $clr->{'MATCH'}[$i] = Font::TTF::Coverage->new(1); }
                        foreach (@{$glyphs})
                        { $clr->{'MATCH'}[$i]->add($_); }
                        $clr->{'MATCH'}[$i]->sort;
                    }
                }
            }
            else
            {
                @clr = ();
                foreach $lk (@{$l->{'lookup'}[1]})
                {
                    my (@match);
                    if ($lk->{'type'} eq 'ATTACH')
                    {
                        push (@match, $self->make_coverage(@{$lk->{'context'}}),
                                $self->make_coverage(@{$lk->{'to'}}));
                    }
                    elsif ($lk->{'type'} eq 'ADJUST_PAIR')
                    {
                        push (@match, $self->make_coverage(@{$lk->{'first'}}),
                                $self->make_coverage(@{$lk->{'second'}}));
                    }
                    elsif ($lk->{'type'} eq 'ATTACH_CURSIVE')
                    {
                        push (@match, $self->make_coverage(@{$lk->{'exits'}}),
                                $self->make_coverage(@{$lk->{'enters'}}));
                    }
                    else        # ADJUST_SINGLE
                    {
                        push (@match, $self->make_coverage(@{$lk->{'context'}}));
                    }
                    push (@clr, {MATCH => [@match], ACTION => [[0, $dat->{"${type}_counters"}]]});
                }
            }
            foreach $c (@{$l->{'contexts'}})        # multiple context subrules
            {
                my ($contlength);
                my ($clr1) = {};
                foreach $e (@{$c}[1..$#{$c}])
                { push (@{$clr1->{$e->[0] eq 'LEFT' ? 'PRE' : 'POST'}}, $self->make_coverage(@{$e}[1..$#{$e}])); }
                if (exists $clr1->{'PRE'} && @{$clr1->{'PRE'}})
                {
                    $clr1->{'PRE'} = [reverse (@{$clr1->{'PRE'}})];
                    $contlength = scalar @{$clr1->{'PRE'}};
                }
                $contlength += scalar @{$clr1->{'POST'}} if exists $clr1->{'POST'};

                foreach (@clr)
                {
                    if ($c->[0] =~ m/^EXCEPT/o)
                    {
                        # first the full context match that does nothing
                        push (@{$font->{$type}{'LOOKUP'}[$l->{' index'}]{'SUB'}}, {'FORMAT' => 3,
                            'MATCH_TYPE' => 'o',
                            'ACTION_TYPE' => 'l',
                            'RULES' => [[{'MATCH' => $_->{'MATCH'}, 'ACTION' => [], %$clr1}]]});
                        # then just match the core string and do the lookup
                        push (@{$font->{$type}{'LOOKUP'}[$l->{' index'}]{'SUB'}}, {'FORMAT' => 3,
                            'MATCH_TYPE' => 'o',
                            'ACTION_TYPE' => 'l',
                            'RULES' => [[{%$_}]]});
                    }
                    else
                    {
                        push (@{$font->{$type}{'LOOKUP'}[$l->{' index'}]{'SUB'}}, {'FORMAT' => 3,
                            'MATCH_TYPE' => 'o',
                            'ACTION_TYPE' => 'l',
                            'RULES' => [[{%$_, %$clr1}]]});
                    }
                }
                $contlength += scalar @{$clr->{'MATCH'}} if exists $clr->{'MATCH'};
                $self->{'maxcontext'} = $contlength if ($contlength > $self->{'maxcontext'});
#                print STDERR "cl=$contlength ($ltype $l->{' index'})\n";
            }
            $index = $dat->{"${type}_counters"}++;
        }
        else
        { $index = $l->{' index'}; }

        if ($type eq 'GSUB')
        { $self->add_gsub_lookup($index, $l); }
        else
        { $self->add_gpos_lookup($index, $l); }
    }
}

sub add_gsub_lookup
{
    my ($self, $index, $l) = @_;
    my ($dat) = $self->{'voltdat'};
    my ($font) = $self->{'font'};
    my ($s, $maxi, $maxo, @map, $sub, $i, $j);

# which of the 3 types of subst are we? 1:1, 1:many, many:1
    foreach $s (@{$l->{'lookup'}[1]})
    {
        my ($sm) = scalar @{$s->[1]};
        $maxo = $sm if ($sm > $maxo);
        my ($m) = scalar @{$s->[0]};
        $maxi = $m if ($m > $maxi);
    }
    $self->{'maxcontext'} = $maxi if ($maxi > $self->{'maxcontext'});
#    print STDERR "cl=$maxi (gsub $index)\n";


    if ($maxi == 1 && $maxo == 1)
    {
        $sub = {'ACTION_TYPE' => 'g', 'FORMAT' => 2, ' index' => $index};
        $font->{'GSUB'}{'LOOKUP'}[$index]{'TYPE'} = 1;
        $sub->{'COVERAGE'} = Font::TTF::Coverage->new(1);
        foreach $s (@{$l->{'lookup'}[1]})
        {
            my (@input) = @{$self->scon_glyphs($s->[0][0])};
            my (@output) = @{$self->scon_glyphs($s->[1][0])};
            for ($i = 0; $i < @input; $i++)
            {
                my ($j) = $sub->{'COVERAGE'}->add($input[$i]);
                push (@{$sub->{'RULES'}[$j][0]{'ACTION'}}, $output[$i]);
                if ($sub->{'ACTION_TYPE'} eq 'g' && scalar @{$sub->{'RULES'}[$j][0]{'ACTION'}} > 1)
                {
                    $font->{'GSUB'}{'LOOKUP'}[$index]{'TYPE'} = 3;
                    $sub->{'ACTION_TYPE'} = 'a';
                    $sub->{'FORMAT'} = 1;
                }
            }
        }
    }
    elsif ($maxi == 1)
    {
        $sub = {'ACTION_TYPE' => 'g', 'FORMAT' => 1, ' index' => $index};
        my ($i);
        $font->{'GSUB'}{'LOOKUP'}[$index]{'TYPE'} = 2;
        $sub->{'COVERAGE'} = Font::TTF::Coverage->new(1);

        foreach $s (@{$l->{'lookup'}[1]})
        {
            my ($input) = $self->scon_glyphs($s->[0][0]);
            my (@output) = $self->context_glyphs(@{$s->[1]});
            for ($i = 0; $i < @{$input}; $i++)
            { $sub->{'RULES'}[$sub->{'COVERAGE'}->add($input->[$i])][0]{'ACTION'} = $output[$i]; }
        }
    }
    else        # ligature - many:1
    {
        $sub = {'ACTION_TYPE' => 'g', 'MATCH_TYPE' => 'g', 'FORMAT' => 1, ' index' => $index};
        my (@input, @new_input);
        $font->{'GSUB'}{'LOOKUP'}[$index]{'TYPE'} = 4;
        $sub->{'COVERAGE'} = Font::TTF::Coverage->new(1);

        foreach $s (@{$l->{'lookup'}[1]})
        {
            my ($first) = $self->scon_glyphs($s->[0][0]);
            my (@all) = $self->context_glyphs(@{$s->[0]});
            my (@output) = $self->context_glyphs(@{$s->[1]});
            for ($i = 0; $i < @{$first}; $i++)
            {
                my (@i) = grep {$all[$_][0] == $first->[$i]}(0 .. $#all);
                foreach $j (@i)
                {
                    push (@{$sub->{'RULES'}[$sub->{'COVERAGE'}->add($first->[$i])]}, {'ACTION' => $output[$j], 'MATCH' => [@{$all[$j]}[1..$#{$all[$j]}]]});
                }
            }
        }
    }
    @map = $sub->{'COVERAGE'}->sort;
    $sub->{'RULES'} = [map {$sub->{'RULES'}[$map[$_]]} (0 .. @map-1)];
    push (@{$font->{'GSUB'}{'LOOKUP'}[$index]{'SUB'}}, $sub);
    return;
}


sub add_gpos_lookup
{
    my ($self, $index, $l) = @_;
    my ($dat) = $self->{'voltdat'};
    my ($font) = $self->{'font'};
    my ($s, $i, $g, $r, $c, $k, $contlength, @map);

    foreach $s (@{$l->{'lookup'}[1]})
    {
        if ($s->{'type'} eq 'ADJUST_SINGLE')
        {
            my ($cover) = Font::TTF::Coverage->new(1);
            my (@rules);
            $font->{'GPOS'}{'LOOKUP'}[$index]{'TYPE'} = 1;
            my ($sub) = {
                'FORMAT' => 2,
                'ACTION_TYPE' => 'v',
                'COVERAGE' => $cover};
            for ($i = 0; $i < @{$s->{'context'}}; $i++)
            {
                foreach $g (@{$self->scon_glyphs($s->{'context'}[$i])})
                {
                    next unless ($cover->add($g) > $#rules);
                    push (@rules, [{'ACTION' => [{make_value(%{$s->{'adj'}[$i]})}]}]);
                }
            }
            @map = $sub->{'COVERAGE'}->sort;
            $sub->{'RULES'} = [map {$rules[$map[$_]]} (0 .. @map-1)];
            push (@{$font->{'GPOS'}{'LOOKUP'}[$index]{'SUB'}}, $sub);
            $contlength = 1;
        }
        elsif ($TYPE2 && $s->{'type'} eq 'ADJUST_PAIR')
        {
            my ($cover1) = Font::TTF::Coverage->new(0);
            my ($cover2) = Font::TTF::Coverage->new(0);
            my ($cover) = Font::TTF::Coverage->new(1);
            my (@rules, @firsts, @seconds);
            $font->{'GPOS'}{'LOOKUP'}[$index]{'TYPE'} = 2;
            my ($sub) = {
                'FORMAT' => 2,
                'ACTION_TYPE' => 'p',
                'MATCH_TYPE' => 'g',
                'COVERAGE' => $cover,
                'CLASS' => $cover1,
                'MATCH' => [$cover2]};
            my ($c, $g, $count);
            for ($i = 0; $i < @{$s->{'first'}}; $i++)
            {
                foreach $g (@{$self->scon_glyphs($s->{'first'}[$i])})
                { $cover1->add($g, $i+1); }
            }
            foreach $g (sort {$a <=> $b} keys %{$cover1->{'val'}})
            { $cover->add($g); }
            for ($i = 0; $i < @{$s->{'second'}}; $i++)
            {
                foreach $g (@{$self->scon_glyphs($s->{'second'}[$i])})
                { $cover2->add($g, $i+1); }
            }
            foreach $r (@{$s->{'adj'}})
            {
                $sub->{'RULES'}[$r->[0]][$r->[1]]{'ACTION'} = 
                    [{make_value(%{$r->[2][0]})}, {make_value(%{$r->[2][1]})}];
            }
            push (@{$font->{'GPOS'}{'LOOKUP'}[$index]{'SUB'}}, $sub);
            $contlength = 2;
        }
        elsif ($s->{'type'} eq 'ADJUST_PAIR')
        {
            my (@rules);
            my ($cover) = Font::TTF::Coverage->new(1);
            $font->{'GPOS'}{'LOOKUP'}[$index]{'TYPE'} = 2;
            $font->{'GPOS'}{'LOOKUP'}[$index]{'SUB'} = [{
                'FORMAT' => 1,
                'ACTION_TYPE' => 'p',
                'MATCH_TYPE' => 'g',
                'COVERAGE' => $cover}];
            foreach $r (@{$s->{'adj'}})
            {
                my (@seconds) = @{$self->scon_glyphs($s->{'second'}[$r->[1]-1])};
                my ($action) = [{make_value(%{$r->[2][0]})}, {make_value(%{$r->[2][1]})}];
                foreach (@seconds)
                { push (@{$rules[$r->[0]-1]}, {'MATCH' => [$_], 'ACTION' => $action}); }
            }
            for ($i = 0; $i < @{$s->{'first'}}; $i++)
            {
                $rules[$i] = [sort {$a->{'MATCH'}[0] <=> $b->{'MATCH'}[0]} @{$rules[$i]}];
                foreach $g (@{$self->scon_glyphs($s->{'first'}[$i])})
                { $font->{'GPOS'}{'LOOKUP'}[$index]{'SUB'}[0]{'RULES'}[$cover->add($g)] = $rules[$i]; }
            }
            $contlength = 2;
        }
        elsif ($s->{'type'} eq 'ATTACH_CURSIVE')
        {
            my ($cover) = Font::TTF::Coverage->new(1);
            $font->{'GPOS'}{'LOOKUP'}[$index]{'TYPE'} = 3;
            my ($sub) = {
                    'FORMAT' => 1,
                    'ACTION_TYPE' => 'e',
                    'COVERAGE' => $cover};
            foreach $g (@{$self->or_context_glyphs(@{$s->{'enters'}})})
            { $sub->{'RULES'}[$cover->add($g)][0]{'ACTION'}[0] = make_anchor($dat->{'glyphs'}[$g]{'anchors'}{'entry'}[0]); }
            foreach $g (@{$self->or_context_glyphs(@{$s->{'exits'}})})
            { $sub->{'RULES'}[$cover->add($g)][0]{'ACTION'}[1] = make_anchor($dat->{'glyphs'}[$g]{'anchors'}{'exit'}[0]); }
            push (@{$font->{'GPOS'}{'LOOKUP'}[$index]{'SUB'}}, $sub);
            $contlength = 2;
        }
        elsif ($s->{'type'} eq 'ATTACH')
        {
            my ($basec) = Font::TTF::Coverage->new(1);
            my ($markc) = Font::TTF::Coverage->new(1);
            my ($acount, %anchors, @marks, @rules, $type);
            foreach $c (@{$s->{'to'}})
            {
                $anchors{$c->[1]} = $acount++ unless (defined $anchors{$c->[1]});
                foreach $g (@{$self->scon_glyphs($c->[0])})
                { $marks[$markc->add($g)] = [$anchors{$c->[1]}, make_anchor($dat->{'glyphs'}[$g]{'anchors'}{"MARK_$c->[1]"}[0])]; }
            }
            foreach $g (sort {$a <=> $b} @{$self->or_context_glyphs(@{$s->{'context'}})})
            {
                my ($i) = $basec->add($g);
                my $thistype = 
                    ($dat->{'glyphs'}[$g]{'type'} eq 'MARK') ? 6 :      # mark-to-mark
                    ($dat->{'glyphs'}[$g]{'type'} eq 'LIGATURE') ? 5 :  # mark-to-ligature
                    4;                                                  # mark-to-base
                if ($type)
                { $self->error("Mix of base character types in lookup $l->{'id'} at glyph $dat->{'glyphs'}[$g]{'name'}\n") if $type != $thistype; }
                else
                { $type = $thistype; }
                    
                foreach $k (sort {$anchors{$a} <=> $anchors{$b}} keys %anchors)
                {
                    if (defined $dat->{'glyphs'}[$g]{'anchors'}{$k})
                    { 
                        for my $comp (0 .. $#{$dat->{'glyphs'}[$g]{'anchors'}{$k}})
                        { 
                            if (defined $dat->{'glyphs'}[$g]{'anchors'}{$k}[$comp])
                            { $rules[$i][$comp]{'ACTION'}[$anchors{$k}] = make_anchor($dat->{'glyphs'}[$g]{'anchors'}{$k}[$comp]); }
                        }
                    }
                }
            }
            $font->{'GPOS'}{'LOOKUP'}[$index]{'TYPE'} = $type;
            push (@{$font->{'GPOS'}{'LOOKUP'}[$index]{'SUB'}}, {
                    'FORMAT' => 1,
                    'COVERAGE' => $basec,
                    'MATCH' => [$markc],
                    'MARKS' => [@marks],
                    'RULES' => [@rules]});
            $contlength = 2;
        }
    }
    $self->{'maxcontext'} = $contlength if ($contlength > $self->{'maxcontext'});
#    print STDERR "cl=$contlength (gpos $index\n";

}


sub add_gdef
{
    my ($self) = @_;
    my ($font) = $self->{'font'};
    my ($dat) = $self->{'voltdat'};
    my ($gdc) = Font::TTF::Coverage->new(0);       # glyph types
    my ($gdm) = Font::TTF::Coverage->new(0);
    my %types = (
        'BASE' => 1, 'LIGATURE' => 2, 'MARK' => 3, 'COMPONENT' => 4
    );
    my ($g);

    foreach $g (@{$dat->{'glyphs'}})
    {
        $gdc->add($g->{'gnum'}, $types{$g->{'type'}});
        $gdm->add($g->{'gnum'}, $g->{'mark_class'}) if (defined $g->{'mark_class'});
    }

    $font->{'GDEF'} = Font::TTF::GDEF->new('parent' => $font, 'read' => 1);
    $font->{'GDEF'}{'Version'} = 1.0;
    $font->{'GDEF'}{'GLYPH'} = $gdc;
    $font->{'GDEF'}{'MARKS'} = $gdm if ($gdm->{'max'} > 0);
}


=begin comment

Takes a lookup context list (as from IN_CONTEXT) and returns an array of flattened arrays of glyph ids that
map to the context list.

=end comment

=cut

sub context_glyphs
{
    my ($dat, @list) = @_;
    my (@input, @new_input, $l, $g);

    foreach $l (@list)
    {
        my ($glyphs) = $dat->scon_glyphs($l);
        my (@new_input) = ();
        foreach $g (@$glyphs)
        { push (@new_input, @input ? (map {[@$_, $g]} @input) : [$g]); }
        @input = @new_input;
    }
    return @input;
}


=begin comment

Takes a context list and returns a flattened array of glyph ids, duplicates removed
and sorted in order of appearance in the context list. (If a glyph appears multiple
times, the earliest one is kept).  [This is very un-VOLTish since order within a group
does not matter. Need to investigate. -bh]

=end comment

=cut

sub or_context_glyphs
{
    my ($dat, @list) = @_;
    my ($l, $g, %res, $c);

    foreach $l (@list)
    {
        my ($glyphs) = $dat->scon_glyphs($l);
        foreach $g (@{$glyphs})
        { $res{$g} = ++$c unless (defined $res{$g}); }
    }
    return [sort {$res{$a} <=> $res{$b}} keys %res];
}

=begin comment

Takes single C<context_item> and returns a array of glyph ids.
The array is normally sorted by glyph id, but not so in the
case of an ENUM -- this might be a bug.

=end comment

=cut

sub scon_glyphs
{
    my ($dat, $context) = @_;

    if ($context->[0] eq 'GLYPH')
    { return defined $context->[1] ? [$context->[1]] : warn("Undefined glyph"); }
    elsif ($context->[0] eq 'GROUP')
    { return defined $dat->{'voltdat'}{'groups'}{$context->[1]} ? [sort {$a <=> $b} @{$dat->or_context_glyphs(@{$dat->{'voltdat'}{'groups'}{$context->[1]}})}] : warn "Unknown glyph group $context->[1]"; }
    elsif ($context->[0] eq 'RANGE')
    { return [$context->[1] .. $context->[2]]; }
    elsif ($context->[0] eq 'ENUM')
    { return $dat->or_context_glyphs($context->[1]); }
    return [];
}

sub make_coverage
{
    my ($self, @contexts) = @_;
    my ($cover) = Font::TTF::Coverage->new(1);
    my ($c);

    foreach $c (@contexts)
    {
        my ($glyphs) = $self->scon_glyphs($c);

        foreach (@{$glyphs})
        { $cover->add($_); }
    }
    $cover->sort;
    return $cover;
}

sub make_value
{
    my (%pos) = @_;
    my (%res, $s);
    my %map = (
        'x' => ['XPlacement', 'XPlaDevice'],
        'y' => ['YPlacement', 'YPlaDevice'],
        'adv' => ['XAdvance', 'XAdvDevice']);

    foreach $s (qw(x y adv))
    {
        if (defined $pos{$s})
        {
            $res{$map{$s}[0]} = $pos{$s}[0];
            if (defined $pos{$s}[1])
            {
#                $res{$map{$s}[1]} = make_delta($pos{$s}[1]);
            }           
        }
    }
    %res
}

sub make_anchor
{
    my ($point) = @_;
    my (%res, $s, $r);
    
    return undef unless (defined $point->{'pos'});
    return ($point->{'anchor'}) if defined ($point->{'anchor'});

    foreach $s (qw(x y))
    { $res{$s} = $point->{'pos'}{$s}[0]; }
    $r = Font::TTF::Anchor->new(%res);
    $point->{'anchor'} = $r;
    return $r;
}


__END__

=head1 TITLE

volt2ttf - compiles volt code into OT tables in a font

=head1 SYNOPSIS

  volt2ttf [-a attach.xml] [-t volt.txt] infile.ttf outfile.ttf

Compiles volt source into OT tables in the font.

=head1 OPTIONS

  -a file     Attachment point database
  -i          Include all lookups even those not referenced by a used feature
  -t file     Volt source as text file to use instead of what is in the font
  -h          Help
  -x file     Generate TypeTuner aliases .xml file
    
=head1 DESCRIPTION

volt2ttf is like loading a font into VOLT and hitting compile and saving the
result. Note that it doesn't compile a new cmap, though. Just the OT tables
are built.

=cut
