deri pushed a commit to branch master
in repository groff.

commit 77e43d8c64a73e7bc05d6e353563f66f8665bb47
Author: Deri James <[email protected]>
AuthorDate: Mon Aug 19 18:37:29 2024 +0100

    Add support for embedding bit-mapped images to a generated pdf.
    
    Currently gropdf supports the inclusion of pdf files as separate images,
    now many other image formats are supported. The pdf standard requires
    programs which process pdfs to handle embedded jpegs, and from version
    1.5 jpeg2000 images are handled natively as well. (Current gropdf
    produces pdfs which conform to version 1.7).
    
    The pdf standard supports a raw format with separate channels
    (RGB/CMYK/Grey) in 8/16/32 bit formats. This is used for other image
    formats. If there is a transparency channel it is converted to an 8-bit
    soft mask for the image.
    
    * src/devices/devpdf/gropdf.pl: add code to \X'pdf: pdfpic’ to handle
    other image types, not just pdfs.
    
    * man/groff_tmac.5.man: document changes to PDFPIC macro.
    
    * src/devices/devpdf/gropdf.1.man: explain use of \X'pdf: pdfpic ...'
    
    * tmac/pdfpic.tmac: expand the PDFPIC macro to handle multiple image
    formats, not just embedding pdfs.
---
 man/groff_tmac.5.man            |  54 +++++++---
 src/devices/gropdf/gropdf.1.man |  36 +++++--
 src/devices/gropdf/gropdf.pl    | 224 ++++++++++++++++++++++++++++++++++++++--
 tmac/pdfpic.tmac                |  32 +++---
 4 files changed, 299 insertions(+), 47 deletions(-)

diff --git a/man/groff_tmac.5.man b/man/groff_tmac.5.man
index 822aa088a..c7b40458f 100644
--- a/man/groff_tmac.5.man
+++ b/man/groff_tmac.5.man
@@ -647,20 +647,46 @@ file when formatting for a typesetter
 .I pdfpic
 provides a single macro,
 .BR PDFPIC ,
-to include a PDF graphic in a document using features of the
-.B pdf
-output driver.
-.
-For other output devices,
-.B PDFPIC
-calls
-.BR PSPIC ,
-with which it shares an interface
-(see below).
-.
-This macro file is normally loaded at startup by the
-.I troffrc
-file.
+it operates in two modes.
+If it is not used with the pdf output driver -T
+.IR pdf ,
+the given
+.I file
+must be a pdf and it relies on the external program
+.MR pdftops 1
+to convert the pdf to an encapsulated postscript file
+and calls the
+.B PSPIC
+macro with which it shares an interface.
+If output is to a pdf it calls \X[rs]'pdf: pdfpic ...' (see
+.MR gropdf @MAN1EXT@ )
+and the given
+.I file
+can be a pdf or any graphic format supported by
+.IR gropdf .
+.IP
+Since this macro needs to discover the actual width and
+height of the image (in order to check if there is sufficient
+room on the page) there are dependencies on external programs
+shown in this table:-
+.TS
+allbox  centre;
+Cb Cb Cb Cb
+L C C C.
+       pdfinfo file    identify
+\&.pdf \[OK]   \[OK]   \[OK]
+\&.jpg \[u2717]        \[OK]   \[OK]
+\&.jp2 \[u2717]        \[u2717]        \[OK]
+other  \[u2717]        \[u2717]        \[OK]
+.TE
+.IP
+To include \[oq]other\[cq] (e.g. png/pam/gif) image formats the
+perlmagick modules must be installed as well, they are used by
+.I gropdf
+to embed the graphic. They are not needed for the other 3 types.
+.IP
+If the required programs are not available, the file is treated as
+a pdf, so will likely fail if it is not a pdf.
 .
 .
 .TP
