| [4] | 1 | #!/usr/bin/env perl | 
|---|
|  | 2 |  | 
|---|
|  | 3 | # Copyright 1998-2001 by Sebastian Rahtz et al. | 
|---|
|  | 4 | # epstopdf is free software; you can redistribute it and/or modify | 
|---|
|  | 5 | # it under the terms of the GNU General Public License as published by | 
|---|
|  | 6 | # the Free Software Foundation; either version 2 of the License, or | 
|---|
|  | 7 | # (at your option) any later version. | 
|---|
|  | 8 | # | 
|---|
|  | 9 | # epstopdf is distributed in the hope that it will be useful, | 
|---|
|  | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|---|
|  | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|---|
|  | 12 | # GNU General Public License for more details. | 
|---|
|  | 13 | # | 
|---|
|  | 14 | # You should have received a copy of the GNU General Public License | 
|---|
|  | 15 | # along with epstopdf; if not, write to the Free Software | 
|---|
|  | 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
|---|
|  | 17 |  | 
|---|
|  | 18 | use strict; | 
|---|
|  | 19 |  | 
|---|
|  | 20 | # A script to transform an EPS file so that: | 
|---|
|  | 21 | #   a) it is guarenteed to start at the 0,0 coordinate | 
|---|
|  | 22 | #   b) it sets a page size exactly corresponding to the BoundingBox | 
|---|
|  | 23 | # This means that when Ghostscript renders it, the result needs no | 
|---|
|  | 24 | # cropping, and the PDF MediaBox is correct. | 
|---|
|  | 25 | #   c) the result is piped to Ghostscript and a PDF version written | 
|---|
|  | 26 | # | 
|---|
|  | 27 | # It needs a Level 2 PS interpreter. | 
|---|
|  | 28 | # If the bounding box is not right, of course, you have problems... | 
|---|
|  | 29 | # | 
|---|
|  | 30 | # The only thing I have not allowed for is the case of | 
|---|
|  | 31 | # "%%BoundingBox: (atend)", which is more complicated. | 
|---|
|  | 32 | # | 
|---|
|  | 33 | # Sebastian Rahtz, for Elsevier Science | 
|---|
|  | 34 | # | 
|---|
|  | 35 | # now with extra tricks from Hans Hagen's texutil. | 
|---|
|  | 36 | # | 
|---|
|  | 37 | # History | 
|---|
|  | 38 | #  1999/05/06 v2.5 (Heiko Oberdiek) | 
|---|
|  | 39 | #    * New options: --hires, --exact, --filter, --help. | 
|---|
|  | 40 | #    * Many cosmetics: title, usage, ... | 
|---|
|  | 41 | #    * New code for debug, warning, error | 
|---|
|  | 42 | #    * Detecting of cygwin perl | 
|---|
|  | 43 | #    * Scanning for %%{Hires,Exact,}BoundingBox. | 
|---|
|  | 44 | #    * Scanning only the header in order not to get a wrong | 
|---|
|  | 45 | #      BoundingBox of an included file. | 
|---|
|  | 46 | #    * (atend) supported. | 
|---|
|  | 47 | #    * uses strict; (earlier error detecting). | 
|---|
|  | 48 | #    * changed first comment from '%!PS' to '%!'; | 
|---|
|  | 49 | #    * corrected (atend) pattern: '\s*\(atend\)' | 
|---|
|  | 50 | #    * using of $bbxpat in all BoundingBox cases, | 
|---|
|  | 51 | #      correct the first white space to '...Box:\s*$bb...' | 
|---|
|  | 52 | #    * corrected first line (one line instead of two before 'if 0;'; | 
|---|
|  | 53 | #  2000/11/05 v2.6 (Heiko Oberdiek) | 
|---|
|  | 54 | #    * %%HiresBoundingBox corrected to %%HiResBoundingBox | 
|---|
|  | 55 | #  2001/03/05 v2.7 (Heiko Oberdiek) | 
|---|
|  | 56 | #    * Newline before grestore for the case that there is no | 
|---|
|  | 57 | #      whitespace at the end of the eps file. | 
|---|
|  | 58 | #  2006/05/09 v2.8 (David W. Rankin, Jr.) | 
|---|
|  | 59 | #    * option --pdfvers to specify the Ghostscript command to set | 
|---|
|  | 60 | #      the PDF version output. | 
|---|
|  | 61 |  | 
|---|
|  | 62 | my $IsWin32 = ($^O =~ /MSWin32/i); | 
|---|
|  | 63 |  | 
|---|
|  | 64 | ### program identification | 
|---|
|  | 65 | my $program = "epstosmth"; | 
|---|
|  | 66 | my $filedate="2006/05/09"; | 
|---|
|  | 67 | my $fileversion="2.8"; | 
|---|
|  | 68 | my $copyright = "Copyright 1998-2001 by Sebastian Rahtz et al."; | 
|---|
|  | 69 | my $title = "\U$program\E $fileversion, $filedate - $copyright\n"; | 
|---|
|  | 70 |  | 
|---|
|  | 71 | ### ghostscript command name | 
|---|
|  | 72 | my $GS = "gs"; | 
|---|
|  | 73 | $GS = "gswin32c" if $^O eq 'MSWin32'; | 
|---|
|  | 74 |  | 
|---|
|  | 75 | if ($IsWin32) { | 
|---|
|  | 76 | $GS = `kpsecheck --ghostscript`; | 
|---|
|  | 77 | $GS =~ m/^dll\s*:\s*(.+)/mio; | 
|---|
|  | 78 | $GS = $1; | 
|---|
|  | 79 | $GS =~ s/gsdll32.dll/gswin32c.exe/io; | 
|---|
|  | 80 | if ($GS eq "") { | 
|---|
|  | 81 | $GS = "gswin32c.exe"; | 
|---|
|  | 82 | } | 
|---|
|  | 83 | $GS = "\"$GS\"" if ($GS =~ m/\s/); | 
|---|
|  | 84 | } | 
|---|
|  | 85 |  | 
|---|
|  | 86 | ### options | 
|---|
|  | 87 | $::opt_help=0; | 
|---|
|  | 88 | $::opt_debug=0; | 
|---|
|  | 89 | $::opt_compress=1; | 
|---|
|  | 90 | $::opt_gs=1; | 
|---|
|  | 91 | $::opt_hires=0; | 
|---|
|  | 92 | $::opt_exact=0; | 
|---|
|  | 93 | $::opt_filter=0; | 
|---|
|  | 94 | $::opt_outfile=""; | 
|---|
|  | 95 | $::opt_pdfvers=""; | 
|---|
|  | 96 | $::opt_gsdev="pdfwrite"; | 
|---|
|  | 97 | $::opt_gsopt=""; | 
|---|
|  | 98 |  | 
|---|
|  | 99 | ### usage | 
|---|
|  | 100 | my @bool = ("false", "true"); | 
|---|
|  | 101 | my $usage = <<"END_OF_USAGE"; | 
|---|
|  | 102 | ${title}Syntax:  $program [options] <eps file> | 
|---|
|  | 103 | Options: | 
|---|
|  | 104 | --help:           print usage | 
|---|
|  | 105 | --outfile=<file>: write result to <file> | 
|---|
|  | 106 | --(no)filter:     read standard input    (default: $bool[$::opt_filter]) | 
|---|
|  | 107 | --(no)gs:         run ghostscript        (default: $bool[$::opt_gs]) | 
|---|
|  | 108 | --(no)compress:   use compression        (default: $bool[$::opt_compress]) | 
|---|
|  | 109 | --(no)pdfvers:    PDF Version for Output (default: [GhostScript's default]) | 
|---|
|  | 110 | --(no)hires:      scan HiResBoundingBox  (default: $bool[$::opt_hires]) | 
|---|
|  | 111 | --(no)exact:      scan ExactBoundingBox  (default: $bool[$::opt_exact]) | 
|---|
|  | 112 | --(no)debug:      debug informations     (default: $bool[$::opt_debug]) | 
|---|
|  | 113 | --gsdev=<dev>:    select gs device | 
|---|
|  | 114 | --gsopt=<opts>:   additional gs options | 
|---|
|  | 115 | Examples for producing 'test.pdf': | 
|---|
|  | 116 | * $program test.eps | 
|---|
|  | 117 | * produce postscript | $program --filter >test.pdf | 
|---|
|  | 118 | * produce postscript | $program -f -d -o=test.pdf | 
|---|
|  | 119 | Example: look for HiResBoundingBox and produce corrected PostScript: | 
|---|
|  | 120 | * $program -d --nogs -hires test.ps>testcorr.ps | 
|---|
|  | 121 | END_OF_USAGE | 
|---|
|  | 122 |  | 
|---|
|  | 123 | ### process options | 
|---|
|  | 124 | use Getopt::Long; | 
|---|
|  | 125 | GetOptions ( | 
|---|
|  | 126 | "help!", | 
|---|
|  | 127 | "debug!", | 
|---|
|  | 128 | "filter!", | 
|---|
|  | 129 | "compress!", | 
|---|
|  | 130 | "gs!", | 
|---|
|  | 131 | "hires!", | 
|---|
|  | 132 | "exact!", | 
|---|
|  | 133 | "pdfvers=s", | 
|---|
|  | 134 | "outfile=s", | 
|---|
|  | 135 | "gsdev=s", | 
|---|
|  | 136 | "gsopt=s", | 
|---|
|  | 137 | ) or die $usage; | 
|---|
|  | 138 |  | 
|---|
|  | 139 | ### help functions | 
|---|
|  | 140 | sub debug { | 
|---|
|  | 141 | print STDERR "* @_\n" if $::opt_debug; | 
|---|
|  | 142 | } | 
|---|
|  | 143 | sub warning { | 
|---|
|  | 144 | print STDERR "==> Warning: @_!\n"; | 
|---|
|  | 145 | } | 
|---|
|  | 146 | sub error { | 
|---|
|  | 147 | die "$title!!! Error: @_!\n"; | 
|---|
|  | 148 | } | 
|---|
|  | 149 | sub errorUsage { | 
|---|
|  | 150 | die "$usage\n!!! Error: @_!\n"; | 
|---|
|  | 151 | } | 
|---|
|  | 152 |  | 
|---|
|  | 153 | ### option help | 
|---|
|  | 154 | die $usage if $::opt_help; | 
|---|
|  | 155 |  | 
|---|
|  | 156 | ### get input filename | 
|---|
|  | 157 | my $InputFilename = ""; | 
|---|
|  | 158 | if ($::opt_filter) { | 
|---|
|  | 159 | @ARGV == 0 or | 
|---|
|  | 160 | die errorUsage "Input file cannot be used with filter option"; | 
|---|
|  | 161 | $InputFilename = "-"; | 
|---|
|  | 162 | debug "Input file: standard input"; | 
|---|
|  | 163 | } | 
|---|
|  | 164 | else { | 
|---|
|  | 165 | @ARGV > 0 or die errorUsage "Input filename missing"; | 
|---|
|  | 166 | @ARGV < 2 or die errorUsage "Unknown option or too many input files"; | 
|---|
|  | 167 | $InputFilename = $ARGV[0]; | 
|---|
|  | 168 | -f $InputFilename or error "'$InputFilename' does not exist"; | 
|---|
|  | 169 | debug "Input filename:", $InputFilename; | 
|---|
|  | 170 | } | 
|---|
|  | 171 |  | 
|---|
|  | 172 | ### option compress | 
|---|
|  | 173 | my $GSOPTS = "$::opt_gsopt "; | 
|---|
|  | 174 |  | 
|---|
|  | 175 | debug "gs opts:", $GSOPTS; | 
|---|
|  | 176 |  | 
|---|
|  | 177 | $GSOPTS .= "-dUseFlateCompression=false " unless $::opt_compress; | 
|---|
|  | 178 |  | 
|---|
|  | 179 | ### option pdfvers (GhostScript PDF Compatability options) | 
|---|
|  | 180 | $GSOPTS .= "-dCompatibilityLevel=$::opt_pdfvers " unless $::opt_pdfvers eq ""; | 
|---|
|  | 181 |  | 
|---|
|  | 182 | ### option BoundingBox types | 
|---|
|  | 183 | my $BBName = "%%BoundingBox:"; | 
|---|
|  | 184 | !($::opt_hires and $::opt_exact) or | 
|---|
|  | 185 | error "Options --hires and --exact cannot be used together"; | 
|---|
|  | 186 | $BBName = "%%HiResBoundingBox:" if $::opt_hires; | 
|---|
|  | 187 | $BBName = "%%ExactBoundingBox:" if $::opt_exact; | 
|---|
|  | 188 | debug "BoundingBox comment:", $BBName; | 
|---|
|  | 189 |  | 
|---|
|  | 190 | ### option outfile | 
|---|
|  | 191 | my $OutputFilename = $::opt_outfile; | 
|---|
|  | 192 | if ($OutputFilename eq "") { | 
|---|
|  | 193 | if ($::opt_gs) { | 
|---|
|  | 194 | $OutputFilename = $InputFilename; | 
|---|
|  | 195 | if (!$::opt_filter) { | 
|---|
|  | 196 | $OutputFilename =~ s/\.[^\.]*$//; | 
|---|
|  | 197 | if ($::opt_gsdev =~ m/^pdf/) { | 
|---|
|  | 198 | $OutputFilename .= '.pdf'; | 
|---|
|  | 199 | } elsif ($::opt_gsdev =~ m/^png/) { | 
|---|
|  | 200 | $OutputFilename .= '.png'; | 
|---|
|  | 201 | } elsif ($::opt_gsdev =~ m/^jpeg/) { | 
|---|
|  | 202 | $OutputFilename .= '.jpg'; | 
|---|
|  | 203 | } else { | 
|---|
|  | 204 | $OutputFilename .= ($::opt_gsdev eq "" ? ".pdf" : ".$::opt_gsdev"); | 
|---|
|  | 205 | } | 
|---|
|  | 206 | } | 
|---|
|  | 207 | } | 
|---|
|  | 208 | else { | 
|---|
|  | 209 | $OutputFilename = "-"; # standard output | 
|---|
|  | 210 | } | 
|---|
|  | 211 | } | 
|---|
|  | 212 | if ($::opt_filter) { | 
|---|
|  | 213 | debug "Output file: standard output"; | 
|---|
|  | 214 | } | 
|---|
|  | 215 | else { | 
|---|
|  | 216 | debug "Output filename:", $OutputFilename; | 
|---|
|  | 217 | } | 
|---|
|  | 218 |  | 
|---|
|  | 219 | ### option gs | 
|---|
|  | 220 | if ($::opt_gs) { | 
|---|
|  | 221 | debug "Ghostscript command:", $GS; | 
|---|
|  | 222 | debug "Compression:", ($::opt_compress) ? "on" : "off"; | 
|---|
|  | 223 | } | 
|---|
|  | 224 |  | 
|---|
|  | 225 | ### open input file | 
|---|
|  | 226 | open(IN,"<$InputFilename") or error "Cannot open", | 
|---|
|  | 227 | ($::opt_filter) ? "standard input" : "'$InputFilename'"; | 
|---|
|  | 228 | binmode IN; | 
|---|
|  | 229 |  | 
|---|
|  | 230 | ### open output file | 
|---|
|  | 231 | if ($::opt_gs) { | 
|---|
|  | 232 | my $pipe = "$GS -q -sDEVICE=$::opt_gsdev $GSOPTS " . | 
|---|
|  | 233 | "-sOutputFile=$OutputFilename - -c quit"; | 
|---|
|  | 234 | debug "Ghostscript pipe:", $pipe; | 
|---|
|  | 235 | open(OUT,"|$pipe") or error "Cannot open Ghostscript for piped input"; | 
|---|
|  | 236 | } | 
|---|
|  | 237 | else { | 
|---|
|  | 238 | open(OUT,">$OutputFilename") or error "Cannot write '$OutputFilename"; | 
|---|
|  | 239 | } | 
|---|
|  | 240 |  | 
|---|
|  | 241 | ### scan first line | 
|---|
|  | 242 | my $header = 0; | 
|---|
|  | 243 | $_ = <IN>; | 
|---|
|  | 244 | if (/%!/) { | 
|---|
|  | 245 | # throw away binary junk before %! | 
|---|
|  | 246 | s/(.*)%!/%!/o; | 
|---|
|  | 247 | } | 
|---|
|  | 248 | $header = 1 if /^%/; | 
|---|
|  | 249 | debug "Scanning header for BoundingBox"; | 
|---|
|  | 250 | print OUT; | 
|---|
|  | 251 |  | 
|---|
|  | 252 | ### variables and pattern for BoundingBox search | 
|---|
|  | 253 | my $bbxpatt = '[0-9eE\.\-]'; | 
|---|
|  | 254 | # protect backslashes: "\\" gets '\' | 
|---|
|  | 255 | my $BBValues = "\\s*($bbxpatt+)\\s+($bbxpatt+)\\s+($bbxpatt+)\\s+($bbxpatt+)"; | 
|---|
|  | 256 | my $BBCorrected = 0; | 
|---|
|  | 257 |  | 
|---|
|  | 258 | sub CorrectBoundingBox { | 
|---|
|  | 259 | my ($llx, $lly, $urx, $ury) = @_; | 
|---|
|  | 260 | debug "Old BoundingBox:", $llx, $lly, $urx, $ury; | 
|---|
|  | 261 | my ($width, $height) = ($urx - $llx, $ury - $lly); | 
|---|
|  | 262 | my ($xoffset, $yoffset) = (-$llx, -$lly); | 
|---|
|  | 263 | debug "New BoundingBox: 0 0", $width, $height; | 
|---|
|  | 264 | debug "Offset:", $xoffset, $yoffset; | 
|---|
|  | 265 |  | 
|---|
|  | 266 | print OUT "%%BoundingBox: 0 0 $width $height\n"; | 
|---|
|  | 267 | print OUT "<< /PageSize [$width $height] >> setpagedevice\n"; | 
|---|
|  | 268 | print OUT "gsave $xoffset $yoffset translate\n"; | 
|---|
|  | 269 | } | 
|---|
|  | 270 |  | 
|---|
|  | 271 | ### scan header | 
|---|
|  | 272 | if ($header) { | 
|---|
|  | 273 | while (<IN>) { | 
|---|
|  | 274 |  | 
|---|
|  | 275 | ### end of header | 
|---|
|  | 276 | if (!/^%/ or /^%%EndComments/) { | 
|---|
|  | 277 | print OUT; | 
|---|
|  | 278 | last; | 
|---|
|  | 279 | } | 
|---|
|  | 280 |  | 
|---|
|  | 281 | ### BoundingBox with values | 
|---|
|  | 282 | if (/^$BBName$BBValues/) { | 
|---|
|  | 283 | CorrectBoundingBox $1, $2, $3, $4; | 
|---|
|  | 284 | $BBCorrected = 1; | 
|---|
|  | 285 | last; | 
|---|
|  | 286 | } | 
|---|
|  | 287 |  | 
|---|
|  | 288 | ### BoundingBox with (atend) | 
|---|
|  | 289 | if (/^$BBName\s*\(atend\)/) { | 
|---|
|  | 290 | debug $BBName, "(atend)"; | 
|---|
|  | 291 | if ($::opt_filter) { | 
|---|
|  | 292 | warning "Cannot look for BoundingBox in the trailer", | 
|---|
|  | 293 | "with option --filter"; | 
|---|
|  | 294 | last; | 
|---|
|  | 295 | } | 
|---|
|  | 296 | my $pos = tell(IN); | 
|---|
|  | 297 | debug "Current file position:", $pos; | 
|---|
|  | 298 |  | 
|---|
|  | 299 | # looking for %%BoundingBox | 
|---|
|  | 300 | while (<IN>) { | 
|---|
|  | 301 | # skip over included documents | 
|---|
|  | 302 | if (/^%%BeginDocument/) { | 
|---|
|  | 303 | while (<IN>) { | 
|---|
|  | 304 | last if /^%%EndDocument/; | 
|---|
|  | 305 | } | 
|---|
|  | 306 | } | 
|---|
|  | 307 | if (/^$BBName$BBValues/) { | 
|---|
|  | 308 | CorrectBoundingBox $1, $2, $3, $4; | 
|---|
|  | 309 | $BBCorrected = 1; | 
|---|
|  | 310 | last; | 
|---|
|  | 311 | } | 
|---|
|  | 312 | } | 
|---|
|  | 313 |  | 
|---|
|  | 314 | # go back | 
|---|
|  | 315 | seek(IN, $pos, 0) or error "Cannot go back to line '$BBName (atend)'"; | 
|---|
|  | 316 | last; | 
|---|
|  | 317 | } | 
|---|
|  | 318 |  | 
|---|
|  | 319 | # print header line | 
|---|
|  | 320 | print OUT; | 
|---|
|  | 321 | } | 
|---|
|  | 322 | } | 
|---|
|  | 323 |  | 
|---|
|  | 324 | ### print rest of file | 
|---|
|  | 325 | while (<IN>) { | 
|---|
|  | 326 | print OUT; | 
|---|
|  | 327 | } | 
|---|
|  | 328 |  | 
|---|
|  | 329 | ### close files | 
|---|
|  | 330 | close(IN); | 
|---|
|  | 331 | print OUT "\ngrestore\n" if $BBCorrected; | 
|---|
|  | 332 | close(OUT); | 
|---|
|  | 333 | warning "BoundingBox not found" unless $BBCorrected; | 
|---|
|  | 334 | debug "Ready."; | 
|---|
|  | 335 | ; | 
|---|