package PDF::API2::Util;

our $VERSION = '2.028'; # VERSION

no warnings qw[ recursion uninitialized ];

BEGIN {

    use Encode qw(:all);

    use vars qw(
        @ISA 
        @EXPORT 
        @EXPORT_OK 
        %colors 
        $key_var 
        %u2n 
        %n2u 
        $pua
        %PaperSizes
    );
    use Math::Trig;
    use List::Util qw(min max);
    use PDF::API2::Basic::PDF::Utils;
    use PDF::API2::Basic::PDF::Filter;
    use PDF::API2::Resource::Colors;
    use PDF::API2::Resource::Glyphs;
    use PDF::API2::Resource::PaperSizes;

    use POSIX qw( HUGE_VAL floor );

    use Exporter;
    @ISA = qw(Exporter);
    @EXPORT = qw(
        pdfkey
        float floats floats5 intg intgs
        mMin mMax
        HSVtoRGB RGBtoHSV HSLtoRGB RGBtoHSL RGBtoLUM
        namecolor namecolor_cmyk namecolor_lab optInvColor defineColor
        dofilter unfilter
        nameByUni uniByName initNameTable defineName
        page_size
        getPaperSizes
    );
    @EXPORT_OK = qw(
        pdfkey
        digest digestx digest16 digest32
        float floats floats5 intg intgs
        mMin mMax
        cRGB cRGB8 RGBasCMYK
        HSVtoRGB RGBtoHSV HSLtoRGB RGBtoHSL RGBtoLUM
        namecolor namecolor_cmyk namecolor_lab optInvColor defineColor
        dofilter unfilter
        nameByUni uniByName initNameTable defineName
        page_size
    );

    %colors = PDF::API2::Resource::Colors->get_colors();
    %PaperSizes = PDF::API2::Resource::PaperSizes->get_paper_sizes();

    no warnings qw[ recursion uninitialized ];

    $key_var='CBA';

    $pua=0xE000;

    %u2n = %{$PDF::API2::Resource::Glyphs::u2n};
    %n2u = %{$PDF::API2::Resource::Glyphs::n2u};
}

sub pdfkey {
    return($PDF::API2::Util::key_var++);
}

sub digestx {
    my $len=shift @_;
    my $mask=$len-1;
    my $ddata=join('',@_);
    my $mdkey='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789gT';
    my $xdata="0" x $len;
    my $off=0;
    my $set;
    foreach $set (0..(length($ddata)<<1)) {
        $off+=vec($ddata,$set,4);
        $off+=vec($xdata,($set & $mask),8);
        vec($xdata,($set & ($mask<<1 |1)),4)=vec($mdkey,($off & 0x7f),4);
    }

#   foreach $set (0..$mask) {
#       vec($xdata,$set,8)=(vec($xdata,$set,8) & 0x7f) | 0x40;
#   }

#   $off=0;
#   foreach $set (0..$mask) {
#       $off+=vec($xdata,$set,8);
#       vec($xdata,$set,8)=vec($mdkey,($off & 0x3f),8);
#   }

    return($xdata);
}

sub digest {
    return(digestx(32,@_));
}

sub digest16 {
    return(digestx(16,@_));
}

sub digest32 {
    return(digestx(32,@_));
}

sub xlog10 {
    my $n = shift;
    if($n) {
            return log(abs($n))/log(10);
    } else { return 0; }
}

sub float {
    my $f=shift @_;
    my $mxd=shift @_||4;
    $f=0 if(abs($f)<0.0000000000000001);
    my $ad=floor(xlog10($f)-$mxd);
    if(abs($f-int($f)) < (10**(-$mxd))) {
        # just in case we have an integer
        return sprintf('%i',$f);
    } elsif($ad>0){
        my $value = sprintf('%f',$f);
        # Remove trailing zeros
        $value =~ s/(\.\d*?)0+$/$1/;
        $value =~ s/\.$//;
        return $value;
    } else {
        my $value = sprintf('%.*f', abs($ad), $f);
        # Remove trailing zeros
        $value =~ s/(\.\d*?)0+$/$1/;
        $value =~ s/\.$//;
        return $value;
    }
}
sub floats { return map { float($_); } @_; }
sub floats5 { return map { float($_,5); } @_; }


sub intg {
    my $f=shift @_;
    return sprintf('%i',$f);
}
sub intgs { return map { intg($_); } @_; }

sub mMin {
    my $n=HUGE_VAL;
    map { $n=($n>$_) ? $_ : $n } @_;
    return($n);
}