diff --git a/src/devices/gropdf/gropdf.1.man b/src/devices/gropdf/gropdf.1.man
index 4ef8a97e2..5f3f39d15 100644
--- a/src/devices/gropdf/gropdf.1.man
+++ b/src/devices/gropdf/gropdf.1.man
@@ -934,9 +934,7 @@ that take the same arguments and behave equivalently.
 .TP
 .BI "\[rs]X\[aq]pdf: pdfpic\~" file\~\c
 .IR "alignment width height line-length" \[aq]
-Place an image of the specified
-.I width
-containing the PDF drawing from file
+Place an image from file
 .I file
 of desired
 .I width
@@ -965,13 +963,31 @@ If
 is specified as zero then the width is scaled in proportion to the
 height.
 .
-.\" .IP
-.\" See
-.\" .BR groff_tmac (@MAN7EXT@)
-.\" for a description of the
-.\" .B PSPIC
-.\" macro which provides a convenient high-level interface for inclusion
-.\" of PostScript graphics.
+If both
+.I width
+and
+.I height
+are non-zero the image is scaled to \[oq]best fit\[cq].
+.
+.IP
+Three elements control which type of image files you can now embed into a -T 
pdf produced file. If you have perlmagick installed, if you have the program 
"identify" installed (part of ImageMagick), if you have the program "file" 
installed, or if you have none of these. Here's a table showing the different 
possibilities:-
+.TS
+allbox  centre;
+Cb Cb Cb Cb Cb
+L C C C C.
+       none    file    identify        perlmagick
+\&.pdf \[OK]   \[OK]   \[OK]   \[OK]
+\&.jpg \[u2717]        \[OK]   \[OK]   \[OK]
+\&.jp2 \[u2717]        \[u2717]        \[OK]   \[OK]
+other  \[u2717]        \[u2717]        \[u2717]        \[OK]
+.TE
+.IP
+See
+.MR groff_tmac @MAN5EXT@
+for a description of the
+.B PDFPIC
+macro which provides a convenient high-level interface for inclusion
+of various graphics files.
 .
 .TP
 .B \[rs]X\[aq]pdf: xrev\[aq]
diff --git a/src/devices/gropdf/gropdf.pl b/src/devices/gropdf/gropdf.pl
index 5a88bc65a..ac0dcefd8 100644
--- a/src/devices/gropdf/gropdf.pl
+++ b/src/devices/gropdf/gropdf.pl
@@ -223,6 +223,7 @@ unshift(@ARGV,split(' ',$ENV{GROPDF_OPTIONS})) if 
exists($ENV{GROPDF_OPTIONS});
 
 my $gotzlib=0;
 my $gotinline=0;
+my $gotexif=0;
 
 my $rc = eval
 {
@@ -280,6 +281,20 @@ if($rc)
     $gotinline=1;
 }
 
+$rc = eval
+{
+    require Image::ExifTool;
+    Image::ExifTool->import();
+    require Image::Magick;
+    Image::Magick->import();
+    1;
+};
+
+if($rc)
+{
+    $gotexif=1;
+}
+
 my %cfg;
 
 $cfg{GROFF_VERSION}='@VERSION@';
@@ -1657,16 +1672,77 @@ sub do_x
                my $hgt=GetPoints($xprm[5]||-1);
                my $ll=GetPoints($xprm[6]||0);
                my $mat=[1,0,0,1,0,0];
