package PDF::API2::Resource::CIDFont::TrueType::FontFile;

use base 'PDF::API2::Basic::PDF::Dict';

use strict;
no warnings qw[ recursion uninitialized ];

our $VERSION = '2.038'; # VERSION

use Carp;
use Encode qw(:all);
use Font::TTF::Font;
use POSIX qw(ceil floor);

use PDF::API2::Util;
use PDF::API2::Basic::PDF::Utils;

our $cmap = {};

sub _look_for_cmap {
    my $fname=lc(shift);
    $fname=~s/[^a-z0-9]+//gi;
    return({%{$cmap->{$fname}}}) if(defined $cmap->{$fname});
    eval "require 'PDF/API2/Resource/CIDFont/CMap/$fname.cmap'";
    unless($@){
        return({%{$cmap->{$fname}}});
    } else {
        die "requested cmap '$fname' not installed ";
    }
}

sub readcffindex
{
    my ($fh,$off,$buf)=@_;
    my @idx=();
    my $index=[];
    seek($fh,$off,0);
    read($fh,$buf,3);
    my ($count,$offsize)=unpack('nC',$buf);
    foreach (0..$count)
    {
        read($fh,$buf,$offsize);
        $buf=substr("\x00\x00\x00$buf",-4,4);
        my $id=unpack('N',$buf);
        push @idx,$id;
    }
    my $dataoff=tell($fh)-1;

    foreach my $i (0..$count-1)
    {
        push @{$index},{ 'OFF' => $dataoff+$idx[$i], 'LEN' => $idx[$i+1]-$idx[$i] };
    }
    return($index);
}