sub mMax {
    my $n=-(HUGE_VAL);
    map { $n=($n<$_) ? $_ : $n } @_;
    return($n);
}

sub cRGB {
    my @cmy=(map { 1-$_ } @_);
    my $k=mMin(@cmy);
    return((map { $_-$k } @cmy),$k);
}

sub cRGB8 {
    return cRGB(map { $_/255 } @_);
}

sub RGBtoLUM {
    my ($r,$g,$b)=@_;
    return($r*0.299+$g*0.587+$b*0.114);
}

sub RGBasCMYK {
    my @rgb=@_;
    my @cmy=(map { 1-$_ } @rgb);
    my $k=mMin(@cmy)*0.44;
    return((map { $_-$k } @cmy),$k);
}

sub HSVtoRGB {
    my ($h,$s,$v)=@_;
    my ($r,$g,$b,$i,$f,$p,$q,$t);

    if( $s == 0 ) {
        ## achromatic (grey)
        return ($v,$v,$v);
    }

    $h %= 360;
    $h /= 60;       ## sector 0 to 5
    $i = POSIX::floor( $h );
    $f = $h - $i;   ## factorial part of h
    $p = $v * ( 1 - $s );
    $q = $v * ( 1 - $s * $f );
    $t = $v * ( 1 - $s * ( 1 - $f ) );

    if($i<1) {
        $r = $v;
        $g = $t;
        $b = $p;
    } elsif($i<2){
        $r = $q;
        $g = $v;
        $b = $p;
    } elsif($i<3){
        $r = $p;
        $g = $v;
        $b = $t;
    } elsif($i<4){
        $r = $p;
        $g = $q;
        $b = $v;
    } elsif($i<5){
        $r = $t;
        $g = $p;
        $b = $v;
    } else {
        $r = $v;
        $g = $p;
        $b = $q;
    }
    return ($r,$g,$b);
}
sub _HSVtoRGB { # test
    my ($h,$s,$v)=@_;
    my ($r,$g,$b,$i,$f,$p,$q,$t);

    if( $s == 0 ) {
        ## achromatic (grey)
        return ($v,$v,$v);
    }
    
    $h %= 360;
    
    $r = 2*cos(deg2rad($h));
    $g = 2*cos(deg2rad($h+120));
    $b = 2*cos(deg2rad($h+240));

    $p = max($r,$g,$b);
    $q = min($r,$g,$b);
    ($p,$q) = map { ($_<0 ? 0 : ($_>1 ? 1 : $_)) } ($p,$q);
    $f = $p - $q;
    
    #if($p>=$v) {
    #    ($r,$g,$b) = map { $_*$v/$p } ($r,$g,$b);
    #} else {
    #    ($r,$g,$b) = map { $_*$p/$v } ($r,$g,$b);
    #}
    #
    #if($f>=$s) {
    #    ($r,$g,$b) = map { (($_-$q/2)*$f/$s)+$q/2 } ($r,$g,$b);
    #} else {
    #    ($r,$g,$b) = map { (($_-$q/2)*$s/$f)+$q/2 } ($r,$g,$b);
    #}

    ($r,$g,$b) = map { ($_<0 ? 0 : ($_>1 ? 1 : $_)) } ($r,$g,$b);

    return ($r,$g,$b);
}

sub RGBquant ($$$) {
    my($q1,$q2,$h)=@_;
    while($h<0){$h+=360;}
    $h%=360;
    if ($h<60) {
        return($q1+(($q2-$q1)*$h/60));
    } elsif ($h<180) {
        return($q2);
    } elsif ($h<240) {
        return($q1+(($q2-$q1)*(240-$h)/60));
    } else {
        return($q1);
    }
}

sub RGBtoHSV {
    my ($r,$g,$b)=@_;
    my ($h,$s,$v,$min,$max,$delta);

    $min= mMin($r,$g,$b);
    $max= mMax($r,$g,$b);

    $v = $max;

    $delta = $max - $min;

    if( $delta > 0.000000001 ) {
        $s = $delta / $max;
    } else {
        $s = 0;
        $h = 0;
        return($h,$s,$v);
    }

    if( $r == $max ) {
        $h = ( $g - $b ) / $delta;
    } elsif( $g == $max ) {
        $h = 2 + ( $b - $r ) / $delta;
    } else {
        $h = 4 + ( $r - $g ) / $delta;
    }
    $h *= 60;
    if( $h < 0 ) {$h += 360;}
    return($h,$s,$v);
}