+               my $imgtype='PDF';
+               my $info;
+
+               my ($FD,$FDnm)=OpenInc($fil);
+
+               if (!defined($FD))
+               {
+                   Warn("failed to open image file '$FDnm'");
+                   return;
+               }
 
                if (!exists($incfil{$fil}))
                {
-                   $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"pdfpic");
+                   if ($gotexif)
+                   {
+                       my $exifTool = Image::ExifTool->new;
+                       $info = $exifTool->ImageInfo($FD);
+
+                       $imgtype=$info->{FileType}||'' if defined($info);
+                   }
+                   else
+                   {
+                       my $dim=`( identify $FDnm 2>/dev/null || file $FDnm )`;
+                       $dim=~m/(?:(?:[,=A-Z]|JP2) 
(?<w>\d+)\s*x\s*(?<h>\d+))|(?:height=(?<h>\d+).+width=(?<w>\d+))/;
+
+                       $info->{ImageWidth}=$+{w};
+                       $info->{ImageHeight}=$+{h};
+
+                       if ($dim=~m/JPEG \d+x|JFIF/)
+                       {
+                           $imgtype='JPEG';
+                           $info->{ColorComponents}=3;
+
+                           if ($dim=~m/Gray|components 1/)
+                           {
+                               $info->{ColorComponents}=1;
+                           }
+                       }
+                       elsif ($dim=~m/JP2 \d+x/)
+                       {
+                           $imgtype='JP2';
+                       }
+                   }
+
+                   if ($imgtype eq 'PDF')
+                   {
+                       
$incfil{$fil}=LoadPDF($FD,$FDnm,$mat,$wid,$hgt,"pdfpic");
+                   }
+                   elsif ($imgtype eq 'JPEG')
+                   {
+                       $incfil{$fil}=LoadJPEG($FD,$FDnm,$info);
+                   }
+                   elsif ($imgtype eq 'JP2')
+                   {
+                       $incfil{$fil}=LoadJP2($FD,$FDnm,$info);
+                   }
+                   else
+                   {
+                       $incfil{$fil}=LoadMagick($FD,$FDnm,$info);
+                   }
+
+                   return if !defined($incfil{$fil});
+                   $incfil{$fil}->[2]=$imgtype;
                }
 
                if (defined($incfil{$fil}))
                {
                    IsGraphic();
                    my $bbox=$incfil{$fil}->[1];
+                   $imgtype=$incfil{$fil}->[2];
+                   Warn("Failed to extract width x height for '$FDnm'"),return 
if !defined($bbox->[2]) or !defined($bbox->[3]);
                    $wid=($bbox->[2]-$bbox->[0]) if $wid <= 0;
                    my $xscale=d3($wid/($bbox->[2]-$bbox->[0]));
                    my 
$yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1])));
@@ -1685,6 +1761,21 @@ sub do_x
                        $xpos+=$ll-$wid;
                    }
 
+                   if ($imgtype ne 'PDF')
+                   {
+                       if ($rot)
+                       {
+                           $xscale*=$bbox->[3];
+                           $yscale*=$bbox->[2];
+                       }
+                       else
+                       {
+                           $xscale*=$bbox->[2];
+                           $yscale*=$bbox->[3];
+                       }
+
+                   }
+
                    $ypos+=$hgt;
                    $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
                    $stream.=" 0 1 -1 0 0 0 cm" if $rot;
@@ -2342,7 +2433,8 @@ sub OpenInc
 
 sub LoadPDF
 {
-    my $pdfnm=shift;
+    my $PD=shift;
+    my $PDnm=shift;
     my $mat=shift;
     my $wid=shift;
     my $hgt=shift;
@@ -2356,16 +2448,15 @@ sub LoadPDF
     my $adj=0;
     my $keepsep=$/;
 
-    my ($PD,$PDnm)=OpenInc($pdfnm);
+    seek($PD,0,0);
+    my $hdr=<$PD>;
 
-    if (!defined($PD))
+    if ($hdr!~m/^%PDF/)
     {
-       Warn("failed to open PDF '$pdfnm'");
+       Warn("'$PDnm' does not appear to be a pdf file");
        return undef;
     }
 
-    my $hdr=<$PD>;
-
     $/="\r",$adj=1 if (length($hdr) > 10);
 
     while (<$PD>)
@@ -2411,7 +2502,7 @@ sub LoadPDF
            }
            else
            {
-               Warn("parsing PDF '$pdfnm' failed");
+               Warn("parsing PDF '$PDnm' failed");
                return undef;
            }
        }
@@ -2547,6 +2638,123 @@ sub LoadPDF
     return([$xonm,$BBox] );
 }
 