sub readcffdict
{
    my ($fh,$off,$len,$foff,$buf)=@_;
    my @idx=();
    my $dict={};
    seek($fh,$off,0);
    my @st=();
    while(tell($fh)<($off+$len))
    {
        read($fh,$buf,1);
        my $b0=unpack('C',$buf);
        my $v='';

        if($b0==12) # two byte commands
        {
            read($fh,$buf,1);
            my $b1=unpack('C',$buf);
            if($b1==0)
            {
                $dict->{Copyright}={ 'SID' => splice(@st,-1) };
            }
            elsif($b1==1)
            {
                $dict->{isFixedPitch}=splice(@st,-1);
            }
            elsif($b1==2)
            {
                $dict->{ItalicAngle}=splice(@st,-1);
            }
            elsif($b1==3)
            {
                $dict->{UnderlinePosition}=splice(@st,-1);
            }
            elsif($b1==4)
            {
                $dict->{UnderlineThickness}=splice(@st,-1);
            }
            elsif($b1==5)
            {
                $dict->{PaintType}=splice(@st,-1);
            }
            elsif($b1==6)
            {
                $dict->{CharstringType}=splice(@st,-1);
            }
            elsif($b1==7)
            {
                $dict->{FontMatrix}=[ splice(@st,-4) ];
            }
            elsif($b1==8)
            {
                $dict->{StrokeWidth}=splice(@st,-1);
            }
            elsif($b1==20)
            {
                $dict->{SyntheticBase}=splice(@st,-1);
            }
            elsif($b1==21)
            {
                $dict->{PostScript}={ 'SID' => splice(@st,-1) };
            }
            elsif($b1==22)
            {
                $dict->{BaseFontName}={ 'SID' => splice(@st,-1) };
            }
            elsif($b1==23)
            {
                $dict->{BaseFontBlend}=[ splice(@st,0) ];
            }
            elsif($b1==24)
            {
                $dict->{MultipleMaster}=[ splice(@st,0) ];
            }
            elsif($b1==25)
            {
                $dict->{BlendAxisTypes}=[ splice(@st,0) ];
            }
            elsif($b1==30)
            {
                $dict->{ROS}=[ splice(@st,-3) ];
            }
            elsif($b1==31)
            {
                $dict->{CIDFontVersion}=splice(@st,-1);
            }
            elsif($b1==32)
            {
                $dict->{CIDFontRevision}=splice(@st,-1);
            }
            elsif($b1==33)
            {
                $dict->{CIDFontType}=splice(@st,-1);
            }
            elsif($b1==34)
            {
                $dict->{CIDCount}=splice(@st,-1);
            }
            elsif($b1==35)
            {
                $dict->{UIDBase}=splice(@st,-1);
            }
            elsif($b1==36)
            {
                $dict->{FDArray}={ 'OFF' => $foff+splice(@st,-1) };
            }
            elsif($b1==37)
            {
                $dict->{FDSelect}={ 'OFF' => $foff+splice(@st,-1) };
            }
            elsif($b1==38)
            {
                $dict->{FontName}={ 'SID' => splice(@st,-1) };
            }
            elsif($b1==39)
            {
                $dict->{Chameleon}=splice(@st,-1);
            }
            next;
        }
        elsif($b0<28) # commands
        {
            if($b0==0)
            {
                $dict->{Version}={ 'SID' => splice(@st,-1) };
            }
            elsif($b0==1)
            {
                $dict->{Notice}={ 'SID' => splice(@st,-1) };
            }
            elsif($b0==2)
            {
                $dict->{FullName}={ 'SID' => splice(@st,-1) };
            }
            elsif($b0==3)
            {
                $dict->{FamilyName}={ 'SID' => splice(@st,-1) };
            }
            elsif($b0==4)
            {
                $dict->{Weight}={ 'SID' => splice(@st,-1) };
            }
            elsif($b0==5)
            {
                $dict->{FontBBX}=[ splice(@st,-4) ];
            }
            elsif($b0==13)
            {
                $dict->{UniqueID}=splice(@st,-1);
            }
            elsif($b0==14)
            {
                $dict->{XUID}=[splice(@st,0)];
            }
            elsif($b0==15)
            {
                $dict->{CharSet}={ 'OFF' => $foff+splice(@st,-1) };
            }
            elsif($b0==16)
            {
                $dict->{Encoding}={ 'OFF' => $foff+splice(@st,-1) };
            }
            elsif($b0==17)
            {
                $dict->{CharStrings}={ 'OFF' => $foff+splice(@st,-1) };
            }
            elsif($b0==18)
            {
                $dict->{Private}={ 'LEN' => splice(@st,-1), 'OFF' => $foff+splice(@st,-1) };
            }
            next;
        }
        elsif($b0==28) # int16
        {
            read($fh,$buf,2);
            $v=unpack('n',$buf);
            $v=-(0x10000-$v) if($v>0x7fff);
        }
        elsif($b0==29) # int32
        {
            read($fh,$buf,4);
            $v=unpack('N',$buf);
            $v=-$v+0xffffffff+1 if($v>0x7fffffff);
        }
        elsif($b0==30) # float
        {
            my $e=1;
            while($e)
            {
                read($fh,$buf,1);
                my $v0=unpack('C',$buf);
                foreach my $m ($v0>>8,$v0&0xf)
                {
                    if($m<10)
                    {
                        $v.=$m;
                    }
                    elsif($m==10)
                    {
                        $v.='.';
                    }
                    elsif($m==11)
                    {
                        $v.='E+';
                    }
                    elsif($m==12)
                    {
                        $v.='E-';
                    }
                    elsif($m==14)
                    {
                        $v.='-';
                    }
                    elsif($m==15)
                    {
                        $e=0;
                        last;
                    }
                }
            }
        }
        elsif($b0==31) # command
        {
            $v="c=$b0";
            next;
        }
        elsif($b0<247) # 1 byte signed
        {
            $v=$b0-139;
        }
        elsif($b0<251) # 2 byte plus
        {
            read($fh,$buf,1);
            $v=unpack('C',$buf);
            $v=($b0-247)*256+($v+108);
        }
        elsif($b0<255) # 2 byte minus
        {
            read($fh,$buf,1);
            $v=unpack('C',$buf);
            $v=-($b0-251)*256-$v-108;
        }
        push @st,$v;
    }

    return($dict);
}

