commit 05fc95fdd98e
Author: Don Huettl <don.huettl@grantstreet.com>
Date:   Thu Mar 19 15:24:29 2015 -0400

    refactor PNG predictor logic into shared location
    
    This pulls the PNG predictor logic into its own filter module, to be
    used when parsing cross-reference streams as well.
    
    Also fixes a bug when the parser encounters an object stream near the
    end of the file.  The stream location would be off by one, preventing
    it from parsing correctly.

diff --git a/PDF-API2/lib/PDF/API2/Basic/PDF/File.pm b/PDF-API2/lib/PDF/API2/Basic/PDF/File.pm
index 9c7503c09de6..c74fd774e43e 100644
--- a/PDF-API2/lib/PDF/API2/Basic/PDF/File.pm
+++ b/PDF-API2/lib/PDF/API2/Basic/PDF/File.pm
@@ -166,6 +166,7 @@ use PDF::API2::Basic::PDF::Utils;
 use PDF::API2::Basic::PDF::Array;
 use PDF::API2::Basic::PDF::Bool;
 use PDF::API2::Basic::PDF::Dict;
+use PDF::API2::Basic::PDF::Filter::Predictor;
 use PDF::API2::Basic::PDF::Name;
 use PDF::API2::Basic::PDF::Number;
 use PDF::API2::Basic::PDF::Objind;