sub RGBtoHSL {
    my ($r,$g,$b)=@_;
    my ($h,$s,$v,$l,$min,$max,$delta);

    $min= mMin($r,$g,$b);
    $max= mMax($r,$g,$b);
    ($h,$s,$v)=RGBtoHSV($r,$g,$b);
    $l=($max+$min)/2.0;
        $delta = $max - $min;
    if($delta<0.00000000001){
        return(0,0,$l);
    } else {
        if($l<=0.5){
            $s=$delta/($max+$min);
        } else {
            $s=$delta/(2-$max-$min);
        }
    }
    return($h,$s,$l);
}

sub HSLtoRGB {
    my($h,$s,$l,$r,$g,$b,$p1,$p2)=@_;
    if($l<=0.5){
        $p2=$l*(1+$s);
    } else {
        $p2=$l+$s-($l*$s);
    }
    $p1=2*$l-$p2;
    if($s<0.0000000000001){
        $r=$l; $g=$l; $b=$l;
    } else {
        $r=RGBquant($p1,$p2,$h+120);
        $g=RGBquant($p1,$p2,$h);
        $b=RGBquant($p1,$p2,$h-120);
    }
    return($r,$g,$b);
}

sub optInvColor {
    my ($r,$g,$b) = @_;

    my $ab = (0.2*$r) + (0.7*$g) + (0.1*$b);

    if($ab > 0.45) {
        return(0,0,0);
    } else {
        return(1,1,1);
    }
}

sub defineColor {
    my ($name,$mx,$r,$g,$b)=@_;
    $colors{$name}||=[ map {$_/$mx} ($r,$g,$b) ];
    return($colors{$name});
}

sub rgbHexValues {
    my $name=lc(shift @_);
    my ($r,$g,$b);
    if(length($name)<5) {       # zb. #fa4,          #cf0
        $r=hex(substr($name,1,1))/0xf;
        $g=hex(substr($name,2,1))/0xf;
        $b=hex(substr($name,3,1))/0xf;
    } elsif(length($name)<8) {  # zb. #ffaa44,       #ccff00
        $r=hex(substr($name,1,2))/0xff;
        $g=hex(substr($name,3,2))/0xff;
        $b=hex(substr($name,5,2))/0xff;
    } elsif(length($name)<11) { # zb. #fffaaa444,    #cccfff000
        $r=hex(substr($name,1,3))/0xfff;
        $g=hex(substr($name,4,3))/0xfff;
        $b=hex(substr($name,7,3))/0xfff;
    } else {            # zb. #ffffaaaa4444, #ccccffff0000
        $r=hex(substr($name,1,4))/0xffff;
        $g=hex(substr($name,5,4))/0xffff;
        $b=hex(substr($name,9,4))/0xffff;
    }
    return($r,$g,$b);
}
sub cmykHexValues {
    my $name=lc(shift @_);
    my ($c,$m,$y,$k);
    if(length($name)<6) {       # zb. %cmyk
        $c=hex(substr($name,1,1))/0xf;
        $m=hex(substr($name,2,1))/0xf;
        $y=hex(substr($name,3,1))/0xf;
        $k=hex(substr($name,4,1))/0xf;
    } elsif(length($name)<10) { # zb. %ccmmyykk
        $c=hex(substr($name,1,2))/0xff;
        $m=hex(substr($name,3,2))/0xff;
        $y=hex(substr($name,5,2))/0xff;
        $k=hex(substr($name,7,2))/0xff;
    } elsif(length($name)<14) { # zb. %cccmmmyyykkk
        $c=hex(substr($name,1,3))/0xfff;
        $m=hex(substr($name,4,3))/0xfff;
        $y=hex(substr($name,7,3))/0xfff;
        $k=hex(substr($name,10,3))/0xfff;
    } else {            # zb. %ccccmmmmyyyykkkk
        $c=hex(substr($name,1,4))/0xffff;
        $m=hex(substr($name,5,4))/0xffff;
        $y=hex(substr($name,9,4))/0xffff;
        $k=hex(substr($name,13,4))/0xffff;
    }
    return($c,$m,$y,$k);
}
sub hsvHexValues {
    my $name=lc(shift @_);
    my ($h,$s,$v);
    if(length($name)<5) {
        $h=360*hex(substr($name,1,1))/0x10;
        $s=hex(substr($name,2,1))/0xf;
        $v=hex(substr($name,3,1))/0xf;
    } elsif(length($name)<8) {
        $h=360*hex(substr($name,1,2))/0x100;
        $s=hex(substr($name,3,2))/0xff;
        $v=hex(substr($name,5,2))/0xff;
    } elsif(length($name)<11) {
        $h=360*hex(substr($name,1,3))/0x1000;
        $s=hex(substr($name,4,3))/0xfff;
        $v=hex(substr($name,7,3))/0xfff;
    } else {
        $h=360*hex(substr($name,1,4))/0x10000;
        $s=hex(substr($name,5,4))/0xffff;
        $v=hex(substr($name,9,4))/0xffff;
    }
    return($h,$s,$v);
}
sub labHexValues {
    my $name=lc(shift @_);
    my ($l,$a,$b);
    if(length($name)<5) {
        $l=100*hex(substr($name,1,1))/0xf;
        $a=(200*hex(substr($name,2,1))/0xf)-100;
        $b=(200*hex(substr($name,3,1))/0xf)-100;
    } elsif(length($name)<8) {
        $l=100*hex(substr($name,1,2))/0xff;
        $a=(200*hex(substr($name,3,2))/0xff)-100;
        $b=(200*hex(substr($name,5,2))/0xff)-100;
    } elsif(length($name)<11) {
        $l=100*hex(substr($name,1,3))/0xfff;
        $a=(200*hex(substr($name,4,3))/0xfff)-100;
        $b=(200*hex(substr($name,7,3))/0xfff)-100;
    } else {
        $l=100*hex(substr($name,1,4))/0xffff;
        $a=(200*hex(substr($name,5,4))/0xffff)-100;
        $b=(200*hex(substr($name,9,4))/0xffff)-100;
    }
    return($l,$a,$b);
}