sub read_kern_table
{
    my $font=shift @_;
    my $upem=shift @_;
    my $self=shift @_;
    my $fh=$font->{' INFILE'};
    my $data=undef;

    return(undef) unless($font->{kern});

    my $buf = undef;

    seek($fh,$font->{kern}->{' OFFSET'}+2,0);
    read($fh,$buf, 2);
    my $num=unpack('n',$buf);
    foreach my $n (1..$num)
    {
        read($fh, $buf, 6);
        my ($ver, $len, $cov) = unpack('n3', $buf);
        $len-=6;
        my $fmt=$cov>>8;
        if($fmt==0)
        {
            $data||={};
            read($fh, $buf, 8);
            my $nc = unpack('n', $buf);
            foreach (1..$nc)
            {
                read($fh, $buf, 6);
                my ($idx1,$idx2,$val)=unpack('nnn',$buf);
                $val-=65536 if($val>32767);
                $val= $val < 0 ? -floor($val*1000/$upem) : -ceil($val*1000/$upem);
                if($val != 0)
                {
                    $data->{"$idx1:$idx2"}=$val;
                    $data->{$self->data->{g2n}->[$idx1].':'.$self->data->{g2n}->[$idx2]}=$val;
                }
            }
        }
        elsif($fmt==2)
        {
            read($fh, $buf, $len);
        }
        else
        {
            read($fh, $buf, $len);
        }
    }
    return($data);
}

sub readcffstructs
{
    my $font=shift @_;
    my $fh=$font->{' INFILE'};
    my $data={};
    # read CFF table
    seek($fh,$font->{'CFF '}->{' OFFSET'},0);
    my $buf;
    read($fh,$buf, 4);
    my ($cffmajor,$cffminor,$cffheadsize,$cffglobaloffsize)=unpack('C4',$buf);

    $data->{name}=readcffindex($fh,$font->{'CFF '}->{' OFFSET'}+$cffheadsize);
    foreach my $dict (@{$data->{name}})
    {
        seek($fh,$dict->{OFF},0);
        read($fh,$dict->{VAL},$dict->{LEN});
    }

    $data->{topdict}=readcffindex($fh,$data->{name}->[-1]->{OFF}+$data->{name}->[-1]->{LEN});
    foreach my $dict (@{$data->{topdict}})
    {
        $dict->{VAL}=readcffdict($fh,$dict->{OFF},$dict->{LEN},$font->{'CFF '}->{' OFFSET'});
    }

    $data->{string}=readcffindex($fh,$data->{topdict}->[-1]->{OFF}+$data->{topdict}->[-1]->{LEN});
    foreach my $dict (@{$data->{string}})
    {
        seek($fh,$dict->{OFF},0);
        read($fh,$dict->{VAL},$dict->{LEN});
    }
    push @{$data->{string}},{ 'VAL' => '001.000' };
    push @{$data->{string}},{ 'VAL' => '001.001' };
    push @{$data->{string}},{ 'VAL' => '001.002' };
    push @{$data->{string}},{ 'VAL' => '001.003' };
    push @{$data->{string}},{ 'VAL' => 'Black' };
    push @{$data->{string}},{ 'VAL' => 'Bold' };
    push @{$data->{string}},{ 'VAL' => 'Book' };
    push @{$data->{string}},{ 'VAL' => 'Light' };
    push @{$data->{string}},{ 'VAL' => 'Medium' };
    push @{$data->{string}},{ 'VAL' => 'Regular' };
    push @{$data->{string}},{ 'VAL' => 'Roman' };
    push @{$data->{string}},{ 'VAL' => 'Semibold' };

    foreach my $dict (@{$data->{topdict}})
    {
        foreach my $k (keys %{$dict->{VAL}})
        {
            my $dt=$dict->{VAL}->{$k};
            if($k eq 'ROS')
            {
                $dict->{VAL}->{$k}->[0]=$data->{string}->[$dict->{VAL}->{$k}->[0]-391]->{VAL};
                $dict->{VAL}->{$k}->[1]=$data->{string}->[$dict->{VAL}->{$k}->[1]-391]->{VAL};
                next;
            }
            next unless(ref($dt) eq 'HASH' && defined $dt->{SID});
            if($dt->{SID}>=379)
            {
                $dict->{VAL}->{$k}=$data->{string}->[$dt->{SID}-391]->{VAL};
            }
        }
    }
    my $dict={};
    foreach my $k (qw[ CIDCount CIDFontVersion FamilyName FontBBX FullName ROS Weight XUID ])
    {
        $dict->{$k}=$data->{topdict}->[0]->{VAL}->{$k} if(defined $data->{topdict}->[0]->{VAL}->{$k});
    }
    return($dict);
}

