3 # gropdf : PDF post processor for groff
5 # Copyright (C) 2011-2014 Free Software Foundation, Inc.
6 # Written by Deri James <deri@chuzzlewit.demon.co.uk>
8 # This file is part of groff.
10 # groff is free software; you can redistribute it and/or modify it under
11 # the terms of the GNU General Public License as published by the Free
12 # Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
15 # groff is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 use Getopt::Long qw(:config bundling);
29 $cfg{GROFF_VERSION}='@VERSION@';
30 $cfg{GROFF_FONT_PATH}='@GROFF_FONT_DIR@';
31 $cfg{RT_SEP}='@RT_SEP@';
34 my @obj; # Array of PDF objects
35 my $objct=0; # Count of Objects
36 my $fct=0; # Output count
38 my $lct=0; # Input Line Count
40 my %env; # Current environment
41 my %fontlst; # Fonts Loaded
43 my %desc; # Contents of DESC
44 my %download; # Contents of downlopad file
45 my $pages; # Pointer to /Pages object
47 my $cpage; # Pointer to current pages
48 my $cpageno=0; # Object no of current page
49 my $cat; # Pointer to catalogue
50 my $dests; # Pointer to Dests
51 my @mediabox=(0,0,595,842);
52 my @defaultmb=(0,0,595,842);
53 my $stream=''; # Current Text/Graphics stream
54 my $cftsz=10; # Current font sz
55 my $cft; # Current Font
56 my $lwidth=1; # current linewidth
59 my $textcol=''; # Current groff text
60 my $fillcol=''; # Current groff fill
61 my $curfill=''; # Current PDF fill
64 my @lin=(); # Array holding current line of text
65 my @ahead=(); # Buffer used to hol the next line
66 my $mode='g'; # Graphic (g) or Text (t) mode;
67 my $xpos=0; # Current X position
68 my $ypos=0; # Current Y position
72 my $widtbl; # Pointer to width table for current font size
73 my $origwidtbl; # Pointer to width table
74 my $krntbl; # Pointer to kern table
76 my $whtsz; # Current width of a space
77 my $poschg=0; # V/H pending
78 my $fontchg=0; # font change pending
79 my $tnum=2; # flatness of B-Spline curve
80 my $tden=3; # flatness of B-Spline curve
86 my $suppress=0; # Suppress processing?
87 my %incfil; # Included Files
88 my @outlev=([0,undef,0,0]); # Structure pdfmark /OUT entries
89 my $curoutlev=\@outlev;
90 my $curoutlevno=0; # Growth point for @curoutlev
92 my $xrev=0; # Reverse x direction of font
97 my $suspendmark=undef;
99 my $pginsert=-1; # Growth point for kids array
100 my %pgnames; # 'names' of pages for switchtopage
101 my @outlines=(); # State of Bookmark Outlines at end of each page
102 my $custompaper=0; # Has there been an X papersize
103 my $textenccmap=''; # CMap for groff text.enc encoding
105 my %ppsz=( 'ledger'=>[1224,792],
119 'isob0'=>[2835,4008],
120 'isob1'=>[2004,2835],
121 'isob2'=>[1417,2004],
122 'isob3'=>[1001,1417],
135 /CIDInit /ProcSet findresource begin
143 /CMapName /Adobe-Identity-UCS def
145 1 begincodespacerange
149 <008b> <008f> [<00660066> <00660069> <0066006c> <006600660069> <00660066006C>]
153 CMapName currentdict /CMap defineresource pop
169 GetOptions("F=s" => \$fd, 'l' => \$frot, 'p=s' => \$fpsz, 'd!' => \$debug, 'v' => \$version, 'e' => \$embedall, 'y=s' => \$Foundry, 's' => \$stats, 'u:s' => \$unicodemap);
173 print "GNU gropdf (groff) version $cfg{GROFF_VERSION}\n";
177 if (defined($unicodemap))
179 if ($unicodemap eq '')
183 elsif (-r $unicodemap)
186 open(F,"<$unicodemap") or die "gropdf: Failed to open '$unicodemap'";
192 Msg(0,"Failed to find '$unicodemap' - ignoring");
196 # Search for 'font directory': paths in -f opt, shell var GROFF_FONT_PATH, default paths
198 my $fontdir=$cfg{GROFF_FONT_PATH};
199 $fontdir=$ENV{GROFF_FONT_PATH}.$cfg{RT_SEP}.$fontdir if exists($ENV{GROFF_FONT_PATH});
200 $fontdir=$fd.$cfg{RT_SEP}.$fontdir if defined($fd);
203 $matrix="0 1 -1 0" if $frot;
208 my $unitwidth=$desc{unitwidth};
209 my $papersz=$desc{papersize};
210 $papersz=lc($fpsz) if $fpsz;
216 if (substr($papersz,0,1) eq '/' and -r $papersz)
218 if (open(P,"<$papersz"))
233 if ($papersz=~m/([\d.]+)([cipP]),([\d.]+)([cipP])/)
235 @defaultmb=@mediabox=(0,0,ToPoints($3,$4),ToPoints($1,$2));
237 elsif (exists($ppsz{$papersz}))
239 @defaultmb=@mediabox=(0,0,$ppsz{$papersz}->[0],$ppsz{$papersz}->[1]);
242 my (@dt)=localtime(time);
243 my $dt=PDFDate(\@dt);
245 my %info=('Creator' => "(groff version $cfg{GROFF_VERSION})",
246 'Producer' => "(gropdf version $cfg{GROFF_VERSION})",
247 'ModDate' => "($dt)",
248 'CreationDate' => "($dt)");
255 do # The ahead buffer behaves like 'ungetc'
263 my $cmd=substr($_,0,1);
264 next if $cmd eq '#'; # just a comment
265 my $lin=substr($_,1);
269 $cmd=substr($lin,0,1);
275 # $lin=~s/\s#.*?$//; # remove comment
276 $stream.="\% $_\n" if $debug;
278 do_x($lin),next if ($cmd eq 'x');
280 do_p($lin),next if ($cmd eq 'p');
281 do_f($lin),next if ($cmd eq 'f');
282 do_s($lin),next if ($cmd eq 's');
283 do_m($lin),next if ($cmd eq 'm');
284 do_D($lin),next if ($cmd eq 'D');
285 do_V($lin),next if ($cmd eq 'V');
286 do_v($lin),next if ($cmd eq 'v');
287 do_t($lin),next if ($cmd eq 't');
288 do_u($lin),next if ($cmd eq 'u');
289 do_C($lin),next if ($cmd eq 'C');
290 do_c($lin),next if ($cmd eq 'c');
291 do_N($lin),next if ($cmd eq 'N');
292 do_h($lin),next if ($cmd eq 'h');
293 do_H($lin),next if ($cmd eq 'H');
294 do_n($lin),next if ($cmd eq 'n');
296 my $tmp=scalar(@ahead);
297 }} until scalar(@ahead) == 0;
304 $cpage->{MediaBox}=\@mediabox if $custompaper;
306 OutStream($cpageno+1);
310 PutOutlines(\@outlev);
314 my $info=BuildObj(++$objct,\%info);
318 foreach my $fontno (keys %fontlst)
320 my $o=$fontlst{$fontno}->{FNT};
321 my $p=GetObj($fontlst{$fontno}->{OBJ});
323 if (exists($p->{LastChar}) and $p->{LastChar} > 255)
325 $p->{LastChar} = 255;
326 splice(@{$o->{GNO}},256);
327 splice(@{$o->{WID}},256);
331 foreach my $o (3..$objct)
333 PutObj($o) if (!exists($obj[$o]->{XREF}));
336 #my $encrypt=BuildObj(++$objct,{'Filter' => '/Standard', 'V' => 1, 'R' => 2, 'P' => 252});
343 print "xref\n0 $objct\n0000000000 65535 f \n";
345 foreach my $xr (@obj)
347 next if !defined($xr);
348 printf("%010d 00000 n \n",$xr->{XREF});
351 print "trailer\n<<\n/Info $info\n/Root 1 0 R\n/Size $objct\n>>\nstartxref\n$fct\n\%\%EOF\n";
352 print "\% Pages=$pages->{Count}\n" if $stats;
357 my $fontxrev=shift||0;
358 my @mat=($frot)?(0,1,-1,0):(1,0,0,1);
362 if ($env{FontHT} != 0)
364 $mat[3]=sprintf('%.3f',$env{FontHT}/$cftsz);
367 if ($env{FontSlant} != 0)
369 my $slant=$env{FontSlant};
370 $slant*=$env{FontHT}/$cftsz if $env{FontHT} != 0;
373 $mat[2]=sprintf('%.3f',sin($ang)/cos($ang));
382 $matrix=join(' ',@mat);
393 # We've got Outlines to deal with
394 my $openct=$curoutlev->[0]->[2];
396 while ($thislev-- > 1)
398 my $nxtoutlev=$curoutlev->[0]->[1];
399 $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1;
400 $openct=0 if $nxtoutlev->[0]->[3]==-1;
401 $curoutlev=$nxtoutlev;
404 $cat->{Outlines}=BuildObj(++$objct,{'Count' => abs($o->[0]->[0])+$o->[0]->[2]});
405 $outlines=$obj[$objct]->{DATA};
414 $outlines->{First}=$o->[1]->[2];
415 $outlines->{Last}=$o->[$#{$o}]->[2];
417 LinkOutObj($o,$cat->{Outlines});
424 for my $j (1..$#{$o})
426 my $ono=BuildObj(++$objct,$o->[$j]->[0]);
429 SetOutObj($o->[$j]->[1]) if $#{$o->[$j]->[1]} > -1;
438 for my $j (1..$#{$o})
440 my $op=GetObj($o->[$j]->[2]);
442 $op->{Next}=$o->[$j+1]->[2] if ($j < $#{$o});
443 $op->{Prev}=$o->[$j-1]->[2] if ($j > 1);
444 $op->{Parent}=$parent;
446 if ($#{$o->[$j]->[1]} > -1)
448 $op->{Count}=$o->[$j]->[1]->[0]->[2]*$o->[$j]->[1]->[0]->[3];# if exists($op->{Count}) and $op->{Count} > 0;
449 $op->{First}=$o->[$j]->[1]->[1]->[2];
450 $op->{Last}=$o->[$j]->[1]->[$#{$o->[$j]->[1]}]->[2];
451 LinkOutObj($o->[$j]->[1],$o->[$j]->[2]);
459 ($ono)=split(' ',$ono);
460 return($obj[$ono]->{DATA});
468 return(sprintf("D:%04d%02d%02d%02d%02d%02d% +02d'00'",$dt->[5]+1900,$dt->[4]+1,$dt->[3],$dt->[2],$dt->[1],$dt->[0],( localtime time() + 3600*( 12 - (gmtime)[2] ) )[2] - 12));
482 return int($num*72/2.54);
484 elsif ($unit eq 'm') # millimetres
486 return int($num*72/25.4);
498 return($num/$unitwidth);
502 Msg(1,"Unknown scaling factor '$unit'");
508 open(CFG,"<gropdf_config") or die "Can't open config file: $!";
513 my ($key,$val)=split(/ ?= ?/);
525 OpenFile(\$f,$fontdir,"download");
526 Msg(1,"Failed to open 'download'") if !defined($f);
533 my ($foundry,$name,$file)=split(/\t+/);
534 if (substr($file,0,1) eq '*')
537 $file=substr($file,1);
540 $download{"$foundry $name"}=$file;
552 if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos
554 return if -r "$fnm" and open($$f,"<$fnm");
557 my (@dirs)=split($cfg{RT_SEP},$dirs);
559 foreach my $dir (@dirs)
561 last if -r "$dir/$devnm/$fnm" and open($$f,"<$dir/$devnm/$fnm");
569 OpenFile(\$f,$fontdir,"DESC");
570 Msg(1,"Failed to open 'DESC'") if !defined($f);
577 my ($name,$prms)=split(' ',$_,2);
578 $desc{lc($name)}=$prms;
584 sub rad { $_[0]*3.14159/180 }
591 my ($xcmd,@xprm)=split(' ',$l);
592 $xcmd=substr($xcmd,0,1);
596 Msg(0,"Expecting a pdf pipe (got $xprm[0])") if $xprm[0] ne substr($devnm,3);
598 elsif ($xcmd eq 'f') # Register Font
600 $xprm[1]="${Foundry}-$xprm[1]" if $Foundry ne '';
601 LoadFont($xprm[0],$xprm[1]);
603 elsif ($xcmd eq 'F') # Source File (for errors)
605 $env{SourceFile}=$xprm[0];
607 elsif ($xcmd eq 'H') # FontHT
609 $xprm[0]/=$unitwidth;
610 $xprm[0]=0 if $xprm[0] == $cftsz;
611 $env{FontHT}=$xprm[0];
614 elsif ($xcmd eq 'S') # FontSlant
616 $env{FontSlant}=$xprm[0];
619 elsif ($xcmd eq 'i') # Initialise
624 @defaultmb=@mediabox;
625 BuildObj($objct,{'Pages' => BuildObj($objct+1,
630 'MediaBox' => \@defaultmb,
633 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI']}
636 'Type' => '/Catalog'});
638 $cat=$obj[$objct]->{DATA};
640 $pages=$obj[2]->{DATA};
641 Put("%PDF-1.4\n\x25\xe2\xe3\xcf\xd3\n");
646 # There could be extended args
650 if (substr($ahead[0],0,1) eq '+')
652 $l.="\n".substr($ahead[0],1);
657 ($xcmd,@xprm)=split(' ',$l);
658 $xcmd=substr($xcmd,0,1);
660 if ($xprm[0]=~m/^(.+:)(.+)/)
662 splice(@xprm,1,0,$2);
666 my $par=join(' ',@xprm[1..$#xprm]);
668 if ($xprm[0] eq 'ps:')
670 if ($xprm[1] eq 'invis')
674 elsif ($xprm[1] eq 'endinvis')
678 elsif ($par=~m/exec gsave currentpoint 2 copy translate (.+) rotate neg exch neg exch translate/)
680 # This is added by gpic to rotate a single object
685 my ($curangle,$hyp)=RtoP($xpos,GraphY($ypos));
686 my ($x,$y)=PtoR($theta+$curangle,$hyp);
687 $stream.="q\n".sprintf("%.3f %.3f %.3f %.3f %.3f %.3f cm",cos($theta),sin($theta),-sin($theta),cos($theta),$xpos-$x,GraphY($ypos)-$y)."\n";
690 elsif ($par=~m/exec grestore/ and $InPicRotate)
696 elsif ($par=~m/exec (\d) setlinejoin/)
700 $stream.="$linejoin j\n";
702 elsif ($par=~m/exec (\d) setlinecap/)
706 $stream.="$linecap J\n";
708 elsif ($par=~m/\[(.+) pdfmark/)
711 $pdfmark=~s((\d{4,6}) u)(sprintf("%.1f",$1/$desc{sizescale}))eg;
712 $pdfmark=~s(\\\[u00(..)\])(chr(hex($1)))eg;
714 if ($pdfmark=~m/(.+) \/DOCINFO/)
716 my @xwds=split(' ',"<< $1 >>");
717 my $docinfo=ParsePDFValue(\@xwds);
719 foreach my $k (keys %{$docinfo})
721 $info{$k}=$docinfo->{$k} if $k ne 'Producer';
724 elsif ($pdfmark=~m/(.+) \/DOCVIEW/)
726 my @xwds=split(' ',"<< $1 >>");
727 my $docview=ParsePDFValue(\@xwds);
729 foreach my $k (keys %{$docview})
731 $cat->{$k}=$docview->{$k} if !exists($cat->{$k});
734 elsif ($pdfmark=~m/(.+) \/DEST/)
736 my @xwds=split(' ',"<< $1 >>");
737 my $dest=ParsePDFValue(\@xwds);
738 foreach my $v (@{$dest->{View}})
740 $v=GraphY(abs($v)) if substr($v,0,1) eq '-';
742 unshift(@{$dest->{View}},"$cpageno 0 R");
744 if (!defined($dests))
746 $cat->{Dests}=BuildObj(++$objct,{});
747 $dests=$obj[$objct]->{DATA};
750 my $k=substr($dest->{Dest},1);
751 $dests->{$k}=$dest->{View};
753 elsif ($pdfmark=~m/(.+) \/ANN/)
759 $l=~s'/Subtype /URI'/S /URI';
760 my @xwds=split(' ',"<< $l >>");
761 my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds));
762 my $annot=$obj[$objct];
763 $annot->{DATA}->{Type}='/Annot';
764 FixRect($annot->{DATA}->{Rect}); # Y origin to ll
765 FixPDFColour($annot->{DATA});
766 push(@{$cpage->{Annots}},$annotno);
768 elsif ($pdfmark=~m/(.+) \/OUT/)
770 my @xwds=split(' ',"<< $1 >>");
771 my $out=ParsePDFValue(\@xwds);
775 if (exists($out->{Level}))
777 my $lev=abs($out->{Level});
778 my $levsgn=sgn($out->{Level});
779 delete($out->{Level});
783 my $thisoutlev=$curoutlev->[$#{$curoutlev}]->[1];
784 $thisoutlev->[0]=[0,$curoutlev,0,$levsgn];
785 $curoutlev=$thisoutlev;
786 $curoutlevno=$#{$curoutlev};
789 elsif ($lev < $thislev)
791 my $openct=$curoutlev->[0]->[2];
793 while ($thislev > $lev)
795 my $nxtoutlev=$curoutlev->[0]->[1];
796 $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1;
797 $openct=0 if $nxtoutlev->[0]->[3]==-1;
798 $curoutlev=$nxtoutlev;
802 $curoutlevno=$#{$curoutlev};
805 # push(@{$curoutlev},$this);
806 splice(@{$curoutlev},++$curoutlevno,0,$this);
807 $curoutlev->[0]->[2]++;
811 # This code supports old pdfmark.tmac, unused by pdf.tmac
812 while ($curoutlev->[0]->[0] == 0 and defined($curoutlev->[0]->[1]))
814 $curoutlev=$curoutlev->[0]->[1];
817 $curoutlev->[0]->[0]--;
818 $curoutlev->[0]->[2]++;
819 push(@{$curoutlev},$this);
822 if (exists($out->{Count}) and $out->{Count} != 0)
824 push(@{$this->[1]},[abs($out->{Count}),$curoutlev,0,sgn($out->{Count})]);
825 $curoutlev=$this->[1];
827 if ($out->{Count} > 0)
833 $p->[0]->[2]+=$out->{Count};
842 elsif (lc($xprm[0]) eq 'pdf:')
844 if (lc($xprm[1]) eq 'import')
852 my $hgt=$xprm[8]||-1;
853 my $mat=[1,0,0,1,0,0];
855 if (!exists($incfil{$fil}))
859 $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"import");
861 elsif ($fil=~m/\.swf$/)
863 my $xscale=$wid/($urx-$llx+1);
864 my $yscale=($hgt<=0)?$xscale:($hgt/($ury-$lly+1));
865 $hgt=($ury-$lly+1)*$yscale;
878 $incfil{$fil}=LoadSWF($fil,[$llx,$lly,$urx,$ury],$mat);
882 Msg(0,"Unknown filetype '$fil'");
887 if (defined($incfil{$fil}))
892 my $bbox=$incfil{$fil}->[1];
893 my $xscale=$wid/($bbox->[2]-$bbox->[0]+1);
894 my $yscale=($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1]+1));
895 $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
896 $stream.=" 0 1 -1 0 0 0 cm" if $rot;
897 $stream.=" /$incfil{$fil}->[0] Do Q\n";
899 elsif ($fil=~m/\.swf$/)
901 $stream.=PutXY($xpos,$ypos)." m /$incfil{$fil} Do\n";
905 elsif (lc($xprm[1]) eq 'pdfpic')
908 my $flag=uc($xprm[3])||'-L';
909 my $wid=GetPoints($xprm[4])||-1;
910 my $hgt=GetPoints($xprm[5]||-1);
911 my $ll=GetPoints($xprm[6]||0);
912 my $mat=[1,0,0,1,0,0];
914 if (!exists($incfil{$fil}))
916 $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"pdfpic");
919 if (defined($incfil{$fil}))
922 my $bbox=$incfil{$fil}->[1];
923 $wid=($bbox->[2]-$bbox->[0]) if $wid <= 0;
924 my $xscale=$wid/($bbox->[2]-$bbox->[0]);
925 my $yscale=($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1]));
926 $xscale=($wid<=0)?$yscale:$xscale;
927 $xscale=$yscale if $yscale < $xscale;
928 $yscale=$xscale if $xscale < $yscale;
929 $wid=($bbox->[2]-$bbox->[0])*$xscale;
930 $hgt=($bbox->[3]-$bbox->[1])*$yscale;
932 if ($flag eq '-C' and $ll > $wid)
934 $xpos=int(($ll-$wid)/2);
936 elsif ($flag eq '-R' and $ll > $wid)
942 $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
943 $stream.=" 0 1 -1 0 0 0 cm" if $rot;
944 $stream.=" /$incfil{$fil}->[0] Do Q\n";
947 elsif (lc($xprm[1]) eq 'xrev')
951 elsif (lc($xprm[1]) eq 'markstart')
953 $mark={'rst' => ($xprm[2]+$xprm[4])/$unitwidth, 'rsb' => ($xprm[3]-$xprm[4])/$unitwidth, 'xpos' => $xpos-($xprm[4]/$unitwidth),
954 'ypos' => $ypos, 'lead' => $xprm[4]/$unitwidth, 'pdfmark' => join(' ',@xprm[5..$#xprm])};
956 elsif (lc($xprm[1]) eq 'markend')
958 PutHotSpot($xpos) if defined($mark);
961 elsif (lc($xprm[1]) eq 'marksuspend')
966 elsif (lc($xprm[1]) eq 'markrestart')
971 elsif (lc($xprm[1]) eq 'pagename')
975 $pgnames{$xprm[2]}=$pages->{Kids}->[$pginsert];
979 $pgnames{$xprm[2]}='top';
982 elsif (lc($xprm[1]) eq 'switchtopage')
989 if (!defined($want) or $want eq '')
996 if (!defined($ba) or $ba eq '' or $want eq 'bottom')
998 $pginsert=$#{$pages->{Kids}};
1000 elsif ($want eq 'top')
1006 if (exists($pgnames{$want}))
1008 my $ref=$pgnames{$want};
1018 foreach my $j (0..$#{$pages->{Kids}})
1020 if ($ref eq $pages->{Kids}->[$j])
1022 if ($ba eq 'before')
1027 elsif ($ba eq 'after')
1034 Msg(0,"Parameter must be top|bottom|before|after not '$ba'");
1041 Msg(0,"Can't find page ref '$ref'");
1049 Msg(0,"Can't find page named '$want'");
1055 ($curoutlev,$curoutlevno,$thislev)=(\@outlev,0,1);
1059 ($curoutlev,$curoutlevno,$thislev)=(@{$outlines[$pginsert]});
1064 elsif (lc(substr($xprm[0],0,9)) eq 'papersize')
1066 my ($px,$py)=split(',',substr($xprm[0],10));
1069 @mediabox=(0,0,$px,$py);
1073 $cpage->{MediaBox}=\@mb;
1089 foreach my $j (0..2)
1091 push(@r,sprintf("%1.3f",$a->[$j]/0xffff));
1097 elsif (substr($c,0,1) eq '#')
1099 if (length($c) == 7)
1101 foreach my $j (0..2)
1103 push(@r,sprintf("%1.3f",hex(substr($c,$j*2+1,2))/0xff));
1108 elsif (length($c) == 14)
1110 foreach my $j (0..2)
1112 push(@r,sprintf("%1.3f",hex(substr($c,$j*4+2,4))/0xffff));
1123 my $l=$mark->{pdfmark};
1126 $l=~s'/Subtype /URI'/S /URI';
1127 $l=~s(\\\[u00(..)\])(chr(hex($1)))eg;
1128 my @xwds=split(' ',"<< $l >>");
1129 my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds));
1130 my $annot=$obj[$objct];
1131 $annot->{DATA}->{Type}='/Annot';
1132 $annot->{DATA}->{Rect}=[$mark->{xpos},$mark->{ypos}-$mark->{rsb},$endx+$mark->{lead},$mark->{ypos}-$mark->{rst}];
1133 FixPDFColour($annot->{DATA});
1134 FixRect($annot->{DATA}->{Rect}); # Y origin to ll
1135 push(@{$cpage->{Annots}},$annotno);
1140 return(1) if $_[0] > 0;
1141 return(-1) if $_[0] < 0;
1149 return if !defined($rect);
1150 $rect->[1]=GraphY($rect->[1]);
1151 $rect->[3]=GraphY($rect->[3]);
1158 $val=ToPoints($1,$2) if ($val=~m/(-?[\d.]+)([cipnz])/);
1163 # Although the PDF reference mentions XObject/Form as a way of incorporating an external PDF page into
1164 # the current PDF, it seems not to work with any current PDF reader (although I am told (by Leonard Rosenthol,
1165 # who helped author the PDF ISO standard) that Acroread 9 does support it, empiorical observation shows otherwise!!).
1166 # So... do it the hard way - full PDF parser and merge required objects!!!
1173 # my $wid=($bbox->[2]-$bbox->[0])*$mat->[0];
1174 # my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3];
1176 # if (!open(PDF,"<$fil"))
1178 # Msg(0,"Failed to open '$fil'");
1187 # my $xonm="XO$objct";
1189 # $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject',
1190 # 'Subtype' => '/Form',
1193 # 'Resources' => $pages->{'Resources'},
1194 # 'Ref' => {'Page' => '1',
1195 # 'F' => BuildObj($objct+1,{'Type' => '/Filespec',
1197 # 'EF' => {'F' => BuildObj($objct+2,{'Type' => '/EmbeddedFile'})}
1202 # $obj[$objct]->{STREAM}="q 1 0 0 1 0 0 cm
1212 # # $obj[$objct]->{STREAM}=PutXY($xpos,$ypos)." m ".PutXY($xpos+$wid,$ypos)." l ".PutXY($xpos+$wid,$ypos+$hgt)." l ".PutXY($xpos,$ypos+$hgt)." l f\n";
1213 # $obj[$objct+2]->{STREAM}=join('',@f);
1226 my $wid=($bbox->[2]-$bbox->[0])*$mat->[0];
1227 my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3];
1228 my (@path)=split('/',$fil);
1229 my $node=pop(@path);
1231 if (!open(PDF,"<$fil"))
1233 Msg(0,"Failed to open '$fil'");
1242 my $xonm="XO$objct";
1244 $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject', 'BBox' => $bbox, 'Matrix' => $mat, 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"});
1245 $obj[$objct]->{STREAM}='';
1248 my $asset=BuildObj($objct,{'EF' => {'F' => BuildObj($objct+1,{})},
1250 'Type' => '/Filespec',
1251 'UF' => "($node)"});
1255 $obj[$objct]->{STREAM}=join('',@f);
1258 my $config=BuildObj($objct,{'Instances' => [BuildObj($objct+1,{'Params' => { 'Binding' => '/Background'}, 'Asset' => $asset})],
1259 'Subtype' => '/Flash'});
1266 my ($x,$y)=split(' ',PutXY($xpos,$ypos));
1268 push(@{$cpage->{Annots}},BuildObj($objct,{'RichMediaContent' => {'Subtype' => '/Flash', 'Configurations' => [$config], 'Assets' => {'Names' => [ "($node)", $asset ] }},
1269 'P' => "$cpageno 0 R",
1270 'RichMediaSettings' => { 'Deactivation' => { 'Condition' => '/PI',
1271 'Type' => '/RichMediaDeactivation'},
1272 'Activation' => { 'Condition' => '/PV',
1273 'Type' => '/RichMediaActivation'}},
1275 'Subtype' => '/RichMedia',
1277 'Rect' => "[ $x $y ".($x+$wid)." ".($y+$hgt)." ]",
1278 'Border' => [0,0,0]}));
1299 if (!open(PD,"<$pdfnm"))
1301 Msg(0,"Failed to open PDF '$pdfnm'");
1307 $/="\r" if (length($hdr) > 10);
1315 if (m/endstream(\s+.*)?$/)
1319 $_.=$1 if defined($1)
1324 if (m'/Length\s+(\d+)(\s+\d+\s+R)?')
1336 if (m'^(\d+) \d+ obj')
1339 $pdf->[$curobj]->{OBJ}=undef;
1342 if (m'stream\s*$' and ! m/^endstream/)
1346 $pdf->[$curobj]->{STREAMPOS}=[tell(PD),$strmlen];
1347 seek(PD,$strmlen,1);
1352 Msg(0,"Parsing PDF '$pdfnm' failed");
1363 # $pdftxt=~s/\]/ \]/g;
1364 my (@pdfwds)=split(' ',$pdftxt);
1367 while ($wd=nextwd(\@pdfwds),length($wd))
1369 if ($wd=~m/\d+/ and defined($pdfwds[1]) and $pdfwds[1]=~m/^obj(.*)/)
1372 shift(@pdfwds); shift(@pdfwds);
1373 unshift(@pdfwds,$1) if defined($1) and length($1);
1374 $pdf->[$curobj]->{OBJ}=ParsePDFObj(\@pdfwds);
1376 elsif ($wd eq 'trailer' and !exists($pdf->[0]->{OBJ}))
1378 $pdf->[0]->{OBJ}=ParsePDFObj(\@pdfwds);
1382 # print "Skip '$wd'\n";
1386 my $catalog=${$pdf->[0]->{OBJ}->{Root}};
1387 my $page=FindPage(1,$pdf);
1392 foreach my $o (@{$pdf})
1394 if (exists($o->{STREAMPOS}))
1398 $l=$o->{OBJ}->{Length} if exists($o->{OBJ}->{Length});
1400 $l=$pdf->[$$l]->{OBJ} if (defined($l) && ref($l) eq 'OBJREF');
1402 Msg(1,"Unable to determine length of stream \@$o->{STREAMPOS}->[0]") if !defined($l);
1404 sysseek(PD,$o->{STREAMPOS}->[0],0);
1405 Msg(0,'Failed to read all the stream') if $l != sysread(PD,$o->{STREAM},$l);
1407 if (exists($o->{OBJ}->{'Filter'}) and $o->{OBJ}->{'Filter'} eq '/FlateDecode')
1409 $o->{STREAM}=Compress::Zlib::uncompress($o->{STREAM});
1410 delete($o->{OBJ }->{'Filter'});
1421 foreach my $k (qw( MediaBox ArtBox TrimBox BleedBox CropBox ))
1423 $BBox=FindKey($pdf,$page,$k);
1427 $BBox=[0,0,595,842] if !defined($BBox);
1429 $wid=($BBox->[2]-$BBox->[0]+1) if $wid==0;
1430 my $xscale=abs($wid)/($BBox->[2]-$BBox->[0]+1);
1431 my $yscale=($hgt<=0)?$xscale:(abs($hgt)/($BBox->[3]-$BBox->[1]+1));
1432 $hgt=($BBox->[3]-$BBox->[1]+1)*$yscale;
1434 if ($type eq "import")
1442 my $res=FindKey($pdf,$page,'Resources');
1445 # Map inserted objects to current PDF
1447 MapInsValue($pdf,$page,'',$insmap,$xobj,$pdf->[$page]->{OBJ});
1449 # Many PDFs include 'Resources' at the 'Page' level but if 'Resources' is held at a higher level (i.e 'Pages')
1450 # then we need to include its objects as well.
1452 MapInsValue($pdf,$page,'',$insmap,$xobj,$res) if !exists($pdf->[$page]->{OBJ}->{Resources});
1458 $incres{ProcSet}=['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI'];
1460 ($mat->[4],$mat->[5])=split(' ',PutXY($xpos,$ypos));
1461 $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => $BBox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject", 'Resources' => \%incres});
1463 BuildStream($xobj,$pdf,$pdf->[$page]->{OBJ}->{Contents});
1465 return([$xonm,$BBox] );
1475 my $refval=ref($val);
1477 if ($refval eq 'OBJREF')
1479 push(@{$objs}, $val);
1481 elsif ($refval eq 'ARRAY')
1487 Msg(0,"unexpected 'Contents'");
1490 foreach my $o (@{$objs})
1492 $strm.="\n" if $strm;
1493 $strm.=$pdf->[$$o]->{STREAM} if exists($pdf->[$$o]->{STREAM});
1496 $obj[$xobj]->{STREAM}=$strm;
1509 foreach my $k (keys(%{$val}))
1511 MapInsValue($pdf,$o,$k,$insmap,$parent,$val->{$k}) if $k ne 'Contents';
1523 my $refval=ref($val);
1525 if ($refval eq 'OBJREF')
1529 if (!exists($insmap->{IMP}->{$$val}))
1532 $insmap->{CUR}->{$objct}=$$val;
1533 $insmap->{IMP}->{$$val}=$objct;
1534 $obj[$objct]->{DATA}=$pdf->[$$val]->{OBJ};
1535 $obj[$objct]->{STREAM}=$pdf->[$$val]->{STREAM} if exists($pdf->[$$val]->{STREAM});
1536 MapInsValue($pdf,$$val,'',$insmap,$o,$pdf->[$$val]->{OBJ});
1539 $$val=$insmap->{IMP}->{$$val};
1546 elsif ($refval eq 'ARRAY')
1548 foreach my $v (@{$val})
1550 MapInsValue($pdf,$o,'',$insmap,$parent,$v)
1553 elsif ($refval eq 'HASH')
1555 MapInsHash($pdf,$o,$insmap,$parent,$val);
1566 if (exists($pdf->[$page]->{OBJ}->{$k}))
1568 my $val=$pdf->[$page]->{OBJ}->{$k};
1569 $val=$pdf->[$$val]->{OBJ} if ref($val) eq 'OBJREF';
1574 if (exists($pdf->[$page]->{OBJ}->{Parent}))
1576 return(FindKey($pdf,${$pdf->[$page]->{OBJ}->{Parent}},$k));
1587 my $catalog=${$pdf->[0]->{OBJ}->{Root}};
1588 my $pages=${$pdf->[$catalog]->{OBJ}->{Pages}};
1590 return(NextPage($pdf,$pages,\$wantpg));
1600 if ($pdf->[$pages]->{OBJ}->{Type} eq '/Pages')
1602 foreach my $kid (@{$pdf->[$pages]->{OBJ}->{Kids}})
1604 $ret=NextPage($pdf,$$kid,$wantpg);
1605 last if $$wantpg<=0;
1608 elsif ($pdf->[$pages]->{OBJ}->{Type} eq '/Page')
1621 my $wd=shift(@{$pdfwds});
1623 return('') if !defined($wd);
1625 if ($wd=~m/^(.*?)(<<|>>|(?:(?<!\\)\[|\]))(.*)/)
1627 if (defined($1) and length($1))
1629 unshift(@{$pdfwds},$3) if defined($3) and length($3);
1630 unshift(@{$pdfwds},$2);
1635 unshift(@{$pdfwds},$3) if defined($3) and length($3);
1650 while ($wd=nextwd($pdfwds),length($wd))
1652 if ($wd eq 'stream' or $wd eq 'endstream')
1656 elsif ($wd eq 'endobj' or $wd eq 'startxref')
1662 unshift(@{$pdfwds},$wd);
1663 $rtn=ParsePDFValue($pdfwds);
1676 while ($wd=nextwd($pdfwds),length($wd))
1683 my (@w)=split('/',$wd,3);
1687 Msg(0,"PDF Dict Key '$wd' does not start with '/'");
1692 unshift(@{$pdfwds},"/$w[2]") if $w[2];
1694 (@w)=split('\(',$wd,2);
1696 unshift(@{$pdfwds},"($w[1]") if defined($w[1]);
1697 (@w)=split('\<',$wd,2);
1699 unshift(@{$pdfwds},"<$w[1]") if defined($w[1]);
1701 $rtn->{$wd}=ParsePDFValue($pdfwds);
1712 my $wd=nextwd($pdfwds);
1714 if ($wd=~m/^\d+$/ and $pdfwds->[0]=~m/^\d+$/ and $pdfwds->[1]=~m/^R(\]|\>|\/)?/)
1717 if (defined($1) and length($1))
1719 $pdfwds->[0]=substr($pdfwds->[0],1);
1725 return(bless(\$wd,'OBJREF'));
1730 return(ParsePDFHash($pdfwds));
1735 return(ParsePDFArray($pdfwds));
1738 if ($wd=~m/(.*?)(\(.*)$/)
1740 if (defined($1) and length($1))
1742 unshift(@{$pdfwds},$2);
1747 return(ParsePDFString($wd,$pdfwds));
1751 if ($wd=~m/(.*?)(\<.*)$/)
1753 if (defined($1) and length($1))
1755 unshift(@{$pdfwds},$2);
1760 return(ParsePDFHexString($wd,$pdfwds));
1764 if ($wd=~m/(.+?)(\/.*)$/)
1766 if (defined($2) and length($2))
1768 unshift(@{$pdfwds},$2);
1785 $rtn.=' ' if length($rtn);
1787 while ($wd=~m/(?<!\\)\(/g) {$lev++;}
1788 while ($wd=~m/(?<!\\)\)/g) {$lev--;}
1791 if ($lev<=0 and $wd=~m/^(.*?\))([^)]+)$/)
1793 unshift(@{$pdfwds},$2) if defined($2) and length($2);
1801 $wd=nextwd($pdfwds);
1807 sub ParsePDFHexString
1814 if ($wd=~m/^(<.+?>)(.*)/)
1816 unshift(@{$pdfwds},$2) if defined($2) and length($2);
1831 $wd=ParsePDFValue($pdfwds);
1832 last if $wd eq ']' or length($wd)==0;
1843 print STDERR "$env{SourceFile}: " if exists($env{SourceFile});
1844 print STDERR "$msg\n";
1873 return($mediabox[3]-$y);
1888 my $msg="$ono 0 obj ";
1889 $obj[$ono]->{XREF}=$fct;
1890 if (exists($obj[$ono]->{STREAM}))
1892 if (!$debug && !exists($obj[$ono]->{DATA}->{'Filter'}))
1894 $obj[$ono]->{STREAM}=Compress::Zlib::compress($obj[$ono]->{STREAM});
1895 $obj[$ono]->{DATA}->{'Filter'}=['/FlateDecode'];
1898 $obj[$ono]->{DATA}->{'Length'}=length($obj[$ono]->{STREAM});
1900 PutField(\$msg,$obj[$ono]->{DATA});
1901 PutStream(\$msg,$ono) if exists($obj[$ono]->{STREAM});
1902 Put($msg."endobj\n");
1910 # We could 'flate' here
1911 $$msg.="stream\n$obj[$ono]->{STREAM}endstream\n";
1918 my $term=shift||"\n";
1923 $$pmsg.="$fld$term";
1925 elsif ($typ eq 'ARRAY')
1928 foreach my $cell (@{$fld})
1930 PutField($pmsg,$cell,' ');
1934 elsif ($typ eq 'HASH')
1937 foreach my $key (sort keys %{$fld})
1940 PutField($pmsg,$fld->{$key});
1944 elsif ($typ eq 'OBJREF')
1946 $$pmsg.="$$fld 0 R$term";
1955 $obj[$ono]->{DATA}=$val;
1957 return("$ono 0 R ");
1964 my $ofontnm=$fontnm;
1966 return $fontlst{$fontno}->{OBJ} if (exists($fontlst{$fontno}));
1969 OpenFile(\$f,$fontdir,"$fontnm");
1971 if (!defined($f) and $Foundry)
1973 # Try with no foundry
1975 OpenFile(\$f,$fontdir,$fontnm);
1978 Msg(1,"Failed to open font '$ofontnm'") if !defined($f);
1981 $foundry=$1 if $fontnm=~m/^(.*?)-/;
1984 my @fntbbox=(0,0,0,0);
1997 s/^#.*// if $stg == 1;
2002 my ($key,$val)=split(' ',$_,2);
2005 $stg=2,next if $key eq 'kernpairs';
2006 $stg=3,next if lc($_) eq 'charset';
2012 $stg=3,next if lc($_) eq 'charset';
2014 my ($ch1,$ch2,$k)=split;
2015 $fnt{KERN}->{$ch1}->{$ch2}=$k;
2020 my (@p)=split(',',$r[1]);
2024 $fnt{GNM}->{$r[0]}=$lastchr;
2028 $r[0]='u0020' if $r[3] == 32;
2029 # next if $r[3] >255;
2030 $fnt{GNM}->{$r[0]}=$r[3];
2031 $fnt{GNO}->[$r[3]]='/'.$r[4];
2032 $fnt{WID}->[$r[3]]=$p[0];
2033 $lastchr=$r[3] if $r[3] > $lastchr;
2034 $fixwid=$p[0] if $fixwid == -1;
2035 $fixwid=-2 if $fixwid > 0 and $p[0] != $fixwid;
2037 $fntbbox[1]=-$p[2] if defined($p[2]) and -$p[2] < $fntbbox[1];
2038 $fntbbox[2]=$p[0] if $p[0] > $fntbbox[2];
2039 $fntbbox[3]=$p[1] if defined($p[1]) and $p[1] > $fntbbox[3];
2040 $ascent=$p[1] if defined($p[1]) and $p[1] > $ascent and $r[3] >= 32 and $r[3] < 128;
2041 $charset.='/'.$r[4] if defined($r[4]);
2042 $capheight=$p[1] if length($r[4]) == 1 and $r[4] ge 'A' and $r[4] le 'Z' and $p[1] > $capheight;
2048 unshift(@{$fnt{GNO}},0);
2050 foreach my $glyph (@{$fnt{GNO}})
2052 $glyph='/.notdef' if !defined($glyph);
2055 foreach my $w (@{$fnt{WID}})
2057 $w=0 if !defined($w);
2062 $slant=-$fnt{'slant'} if exists($fnt{'slant'});
2063 $fnt{'spacewidth'}=700 if !exists($fnt{'spacewidth'});
2065 $t1flags|=2**0 if $fixwid > -1;
2066 $t1flags|=(exists($fnt{'special'}))?2**2:2**5;
2067 $t1flags|=2**6 if $slant != 0;
2068 my $fontkey="$foundry $fnt{internalname}";
2070 if (exists($download{$fontkey}))
2073 my ($l1,$l2,$l3,$t1stream)=GetType1($download{$fontkey});
2074 Msg(0,"Incorrect font format for '$fontkey' ($l1)") if !defined($t1stream);
2076 $fontlst{$fontno}->{OBJ}=BuildObj($objct,
2078 'Subtype' => '/Type1',
2079 'BaseFont' => '/'.$fnt{internalname},
2080 'Widths' => $fnt{WID},
2082 'LastChar' => $lastchr,
2083 'Encoding' => BuildObj($objct+1,
2084 {'Type' => '/Encoding',
2085 'Differences' => $fnt{GNO}
2088 'FontDescriptor' => BuildObj($objct+2,
2089 {'Type' => '/FontDescriptor',
2090 'FontName' => '/'.$fnt{internalname},
2091 'Flags' => $t1flags,
2092 'FontBBox' => \@fntbbox,
2093 'ItalicAngle' => $slant,
2094 'Ascent' => $ascent,
2095 'Descent' => $fntbbox[1],
2096 'CapHeight' => $capheight,
2098 'CharSet' => "($charset)",
2099 'FontFile' => BuildObj($objct+3,
2111 $fontlst{$fontno}->{NM}='/F'.$fontno;
2112 $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ};
2113 $fontlst{$fontno}->{FNT}=\%fnt;
2114 $obj[$objct]->{STREAM}=$t1stream;
2120 $fontlst{$fontno}->{OBJ}=BuildObj($objct,
2122 'Subtype' => '/Type1',
2123 'BaseFont' => '/'.$fnt{internalname},
2124 'Encoding' => BuildObj($objct+1,
2125 {'Type' => '/Encoding',
2126 'Differences' => $fnt{GNO}
2132 $fontlst{$fontno}->{NM}='/F'.$fontno;
2133 $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ};
2134 $fontlst{$fontno}->{FNT}=\%fnt;
2137 if (defined($fnt{encoding}) and $fnt{encoding} eq 'text.enc' and $ucmap ne '')
2139 if ($textenccmap eq '')
2141 $textenccmap = BuildObj($objct+1,{});
2143 $obj[$objct]->{STREAM}=$ucmap;
2145 $obj[$fno]->{DATA}->{'ToUnicode'}=$textenccmap;
2150 # PutObj($fno+2) if defined($obj[$fno+2]);
2151 # PutObj($fno+3) if defined($obj[$fno+3]);
2157 my ($l1,$l2,$l3); # Return lengths
2158 my ($head,$body,$tail); # Font contents
2161 OpenFile(\$f,$fontdir,"$file");
2162 Msg(1,"Failed to open '$file'") if !defined($f);
2167 if (substr($l,0,1) eq "\x80")
2179 my $chk=sysread($f,$hdr,6);
2183 # eof($f) uses buffered i/o (since file was open not sysopen)
2184 # which screws up next sysread. So this will terminate loop if font
2185 # has no terminating section type 3.
2187 return(5,$l2,$l3,undef);
2190 $typ=ord(substr($hdr,1,1));
2194 $sl=unpack('L',substr($hdr,2,4));
2195 $chk=sysread($f,$data,$sl);
2196 return(1,$l2,$l3,undef) if $chk != $sl;
2203 # First text bit(s) must be head
2209 # A text bit after the binary sections must be tail
2216 return(2,$l2,$l3,undef) if $l3; # Found a binary bit after the tail
2222 # What segment type is this!
2223 return(3,$l2,$l3,undef);
2228 return($l1,$l2,$l3,"$head$body$tail");
2236 Msg(1,"Font file '$file' must be an Adobe type 1 font file") if $lines[0]!~m/\%\!PS.Adobe/i;
2237 $head=$body=$tail='';
2239 foreach my $line (@lines)
2243 if (length($line) > 19 and $line=~s/^(currentfile eexec)//)
2252 if ($line=~m/eexec$/)
2259 elsif (!defined($l2))
2261 #$line=~s/(\0\0)0+$/&1/;
2270 $body.=pack('H*',$line);
2283 return($l1,$l2,$l3,"$head$body$tail");
2293 $obj[$ono]->{STREAM}=$stream;
2294 $obj[$ono]->{DATA}->{Length}=length($stream);
2305 $cpage->{MediaBox}=\@mediabox if $custompaper;
2307 OutStream($cpageno+1);
2312 my $thispg=BuildObj($objct,
2314 'Group' => {'CS' => '/DeviceRGB', 'S' => '/Transparency'},
2315 'Parent' => '2 0 R',
2316 'Contents' => [ BuildObj($objct+1,
2322 splice(@{$pages->{Kids}},++$pginsert,0,$thispg);
2323 splice(@outlines,$pginsert,0,[$curoutlev,$#{$curoutlev}+1,$thislev]);
2326 $cpage=$obj[$cpageno]->{DATA};
2327 $pages->{'Count'}++;
2328 $stream="q 1 0 0 1 0 0 cm\n$linejoin J\n$linecap j\n";
2329 $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne '';
2332 # @mediabox=@defaultmb;
2342 # $stream.="/F$cft $cftsz Tf\n" if $cftsz;
2343 $widtbl=CacheWid($par);
2344 $origwidtbl=$fontlst{$par}->{FNT}->{WID};
2345 $krntbl=$fontlst{$par}->{FNT}->{KERN};
2352 if (!defined($fontlst{$par}->{CACHE}->{$cftsz}))
2354 $fontlst{$par}->{CACHE}->{$cftsz}=BuildCache($fontlst{$par}->{FNT}->{WID});
2357 return($fontlst{$par}->{CACHE}->{$cftsz});
2366 foreach my $w (@{$wid})
2368 push(@cwid,$w*$cftsz);
2378 $xpos+=$pendmv/$unitwidth;
2379 $stream.="q BT\n$matrix ".PutXY($xpos,$ypos)." Tm\n";
2385 $stream.=$textcol."\n", $curfill=$textcol if $textcol ne $curfill;
2388 $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
2389 $stream.="/F$cft $cftsz Tf\n";
2391 $stream.="$curkern Tc\n";
2394 if ($poschg or $matrixchg)
2396 PutLine(0) if $matrixchg;
2397 $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
2400 $stream.="$curkern Tc\n";
2406 $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
2407 $stream.="/F$cft $cftsz Tf\n" if $cftsz and defined($cft);
2420 $xpos+=($pendmv-$nomove)/$unitwidth;
2423 $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne $curstrk;
2434 if ($par != $cftsz and defined($cft))
2438 Set_LWidth() if $lwidth < 1;
2439 # $stream.="/F$cft $cftsz Tf\n";
2441 $widtbl=CacheWid($cft);
2446 Set_LWidth() if $lwidth < 1;
2453 $stream.=((($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000)." w\n";
2459 # Groff uses /m[] for text & graphic stroke, and /M[] (DF?) for graphic fill.
2460 # PDF uses G/RG/K for graphic stroke, and g/rg/k for text & graphic fill.
2462 # This means that we must maintain g/rg/k state separately for text colour & graphic fill (this is
2463 # probably why 'gs' maintains seperate graphic states for text & graphics when distilling PS -> PDF).
2465 # To facilitate this:-
2467 # $textcol = current groff stroke colour
2468 # $fillcol = current groff fill colour
2469 # $curfill = current PDF fill colour
2472 my $mcmd=substr($par,0,1);
2474 $par=substr($par,1);
2479 $textcol=set_col($mcmd,$par,0);
2480 $strkcol=set_col($mcmd,$par,1);
2485 $stream.=$textcol."\n";
2490 $stream.="$strkcol\n";
2500 my @oper=('g','k','rg');
2502 @oper=('G','K','RG') if $upper;
2507 return("0 $oper[0]");
2510 my (@c)=split(' ',$par);
2515 return(($c[0]/65535).' '.($c[1]/65535).' '.($c[2]/65535)." 0 $oper[1]");
2517 elsif ($mcmd eq 'k')
2520 return(($c[0]/65535).' '.($c[1]/65535).' '.($c[2]/65535).' '.($c[3]/65535)." $oper[1]");
2522 elsif ($mcmd eq 'g')
2525 return(($c[0]/65535)." $oper[0]");
2527 elsif ($mcmd eq 'r')
2530 return(($c[0]/65535).' '.($c[1]/65535).' '.($c[2]/65535)." $oper[2]");
2537 my $Dcmd=substr($par,0,1);
2539 $par=substr($par,1);
2540 $xpos+=$pendmv/$unitwidth;
2547 my $mcmd=substr($par,0,1);
2549 $par=substr($par,1);
2552 $fillcol=set_col($mcmd,$par,0);
2553 $stream.="$fillcol\n";
2556 elsif ($Dcmd eq 'f')
2558 my $mcmd=substr($par,0,1);
2560 $par=substr($par,1);
2562 ($par)=split(' ',$par);
2564 if ($par >= 0 and $par <= 1000)
2566 $fillcol=set_col('g',int((1000-$par)*65535/1000),0);
2570 $fillcol=lc($textcol);
2573 $stream.="$fillcol\n";
2576 elsif ($Dcmd eq '~')
2579 my (@p)=split(' ',$par);
2582 foreach my $p (@p) { $p/=$unitwidth; }
2583 $stream.=PutXY($xpos,$ypos)." m\n";
2586 $stream.=PutXY($xpos,$ypos)." l\n";
2588 for (my $i=0; $i < $#p-1; $i+=2)
2590 $nxpos=(($p[$i]*$tnum)/(2*$tden));
2591 $nypos=(($p[$i+1]*$tnum)/(2*$tden));
2592 $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." ";
2593 $nxpos=($p[$i]/2 + ($p[$i+2]*($tden-$tnum))/(2*$tden));
2594 $nypos=($p[$i+1]/2 + ($p[$i+3]*($tden-$tnum))/(2*$tden));
2595 $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." ";
2596 $nxpos=(($p[$i]-$p[$i]/2) + $p[$i+2]/2);
2597 $nypos=(($p[$i+1]-$p[$i+1]/2) + $p[$i+3]/2);
2598 $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." c\n";
2603 $xpos+=($p[$#p-1]-$p[$#p-1]/2);
2604 $ypos+=($p[$#p]-$p[$#p]/2);
2605 $stream.=PutXY($xpos,$ypos)." l\nS\n";
2608 elsif ($Dcmd eq 'p' or $Dcmd eq 'P')
2611 my (@p)=split(' ',$par);
2614 foreach my $p (@p) { $p/=$unitwidth; }
2615 $stream.=PutXY($xpos,$ypos)." m\n";
2617 for (my $i=0; $i < $#p; $i+=2)
2621 $stream.=PutXY($xpos,$ypos)." l\n";
2634 elsif ($Dcmd eq 'c')
2637 $par=substr($par,1);
2638 my (@p)=split(' ',$par);
2640 DrawCircle($p[0],$p[0]);
2644 elsif ($Dcmd eq 'C')
2647 $par=substr($par,1);
2648 my (@p)=split(' ',$par);
2650 DrawCircle($p[0],$p[0]);
2654 elsif ($Dcmd eq 'e')
2657 $par=substr($par,1);
2658 my (@p)=split(' ',$par);
2660 DrawCircle($p[0],$p[1]);
2664 elsif ($Dcmd eq 'E')
2667 $par=substr($par,1);
2668 my (@p)=split(' ',$par);
2670 DrawCircle($p[0],$p[1]);
2674 elsif ($Dcmd eq 'l')
2677 $par=substr($par,1);
2678 my (@p)=split(' ',$par);
2680 foreach my $p (@p) { $p/=$unitwidth; }
2681 $stream.=PutXY($xpos,$ypos)." m\n";
2684 $stream.=PutXY($xpos,$ypos)." l\n";
2689 elsif ($Dcmd eq 't')
2692 $par=substr($par,1);
2693 my (@p)=split(' ',$par);
2695 foreach my $p (@p) { $p/=$unitwidth; }
2696 # $xpos+=$p[0]*100; # WTF!!!
2697 #int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
2698 $p[0]=(($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000 if $p[0] < 0;
2700 $stream.="$p[0] w\n";
2704 elsif ($Dcmd eq 'a')
2707 $par=substr($par,1);
2708 my (@p)=split(' ',$par);
2710 my $rad360=$rad180*2;
2711 my $rad90=$rad180/2;
2713 foreach my $p (@p) { $p/=$unitwidth; }
2715 # Documentation is wrong. Groff does not use Dh1,Dv1 as centre of the circle!
2717 my $centre=adjust_arc_centre(\@p);
2719 # Using formula here : http://www.tinaja.com/glib/bezcirc2.pdf
2720 # First calculate angle between start and end point
2722 my ($startang,$r)=RtoP(-$centre->[0],$centre->[1]);
2723 my ($endang,$r2)=RtoP(($p[0]+$p[2])-$centre->[0],-($p[1]+$p[3]-$centre->[1]));
2724 $endang+=$rad360 if $endang < $startang;
2725 my $totang=($endang-$startang)/4; # do it in 4 pieces
2729 my $x0=cos($totang/2);
2730 my $y0=sin($totang/2);
2734 my $y1=((1-$x0)*(3-$x0))/(3*$y0);
2738 # Rotate to start position and draw 4 pieces
2740 foreach my $j (0..3)
2742 PlotArcSegment($totang/2+$startang+$j*$totang,$r,$xpos+$centre->[0],GraphY($ypos+$centre->[1]),$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3);
2754 return int($_[0]*180/3.14159);
2757 sub adjust_arc_centre
2759 # Taken from geometry.cpp
2761 # We move the center along a line parallel to the line between
2762 # the specified start point and end point so that the center
2763 # is equidistant between the start and end point.
2764 # It can be proved (using Lagrange multipliers) that this will
2765 # give the point nearest to the specified center that is equidistant
2766 # between the start and end point.
2770 my $x = $p->[0] + $p->[2]; # (x, y) is the end point
2771 my $y = $p->[1] + $p->[3];
2772 my $n = $x*$x + $y*$y;
2777 my $k = .5 - ($c[0]*$x + $c[1]*$y)/$n;
2791 my ($ang,$r,$transx,$transy,$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3)=@_;
2794 my @mat=($cos,$sin,-$sin,$cos,0,0);
2797 $stream.="q $r 0 0 $r $transx $transy cm ".join(' ',@mat)." cm $lw w $x0 $y0 m $x1 $y1 $x2 $y2 $x3 $y3 c S Q\n";
2804 my $hr=$hd/2/$unitwidth;
2805 my $vr=$vd/2/$unitwidth;
2806 my $kappa=0.5522847498;
2811 $stream.=PutXY(($xpos+$hd),$ypos)." m\n";
2812 $stream.=PutXY(($xpos+$hd),($ypos+$vr*$kappa))." ".PutXY(($xpos+$hr+$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos+$hr),($ypos+$vr))." c\n";
2813 $stream.=PutXY(($xpos+$hr-$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos),($ypos+$vr*$kappa))." ".PutXY(($xpos),($ypos))." c\n";
2814 $stream.=PutXY(($xpos),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hr-$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hr),($ypos-$vr))." c\n";
2815 $stream.=PutXY(($xpos+$hr+$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hd),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hd),($ypos))." c\n";
2823 my ($x1,$y1,$x2,$y2,$x3,$y3)=@_;
2832 my $k=.5-($x2*$x + $y2*$y)/$n;
2833 return(sqrt($n),$x2+$k*$x,$y2+$k*$y);
2846 return($r*cos($theta),$r*sin($theta));
2853 return(atan2($y,$x),sqrt($x**2+$y**2));
2861 IsText() if !defined($f);
2863 return if (scalar(@lin) == 0) or (!defined($lin[0]->[0]) and $#lin == 0);
2865 # $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug;
2867 $lin[$#lin]->[1]=-$pendmv/$cftsz if ($pendmv != 0);
2871 if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0))
2873 $stream.="($lin[0]->[0]) Tj\n";
2879 foreach my $wd (@lin)
2881 $stream.="($wd->[0]) " if defined($wd->[0]);
2882 $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
2890 if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0))
2892 $stream.="0 Tw ($lin[0]->[0]) Tj\n";
2896 if ($wt>=-1 or $#lin == 0 or $lin[0]->[1]>=0)
2900 foreach my $wd (@lin)
2902 $stream.="($wd->[0]) " if defined($wd->[0]);
2903 $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
2910 # $stream.="\%dg 0 Tw [";
2912 # foreach my $wd (@lin)
2914 # $stream.="($wd->[0]) " if defined($wd->[0]);
2915 # $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
2918 # $stream.="] TJ\n";
2920 # my $wt=$lin[0]->[1]||0;
2922 # while ($wt < -$whtsz/$cftsz)
2924 # $wt+=$whtsz/$cftsz;
2927 $stream.=sprintf( "%.3f Tw ",-($whtsz+$wt*$cftsz)/$unitwidth-$curkern );
2928 if (!defined($lin[0]->[0]) and defined($lin[0]->[1]))
2930 $stream.="[ $lin[0]->[1] (";
2938 foreach my $wd (@lin)
2940 my $wwt=$wd->[1]||0;
2942 while ($wwt <= $wt+.1)
2948 if (abs($wwt) < .1 or $wwt == 0)
2950 $stream.="$wd->[0]" if defined($wd->[0]);
2954 $wwt=sprintf("%.3f",$wwt);
2955 $stream.="$wd->[0]) $wwt (" if defined($wd->[0]);
2964 $xpos+=$pendmv/$unitwidth;
2974 foreach my $j (1..$no)
2982 $stream.="%% $lin\n" if $debug;
2996 $xpos+=$pendmv/$unitwidth;
3000 $ypos=$par/$unitwidth;
3004 if (substr($ahead[0],0,1) eq 'H')
3006 $xpos=substr($ahead[0],1)/$unitwidth;
3012 # $nomove=$pendmv=0;
3022 $ypos+=$par/$unitwidth;
3033 foreach my $c (split('',$txt))
3036 $widtbl->[$cn]=$origwidtbl->[$cn]*$cftsz if !defined($widtbl->[$cn]);
3040 $ck=length($txt)*$curkern;
3042 return(($w/$unitwidth)+$ck);
3049 if ($kernadjust != $curkern)
3052 $stream.="$kernadjust Tc\n";
3053 $curkern=$kernadjust;
3056 my $wid=TextWid($par);
3058 $par=reverse(split('',$par)) if $xrev;
3059 if ($n_flg and defined($mark))
3061 $mark->{ypos}=$ypos;
3062 $mark->{xpos}=$xpos;
3069 $xpos+=($pendmv-$nomove)/$unitwidth;
3071 $stream.="% == '$par'=$wid 'xpos=$xpos\n" if $debug;
3072 $par=~s/\\(?!\d\d\d)/\\\\/g;
3076 # $pendmv = 'h' move since last 't'
3077 # $nomove = width of char(s) added by 'C', 'N' or 'c'
3078 # $w-flg = 'w' seen since last t
3083 $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
3084 $stream.="/F$cft $cftsz Tf\n", $fontchg=0 if $fontchg && defined($cft);
3089 $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug;
3091 # if ($w_flg && $#lin > -1)
3093 # $lin[$#lin]->[0].=' ';
3095 # $dontglue=1 if $pendmv==0;
3098 $wt=-$pendmv/$cftsz if $w_flg and $wt==-1;
3105 PutLine(0) if $#lin > -1;
3107 $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
3108 $stream.="$curkern Tc\n";
3110 $stream.="($par) Tj\n";
3112 $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
3114 $stream.="$curkern Tc\n";
3122 push(@lin,[undef,-$pendmv/$cftsz]);
3126 $lin[$#lin]->[1]=-$pendmv/$cftsz;
3129 push(@lin,[$par,undef]);
3130 # $xpos+=$pendmv/$unitwidth;
3137 push(@lin,[$par,undef]);
3141 $lin[$#lin]->[0].=$par;
3150 $par=m/([+-]?\d+) (.*)/;
3151 $kernadjust=$1/$unitwidth;
3171 $xpos+=$pendmv/$unitwidth;
3175 my $newx=$par/$unitwidth;
3176 $stream.=sprintf("%.3f",$newx-$tmxpos)." 0 Td\n" if $mode eq 't';
3177 $tmxpos=$xpos=$newx;
3186 ($par,$nm)=FindChar($par);
3195 my $fnt=$fontlst{$cft}->{FNT};
3197 if (exists($fnt->{GNM}->{$chnm}))
3199 my $ch=$fnt->{GNM}->{$chnm};
3200 $ch=RemapChr($ch,$fnt,$chnm) if ($ch > 255);
3202 return(($ch<32)?sprintf("\\%03o",$ch):chr($ch),$fnt->{WID}->[$ch]*$cftsz);
3217 foreach my $un (2..$#{$fnt->{GNO}})
3219 $unused=$un,last if $fnt->{GNO}->[$un] eq '/.notdef';
3222 if (--$unused <= 255)
3224 $fnt->{GNM}->{$chnm}=$unused++;
3225 $fnt->{GNO}->[$unused]=$fnt->{GNO}->[$ch+1];
3226 $fnt->{WID}->[$unused]=$fnt->{WID}->[$ch+1];
3232 Msg(0,"Too many glyphs used in font '$cft'");
3241 push(@ahead,substr($par,1));
3242 $par=substr($par,0,1);
3245 $nomove=$fontlst{$cft}->{FNT}->{WID}->[$ch]*$cftsz;
3254 my $fnt=$fontlst{$cft}->{FNT};
3257 foreach my $c (keys %{$fnt->{GNM}})
3259 $chnm=$c,last if $fnt->{GNM}->{$c} == $par;
3262 $par=RemapChr($par,$fnt,$chnm);
3266 $nomove=$fontlst{$cft}->{FNT}->{WID}->[$par]*$cftsz;
3276 PutHotSpot($xpos) if defined($mark);
3281 ########################################################################