sub namecolor {
    my $name=shift @_;
    unless(ref $name) {
        $name=lc($name);
        $name=~s/[^\#!%\&\$a-z0-9]//go;
    }
    if($name=~/^[a-z]/) { # name spec.
        return(namecolor($colors{$name}));
    } elsif($name=~/^#/) { # rgb spec.
        return(floats5(rgbHexValues($name)));
    } elsif($name=~/^%/) { # cmyk spec.
        return(floats5(cmykHexValues($name)));
    } elsif($name=~/^!/) { # hsv spec.
        return(floats5(HSVtoRGB(hsvHexValues($name))));
    } elsif($name=~/^&/) { # hsl spec.
        return(floats5(HSLtoRGB(hsvHexValues($name))));
    } else { # or it is a ref ?
        return(floats5(@{$name || [0.5,0.5,0.5]}));
    }
}
sub namecolor_cmyk {
    my $name=shift @_;
    unless(ref $name) {
        $name=lc($name);
        $name=~s/[^\#!%\&\$a-z0-9]//go;
    }
    if($name=~/^[a-z]/) { # name spec.
        return(namecolor_cmyk($colors{$name}));
    } elsif($name=~/^#/) { # rgb spec.
        return(floats5(RGBasCMYK(rgbHexValues($name))));
    } elsif($name=~/^%/) { # cmyk spec.
        return(floats5(cmykHexValues($name)));
    } elsif($name=~/^!/) { # hsv spec.
        return(floats5(RGBasCMYK(HSVtoRGB(hsvHexValues($name)))));
    } elsif($name=~/^&/) { # hsl spec.
        return(floats5(RGBasCMYK(HSLtoRGB(hsvHexValues($name)))));
    } else { # or it is a ref ?
        return(floats5(RGBasCMYK(@{$name || [0.5,0.5,0.5]})));
    }
}
sub namecolor_lab {
    my $name=shift @_;
    unless(ref $name) {
        $name=lc($name);
        $name=~s/[^\#!%\&\$a-z0-9]//go;
    }
    if($name=~/^[a-z]/) { # name spec.
        return(namecolor_lab($colors{$name}));
    } elsif($name=~/^\$/) { # lab spec.
        return(floats5(labHexValues($name)));
    } elsif($name=~/^#/) { # rgb spec.
        my ($h,$s,$v)=RGBtoHSV(rgbHexValues($name));
        my $a=cos(deg2rad $h)*$s*100;
        my $b=sin(deg2rad $h)*$s*100;
        my $l=100*$v;
        return(floats5($l,$a,$b));
    } elsif($name=~/^!/) { # hsv spec.
        # fake conversion
        my ($h,$s,$v)=hsvHexValues($name);
        my $a=cos(deg2rad $h)*$s*100;
        my $b=sin(deg2rad $h)*$s*100;
        my $l=100*$v;
        return(floats5($l,$a,$b));
    } elsif($name=~/^&/) { # hsl spec.
        my ($h,$s,$v)=hsvHexValues($name);
        my $a=cos(deg2rad $h)*$s*100;
        my $b=sin(deg2rad $h)*$s*100;
        ($h,$s,$v)=RGBtoHSV(HSLtoRGB($h,$s,$v));
        my $l=100*$v;
        return(floats5($l,$a,$b));
    } else { # or it is a ref ?
        my ($h,$s,$v)=RGBtoHSV(@{$name || [0.5,0.5,0.5]});
        my $a=cos(deg2rad $h)*$s*100;
        my $b=sin(deg2rad $h)*$s*100;
        my $l=100*$v;
        return(floats5($l,$a,$b));
    }
}

sub unfilter 
{
    my ($filter,$stream)=@_;

    if(defined $filter) 
    {
        # we need to fix filter because it MAY be
        # an array BUT IT COULD BE only a name
        if(ref($filter)!~/Array$/) 
        {
               $filter = PDFArray($filter);
        }
        my @filts;
        my ($hasflate) = -1;
        my ($temp, $i, $temp1);

        @filts=(map { ("PDF::API2::Basic::PDF::Filter::".($_->val))->new } $filter->elementsof);

        foreach my $f (@filts) 
        {
            $stream = $f->infilt($stream, 1);
        }
    }
    return($stream);
}

sub dofilter {
    my ($filter,$stream)=@_;

    if((defined $filter) ) {
        # we need to fix filter because it MAY be
        # an array BUT IT COULD BE only a name
        if(ref($filter)!~/Array$/) {
               $filter = PDFArray($filter);
        }
        my @filts;
        my ($hasflate) = -1;
        my ($temp, $i, $temp1);

        @filts=(map { ("PDF::API2::Basic::PDF::Filter::".($_->val))->new } $filter->elementsof);

        foreach my $f (@filts) {
            $stream = $f->outfilt($stream, 1);
        }
    }
    return($stream);
}

sub nameByUni {
  my ($e)=@_;
  return($u2n{$e} || sprintf('uni%04X',$e));
}

sub uniByName {
  my ($e)=@_;
  if($e=~/^uni([0-9A-F]{4})$/) {
    return(hex($1));
  }
  return($n2u{$e} || undef);
}

sub initNameTable {
    %u2n = %{$PDF::API2::Resource::Glyphs::u2n};
    %n2u = %{$PDF::API2::Resource::Glyphs::n2u};
    $pua = 0xE000;
    1;
}
sub defineName {
    my $name=shift @_;
    return($n2u{$name}) if(defined $n2u{$name});

    while(defined $u2n{$pua}) { $pua++; }

    $u2n{$pua}=$name;
    $n2u{$name}=$pua;

    return($pua);
}

sub page_size {
    my ($x1, $y1, $x2, $y2) = @_;

    # full bbox
    if (defined $x2) {
        return ($x1, $y1, $x2, $y2);
    }

    # half bbox
    elsif (defined $y1) {
        return (0, 0, $x1, $y1);
    }

    # textual spec.
    elsif (defined $PaperSizes{lc $x1}) {
        return (0, 0, @{$PaperSizes{lc $x1}});
    }

    # single quadratic
    elsif ($x1 =~ /^[\d\.]+$/) {
        return(0, 0, $x1, $x1);
    }

    # pdf default.
    else {
        return (0, 0, 612, 792);
    }
}

sub getPaperSizes {
    my %sizes = ();
    foreach my $type (keys %PaperSizes) {
        $sizes{$type} = [@{$PaperSizes{$type}}];
    }
    return %sizes;
}

1;

__END__

=head1 NAME

PDF::API2::Util - utility package for often use methods across the package.

=head1 PREDEFINED PAPERSIZES

=over 4

=item %sizes = getPaperSizes();

Returns a hash containing the available paper size aliases as keys and
their dimensions as a two-element array reference.

=back

=head1 PREDEFINED COLORS

See the source of L<PDF::API2::Resource::Colors> for a complete list.

B<Please Note:> This is an amalgamation of the X11, SGML and (X)HTML
specification sets.

=head1 PREDEFINED GLYPH-NAMES

See the file C<uniglyph.txt> for a complete list.

B<Please Note:> You may notice that apart from the 'AGL/WGL4', names
from the XML, (X)HTML and SGML specification sets have been included
to enable interoperability towards PDF.

=cut