sub new {
    my ($class,$pdf,$file,%opts)=@_;
    my $data={};

    confess "cannot find font '$file'" unless -f $file;
    my $font=Font::TTF::Font->open($file);
    $data->{obj}=$font;

    $class = ref $class if ref $class;
    my $self=$class->SUPER::new();

    $self->{Filter}=PDFArray(PDFName('FlateDecode'));
    $self->{' font'}=$font;
    $self->{' data'}=$data;

	$data->{noembed} = $opts{-noembed}==1 ? 1 : 0;
    $data->{iscff} = (defined $font->{'CFF '}) ? 1 : 0;

    $self->{Subtype}=PDFName('CIDFontType0C') if($data->{iscff});

    $data->{fontfamily}=$font->{'name'}->read->find_name(1);
    $data->{fontname}=$font->{'name'}->read->find_name(4);

    $font->{'OS/2'}->read;
    my @stretch=qw[
        Normal
        UltraCondensed
        ExtraCondensed
        Condensed
        SemiCondensed
        Normal
        SemiExpanded
        Expanded
        ExtraExpanded
        UltraExpanded
    ];
    $data->{fontstretch}=$stretch[$font->{'OS/2'}->{usWidthClass}] || 'Normal';

    $data->{fontweight}=$font->{'OS/2'}->{usWeightClass};

    $data->{panose}=pack('n',$font->{'OS/2'}->{sFamilyClass});

    foreach my $p (qw[bFamilyType bSerifStyle bWeight bProportion bContrast bStrokeVariation bArmStyle bLetterform bMidline bXheight])
    {
        $data->{panose}.=pack('C',$font->{'OS/2'}->{$p});
    }

    $data->{apiname}=join('', map { ucfirst(lc(substr($_, 0, 2))) } split m/[^A-Za-z0-9\s]+/, $data->{fontname});
    $data->{fontname}=~s/[\x00-\x1f\s]//og;

    $data->{altname}=$font->{'name'}->find_name(1);
    $data->{altname}=~s/[\x00-\x1f\s]//og;

    $data->{subname}=$font->{'name'}->find_name(2);
    $data->{subname}=~s/[\x00-\x1f\s]//og;

    $font->{cmap}->read->find_ms($opts{-isocmap}||0);
    if(defined $font->{cmap}->find_ms)
    {
        $data->{issymbol} = ($font->{cmap}->find_ms->{'Platform'} == 3 && $font->{cmap}->read->find_ms->{'Encoding'} == 0) || 0;
    }
    else
    {
        $data->{issymbol} = 0;
    }

    $data->{upem}=$font->{'head'}->read->{'unitsPerEm'};

    $data->{fontbbox}=[
        int($font->{'head'}->{'xMin'} * 1000 / $data->{upem}),
        int($font->{'head'}->{'yMin'} * 1000 / $data->{upem}),
        int($font->{'head'}->{'xMax'} * 1000 / $data->{upem}),
        int($font->{'head'}->{'yMax'} * 1000 / $data->{upem})
    ];

    $data->{stemv}=0;
    $data->{stemh}=0;

    $data->{missingwidth}=int($font->{'hhea'}->read->{'advanceWidthMax'} * 1000 / $data->{upem}) || 1000;
    $data->{maxwidth}=int($font->{'hhea'}->{'advanceWidthMax'} * 1000 / $data->{upem});
    $data->{ascender}=int($font->{'hhea'}->read->{'Ascender'} * 1000 / $data->{upem});
    $data->{descender}=int($font->{'hhea'}{'Descender'} * 1000 / $data->{upem});

    $data->{flags} = 0;
    $data->{flags} |= 1 if ($font->{'OS/2'}->read->{'bProportion'} == 9);
    $data->{flags} |= 2 unless ($font->{'OS/2'}{'bSerifStyle'} > 10 && $font->{'OS/2'}{'bSerifStyle'} < 14);
    $data->{flags} |= 8 if ($font->{'OS/2'}{'bFamilyType'} == 2);
    $data->{flags} |= 32; # if ($font->{'OS/2'}{'bFamilyType'} > 3);
    $data->{flags} |= 64 if ($font->{'OS/2'}{'bLetterform'} > 8);;

    $data->{capheight}=$font->{'OS/2'}->{CapHeight} || int($data->{fontbbox}->[3]*0.8);
    $data->{xheight}=$font->{'OS/2'}->{xHeight} || int($data->{fontbbox}->[3]*0.4);

    if($data->{issymbol})
    {
        $data->{e2u}=[0xf000 .. 0xf0ff];
    }
    else
    {
        $data->{e2u}=[ unpack('U*',decode('cp1252', pack('C*',0..255))) ];
    }

    if(($font->{'post'}->read->{FormatType} == 3) && defined($font->{cmap}->read->find_ms))
    {
        $data->{g2n} = [];
        foreach my $u (sort {$a<=>$b} keys %{$font->{cmap}->read->find_ms->{val}})
        {
            my $n=nameByUni($u);
            $data->{g2n}->[$font->{cmap}->read->find_ms->{val}->{$u}]=$n;
        }
    }
    else
    {
        $data->{g2n} = [ map { $_ || '.notdef' } @{$font->{'post'}->read->{'VAL'}} ];
    }

    $data->{italicangle}=$font->{'post'}->{italicAngle};
    $data->{isfixedpitch}=$font->{'post'}->{isFixedPitch};
    $data->{underlineposition}=$font->{'post'}->{underlinePosition};
    $data->{underlinethickness}=$font->{'post'}->{underlineThickness};

    if($self->iscff)
    {
        $data->{cff}=readcffstructs($font);
    }

    if(defined $data->{cff}->{ROS})
    {
        my %cffcmap=(
            'Adobe:Japan1'=>'japanese',
            'Adobe:Korea1'=>'korean',
            'Adobe:CNS1'=>'traditional',
            'Adobe:GB1'=>'simplified',
        );
        my $ccmap=_look_for_cmap($cffcmap{"$data->{cff}->{ROS}->[0]:$data->{cff}->{ROS}->[1]"});
        $data->{u2g}=$ccmap->{u2g};
        $data->{g2u}=$ccmap->{g2u};
    }
    else
    {
        $data->{u2g} = {};

        my $gmap=$font->{cmap}->read->find_ms->{val};
        foreach my $u (sort {$a<=>$b} keys %{$gmap})
        {
            my $uni=$u||0;
            $data->{u2g}->{$uni}=$gmap->{$uni};
        }
        $data->{g2u}=[ map { $_ || 0 } $font->{'cmap'}->read->reverse ];
    }

    if($data->{issymbol})
    {
        map { $data->{u2g}->{$_} ||= $font->{'cmap'}->read->ms_lookup($_) } (0xf000 .. 0xf0ff);
        map { $data->{u2g}->{$_ & 0xff} ||= $font->{'cmap'}->read->ms_lookup($_) } (0xf000 .. 0xf0ff);
    }

    $data->{e2n}=[ map { $data->{g2n}->[$data->{u2g}->{$_} || 0] || '.notdef' } @{$data->{e2u}} ];

    $data->{e2g}=[ map { $data->{u2g}->{$_ || 0} || 0 } @{$data->{e2u}} ];
    $data->{u2e}={};
    foreach my $n (reverse 0..255)
    {
        $data->{u2e}->{$data->{e2u}->[$n]}=$n unless(defined $data->{u2e}->{$data->{e2u}->[$n]});
    }

    $data->{u2n}={ map { $data->{g2u}->[$_] => $data->{g2n}->[$_] } (0 .. (scalar @{$data->{g2u}} -1)) };

    $data->{wx}=[];
    foreach my $w (0..(scalar @{$data->{g2u}}-1))
    {
        $data->{wx}->[$w]=int($font->{'hmtx'}->read->{'advance'}[$w]*1000/$data->{upem})
            || $data->{missingwidth};
    }

    $data->{kern}=read_kern_table($font,$data->{upem},$self);
    delete $data->{kern} unless(defined $data->{kern});

    $data->{fontname}=~s/\s+//og;
    $data->{fontfamily}=~s/\s+//og;
    $data->{apiname}=~s/\s+//og;
    $data->{altname}=~s/\s+//og;
    $data->{subname}=~s/\s+//og;

    $self->subsetByCId(0);

    return($self,$data);
}

