| 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 | ; | 
|---|