+sub LoadJPEG
+{
+    my $JP=shift;
+    my $JPnm=shift;
+    my $info=shift;
+    my $BBox=[0,0,$info->{ImageWidth},$info->{ImageHeight}];
+
+    local $/=undef;
+
+    seek($JP,0,0);
+
+    my $strm=<$JP>;
+    close($JP);
+
+    my $xobj=++$objct;
+    my $xonm="XO$xobj";
+    my $cs=($info->{ColorComponents}==1)?'/DeviceGray':'/DeviceRGB';
+    $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => 
'/XObject', 'Width' => $BBox->[2], 'Height' => $BBox->[3], 'ColorSpace' => $cs, 
'BitsPerComponent' => $info->{BitsPerSample}||8, 'Subtype' => '/Image', 
'Length' => length($strm), 'Filter' => '/DCTDecode'});
+    $obj[$xobj]->{STREAM}=$strm;
+    return([$xonm,$BBox]);
+}
+
+sub LoadJP2
+{
+    my $JP=shift;
+    my $JPnm=shift;
+    my $info=shift;
+    my $BBox=[0,0,$info->{ImageWidth},$info->{ImageHeight}];
+
+    local $/=undef;
+
+    seek($JP,0,0);
+
+    my $strm=<$JP>;
+    close($JP);
+
+    my $xobj=++$objct;
+    my $xonm="XO$xobj";
+    $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => 
'/XObject', 'Width' => $BBox->[2], 'Height' => $BBox->[3],  'Subtype' => 
'/Image', 'Length' => length($strm), 'SMaskInData' => 1, 'Filter' => 
'/JPXDecode'});
+    $obj[$xobj]->{STREAM}=$strm;
+    return([$xonm,$BBox]);
+}
+
+sub LoadMagick
+{
+    my $JP=shift;
+    my $JPnm=shift;
+    my $info=shift;
+
+    local $/=undef;
+
+    seek($JP,0,0);
+    binmode $JP;
+
+    my ($x, $image);
+
+    $image = Image::Magick->new;
+    $x = $image->Read(file => $JP);
+    Warn("Image '$JPnm': $x"), return if "$x";
+#     my $e=$image->Get('endian');
+#     print STDERR "En: '$JPnm' $e\n";
+#     $image->Set(endian => 'MSB') if $e eq 'Undefined';
+
+    my $BPC;
+    $BPC=$image->Get('depth');
+    $BPC=$info->{BitDepth} if ($info and exists($info->{BitDepth}) and 
$info->{BitDepth} != $BPC);
+#    $image->Set(depth => 8), $BPC=8 if $BPC==16;
+
+    my $BBox=[0,0,$image->Get('width'),$image->Get('height')];
+    my $alpha;
+
+    if ($image->Get('matte'))
+    {
+       $alpha=$image->Clone();
+       $alpha->Separate(channel => 'Alpha');
+       $alpha->Set(magick => 'gray');
+    }
+
+    my $cs=$image->Get('colorspace');
+    $cs='RGB' if $cs eq 'sRGB';
+    $x = $image->Set(alpha => 'off', magick => $cs);
+    Warn("Image '$JPnm': $x"), return if "$x";
+    my @blobs = $image->ImageToBlob();
+    Warn("Image '$JPnm': More than 1 image") if $#blobs > 0;
+    $blobs[0]=pack('v*', unpack('n*', $blobs[0])) if $BPC==16;
+    $blobs[0]=pack('V*', unpack('N*', $blobs[0])) if $BPC==32;
+    close($JP);
+
+    my $xobj=++$objct;
+    my $xonm="XO$xobj";
+
+    if ($cs=~m/^(Gray|RGB|CMYK)$/)
+    {
+       $cs="/Device$cs";
+    }
+    else
+    {
+       Warn("Image '$JPnm' unknown ColourSpace '$cs'");
+       return;
+    }
+
+    $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => 
'/XObject', 'Width' => $BBox->[2], 'Height' => $BBox->[3], 'ColorSpace' => $cs, 
'BitsPerComponent' => $BPC, 'Subtype' => '/Image', 'Interpolate' => 'false', 
'Length' => length($blobs[0])});
+    $obj[$xobj]->{STREAM}=$blobs[0];
+
+    if ($alpha)
+    {
+       $#blobs=-1;
+       $alpha->Set(depth => 8);
+       $BPC=8;
+       @blobs = $alpha->ImageToBlob();
+       $obj[$xobj]->{DATA}->{SMask}=BuildObj(++$objct,{'Type' => '/XObject', 
'Width' => $BBox->[2], 'Height' => $BBox->[3], 'ColorSpace' => '/DeviceGray', 
'BitsPerComponent' => $BPC, 'Subtype' => '/Image', 'Length' => 
length($blobs[0])});
+       $obj[$objct]->{STREAM}=$blobs[0];
+    }
+
+    return([$xonm,$BBox]);
+}
+
 sub ObjMerge
 {
     my $o1=shift;
diff --git a/tmac/pdfpic.tmac b/tmac/pdfpic.tmac
index 72a860fde..80b527474 100644
--- a/tmac/pdfpic.tmac
+++ b/tmac/pdfpic.tmac
@@ -74,16 +74,18 @@
 .\" when the string they populate is later interpolated.
 .eo
 .de pdfpic@get-image-dimensions
-.  ds pdfpic*command pdfinfo \$1
+.  ds pdfpic*command ( identify \$1 2>/dev/null || file \$1 ) \"
 .  eo
-.  as pdfpic*command " | tr -d '\\000'
-.  as pdfpic*command " | sed -n -e '/Page  *size:/
-.  as pdfpic*command s/Page  *size:  *\\([0-9.]*\\)  *x  *\([0-9.]*\\).*$/
-.  as pdfpic*command . \\\\R@pdfpic*width  (p;\\1)@
-.  as pdfpic*command " \\\\R@pdfpic*height (p;\\2)@
-.  as pdfpic*command /p'
+.  as pdfpic*command  | perl -ne 'print qq(. \\\\R\@pdfpic*width $+{w}p@ 
\\\\R\@pdfpic*height $+{h}p\@),qq(\\n) if m/(?:(?:[,=A-Z]|JP2) 
(?<w>\d+)\s*x\s*(?<h>\d+))|(?:height=(?<h>\d+).+width=(?<w>\d+))/;'
 .  ec
 .  pso \*[pdfpic*command]