sub font { return( $_[0]->{' font'} ); }
sub data { return( $_[0]->{' data'} ); }
sub iscff { return( $_[0]->data->{iscff} ); }

sub haveKernPairs { return( $_[0]->data->{kern} ? 1 : 0 ); }

sub kernPairCid
{
    my ($self, $i1, $i2) = @_;
    return(0) if($i1==0 || $i2==0);
    return($self->data->{kern}->{"$i1:$i2"} || 0);
}

sub subsetByCId
{
    my $self = shift @_;
    my $g = shift @_;
    $self->data->{subset}=1;
    vec($self->data->{subvec},$g,1)=1;
    return if($self->iscff);
    if(defined $self->font->{loca}->read->{glyphs}->[$g]) {
        $self->font->{loca}->read->{glyphs}->[$g]->read;
        map { vec($self->data->{subvec},$_,1)=1; } $self->font->{loca}->{glyphs}->[$g]->get_refs;
    }
}

sub subvec {
    my $self = shift @_;
    return(1) if($self->iscff);
    my $g = shift @_;
    return(vec($self->data->{subvec},$g,1));
}

sub glyphNum { return ( $_[0]->font->{'maxp'}->read->{'numGlyphs'} ); }

sub outobjdeep {
    my ($self, $fh, $pdf) = @_;

    my $f = $self->font;

    if($self->iscff) {
        $f->{'CFF '}->read_dat;
        $self->{' stream'} = $f->{'CFF '}->{' dat'};
    } else {
        if ($self->data->{subset} && !$self->data->{nosubset}) {
            $f->{'glyf'}->read;
            for (my $i = 0; $i < $self->glyphNum; $i++) {
                next if($self->subvec($i));
                $f->{'loca'}{'glyphs'}->[$i] = undef;
            #    print STDERR "$i,";
            }
        }

		if($self->data->{noembed} != 1)
		{
        	$self->{' stream'} = "";
        	my $ffh;
        	CORE::open($ffh, '+>', \$self->{' stream'});
        	binmode($ffh,':raw');
        	$f->out($ffh, 'cmap', 'cvt ', 'fpgm', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'prep');
        	$self->{'Length1'}=PDFNum(length($self->{' stream'}));
        	CORE::close($ffh);
		}
    }

    $self->SUPER::outobjdeep($fh, $pdf);
}


1;