@@ -490,6 +491,7 @@ sub readval {
             my $length = $result->{'Length'}->val;
             $result->{' streamsrc'} = $fh;
             $result->{' streamloc'} = $fh->tell - length($str);
+            $result->{' streamloc'}-- if $fh->eof;
             unless ($opts{'nostreams'}) {
                 if ($length > length($str)) {
                     $value = $str;
@@ -1137,47 +1139,17 @@ sub readxrtr {
         # XRef streams.
         ($tdict, $buf) = $self->readval($buf);
 
-        my $stream = $tdict->{' stream'};
-
-        unless ($stream)
+        unless ($tdict->{' stream'})
         {
             die "Malformed XRefStm object in PDF file $self->{' fname'}";
         }
 
-        my $p = $tdict->{DecodeParms}->val;
-        my $pred = defined $p->{Predictor} ? $p->{Predictor}->val : 1;
-
-        if ($pred > 1)
-        {
-            my $bpc = defined $p->{BitsPerComponent} ? $p->{BitsPerComponent} : 8;
-            my $colors = defined $p->{Colors} ? $p->{Colors}->val : 1;
-            my $columns = defined $p->{Columns} ? $p->{Columns}->val : 1;
-
-            my $bpp = ceil($bpc * $colors / 8);
-            my $scanline = 1 + ceil($bpp * $columns);
-
-            if ($pred == 2)
-            {
-                warn "The TIFF predictor logic has not been implemented";
-            }
-            elsif ($pred >= 10 && $pred <= 15)
-            {
-                $stream = PDF::API2::Resource::XObject::Image::PNG::unprocess(
-                    $bpc, $bpp, $colors, $columns, 0, $scanline,
-                    \$tdict->{' stream'}
-                );
-            }
-            else
-            {
-                warn "Invalid predictor: $pred";
-            }
-        }
-  
+        my $stream = PDF::API2::Basic::PDF::Filter::Predictor->new($tdict)->infilt;
         my @widths = map { $_->val } @{$tdict->{W}->val};
-  
+
         my $start = 0;
         my $last;
-       
+
         if (defined $tdict->{Index})
         {
             my $index = $tdict->{Index}->val;
diff --git a/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/FlateDecode.pm b/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/FlateDecode.pm
index 5a9a9a8cdc15..86cc284594e7 100644
--- a/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/FlateDecode.pm
+++ b/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/FlateDecode.pm
@@ -43,6 +43,7 @@ sub infilt
 {
     my ($self, $dat, $last) = @_;
     my ($res, $status) = $self->{'infilt'}->inflate("$dat");
+    # TODO: Ideally we should call the Predictor filter from here.
     $res;
 }
 
diff --git a/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/Predictor.pm b/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/Predictor.pm
new file mode 100644
index 000000000000..7d2c388dcfc0
--- /dev/null
+++ b/PDF-API2/lib/PDF/API2/Basic/PDF/Filter/Predictor.pm
@@ -0,0 +1,140 @@
+package PDF::API2::Basic::PDF::Filter::Predictor;
+
+our $VERSION = '2.023.1'; # VERSION
+
+use base 'PDF::API2::Basic::PDF::Filter';
+
+use strict;
+no warnings qw[ deprecated recursion uninitialized ];
+
+use PDF::API2::Basic::PDF::Utils;
+use POSIX qw(ceil floor);
+
+# This does not behave like the other filters, as it needs access to the
+# source object.
+sub new {
+    my ($class, $obj) = @_;
+
+    my $self = {object => $obj};
+    bless $self, $class;
+}
+
+sub outfilt {
+    my ($self) = @_;
+
+    warn 'The "outfilt" method is not implemented';
+    return;
+}
+
+sub infilt {
+    my ($self) = @_;
+
+    # Decompress.
+    my $obj = $self->{object};
+    $obj->read_stream if $obj->{' nofilt'};
+
+    my $param     = $obj->{DecodeParms};
+    my $predictor = defined $param->{Predictor} ? $param->{Predictor}->val : 0;
+
+    return $obj->{' stream'} unless $predictor > 1;
+
+    # Then de-predict.
+    if ($predictor == 2) {
+        $self->_depredict_tiff;
+    } elsif ($predictor >= 10 && $predictor <= 15) {
+        $self->_depredict_png;
+    } else {
+        warn "Invalid predictor: $predictor";
+    }
+
+    return $obj->{' stream'};
+}
+
+sub _paeth_predictor {
+    my ($a, $b, $c)=@_;
+    my $p = $a + $b - $c;
+    my $pa = abs($p - $a);
+    my $pb = abs($p - $b);
+    my $pc = abs($p - $c);
+    if(($pa <= $pb) && ($pa <= $pc)) {
+        return $a;
+    } elsif($pb <= $pc) {
+        return $b;
+    } else {
+        return $c;
+    }
+}
+
+sub _depredict_png {
+    my ($self) = @_;
+
+    my $obj = $self->{object};
+
+    my $param  = $obj->{DecodeParms};
+    my $stream = $obj->{' stream'};
+
+    $param->{Alpha}            = PDFNum(0) unless $param->{Alpha};
+    $param->{BitsPerComponent} = PDFNum(8) unless $param->{BitsPerComponent};
+    $param->{Colors}           = PDFNum(1) unless $param->{Colors};
+    $param->{Columns}          = PDFNum(1) unless $param->{Columns};
+    $param->{Height}           = PDFNum(0) unless $param->{Height};
+
+    my $alpha   = $param->{Alpha}->val;
+    my $bpc     = $param->{BitsPerComponent}->val;
+    my $colors  = $param->{Colors}->val;
+    my $columns = $param->{Columns}->val;
+    my $height  = $param->{Height}->val;
+
+    my $bpp      = ceil($bpc * $colors / 8);
+    my $comp     = $colors + $alpha;
+    my $scanline = 1 + ceil($bpp * $columns);
+
+    my $prev='';
+    my $clearstream='';
+    my $lastrow=($height||(length($stream)/$scanline))-1;
+    foreach my $n (0..$lastrow) {
+        # print STDERR "line $n:";
+        my $line=substr($stream,$n*$scanline,$scanline);
+        my $filter=vec($line,0,8);
+        my $clear='';
+        $line=substr($line,1);
+        # print STDERR " filter=$filter";
+        if($filter==0) {
+            $clear=$line;
+        } elsif($filter==1) {
+            foreach my $x (0..length($line)-1) {
+                vec($clear,$x,8)=(vec($line,$x,8)+vec($clear,$x-$bpp,8))%256;
+            }
+        } elsif($filter==2) {
+            foreach my $x (0..length($line)-1) {
+                vec($clear,$x,8)=(vec($line,$x,8)+vec($prev,$x,8))%256;
+            }
+        } elsif($filter==3) {
+            foreach my $x (0..length($line)-1) {
+                vec($clear,$x,8)=(vec($line,$x,8)+floor((vec($clear,$x-$bpp,8)+vec($prev,$x,8))/2))%256;
+            }
+        } elsif($filter==4) {
+            # die "paeth/png filter not supported.";
+            foreach my $x (0..length($line)-1) {
+                vec($clear,$x,8)=(vec($line,$x,8)+_paeth_predictor(vec($clear,$x-$bpp,8),vec($prev,$x,8),vec($prev,$x-$bpp,8)))%256;
+            }
+        }
+        $prev=$clear;
+        foreach my $x (0..($columns*$comp)-1) {
+            vec($clearstream,($n*$columns*$comp)+$x,$bpc)=vec($clear,$x,$bpc);
+        #    print STDERR "".vec($clear,$x,$bpc).",";
+        }
+        # print STDERR "\n";
+    }
+
+    $obj->{' stream'} = $clearstream;
+}
+
+sub _depredict_tiff {
+    my ($self) = @_;
+
+    warn "The TIFF predictor logic has not been implemented";
+}
+
+1;
+
diff --git a/PDF-API2/lib/PDF/API2/Resource/XObject/Image/PNG.pm b/PDF-API2/lib/PDF/API2/Resource/XObject/Image/PNG.pm
index 679c1400efb9..25646d4ce05f 100644
--- a/PDF-API2/lib/PDF/API2/Resource/XObject/Image/PNG.pm
+++ b/PDF-API2/lib/PDF/API2/Resource/XObject/Image/PNG.pm
@@ -4,8 +4,7 @@ our $VERSION = '2.023.1'; # VERSION
 
 use base 'PDF::API2::Resource::XObject::Image';
 
-use Compress::Zlib;
-use POSIX qw(ceil floor);
+use POSIX qw(ceil);
 
 use IO::File;
 use PDF::API2::Util;
@@ -152,9 +151,7 @@ sub new {
                 # $dict->{Filter}=PDFArray(PDFName('ASCIIHexDecode'));
                 $dict->{BitsPerComponent}=PDFNum(8);
                 $self->{SMask}=$dict;
-                my $scanline=1+ceil($bpc*$w/8);
-                my $bpp=ceil($bpc/8);
-                my $clearstream=unprocess($bpc,$bpp,1,$w,$h,$scanline,\$self->{' stream'});
+                my $clearstream=PDF::API2::Basic::PDF::Filter::Predictor->new($self)->infilt;
                 foreach my $n (0..($h*$w)-1) {
                     vec($dict->{' stream'},$n,8)=vec($trns,vec($clearstream,$n,$bpc),8);
                 #    print STDERR vec($trns,vec($clearstream,$n,$bpc),8)."=".vec($clearstream,$n,$bpc).",";
@@ -173,6 +170,7 @@ sub new {
             my $dict=PDFDict();
             $self->{DecodeParms}=PDFArray($dict);
             # $dict->{Predictor}=PDFNum(15);
+            $dict->{Alpha}=PDFNum(1);
             $dict->{BitsPerComponent}=PDFNum($bpc);
             $dict->{Colors}=PDFNum(1);
             $dict->{Columns}=PDFNum($w);
@@ -189,9 +187,7 @@ sub new {
                 $dict->{BitsPerComponent}=PDFNum($bpc);
                 $self->{SMask}=$dict;
             }
-            my $scanline=1+ceil($bpc*2*$w/8);
-            my $bpp=ceil($bpc*2/8);
-            my $clearstream=unprocess($bpc,$bpp,2,$w,$h,$scanline,\$self->{' stream'});
+            my $clearstream=PDF::API2::Basic::PDF::Filter::Predictor->new($self)->infilt;
             delete $self->{' nofilt'};
             delete $self->{' stream'};
             foreach my $n (0..($h*$w)-1) {
@@ -210,6 +206,7 @@ sub new {
             my $dict=PDFDict();
             $self->{DecodeParms}=PDFArray($dict);
             # $dict->{Predictor}=PDFNum(15);
+            $dict->{Alpha}=PDFNum(1);
             $dict->{BitsPerComponent}=PDFNum($bpc);
             $dict->{Colors}=PDFNum(3);
             $dict->{Columns}=PDFNum($w);
@@ -226,9 +223,7 @@ sub new {
                 $dict->{BitsPerComponent}=PDFNum($bpc);
                 $self->{SMask}=$dict;
             }
-            my $scanline=1+ceil($bpc*4*$w/8);
-            my $bpp=ceil($bpc*4/8);
-            my $clearstream=unprocess($bpc,$bpp,4,$w,$h,$scanline,\$self->{' stream'});
+            my $clearstream=PDF::API2::Basic::PDF::Filter::Predictor->new($self)->infilt;
             delete $self->{' nofilt'};
             delete $self->{' stream'};
             foreach my $n (0..($h*$w)-1) {
@@ -254,64 +249,6 @@ sub new_api {
     return($obj);
 }
 
-sub PaethPredictor {
-    my ($a, $b, $c)=@_;
-    my $p = $a + $b - $c;
-    my $pa = abs($p - $a);
-    my $pb = abs($p - $b);
-    my $pc = abs($p - $c);
-    if(($pa <= $pb) && ($pa <= $pc)) {
-        return $a;
-    } elsif($pb <= $pc) {
-        return $b;
-    } else {
-        return $c;
-    }
-}
-
-sub unprocess {
-    my ($bpc,$bpp,$comp,$width,$height,$scanline,$sstream)=@_;
-    my $stream=uncompress($$sstream);
-    my $prev='';
-    my $clearstream='';
-    my $lastrow=($height||(length($stream)/$scanline))-1;
-    foreach my $n (0..$lastrow) {
-        # print STDERR "line $n:";
-        my $line=substr($stream,$n*$scanline,$scanline);
-        my $filter=vec($line,0,8);
-        my $clear='';
-        $line=substr($line,1);
-        # print STDERR " filter=$filter";
-        if($filter==0) {
-            $clear=$line;
-        } elsif($filter==1) {
-            foreach my $x (0..length($line)-1) {
-                vec($clear,$x,8)=(vec($line,$x,8)+vec($clear,$x-$bpp,8))%256;
-            }
-        } elsif($filter==2) {
-            foreach my $x (0..length($line)-1) {
-                vec($clear,$x,8)=(vec($line,$x,8)+vec($prev,$x,8))%256;
-            }
-        } elsif($filter==3) {
-            foreach my $x (0..length($line)-1) {
-                vec($clear,$x,8)=(vec($line,$x,8)+floor((vec($clear,$x-$bpp,8)+vec($prev,$x,8))/2))%256;
-            }
-        } elsif($filter==4) {
-            # die "paeth/png filter not supported.";
-            foreach my $x (0..length($line)-1) {
-                vec($clear,$x,8)=(vec($line,$x,8)+PaethPredictor(vec($clear,$x-$bpp,8),vec($prev,$x,8),vec($prev,$x-$bpp,8)))%256;
-            }
-        }
-        $prev=$clear;
-        foreach my $x (0..($width*$comp)-1) {
-            vec($clearstream,($n*$width*$comp)+$x,$bpc)=vec($clear,$x,$bpc);
-        #    print STDERR "".vec($clear,$x,$bpc).",";
-        }
-        # print STDERR "\n";
-    }
-    return($clearstream);
-}
-
 1;
 
 __END__