+.  if !r pdfpic*width \{\
+.     ds pdfpic*command pdfinfo \$1 \"
+.     eo
+.     as pdfpic*command  | perl -ne 'print qq(. \\\\R\@pdfpic*width $+{w}p@ 
\\\\R\@pdfpic*height $+{h}p\@) if 
m/Page\s+size:\s+(?<w>[\d.]+)\sx\s(?<h>[\d.]+)/si;'
+.     ec
+.     pso \*[pdfpic*command]
+.  \}
 .  rm pdfpic*command
 ..
 .ec
@@ -138,14 +140,6 @@
 .  \}
 .  br
 .
-.  ds pdfpic*file-extension \\$1\"
-.  substring pdfpic*file-extension -4
-.  stringdown pdfpic*file-extension
-.  if !'\\*[pdfpic*file-extension]'.pdf' \{\
-.    pdfpic@error '\\$1' lacks a '.pdf' extension; skipping
-.    return
-.  \}
-.
 .  \" Ensure the file exists and is readable.
 .  \"
 .  \" This test is subject to a time-of-check-to-time-of-use (TOCTTOU)
@@ -159,6 +153,14 @@
 .
 .  \" if driver is not gropdf, convert image to .eps
 .  if \\n[pdfpic*do-conversion] \{\
+.    ds pdfpic*file-extension \\$1\"
+.    substring pdfpic*file-extension -4
+.    stringdown pdfpic*file-extension
+.    if !'\\*[pdfpic*file-extension]'.pdf' \{\
+.      pdfpic@error '\\$1' lacks a '.pdf' extension; skipping
+.      return
+.    \}
+.
 .    ds pdfpic*file-name-base \\$1
 .    substring pdfpic*file-name-base 0 -5
 .

_______________________________________________
Groff-commit mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/groff-commit

Reply via email to