├── .github └── FUNDING.yml ├── .gitignore ├── Makefile ├── LICENSE ├── README.md └── pdfScale.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tavinus 2 | ko_fi: tavinus 3 | custom: ["https://bit.ly/paypaltavinus"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store 4 | .DS_Store? 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | ehthumbs.db 9 | Thumbs.db 10 | nppBackup 11 | *.bak 12 | 13 | ## Hidden files 14 | .* 15 | 16 | ## Random stuff 17 | *.pdf 18 | old 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Installs to /usr/local/bin 2 | # Change variables to adjust locations 3 | # 4 | # Jul 10 2016 - Gustavo Neves 5 | 6 | IDIR=/usr/local/bin 7 | IFILE=$(IDIR)/pdfscale 8 | 9 | all: 10 | 11 | install: 12 | cp pdfScale.sh $(IFILE) 13 | chmod 755 $(IFILE) 14 | 15 | uninstall: 16 | rm -f $(IFILE) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Gustavo Arnosti Neves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdfScale 2 2 | Bash Script to ***scale*** and/or ***resize*** PDFs from the command line. 3 | Uses ghostscript (`gs`) to create a scaled and/or resized version of the pdf input. 4 | 5 | In `scaling mode`, the PDF paper size does not change, just the elements are scaled. 6 | In `resize mode`, the PDF paper will be changed and fit-to-page will be applied. 7 | In `mixed mode`, the PDF will first be `resized` then `scaled` with two Ghostscript calls. 8 | A temporary file is used in `mixed mode`, at the target location. 9 | 10 | ---------------------------------------------- 11 | #### If you want to support this project, you can do it here :coffee: :beer: 12 | 13 | [![paypal-image](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HP344TKTWXFXE&source=url) 14 | 15 | ---------------------------------------------- 16 | 17 | ## Example Runs 18 | Better than explaining is showing it: 19 | #### Checking File Information 20 | ``` 21 | $ ./pdfScale.sh -i test.pdf 22 | pdfScale.sh v2.6.2 - Paper Sizes 23 | -------------+----------------------------- 24 | File | test.pdf 25 | Paper Type | A4 Portrait 26 | Pages | 4 27 | -------------+----------------------------- 28 | FIRST PAGE | WIDTH x HEIGHT 29 | Points | 595 x 842 30 | Millimeters | 210 x 297 31 | Inches | 8.3 x 11.7 32 | -------------+----------------------------- 33 | ALL PAGES | WIDTH x HEIGHT (pts) 34 | 1 | 595 x 842 35 | 2 | 595 x 842 36 | 3 | 595 x 842 37 | 4 | 595 x 842 38 | -------------+----------------------------- 39 | ``` 40 | #### Scale by 0.95 (-5%) 41 | This also shows a very special case of a PDF file that has no `/MediaBox` defined. It is a dumb container of n-up binary PDF pages. 42 | 43 | 1. `Ggrep` fails, then 44 | 2. `PDFInfo` fails (not installed), then 45 | 3. `ImageMagick` fails (not installed), then 46 | 4. The Ghostscript PS script does the job 47 | 48 | *This was on CygWin64 `@` Windows11 x64, MacOS would try `mdls` as well.* 49 | ``` 50 | $ ./pdfScale.sh -v ../input-nup.pdf 51 | pdfScale.sh v2.6.2 - Verbose Execution 52 | Single Task: Scale PDF Contents 53 | Dry-Run: FALSE 54 | Input File: ../input-nup.pdf 55 | Output File: ../input-nup.SCALED.pdf 56 | Explode PDF: Disabled 57 | Get Page Size: Adaptive Enabled 58 | Method: Grep 59 | Failed, trying next method 60 | Method: PDFInfo 61 | Failed, trying next method 62 | Method: ImageMagick's Identify 63 | Failed, trying next method 64 | Method: Ghostscript PS Script 65 | Page Range: None (all pages) 66 | Source Width: 842 postscript-points 67 | Source Height: 595 postscript-points 68 | Print Mode: Print ( auto/empty ) 69 | Scale Factor: 0.95 (auto) 70 | Scale Percent: -5% 71 | Vert-Align: CENTER 72 | Hor-Align: CENTER 73 | Translation X: 22.16 = 22.16 + 0.00 (offset) 74 | Translation Y: 15.66 = 15.66 + 0.00 (offset) 75 | Background: No background (default) 76 | Final Status: File created successfully 77 | ``` 78 | #### Resize to A0 and Scale by 1.05 (+5%) 79 | ``` 80 | $ ./pdfScale.sh -v -r a0 -s 1.05 ../mixsync_manual_v1-2-3.pdf 81 | pdfScale.sh v2.6.2 - Verbose Execution 82 | Mixed Tasks: Resize & Scale 83 | Dry-Run: FALSE 84 | Input File: ../mixsync_manual_v1-2-3.pdf 85 | Output File: ../mixsync_manual_v1-2-3.A0.SCALED.pdf 86 | Explode PDF: Disabled 87 | Get Page Size: Adaptive Enabled 88 | Method: Grep 89 | Page Range: None (all pages) 90 | Source Width: 842 postscript-points 91 | Source Height: 595 postscript-points 92 | Print Mode: Print ( auto/empty ) 93 | Fit To Page: Enabled (default) 94 | Auto Rotate: PageByPage 95 | Flip Detect: Wrong orientation detected! 96 | Inverting Width <-> Height 97 | Run Resizing: A0 ( 3370 x 2384 ) pts 98 | New Width: 3370 postscript-points 99 | New Height: 2384 postscript-points 100 | Scale Factor: 1.05 101 | Scale Percent: +5% 102 | Vert-Align: CENTER 103 | Hor-Align: CENTER 104 | Translation X: -80.24 = -80.24 + 0.00 (offset) 105 | Translation Y: -56.76 = -56.76 + 0.00 (offset) 106 | Background: No background (default) 107 | Final Status: File created successfully 108 | ``` 109 | #### Resize to A3, Scale by 1.11 (+11%) and Explode the results 110 | *Exploding (splitting) will create a PDF file for each page, with the `.Page#.pdf` suffix* 111 | ``` 112 | $ ./pdfScale.sh -v -s 1.11 -r A3 -e ../mixsync_manual_v1-4-2.pdf 113 | pdfScale.sh v2.6.2 - Verbose Execution 114 | Mixed Tasks: Resize & Scale 115 | Dry-Run: FALSE 116 | Input File: ../mixsync_manual_v1-4-2.pdf 117 | Output File: ../mixsync_manual_v1-4-2.A3.SCALED.Page%d.pdf 118 | Explode PDF: Enabled 119 | Get Page Size: Adaptive Enabled 120 | Method: Grep 121 | Page Range: None (all pages) 122 | Source Width: 595 postscript-points 123 | Source Height: 842 postscript-points 124 | Print Mode: Print ( auto/empty ) 125 | Fit To Page: Enabled (default) 126 | Auto Rotate: PageByPage 127 | Flip Detect: No change needed 128 | Run Resizing: A3 ( 842 x 1191 ) pts 129 | New Width: 842 postscript-points 130 | New Height: 1191 postscript-points 131 | Scale Factor: 1.11 132 | Scale Percent: +11% 133 | Vert-Align: CENTER 134 | Hor-Align: CENTER 135 | Translation X: -41.72 = -41.72 + 0.00 (offset) 136 | Translation Y: -59.01 = -59.01 + 0.00 (offset) 137 | Background: No background (default) 138 | Final Status: File created successfully 139 | ``` 140 | #### Resize to A2 and disables Auto-Rotation 141 | ``` 142 | $ ./pdfScale.sh -v -r A2 -a none ../input.pdf 143 | pdfScale.sh v2.6.2 - Verbose Execution 144 | Single Task: Resize PDF Paper 145 | Dry-Run: FALSE 146 | Input File: ../input.pdf 147 | Output File: ../input.A2.pdf 148 | Explode PDF: Disabled 149 | Get Page Size: Adaptive Enabled 150 | Method: Grep 151 | Page Range: None (all pages) 152 | Source Width: 595 postscript-points 153 | Source Height: 842 postscript-points 154 | Print Mode: Print ( auto/empty ) 155 | Scale Factor: Disabled (resize only) 156 | Fit To Page: Enabled (default) 157 | Auto Rotate: None 158 | Flip Detect: No change needed 159 | Run Resizing: A2 ( 1191 x 1684 ) pts 160 | Final Status: File created successfully 161 | ``` 162 | #### Resize to custom 200x300 mm, disable Flip-Detection and Scale by 0.95 (-5%) 163 | ``` 164 | $ ./pdfScale.sh -v -v -r 'custom mm 200 300' -f disable -s 0.95 ../mixsync_manual_v1-2-3.pdf 165 | 2024-07-17:14:43:15 | pdfScale.sh v2.6.2 - Verbose Execution 166 | 2024-07-17:14:43:15 | Mixed Tasks: Resize & Scale 167 | 2024-07-17:14:43:15 | Dry-Run: FALSE 168 | 2024-07-17:14:43:15 | Input File: ../mixsync_manual_v1-2-3.pdf 169 | 2024-07-17:14:43:15 | Output File: ../mixsync_manual_v1-2-3.CUSTOM.SCALED.pdf 170 | 2024-07-17:14:43:15 | Explode PDF: Disabled 171 | 2024-07-17:14:43:15 | Get Page Size: Adaptive Enabled 172 | 2024-07-17:14:43:15 | Method: Grep 173 | 2024-07-17:14:43:15 | Page Range: None (all pages) 174 | 2024-07-17:14:43:16 | Source Width: 842 postscript-points 175 | 2024-07-17:14:43:16 | Source Height: 595 postscript-points 176 | 2024-07-17:14:43:16 | Print Mode: Print ( auto/empty ) 177 | 2024-07-17:14:43:16 | Fit To Page: Enabled (default) 178 | 2024-07-17:14:43:16 | Auto Rotate: PageByPage 179 | 2024-07-17:14:43:16 | Flip Detect: Disabled 180 | 2024-07-17:14:43:16 | Run Resizing: CUSTOM ( 567 x 850 ) pts 181 | 2024-07-17:14:43:16 | New Width: 567 postscript-points 182 | 2024-07-17:14:43:16 | New Height: 850 postscript-points 183 | 2024-07-17:14:43:16 | Scale Factor: 0.95 184 | 2024-07-17:14:43:16 | Scale Percent: -5% 185 | 2024-07-17:14:43:16 | Vert-Align: CENTER 186 | 2024-07-17:14:43:16 | Hor-Align: CENTER 187 | 2024-07-17:14:43:16 | Translation X: 14.92 = 14.92 + 0.00 (offset) 188 | 2024-07-17:14:43:16 | Translation Y: 22.37 = 22.37 + 0.00 (offset) 189 | 2024-07-17:14:43:16 | Background: No background (default) 190 | 2024-07-17:14:43:17 | Final Status: File created successfully 191 | ``` 192 | 193 | ## Help info 194 | ``` 195 | $ ./pdfScale.sh -h 196 | pdfScale.sh v2.6.2 197 | 198 | Usage: pdfScale.sh 199 | pdfScale.sh -i 200 | pdfScale.sh [-v] [-s ] [-m ] [outfile.pdf] 201 | pdfScale.sh [-v] [-r ] [-f ] [-a ] [outfile.pdf] 202 | pdfScale.sh -p 203 | pdfScale.sh -h 204 | pdfScale.sh -V 205 | 206 | Parameters: 207 | -v, --verbose 208 | Verbose mode, prints extra information 209 | Use twice for timestamp 210 | -h, --help 211 | Print this help to screen and exits 212 | -V, --version 213 | Prints version to screen and exits 214 | --install, --self-install [target-path] 215 | Install itself to [target-path] or /usr/local/bin/pdfscale if not specified 216 | Should contain the full path with the desired executable name 217 | --upgrade, --self-upgrade 218 | Upgrades itself in-place (same path/name of the pdfScale.sh caller) 219 | Downloads the master branch tarball and tries to self-upgrade 220 | --insecure, --no-check-certificate 221 | Use curl/wget without SSL library support 222 | --yes, --assume-yes 223 | Will answer yes to any prompt on install or upgrade, use with care 224 | -n, --no-overwrite 225 | Aborts execution if the output PDF file already exists 226 | By default, the output file will be overwritten 227 | Does NOT work if using --explode 228 | -m, --mode 229 | Paper size detection mode 230 | Modes: a, adaptive Default mode, tries all the methods below 231 | g, grep Forces the use of Grep method 232 | m, mdls Forces the use of MacOS Quartz mdls 233 | p, pdfinfo Forces the use of PDFInfo 234 | i, identify Forces the use of ImageMagick's Identify 235 | s, gs Forces the use of Ghostscript (PS script) 236 | -i, --info 237 | Prints Paper Size information to screen and exits 238 | -e, --explode 239 | Explode (split) outuput PDF into many files (one per page) 240 | --range, --page-range 241 | Defines the page range to be processed, using the -sPageList notation 242 | Read below for more information on valid page ranges 243 | -s, --scale 244 | Changes the scaling factor or forces mixed mode 245 | Defaults: 0.95 (scale mode) / Disabled (resize mode) 246 | MUST be a number bigger than zero 247 | Eg. -s 0.8 for 80% of the original size 248 | -r, --resize 249 | Triggers the Resize Paper Mode, disables auto-scaling of 0.95 250 | Resize PDF and fit-to-page 251 | can be: source, custom or a valid std paper name, read below 252 | -c, --cropbox 253 | Resets Cropboxes on all pages to a specific paper size 254 | Only applies to resize mode 255 | can be: full | fullsize - Uses the same size as the main paper/mediabox 256 | custom - Define a custom cropbox size in inches, mm or points 257 | std paper name - Uses a paper size name (eg. a4, letter, etc) 258 | -f, --flip-detect 259 | Flip Detection Mode, defaults to 'auto' 260 | Inverts Width <-> Height of a Resized PDF 261 | Modes: a, auto Keeps source orientation, default 262 | f, force Forces flip W <-> H 263 | d, disable Disables flipping 264 | -a, --auto-rotate 265 | Setting for GS -dAutoRotatePages, defaults to 'PageByPage' 266 | Uses text-orientation detection to set Portrait/Landscape 267 | Modes: p, pagebypage Auto-rotates pages individually 268 | n, none Retains orientation of each page 269 | a, all Rotates all pages (or none) depending 270 | on a kind of "majority decision" 271 | --no-fit-to-page 272 | Disables GS option dPDFFitPage (used when resizing) 273 | --hor-align, --horizontal-alignment 274 | Where to translate the scaled page 275 | Default: center 276 | Options: left, right, center 277 | --vert-align, --vertical-alignment 278 | Where to translate the scaled page 279 | Default: center 280 | Options: top, bottom, center 281 | --xoffset, --xtrans-offset 282 | Add/Subtract from the X translation (move left-right) 283 | Default: 0.0 (zero) 284 | Options: Positive or negative floating point number 285 | --yoffset, --ytrans-offset 286 | Add/Subtract from the Y translation (move top-bottom) 287 | Default: 0.0 (zero) 288 | Options: Positive or negative floating point number 289 | --pdf-settings 290 | Ghostscript PDF Profile to use in -dPDFSETTINGS 291 | Default: printer 292 | Options: screen, ebook, printer, prepress, default 293 | --print-mode 294 | Setting for GS -dPrinted, loads options for screen or printer 295 | Defaults to nothing, which uses the print profile for files 296 | The screen profile preserves URLs, but loses print annotations 297 | Modes: s, screen Use screen options > '-dPrinted=false' 298 | p, printer Use print options > '-dPrinted' 299 | --image-downsample 300 | Ghostscript Image Downsample Method 301 | Default: bicubic 302 | Options: subsample, average, bicubic 303 | --image-resolution 304 | Resolution in DPI of color and grayscale images in output 305 | Default: 300 306 | --background-gray 307 | Creates a background with a gray color setting on PDF scaling 308 | Percentage is a floating point percentage number between 0(black) and 1(white) 309 | --background-cmyk <"C M Y K"> 310 | Creates a background with a CMYK color setting on PDF scaling 311 | Must be quoted into a single parameter as in "0.2 0.2 0.2 0.2" 312 | Each color parameter is a floating point percentage number (between 0 and 1) 313 | --background-rgb <"R G B"> 314 | Creates a background with a RGB color setting on PDF scaling 315 | Must be quoted into a single parameter as in "100 100 200" 316 | RGB numbers are integers between 0 and 255 (255 122 50) 317 | --newpdf Uses the -dNEWPDF flag in the GS Call (deprecated in new versions of GS) 318 | --dry-run, --simulate 319 | Just simulate execution. Will not run ghostscript 320 | --print-gs-call, --gs-call 321 | Print GS call to stdout. Will print at the very end between markers 322 | -p, --print-papers 323 | Prints Standard Paper info tables to screen and exits 324 | 325 | Scaling Mode: 326 | - The default mode of operation is scaling mode with fixed paper 327 | size and scaling pre-set to 0.95 328 | - By not using the resize mode you are using scaling mode 329 | - Flip-Detection and Auto-Rotation are disabled in Scaling mode, 330 | you can use '-r source -s ' to override. 331 | - Ghostscript placement is from bottom-left position. This means that 332 | a bottom-left placement has ZERO for both X and Y translations. 333 | 334 | Resize Paper Mode: 335 | - Disables the default scaling factor! (0.95) 336 | - Changes the PDF Paper Size in points. Will fit-to-page 337 | 338 | Mixed Mode: 339 | - In mixed mode both the -s option and -r option must be specified 340 | - The PDF will be first resized then scaled 341 | 342 | Page Ranges: 343 | - Please refer to the Ghostscript manual on '-sPageList' for more info and examples. 344 | - May cause execution warnings from Ghostscript if the PDF refences pages that were 345 | removed. The output file should still be created, but with broken internal links. 346 | - Using a range with an inexistant page will raise a warning from Ghostscript and 347 | may also generate blank pages. 348 | - Single page number | ex: --range 2 349 | - Interval | ex: --range 2-4 350 | - List of pages | ex: --range 1,3,6 351 | - From page to end | ex: --range 3- 352 | - odd/even specifier | ex: --range odd 353 | - odd/even range | ex: --range even:1-4 354 | - mixed entries | ex: --range 1,3-5,8- 355 | 356 | Output filename: 357 | - Having the extension .pdf on the output file name is optional, 358 | it will be added if not present. 359 | - The output filename is optional. If no file name is passed 360 | the output file will have the same name/destination of the 361 | input file with added suffixes: 362 | .SCALED.pdf is added to scaled files 363 | ..pdf is added to resized files 364 | ..SCALED.pdf is added in mixed mode 365 | 366 | Standard Paper Names: (case-insensitive) 367 | A0 A1 A2 A3 A4 368 | A4SMALL A5 A6 A7 A8 369 | A9 A10 ISOB0 ISOB1 ISOB2 370 | ISOB3 ISOB4 ISOB5 ISOB6 C0 371 | C1 C2 C3 C4 C5 372 | C6 11X17 LEDGER LEGAL LETTER 373 | LETTERSMALL ARCHE ARCHD ARCHC ARCHB 374 | ARCHA JISB0 JISB1 JISB2 JISB3 375 | JISB4 JISB5 JISB6 FLSA FLSE 376 | HALFLETTER HAGAKI 377 | 378 | Custom Paper Size: 379 | - Paper size can be set manually in Millimeters, Inches or Points 380 | - Custom paper definition MUST be quoted into a single parameter 381 | - Actual size is applied in points (mms and inches are transformed) 382 | - Measurements: mm, mms, millimeters 383 | pt, pts, points 384 | in, inch, inches 385 | Use: pdfScale.sh -r 'custom ' 386 | Ex: pdfScale.sh -r 'custom mm 300 300' 387 | 388 | Using Source Paper Size: (no-resizing) 389 | - Wildcard 'source' is used to keep paper size the same as the input 390 | - Useful to run Auto-Rotation without resizing 391 | - Eg. pdfScale.sh -r source ./input.pdf 392 | 393 | Backgrounding: (paint a background) 394 | - Backgrounding only happens when scaling 395 | - Use a scale of 1.0 to force mixed mode and add background while resizing 396 | 397 | Options and Parameters Parsing: 398 | - From v2.1.0 (long-opts) there is no need to pass file names at the end 399 | - Anything that is not a short-option is case-insensitive 400 | - Short-options: case-sensitive Eg. -v for Verbose, -V for Version 401 | - Long-options: case-insensitive Eg. --SCALE and --scale are the same 402 | - Subparameters: case-insensitive Eg. -m PdFinFo is valid 403 | - Grouping short-options is not supported Eg. -vv, or -vs 0.9 404 | 405 | Additional Notes: 406 | - File and folder names with spaces should be quoted or escaped 407 | - Using a scale bigger than 1.0 may result on cropping parts of the PDF 408 | - For detailed paper types information, use: pdfScale.sh -p 409 | 410 | Examples: 411 | pdfScale.sh myPdfFile.pdf 412 | pdfScale.sh -i '/home/My Folder/My PDF File.pdf' 413 | pdfScale.sh myPdfFile.pdf "My Scaled Pdf" 414 | pdfScale.sh -v -v myPdfFile.pdf 415 | pdfScale.sh -s 0.85 myPdfFile.pdf My\ Scaled\ Pdf.pdf 416 | pdfScale.sh -m pdfinfo -s 0.80 -v myPdfFile.pdf 417 | pdfScale.sh -v -v -m i -s 0.7 myPdfFile.pdf 418 | pdfScale.sh -r A4 myPdfFile.pdf 419 | pdfScale.sh -v -v -r "custom mm 252 356" -s 0.9 -f "../input file.pdf" "../my new pdf" 420 | ``` 421 | 422 | ## Standard Paper Tables 423 | The `-p` parameter prints detailed paper types information 424 | ``` 425 | $ pdfscale -p 426 | pdfscale v2.3.7 427 | 428 | Paper Sizes Information 429 | 430 | +-----------------------------------------------------------------+ 431 | | ISO STANDARD | 432 | +-----------------------------------------------------------------+ 433 | | Name | inchW | inchH | mm W | mm H | pts W | pts H | 434 | +-----------------+-------+-------+-------+-------+-------+-------+ 435 | | a0 | 33.1 | 46.8 | 841 | 1189 | 2384 | 3370 | 436 | | a1 | 23.4 | 33.1 | 594 | 841 | 1684 | 2384 | 437 | | a2 | 16.5 | 23.4 | 420 | 594 | 1191 | 1684 | 438 | | a3 | 11.7 | 16.5 | 297 | 420 | 842 | 1191 | 439 | | a4 | 8.3 | 11.7 | 210 | 297 | 595 | 842 | 440 | | a4small | 8.3 | 11.7 | 210 | 297 | 595 | 842 | 441 | | a5 | 5.8 | 8.3 | 148 | 210 | 420 | 595 | 442 | | a6 | 4.1 | 5.8 | 105 | 148 | 297 | 420 | 443 | | a7 | 2.9 | 4.1 | 74 | 105 | 210 | 297 | 444 | | a8 | 2.1 | 2.9 | 52 | 74 | 148 | 210 | 445 | | a9 | 1.5 | 2.1 | 37 | 52 | 105 | 148 | 446 | | a10 | 1.0 | 1.5 | 26 | 37 | 73 | 105 | 447 | | isob0 | 39.4 | 55.7 | 1000 | 1414 | 2835 | 4008 | 448 | | isob1 | 27.8 | 39.4 | 707 | 1000 | 2004 | 2835 | 449 | | isob2 | 19.7 | 27.8 | 500 | 707 | 1417 | 2004 | 450 | | isob3 | 13.9 | 19.7 | 353 | 500 | 1001 | 1417 | 451 | | isob4 | 9.8 | 13.9 | 250 | 353 | 709 | 1001 | 452 | | isob5 | 6.9 | 9.8 | 176 | 250 | 499 | 709 | 453 | | isob6 | 4.9 | 6.9 | 125 | 176 | 354 | 499 | 454 | | c0 | 36.1 | 51.1 | 917 | 1297 | 2599 | 3677 | 455 | | c1 | 25.5 | 36.1 | 648 | 917 | 1837 | 2599 | 456 | | c2 | 18.0 | 25.5 | 458 | 648 | 1298 | 1837 | 457 | | c3 | 12.8 | 18.0 | 324 | 458 | 918 | 1298 | 458 | | c4 | 9.0 | 12.8 | 229 | 324 | 649 | 918 | 459 | | c5 | 6.4 | 9.0 | 162 | 229 | 459 | 649 | 460 | | c6 | 4.5 | 6.4 | 114 | 162 | 323 | 459 | 461 | +-----------------+-------+-------+-------+-------+-------+-------+ 462 | 463 | +-----------------------------------------------------------------+ 464 | | US STANDARD | 465 | +-----------------------------------------------------------------+ 466 | | Name | inchW | inchH | mm W | mm H | pts W | pts H | 467 | +-----------------+-------+-------+-------+-------+-------+-------+ 468 | | 11x17 | 11.0 | 17.0 | 279 | 432 | 792 | 1224 | 469 | | ledger | 17.0 | 11.0 | 432 | 279 | 1224 | 792 | 470 | | legal | 8.5 | 14.0 | 216 | 356 | 612 | 1008 | 471 | | letter | 8.5 | 11.0 | 216 | 279 | 612 | 792 | 472 | | lettersmall | 8.5 | 11.0 | 216 | 279 | 612 | 792 | 473 | | archE | 36.0 | 48.0 | 914 | 1219 | 2592 | 3456 | 474 | | archD | 24.0 | 36.0 | 610 | 914 | 1728 | 2592 | 475 | | archC | 18.0 | 24.0 | 457 | 610 | 1296 | 1728 | 476 | | archB | 12.0 | 18.0 | 305 | 457 | 864 | 1296 | 477 | | archA | 9.0 | 12.0 | 229 | 305 | 648 | 864 | 478 | +-----------------+-------+-------+-------+-------+-------+-------+ 479 | 480 | +-----------------------------------------------------------------+ 481 | | JIS STANDARD *Aproximated Points | 482 | +-----------------------------------------------------------------+ 483 | | Name | inchW | inchH | mm W | mm H | pts W | pts H | 484 | +-----------------+-------+-------+-------+-------+-------+-------+ 485 | | jisb0 | NA | NA | 1030 | 1456 | 2920 | 4127 | 486 | | jisb1 | NA | NA | 728 | 1030 | 2064 | 2920 | 487 | | jisb2 | NA | NA | 515 | 728 | 1460 | 2064 | 488 | | jisb3 | NA | NA | 364 | 515 | 1032 | 1460 | 489 | | jisb4 | NA | NA | 257 | 364 | 729 | 1032 | 490 | | jisb5 | NA | NA | 182 | 257 | 516 | 729 | 491 | | jisb6 | NA | NA | 128 | 182 | 363 | 516 | 492 | +-----------------+-------+-------+-------+-------+-------+-------+ 493 | 494 | +-----------------------------------------------------------------+ 495 | | OTHERS | 496 | +-----------------------------------------------------------------+ 497 | | Name | inchW | inchH | mm W | mm H | pts W | pts H | 498 | +-----------------+-------+-------+-------+-------+-------+-------+ 499 | | flsa | 8.5 | 13.0 | 216 | 330 | 612 | 936 | 500 | | flse | 8.5 | 13.0 | 216 | 330 | 612 | 936 | 501 | | halfletter | 5.5 | 8.5 | 140 | 216 | 396 | 612 | 502 | | hagaki | 3.9 | 5.8 | 100 | 148 | 283 | 420 | 503 | +-----------------+-------+-------+-------+-------+-------+-------+ 504 | ``` 505 | 506 | ## Dependencies 507 | The script uses `basename`, `grep`, `bc` and `gs` (ghostscript). 508 | You probably have everything installed already, except for ghostscript. 509 | Optional dependencies are `imagemagick`, `pdfinfo` and `mdls` (Mac). 510 | This app is focused in `Bash`, so it will probably not run in other shells. 511 | The script will need to see the dependencies on your `$PATH` variable. 512 | 513 | ##### apt-get 514 | ``` 515 | sudo apt-get install ghostscript bc 516 | ``` 517 | ##### yum 518 | ``` 519 | sudo yum install ghostscript bc 520 | ``` 521 | ##### homebrew MacOS 522 | ``` 523 | brew install ghostscript 524 | ``` 525 | ##### Optionals 526 | Page Size detection is by default in Adaptive Mode. 527 | It will try the following methods in sequence: 528 | 1. Try to get `/MediaBox` with `grep` (fastest) 529 | 2. Failed AND MacOS ? Try `mdls` 530 | 3. Failed ? Try `pdfinfo` 531 | 4. Failed ? Try ImageMagick's `identify` 532 | 5. Failed ? Try Ghostscript with a PS script 533 | 6. Failed ? `Exit` with error message 534 | 535 | The `grep` method will fail on PDFs without a `/MediaBox`. 536 | You may install any of the optionals to be used in that case. 537 | 538 | MacOS is fine using `mdls` if the metadata of the file is accurate. 539 | The metadata is generated automatically by the OS (Spotlight) 540 | 541 | ##### apt-get 542 | ``` 543 | sudo apt-get install imagemagick pdfinfo 544 | ``` 545 | ##### yum 546 | ``` 547 | sudo yum install imagemagick pdfinfo 548 | ``` 549 | ##### homebrew MacOS 550 | ``` 551 | brew install imagemagick xpdf 552 | ``` 553 | 554 | ## Windows 555 | - The script should work fine in cygwin. 556 | - If you are using msys/git for windows, and the script exits with a 'file not found' error, 557 | - try running `export MSYS_NO_PATHCONV=1` 558 | - and `export MSYS2_ARG_CONV_EXCL="*"` 559 | - and then running again. 560 | 561 | ## Clone using git 562 | ``` 563 | git clone https://github.com/tavinus/pdfScale.git 564 | cd ./pdfScale 565 | ./pdfScale.sh --version 566 | ``` 567 | 568 | ## Self-Install 569 | Since `v2.3.0` *pdfScale* can install itself using the parameter `--install`. 570 | 571 | By default it will install to `/usr/local/bin/pdfscale` 572 | ``` 573 | ./pdfScale.sh --install 574 | ``` 575 | A custom location can be specified as a parameter. 576 | Should contain full path to executable file. 577 | ``` 578 | ./pdfScale.sh --install /opt/pdfscale/pdfscale 579 | ``` 580 | 581 | ## Run installer using `curl` or `wget` 582 | #### wget oneliners 583 | ```bash 584 | # Normal install with prompts 585 | wget -q -O /tmp/pdfScale.sh 'https://raw.githubusercontent.com/tavinus/pdfScale/master/pdfScale.sh' && bash /tmp/pdfScale.sh --install 586 | 587 | # Automated install with --assume-yes 588 | wget -q -O /tmp/pdfScale.sh 'https://raw.githubusercontent.com/tavinus/pdfScale/master/pdfScale.sh' && bash /tmp/pdfScale.sh --install --assume-yes 589 | 590 | # To ignore SSL, use --no-check-certificate 591 | wget --no-check-certificate -q -O /tmp/pdfScale.sh 'https://raw.githubusercontent.com/tavinus/pdfScale/master/pdfScale.sh' && bash /tmp/pdfScale.sh --install 592 | ``` 593 | #### curl oneliners 594 | ```bash 595 | # Normal install with prompts 596 | curl -s -o /tmp/pdfScale.sh 'https://raw.githubusercontent.com/tavinus/pdfScale/master/pdfScale.sh' && bash /tmp/pdfScale.sh --install 597 | 598 | # Automated install with --assume-yes 599 | curl -s -o /tmp/pdfScale.sh 'https://raw.githubusercontent.com/tavinus/pdfScale/master/pdfScale.sh' && bash /tmp/pdfScale.sh --install --assume-yes 600 | 601 | # To ignore SSL, use --insecure 602 | curl --insecure -s -o /tmp/pdfScale.sh 'https://raw.githubusercontent.com/tavinus/pdfScale/master/pdfScale.sh' && bash /tmp/pdfScale.sh --install 603 | ``` 604 | #### Remove /tmp/pdfScale.sh after done 605 | ```bash 606 | rm /tmp/pdfScale.sh 607 | ``` 608 | 609 | ## Install with `make` 610 | The `make` installer will name the executable as `pdfscale` with no uppercase chars and without the `.sh` extension. 611 | 612 | If you have `make` installed you can use it to install to `/usr/local/bin/pdfscale` with: 613 | ``` 614 | sudo make install 615 | ``` 616 | To remove the installation use: 617 | ``` 618 | sudo make uninstall 619 | ``` 620 | 621 | ## Self-Upgrade 622 | Since `v2.3.0` *pdfScale* can upgrade itself using the parameter `--upgrade`. 623 | 624 | It will try to get the master branch and update itself in-place. 625 | ``` 626 | pdfscale --upgrade 627 | ``` 628 | More info on the [Self-Upgrade Wiki](https://github.com/tavinus/pdfScale/wiki/Self-Upgrade) 629 | 630 | --- 631 | 632 | # Links 633 | #### [ma.juii.net - The History](https://ma.juii.net/blog/scale-page-content-of-pdf-files) 634 | #### [SO - Scale pdf to add border for printing full size pages](https://stackoverflow.com/questions/18343813/scale-pdf-to-add-border-for-printing-full-size-pages/) 635 | #### [MichaelJCole original gist - pdfScale.sh](https://gist.github.com/MichaelJCole/86e4968dbfc13256228a) 636 | -------------------------------------------------------------------------------- /pdfScale.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ################################################################ 4 | # 5 | # pdfScale.sh 6 | # 7 | # Manipulate PDFs using Ghostscript. 8 | # Scale, Resize and Split PDFs. 9 | # Writen for Bash. 10 | # 11 | # Gustavo Arnosti Neves - 2016 / 07 / 10 12 | # Latest Version - 2024 / 07 / 16 13 | # 14 | # This app: https://github.com/tavinus/pdfScale 15 | # 16 | # THIS SOFTWARE IS FREE - HAVE FUN WITH IT 17 | # I hope this can be of help to people. Thanks to the people 18 | # that helped and donated. It has been a long run with this 19 | # script. I wish you all the best! -] 20 | # 21 | # Ver 2.5.9 Added MIT License 22 | # 23 | ################################################################ 24 | 25 | VERSION="2.6.3" 26 | 27 | 28 | ###################### EXTERNAL PROGRAMS ####################### 29 | 30 | GSBIN="" # GhostScript Binary 31 | BCBIN="" # BC Math Binary 32 | IDBIN="" # Identify Binary 33 | PDFINFOBIN="" # PDF Info Binary 34 | MDLSBIN="" # MacOS mdls Binary 35 | 36 | 37 | ##################### ENVIRONMENT SET-UP ####################### 38 | 39 | LC_MEASUREMENT="C" # To make sure our numbers have .decimals 40 | LC_ALL="C" # Some languages use , as decimal token 41 | LC_CTYPE="C" 42 | LC_NUMERIC="C" 43 | 44 | TRUE=0 # Silly stuff 45 | FALSE=1 46 | 47 | 48 | ########################### GLOBALS ############################ 49 | 50 | SCALE="0.95" # scaling factor (0.95 = 95%, e.g.) 51 | VERBOSE=0 # verbosity Level 52 | PDFSCALE_NAME="$(basename "$0")" # simplified name of this script 53 | OSNAME="$(uname 2>/dev/null)" # Check where we are running 54 | GS_RUN_STATUS="" # Holds GS error messages, signals errors 55 | 56 | INFILEPDF="" # Input PDF file name 57 | OUTFILEPDF="" # Output PDF file name 58 | JUST_IDENTIFY=$FALSE # Flag to just show PDF info 59 | ABORT_ON_OVERWRITE=$FALSE # Flag to abort if OUTFILEPDF already exists 60 | EXPLODE_MODE=$FALSE # Turn explode mode (split) on or off 61 | ADAPTIVE_MODE=$TRUE # Automatically try to guess best mode 62 | AUTOMATIC_SCALING=$TRUE # Default scaling in $SCALE, disabled in resize mode 63 | MODE="" # Which page size detection to use 64 | RESIZE_PAPER_TYPE="" # Pre-defined paper to use 65 | CUSTOM_RESIZE_PAPER=$FALSE # If we are using a custom-defined paper 66 | CROPBOX_PAPER_TYPE="" # Pre-defined paper to use for cropboxes 67 | CUSTOM_CROPBOX_PAPER=$FALSE # If we are using a custom-defined cropbox 68 | FLIP_DETECTION=$TRUE # If we should run the Flip-detection 69 | FLIP_FORCE=$FALSE # If we should force Flipping 70 | AUTO_ROTATION='/PageByPage' # GS call auto-rotation setting 71 | FIT_PAGE='-dPDFFitPage' # GS call resize fit page setting 72 | DPRINTED="" # Print to screen or printer ? -dPrinted=false 73 | PGWIDTH="" # Input PDF Page Width 74 | PGHEIGHT="" # Input PDF Page Height 75 | RESIZE_WIDTH="" # Resized PDF Page Width 76 | RESIZE_HEIGHT="" # Resized PDF Page Height 77 | PAGE_RANGE="" # Pages to be processed (feeds -sPageList) 78 | PAGE_COUNT="" # To store the PDF page count 79 | PAGE_SIZES=() # To list all page sizes 80 | EXPLODE_SUFFIX='.Page%d' # Suffix for exploded pages 81 | 82 | ############################# Image resolution (dpi) 83 | IMAGE_RESOLUTION=300 # 300 is /Printer default 84 | 85 | ############################# Image compression setting 86 | # default screen ebook printer prepress 87 | # ColorImageDownsampleType /Subsample /Average /Bicubic /Bicubic /Bicubic 88 | IMAGE_DOWNSAMPLE_TYPE='/Bicubic' 89 | 90 | ############################# default PDF profile 91 | # /screen /ebook /printer /prepress /default 92 | # -dPDFSETTINGS=/screen (screen-view-only quality, 72 dpi images) 93 | # -dPDFSETTINGS=/ebook (low quality, 150 dpi images) 94 | # -dPDFSETTINGS=/printer (high quality, 300 dpi images) 95 | # -dPDFSETTINGS=/prepress (high quality, color preserving, 300 dpi imgs) 96 | # -dPDFSETTINGS=/default (almost identical to /screen) 97 | PDF_SETTINGS='/printer' 98 | 99 | ############################# default Scaling alignment 100 | VERT_ALIGN="CENTER" 101 | HOR_ALIGN="CENTER" 102 | 103 | ############################# Translation Offset to apply 104 | XTRANSOFFSET=0.0 105 | YTRANSOFFSET=0.0 106 | 107 | ############################# Background/Bleed color creation 108 | BACKGROUNDTYPE="NONE" # Should be NONE, CMYK or RGB only 109 | BACKGROUNDCOLOR="" # Color parameters for CMYK(4) or RGB(3) 110 | BACKGROUNDCALL="" # Actual PS call to be embedded 111 | BACKGROUNDLOG="No background (default)" 112 | 113 | ############################# Execution Flags 114 | SIMULATE=$FALSE # Avoid execution 115 | PRINT_GS_CALL=$FALSE # Print GS Call to stdout 116 | GS_CALL_STRING="" # Buffer 117 | RESIZECOMMANDS="" # command to run on resize call 118 | GSNEWPDF="" # for -dNEWDPF flag 119 | 120 | ############################# Project Info 121 | PROJECT_NAME="pdfScale" 122 | PROJECT_URL="https://github.com/tavinus/$PROJECT_NAME" 123 | PROJECT_BRANCH='master' 124 | HTTPS_INSECURE=$FALSE 125 | ASSUME_YES=$FALSE 126 | RUN_SELF_INSTALL=$FALSE 127 | TARGET_LOC="/usr/local/bin/pdfscale" 128 | 129 | ########################## EXIT FLAGS ########################## 130 | 131 | EXIT_SUCCESS=0 132 | EXIT_ERROR=1 133 | EXIT_INVALID_PAGE_SIZE_DETECTED=10 134 | EXIT_FILE_NOT_FOUND=20 135 | EXIT_INPUT_NOT_PDF=21 136 | EXIT_INVALID_OPTION=22 137 | EXIT_NO_INPUT_FILE=23 138 | EXIT_INVALID_SCALE=24 139 | EXIT_MISSING_DEPENDENCY=25 140 | EXIT_IMAGEMAGIK_NOT_FOUND=26 141 | EXIT_MAC_MDLS_NOT_FOUND=27 142 | EXIT_PDFINFO_NOT_FOUND=28 143 | EXIT_NOWRITE_PERMISSION=29 144 | EXIT_NOREAD_PERMISSION=30 145 | EXIT_TEMP_FILE_EXISTS=40 146 | EXIT_INVALID_PAPER_SIZE=50 147 | EXIT_INVALID_IMAGE_RESOLUTION=51 148 | 149 | 150 | ############################# MAIN ############################# 151 | 152 | # Main function called at the end 153 | main() { 154 | isJustIdentify && printPDFSizes # will exit here 155 | local finalRet=$EXIT_ERROR 156 | 157 | if isMixedMode; then 158 | initMain " Mixed Tasks: Resize & Scale" 159 | local tempFile="" 160 | local tempSuffix="$RANDOM$RANDOM""_TEMP_$RANDOM$RANDOM.pdf" 161 | outputFile="$OUTFILEPDF" # backup outFile name 162 | tempFile="${OUTFILEPDF%.pdf}" # set temp file, remove .pdf extension 163 | tempFile="${tempFile%$EXPLODE_SUFFIX}.$tempSuffix" # remove explode and add temp suffix 164 | if isFile "$tempFile"; then 165 | printError $'Error! Temporary file name already exists!\n'"File: $tempFile"$'\nAborting execution to avoid overwriting the file.\nPlease Try again...' 166 | exit $EXIT_TEMP_FILE_EXISTS 167 | fi 168 | OUTFILEPDF="$tempFile" # set output to tmp file 169 | pageResize # resize to tmp file 170 | finalRet=$? 171 | INFILEPDF="$tempFile" # get tmp file as input 172 | OUTFILEPDF="$outputFile" # reset final target 173 | PGWIDTH=$RESIZE_WIDTH # we already know the new page size 174 | PGHEIGHT=$RESIZE_HEIGHT # from the last command (Resize) 175 | vPrintPageSizes ' New' 176 | vPrintScaleFactor 177 | PAGE_RANGE="" # reset page range, we already got the pages 178 | pageScale # scale the resized pdf 179 | finalRet=$(($finalRet+$?)) 180 | # remove tmp file 181 | if isFile "$tempFile"; then 182 | rm "$tempFile" >/dev/null 2>&1 || printError "Error when removing temporary file: $tempFile" 183 | fi 184 | elif isResizeMode; then 185 | initMain " Single Task: Resize PDF Paper" 186 | vPrintScaleFactor "Disabled (resize only)" 187 | pageResize 188 | finalRet=$? 189 | else 190 | initMain " Single Task: Scale PDF Contents" 191 | local scaleMode="" 192 | isManualScaledMode && scaleMode='(manual)' || scaleMode='(auto)' 193 | vPrintScaleFactor "$SCALE $scaleMode" 194 | pageScale 195 | finalRet=$? 196 | fi 197 | 198 | if [[ $finalRet -eq $EXIT_SUCCESS ]] && isEmpty "$GS_RUN_STATUS"; then 199 | if isDryRun; then 200 | vprint " Final Status: Simulation completed successfully" 201 | else 202 | vprint " Final Status: File created successfully" 203 | fi 204 | elif [[ $finalRet -eq $EXIT_SUCCESS ]] && isNotEmpty "$GS_RUN_STATUS"; then 205 | vprint " Final Status: Succeeded with Warnings" 206 | printError "-------------------------------------------------"$'\n'"Ghostscript Debug Info:"$'\n'"$GS_RUN_STATUS" 207 | else 208 | vprint " Final Status: Error detected. Exit status: $finalRet" 209 | printError "-------------------------------------------------"$'\n'"Ghostscript Debug Info:"$'\n'"$GS_RUN_STATUS" 210 | fi 211 | 212 | if isNotEmpty "$GS_CALL_STRING" && shouldPrintGSCall; then 213 | printf "%s" "$GS_CALL_STRING" 214 | fi 215 | 216 | return $finalRet 217 | } 218 | 219 | # Initializes PDF processing for all modes of operation 220 | initMain() { 221 | printVersion 1 'verbose' 222 | isNotEmpty "$1" && vprint "$1" 223 | local sim="FALSE" 224 | isDryRun && sim="TRUE (Simulating)" 225 | vprint " Dry-Run: $sim" 226 | vPrintFileInfo 227 | local exp="Disabled" 228 | isExplodeMode && exp="Enabled" 229 | vprint " Explode PDF: $exp" 230 | getPageSize 231 | vPrintRange 232 | vPrintPageSizes ' Source' 233 | vShowPrintMode 234 | } 235 | 236 | # Prints PDF Info and exits with $EXIT_SUCCESS, but only if $JUST_IDENTIFY is $TRUE 237 | printPDFSizes() { 238 | VERBOSE=0 239 | printVersion 3 " - Paper Sizes" 240 | getPageSize || initError "Could not get pagesize!" 241 | local paperType="$(getGSPaperName $PGWIDTH $PGHEIGHT)" 242 | isEmpty "$paperType" && paperType="Custom Paper Size" 243 | getPageCountAndSizes 244 | printf '%s\n' "-------------+-----------------------------" 245 | printf " File | %s\n" "$(basename "$INFILEPDF")" 246 | printf " Paper Type | %s\n" "$paperType" 247 | printf " Pages | %s\n" "$PAGE_COUNT" 248 | printf '%s\n' "-------------+-----------------------------" 249 | printf '%s\n' " FIRST PAGE | WIDTH x HEIGHT" 250 | printf " Points | %+8s x %-8s\n" "$PGWIDTH" "$PGHEIGHT" 251 | printf " Millimeters | %+8s x %-8s\n" "$(pointsToMillimeters $PGWIDTH)" "$(pointsToMillimeters $PGHEIGHT)" 252 | printf " Inches | %+8s x %-8s\n" "$(pointsToInches $PGWIDTH)" "$(pointsToInches $PGHEIGHT)" 253 | printf '%s\n' "-------------+-----------------------------" 254 | printf '%s\n' " ALL PAGES | WIDTH x HEIGHT (pts)" 255 | local t=0 256 | local wh=() 257 | for l in "${PAGE_SIZES[@]}" ; do 258 | wh=($l) 259 | printf " %+11s | %+8s x %-8s\n" $((t+1)) ${wh[0]} ${wh[1]} 260 | ((t++)) 261 | done 262 | printf '%s\n' "-------------+-----------------------------" 263 | exit $EXIT_SUCCESS 264 | } 265 | 266 | 267 | ###################### GHOSTSCRIPT CALLS ####################### 268 | 269 | # Runs the ghostscript scaling script 270 | pageScale() { 271 | # Compute translation factors to position pages 272 | CENTERXTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGWIDTH" | "$BCBIN") 273 | CENTERYTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGHEIGHT" | "$BCBIN") 274 | BXTRANS=$CENTERXTRANS 275 | BYTRANS=$CENTERYTRANS 276 | 277 | if [[ "$VERT_ALIGN" = "TOP" ]]; then 278 | BYTRANS=$(echo "scale=6; 2*$CENTERYTRANS" | "$BCBIN") 279 | elif [[ "$VERT_ALIGN" = "BOTTOM" ]]; then 280 | BYTRANS=0 281 | fi 282 | if [[ "$HOR_ALIGN" = "LEFT" ]]; then 283 | BXTRANS=0 284 | elif [[ "$HOR_ALIGN" = "RIGHT" ]]; then 285 | BXTRANS=$(echo "scale=6; 2*$CENTERXTRANS" | "$BCBIN") 286 | fi 287 | 288 | XTRANS=$(echo "scale=6; $BXTRANS + $XTRANSOFFSET" | "$BCBIN") 289 | YTRANS=$(echo "scale=6; $BYTRANS + $YTRANSOFFSET" | "$BCBIN") 290 | 291 | local increase=$(echo "scale=0; (($SCALE - 1) * 100)/1" | "$BCBIN") 292 | [[ $increase -gt 0 ]] && increase="+$increase" 293 | 294 | vprint " Scale Percent: $increase%" 295 | vprint " Vert-Align: $VERT_ALIGN" 296 | vprint " Hor-Align: $HOR_ALIGN" 297 | vprint "$(printf ' Translation X: %.2f = %.2f + %.2f (offset)' $XTRANS $BXTRANS $XTRANSOFFSET)" 298 | vprint "$(printf ' Translation Y: %.2f = %.2f + %.2f (offset)' $YTRANS $BYTRANS $YTRANSOFFSET)" 299 | 300 | vprint " Background: $BACKGROUNDLOG" 301 | 302 | GS_RUN_STATUS="$GS_RUN_STATUS""$(gsPageScale 2>&1)" 303 | GS_CALL_STRING="$GS_CALL_STRING"$'[GS SCALE CALL STARTS]\n'"$(gsPrintPageScale)"$'\n[GS SCALE CALL ENDS]\n' 304 | return $? # Last command is always returned I think 305 | } 306 | 307 | # Runs GS call for scaling, nothing else should run here 308 | gsPageScale() { 309 | if isDryRun; then 310 | return $TRUE 311 | fi 312 | # Scale page 313 | "$GSBIN" \ 314 | -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \ 315 | -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \ 316 | -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \ 317 | -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \ 318 | -dColorConversionStrategy=/LeaveColorUnchanged \ 319 | -dSubsetFonts=true -dEmbedAllFonts=true \ 320 | -dDEVICEWIDTHPOINTS=$PGWIDTH -dDEVICEHEIGHTPOINTS=$PGHEIGHT \ 321 | $DPRINTED \ 322 | -sOutputFile="$OUTFILEPDF" $GSNEWPDF $PAGE_RANGE \ 323 | -c "<> setpagedevice" \ 324 | -f "$INFILEPDF" 325 | 326 | } 327 | 328 | # Prints GS call for scaling 329 | gsPrintPageScale() { 330 | local _call_str="" 331 | # Print Scale page command 332 | read -d '' _call_str<< _EOF_ 333 | "$GSBIN" \ 334 | -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \ 335 | -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \ 336 | -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \ 337 | -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \ 338 | -dColorConversionStrategy=/LeaveColorUnchanged \ 339 | -dSubsetFonts=true -dEmbedAllFonts=true \ 340 | -dDEVICEWIDTHPOINTS=$PGWIDTH -dDEVICEHEIGHTPOINTS=$PGHEIGHT \ 341 | $DPRINTED \ 342 | -sOutputFile="$OUTFILEPDF" $GSNEWPDF $PAGE_RANGE \ 343 | -c "<> setpagedevice" \ 344 | -f "$INFILEPDF" 345 | _EOF_ 346 | 347 | echo -ne "$_call_str" 348 | } 349 | 350 | # Runs the ghostscript paper resize script 351 | pageResize() { 352 | # Get paper sizes from source if not resizing 353 | isResizePaperSource && { RESIZE_WIDTH=$PGWIDTH; RESIZE_HEIGHT=$PGHEIGHT; } 354 | # Get new paper sizes if not custom or source paper 355 | isNotCustomPaper && ! isResizePaperSource && getGSPaperSize "$RESIZE_PAPER_TYPE" 356 | local fpStatus="Enabled (default)" 357 | isEmpty $FIT_PAGE && fpStatus="Disabled (manual)" 358 | vprint " Fit To Page: $fpStatus" 359 | vprint " Auto Rotate: $(basename $AUTO_ROTATION)" 360 | runFlipDetect 361 | vprint " Run Resizing: $(uppercase "$RESIZE_PAPER_TYPE") ( "$RESIZE_WIDTH" x "$RESIZE_HEIGHT" ) pts" 362 | if shouldSetCropbox; then 363 | if [[ $CROPBOX_PAPER_TYPE == 'fullsize' ]]; then 364 | CROPBOX_WIDTH=$RESIZE_WIDTH 365 | CROPBOX_HEIGHT=$RESIZE_HEIGHT 366 | elif [[ $CROPBOX_PAPER_TYPE != 'custom' ]]; then 367 | getCropboxPaperSize "$CROPBOX_PAPER_TYPE" 368 | fi 369 | RESIZECOMMANDS='<> setpagedevice' 370 | vprint " Cropbox Reset: $(uppercase "$CROPBOX_PAPER_TYPE") ( "$CROPBOX_WIDTH" x "$CROPBOX_HEIGHT" ) pts" 371 | fi 372 | GS_RUN_STATUS="$GS_RUN_STATUS""$(gsPageResize 2>&1)" 373 | GS_CALL_STRING="$GS_CALL_STRING"$'[GS RESIZE CALL STARTS]\n'"$(gsPrintPageResize)"$'\n[GS RESIZE CALL ENDS]\n' 374 | return $? 375 | } 376 | 377 | # Runs GS call for resizing, nothing else should run here 378 | gsPageResize() { 379 | if isDryRun; then 380 | return $TRUE 381 | fi 382 | 383 | # Change page size 384 | "$GSBIN" \ 385 | -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \ 386 | -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \ 387 | -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \ 388 | -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \ 389 | -dColorConversionStrategy=/LeaveColorUnchanged \ 390 | -dSubsetFonts=true -dEmbedAllFonts=true \ 391 | -dDEVICEWIDTHPOINTS=$RESIZE_WIDTH -dDEVICEHEIGHTPOINTS=$RESIZE_HEIGHT \ 392 | -dAutoRotatePages=$AUTO_ROTATION \ 393 | -dFIXEDMEDIA $FIT_PAGE $DPRINTED $GSNEWPDF $PAGE_RANGE \ 394 | -sOutputFile="$OUTFILEPDF" -c "$RESIZECOMMANDS" \ 395 | -f "$INFILEPDF" 396 | return $? 397 | } 398 | 399 | # Prints GS call for resizing 400 | gsPrintPageResize() { 401 | # Print Resize page command 402 | local _call_str="" 403 | # Print Scale page command 404 | read -d '' _call_str<< _EOF_ 405 | "$GSBIN" \ 406 | -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \ 407 | -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \ 408 | -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \ 409 | -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \ 410 | -dColorConversionStrategy=/LeaveColorUnchanged \ 411 | -dSubsetFonts=true -dEmbedAllFonts=true \ 412 | -dDEVICEWIDTHPOINTS=$RESIZE_WIDTH -dDEVICEHEIGHTPOINTS=$RESIZE_HEIGHT \ 413 | -dAutoRotatePages=$AUTO_ROTATION \ 414 | -dFIXEDMEDIA $FIT_PAGE $DPRINTED $GSNEWPDF $PAGE_RANGE \ 415 | -sOutputFile="$OUTFILEPDF" -c "$RESIZECOMMANDS" \ 416 | -f "$INFILEPDF" 417 | _EOF_ 418 | 419 | echo -ne "$_call_str" 420 | } 421 | 422 | # Returns $TRUE if we should use the source paper size, $FALSE otherwise 423 | isResizePaperSource() { 424 | [[ "$RESIZE_PAPER_TYPE" = 'source' ]] && return $TRUE 425 | return $FALSE 426 | } 427 | 428 | # Filp-Detect Logic 429 | runFlipDetect() { 430 | if isFlipForced; then 431 | vprint " Flip Detect: Forced Mode!" 432 | applyFlipRevert 433 | elif isFlipDetectionEnabled && shouldFlip; then 434 | vprint " Flip Detect: Wrong orientation detected!" 435 | applyFlipRevert 436 | elif ! isFlipDetectionEnabled; then 437 | vprint " Flip Detect: Disabled" 438 | else 439 | vprint " Flip Detect: No change needed" 440 | fi 441 | } 442 | 443 | # Inverts $RESIZE_HEIGHT with $RESIZE_WIDTH 444 | applyFlipRevert() { 445 | local tmpInverter="" 446 | tmpInverter=$RESIZE_HEIGHT 447 | RESIZE_HEIGHT=$RESIZE_WIDTH 448 | RESIZE_WIDTH=$tmpInverter 449 | vprint " Inverting Width <-> Height" 450 | } 451 | 452 | # Returns the $FLIP_DETECTION flag 453 | isFlipDetectionEnabled() { 454 | return $FLIP_DETECTION 455 | } 456 | 457 | # Returns the $FLIP_FORCE flag 458 | isFlipForced() { 459 | return $FLIP_FORCE 460 | } 461 | 462 | # Returns $TRUE if the the paper size will invert orientation from source, $FALSE otherwise 463 | shouldFlip() { 464 | [[ $PGWIDTH -gt $PGHEIGHT && $RESIZE_WIDTH -lt $RESIZE_HEIGHT ]] || [[ $PGWIDTH -lt $PGHEIGHT && $RESIZE_WIDTH -gt $RESIZE_HEIGHT ]] && return $TRUE 465 | return $FALSE 466 | } 467 | 468 | 469 | ########################## INITIALIZERS ######################### 470 | 471 | # Loads external dependencies and checks for errors 472 | initDeps() { 473 | GREPBIN="$(command -v grep 2>/dev/null)" 474 | STRINGSBIN="$(command -v strings 2>/dev/null)" 475 | GSBIN="$(command -v gs 2>/dev/null)" 476 | BCBIN="$(command -v bc 2>/dev/null)" 477 | IDBIN=$(command -v identify 2>/dev/null) 478 | MDLSBIN="$(command -v mdls 2>/dev/null)" 479 | PDFINFOBIN="$(command -v pdfinfo 2>/dev/null)" 480 | 481 | vprint "Checking for basename, grep, ghostscript and bcmath" 482 | basename "" >/dev/null 2>&1 || printDependency 'basename' 483 | isNotAvailable "$GREPBIN" && printDependency 'grep' 484 | isNotAvailable "$STRINGSBIN" && printDependency 'strings (binutils)' 485 | isNotAvailable "$GSBIN" && printDependency 'ghostscript' 486 | isNotAvailable "$BCBIN" && printDependency 'bc' 487 | return $TRUE 488 | } 489 | 490 | # Checks for dependencies errors, run after getting options 491 | checkDeps() { 492 | if [[ $MODE = "IDENTIFY" ]]; then 493 | vprint "Checking for imagemagick's identify" 494 | if isNotAvailable "$IDBIN"; then printDependency 'imagemagick'; fi 495 | fi 496 | if [[ $MODE = "PDFINFO" ]]; then 497 | vprint "Checking for pdfinfo" 498 | if isNotAvailable "$PDFINFOBIN"; then printDependency 'pdfinfo'; fi 499 | fi 500 | if [[ $MODE = "MDLS" ]]; then 501 | vprint "Checking for MacOS mdls" 502 | if isNotAvailable "$MDLSBIN"; then 503 | initError 'mdls executable was not found! Is this even MacOS?' $EXIT_MAC_MDLS_NOT_FOUND 504 | fi 505 | fi 506 | return $TRUE 507 | } 508 | 509 | 510 | ######################### CLI OPTIONS ########################## 511 | 512 | # Parse options 513 | getOptions() { 514 | local _optArgs=() # things that do not start with a '-' 515 | local _tgtFile="" # to set $OUTFILEPDF 516 | local _currParam="" # to enable case-insensitiveness 517 | while [ ${#} -gt 0 ]; do 518 | if [[ "${1:0:2}" = '--' ]]; then 519 | # Long Option, get lowercase version 520 | _currParam="$(lowercase ${1})" 521 | elif [[ "${1:0:1}" = '-' ]]; then 522 | # short Option, just assign 523 | _currParam="${1}" 524 | else 525 | # file name arguments, store as is and reset loop 526 | _optArgs+=("$1") 527 | shift 528 | continue 529 | fi 530 | case "$_currParam" in 531 | -v|--verbose) 532 | ((VERBOSE++)) 533 | shift 534 | ;; 535 | -n|--no-overwrite|--nooverwrite) 536 | ABORT_ON_OVERWRITE=$TRUE 537 | shift 538 | ;; 539 | -h|--help) 540 | printHelp 541 | exit $EXIT_SUCCESS 542 | ;; 543 | -V|--version) 544 | printVersion 3 545 | exit $EXIT_SUCCESS 546 | ;; 547 | -i|--identify|--info) 548 | JUST_IDENTIFY=$TRUE 549 | shift 550 | ;; 551 | -e|--explode|--split) 552 | EXPLODE_MODE=$TRUE 553 | shift 554 | ;; 555 | --range|--page-range|--pagerange|--page-list|--pagelist) 556 | shift 557 | isEmpty "$1" && initError "Invalid (empty) Page Range!" $EXIT_INVALID_OPTION 558 | PAGE_RANGE="-sPageList=$1" 559 | shift 560 | ;; 561 | -s|--scale|--setscale|--set-scale) 562 | shift 563 | parseScale "$1" 564 | shift 565 | ;; 566 | -m|--mode|--paperdetect|--paper-detect|--pagesizemode|--page-size-mode) 567 | shift 568 | parseMode "$1" 569 | shift 570 | ;; 571 | --newpdf|--dnewpdf) 572 | GSNEWPDF="-dNEWPDF=false" 573 | shift 574 | ;; 575 | -r|--resize) 576 | shift 577 | parsePaperResize "$1" 578 | shift 579 | ;; 580 | -c|--cropbox) 581 | shift 582 | parseCropbox "$1" 583 | shift 584 | ;; 585 | -p|--printpapers|--print-papers|--listpapers|--list-papers) 586 | printPaperInfo 587 | exit $EXIT_SUCCESS 588 | ;; 589 | -f|--flipdetection|--flip-detection|--flip-mode|--flipmode|--flipdetect|--flip-detect) 590 | shift 591 | parseFlipDetectionMode "$1" 592 | shift 593 | ;; 594 | -a|--autorotation|--auto-rotation|--autorotate|--auto-rotate) 595 | shift 596 | parseAutoRotationMode "$1" 597 | shift 598 | ;; 599 | --printmode|--print-mode) 600 | shift 601 | parsePrintMode "$1" 602 | shift 603 | ;; 604 | --no-fit-page|--no-fit-to-page|--disable-fit-to-page|--disable-fit-page|--nofitpage|--nofittopage|--disablefittopage|--disablefitpage) 605 | FIT_PAGE='' 606 | shift 607 | ;; 608 | --background-gray) 609 | shift 610 | parseGrayBackground $1 611 | shift 612 | ;; 613 | --background-rgb) 614 | shift 615 | parseRGBBackground $1 616 | shift 617 | ;; 618 | --background-cmyk) 619 | shift 620 | parseCMYKBackground $1 621 | shift 622 | ;; 623 | --pdf-settings) 624 | shift 625 | parsePDFSettings "$1" 626 | shift 627 | ;; 628 | --image-downsample) 629 | shift 630 | parseImageDownSample "$1" 631 | shift 632 | ;; 633 | --image-resolution) 634 | shift 635 | parseImageResolution "$1" 636 | shift 637 | ;; 638 | --horizontal-alignment|--hor-align|--xalign|--x-align) 639 | shift 640 | parseHorizontalAlignment "$1" 641 | shift 642 | ;; 643 | --vertical-alignment|--ver-align|--vert-align|--yalign|--y-align) 644 | shift 645 | parseVerticalAlignment "$1" 646 | shift 647 | ;; 648 | --xtrans|--xtrans-offset|--xoffset) 649 | shift 650 | parseXTransOffset "$1" 651 | shift 652 | ;; 653 | --ytrans|--ytrans-offset|--yoffset) 654 | shift 655 | parseYTransOffset "$1" 656 | shift 657 | ;; 658 | --simulate|--dry-run) 659 | SIMULATE=$TRUE 660 | shift 661 | ;; 662 | --install|--self-install) 663 | RUN_SELF_INSTALL=$TRUE 664 | shift 665 | if [[ ${1:0:1} != "-" ]]; then 666 | TARGET_LOC="$1" 667 | shift 668 | fi 669 | ;; 670 | --upgrade|--self-upgrade) 671 | RUN_SELF_UPGRADE=$TRUE 672 | shift 673 | ;; 674 | --insecure|--no-check-certificate) 675 | HTTPS_INSECURE=$TRUE 676 | shift 677 | ;; 678 | --yes|--assume-yes) 679 | ASSUME_YES=$TRUE 680 | shift 681 | ;; 682 | --print-gs-call|--gs-call) 683 | PRINT_GS_CALL=$TRUE 684 | shift 685 | ;; 686 | *) 687 | initError "Invalid Parameter: \"$1\"" $EXIT_INVALID_OPTION 688 | ;; 689 | esac 690 | done 691 | 692 | shouldInstall && selfInstall "$TARGET_LOC" # WILL EXIT HERE 693 | shouldUpgrade && selfUpgrade # WILL EXIT HERE 694 | 695 | isEmpty "${_optArgs[2]}" || initError "Seems like you passed an extra file name?"$'\n'"Invalid option: ${_optArgs[2]}" $EXIT_INVALID_OPTION 696 | 697 | if isJustIdentify; then 698 | isEmpty "${_optArgs[1]}" || initError "Seems like you passed an extra file name?"$'\n'"Invalid option: ${_optArgs[1]}" $EXIT_INVALID_OPTION 699 | VERBOSE=0 # remove verboseness if present 700 | fi 701 | 702 | # Validate input PDF file 703 | INFILEPDF="${_optArgs[0]}" 704 | isEmpty "$INFILEPDF" && initError "Input file is empty!" $EXIT_NO_INPUT_FILE 705 | isPDF "$INFILEPDF" || initError "Input file is not a PDF file: $INFILEPDF" $EXIT_INPUT_NOT_PDF 706 | isFile "$INFILEPDF" || initError "Input file not found: $INFILEPDF" $EXIT_FILE_NOT_FOUND 707 | isReadable "$INFILEPDF" || initError "No read access to input file: $INFILEPDF"$'\nPermission Denied' $EXIT_NOREAD_PERMISSION 708 | 709 | checkDeps 710 | 711 | if isJustIdentify; then 712 | return $TRUE # no need to get output file, so return already 713 | fi 714 | 715 | _tgtFile="${_optArgs[1]}" 716 | local _autoName="${INFILEPDF%.*}" # remove possible stupid extension, like .pDF 717 | local _exSuffix="" 718 | isExplodeMode && _exSuffix='.Page%d' 719 | if isMixedMode; then 720 | isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.$(uppercase $RESIZE_PAPER_TYPE).SCALED$_exSuffix.pdf" 721 | elif isResizeMode; then 722 | isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.$(uppercase $RESIZE_PAPER_TYPE)$_exSuffix.pdf" 723 | else 724 | isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.SCALED$_exSuffix.pdf" 725 | fi 726 | isNotEmpty "$_tgtFile" && OUTFILEPDF="${_tgtFile%.pdf}$_exSuffix.pdf" 727 | validateOutFile 728 | } 729 | 730 | # Returns $TRUE if the install flag is set 731 | shouldInstall() { 732 | return $RUN_SELF_INSTALL 733 | } 734 | 735 | # Returns $TRUE if the upgrade flag is set 736 | shouldUpgrade() { 737 | return $RUN_SELF_UPGRADE 738 | } 739 | 740 | # Install pdfScale 741 | selfInstall() { 742 | #CURRENT_LOC="$(readlink -f $0)" 743 | CURRENT_LOC="$(readlinkf $0)" 744 | TARGET_LOC="$1" 745 | isEmpty "$TARGET_LOC" && TARGET_LOC="/usr/local/bin/pdfscale" 746 | VERBOSE=0 747 | NEED_SUDO=$FALSE 748 | printVersion 3 " - Self Install" 749 | echo "" 750 | echo "Current location : $CURRENT_LOC" 751 | echo "Target location : $TARGET_LOC" 752 | if [[ "$CURRENT_LOC" = "$TARGET_LOC" ]]; then 753 | echo $'\n'"Error! Source and Target locations are the same!" 754 | echo "Cannot copy to itself..." 755 | exit $EXIT_INVALID_OPTION 756 | fi 757 | TARGET_FOLDER="$(dirname $TARGET_LOC)" 758 | local _answer="NO" 759 | if isNotDir "$TARGET_FOLDER"; then 760 | echo $'\nThe target folder does not exist\n > '"$TARGET_FOLDER" 761 | if assumeYes; then 762 | echo '' 763 | _answer="y" 764 | else 765 | read -p $'\nCreate the target folder? Y/y to continue > ' _answer 766 | _answer="$(lowercase $_answer)" 767 | fi 768 | if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then 769 | _answer="no" 770 | if mkdir -p "$TARGET_FOLDER" 2>/dev/null; then 771 | echo " > Folder Created!" 772 | else 773 | echo $'\n'"There was an error when trying to create the folder." 774 | if assumeYes; then 775 | echo $'\nTrying again with sudo, enter password if needed > ' 776 | _answer="y" 777 | else 778 | read -p $'\nDo you want to try again with sudo (as root)? Y/y to continue > ' _answer 779 | _answer="$(lowercase $_answer)" 780 | fi 781 | if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then 782 | NEED_SUDO=$TRUE 783 | if sudo mkdir -p "$TARGET_FOLDER" 2>/dev/null; then 784 | echo "Folder Created!" 785 | else 786 | echo "There was an error when trying to create the folder." 787 | exit $EXIT_ERROR 788 | fi 789 | else 790 | echo "Exiting..." 791 | exit $EXIT_ERROR 792 | fi 793 | fi 794 | else 795 | echo "Exiting... (cancelled by user)" 796 | exit $EXIT_ERROR 797 | fi 798 | fi 799 | _answer="no" 800 | if isFile "$TARGET_LOC"; then 801 | echo $'\n'"The target file already exists: $TARGET_LOC" 802 | if assumeYes; then 803 | _answer="y" 804 | else 805 | read -p "Y/y to overwrite, anything else to cancel > " _answer 806 | _answer="$(lowercase $_answer)" 807 | fi 808 | if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then 809 | echo "Target will be replaced!" 810 | else 811 | echo "Exiting... (cancelled by user)" 812 | exit $EXIT_ERROR 813 | fi 814 | fi 815 | if [[ $NEED_SUDO -eq $TRUE ]]; then 816 | if sudo cp "$CURRENT_LOC" "$TARGET_LOC"; then 817 | sudo chmod +x "$TARGET_LOC" 818 | echo $'\nSuccess! Program installed!' 819 | echo " > $TARGET_LOC" 820 | exit $EXIT_SUCCESS 821 | else 822 | echo "There was an error when trying to install the program." 823 | exit $EXIT_ERROR 824 | fi 825 | fi 826 | if cp "$CURRENT_LOC" "$TARGET_LOC"; then 827 | chmod +x "$TARGET_LOC" 828 | echo $'\nSuccess! Program installed!' 829 | echo " > $TARGET_LOC" 830 | exit $EXIT_SUCCESS 831 | else 832 | _answer="no" 833 | echo "There was an error when trying to install pdfScale." 834 | if assumeYes; then 835 | echo $'\nTrying again with sudo, enter password if needed > ' 836 | _answer="y" 837 | else 838 | read -p $'Do you want to try again with sudo (as root)? Y/y to continue > ' _answer 839 | _answer="$(lowercase $_answer)" 840 | fi 841 | if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then 842 | NEED_SUDO=$TRUE 843 | if sudo cp "$CURRENT_LOC" "$TARGET_LOC"; then 844 | sudo chmod +x "$TARGET_LOC" 845 | echo $'\nSuccess! Program installed!' 846 | echo " > $TARGET_LOC" 847 | exit $EXIT_SUCCESS 848 | else 849 | echo "There was an error when trying to install the program." 850 | exit $EXIT_ERROR 851 | fi 852 | else 853 | echo "Exiting... (cancelled by user)" 854 | exit $EXIT_ERROR 855 | fi 856 | fi 857 | exit $EXIT_ERROR 858 | } 859 | 860 | # Tries to download with curl or wget 861 | getUrl() { 862 | useInsecure && echo $'\nHTTPS Insecure flag is enabled!\nCertificates will be ignored by curl/wget\n' 863 | local url="$1" 864 | local target="$2" 865 | local _stat="" 866 | if isEmpty "$url" || isEmpty "$target"; then 867 | echo "Error! Invalid parameters for download." 868 | echo "URL > $url" 869 | echo "TARGET > $target" 870 | exit $EXIT_INVALID_OPTION 871 | fi 872 | WGET_BIN="$(command -v wget 2>/dev/null)" 873 | CURL_BIN="$(command -v curl 2>/dev/null)" 874 | if isExecutable "$WGET_BIN"; then 875 | useInsecure && WGET_BIN="$WGET_BIN --no-check-certificate" 876 | echo "Downloading file with wget" 877 | _stat="$($WGET_BIN -O "$target" "$url" 2>&1)" 878 | if [[ $? -eq 0 ]]; then 879 | return $TRUE 880 | else 881 | echo "Error when downloading file!" 882 | echo " > $url" 883 | echo "Status:" 884 | echo "$_stat" 885 | exit $EXIT_ERROR 886 | fi 887 | elif isExecutable "$CURL_BIN"; then 888 | useInsecure && CURL_BIN="$CURL_BIN --insecure" 889 | echo "Downloading file with curl" 890 | _stat="$($CURL_BIN -o "$target" -L "$url" 2>&1)" 891 | if [[ $? -eq 0 ]]; then 892 | return $TRUE 893 | else 894 | echo "Error when downloading file!" 895 | echo " > $url" 896 | echo "Status:" 897 | echo "$_stat" 898 | exit $EXIT_ERROR 899 | fi 900 | else 901 | echo "Error! Could not find Wget or Curl to perform download." 902 | echo "Please install either curl or wget and try again." 903 | exit $EXIT_FILE_NOT_FOUND 904 | fi 905 | } 906 | 907 | # Tries to remove temporary files from upgrade 908 | clearUpgrade() { 909 | echo $'\nCleaning up downloaded files from /tmp' 910 | if isFile "$TMP_TARGET"; then 911 | echo -n " > $TMP_TARGET > " 912 | rm "$TMP_TARGET" 2>/dev/null && echo "Ok" || echo "Fail" 913 | else 914 | echo " > no temporary tarball was found to remove" 915 | fi 916 | if isDir "$TMP_EXTRACTED"; then 917 | echo -n " > $TMP_EXTRACTED > " 918 | rm -rf "$TMP_EXTRACTED" 2>/dev/null && echo "Ok" || echo "Fail" 919 | else 920 | echo " > no temporary master folder was found to remove" 921 | fi 922 | } 923 | 924 | # Exit upgrade with message and status code 925 | # $1 Mensagem (printed if not empty) 926 | # $2 Status (defaults to $EXIT_ERROR) 927 | exitUpgrade() { 928 | isDir "$_cwd" && cd "$_cwd" 929 | isNotEmpty "$1" && echo "$1" 930 | clearUpgrade 931 | isNotEmpty "$2" && exit $2 932 | exit $EXIT_ERROR 933 | } 934 | 935 | # Downloads current version from github's MASTER branch 936 | selfUpgrade() { 937 | #CURRENT_LOC="$(readlink -f $0)" 938 | CURRENT_LOC="$(readlinkf $0)" 939 | _cwd="$(pwd)" 940 | local _cur_tstamp="$(date '+%Y%m%d-%H%M%S')" 941 | TMP_DIR='/tmp' 942 | TMP_TARGET="$TMP_DIR/pdfScale_$_cur_tstamp.tar.gz" 943 | TMP_EXTRACTED="$TMP_DIR/$PROJECT_NAME-$PROJECT_BRANCH" 944 | 945 | local _answer="no" 946 | 947 | printVersion 3 " - Self Upgrade" 948 | echo $'\n'"Preparing download to temp folder" 949 | echo " > $TMP_TARGET" 950 | getUrl "$PROJECT_URL/archive/$PROJECT_BRANCH.tar.gz" "$TMP_TARGET" 951 | if isNotFile "$TMP_TARGET"; then 952 | echo "Error! Could not find downloaded file!" 953 | exit $EXIT_FILE_NOT_FOUND 954 | fi 955 | echo $'\n'"Extracting compressed file" 956 | cd "$TMP_DIR" 957 | if ! (tar xzf "$TMP_TARGET" 2>/dev/null || gtar xzf "$TMP_TARGET" 2>/dev/null); then 958 | exitUpgrade "Extraction error." 959 | fi 960 | if ! cd "$TMP_EXTRACTED" 2>/dev/null; then 961 | exitUpgrade $'Error when accessing temporary folder\n > '"$TMP_EXTRACTED" 962 | fi 963 | if ! chmod +x pdfScale.sh; then 964 | exitUpgrade $'Error when setting new pdfScale to executable\n > '"$TMP_EXTRACTED/pdfScale.sh" 965 | fi 966 | local newver="$(./pdfScale.sh --version 2>/dev/null)" 967 | local curver="$(printVersion 3 2>/dev/null)" 968 | newver=($newver) 969 | curver=($curver) 970 | newver=${newver[1]#v} 971 | curver=${curver[1]#v} 972 | echo $'\n'" Current Version is: $curver" 973 | echo "Downloaded Version is: $newver"$'\n' 974 | if [[ "$newver" = "$curver" ]]; then 975 | echo "Seems like we have downloaded the same version that is installed." 976 | elif isBiggerVersion "$newver" "$curver"; then 977 | echo "Seems like the downloaded version is newer that the one installed." 978 | elif isBiggerVersion "$curver" "$newver"; then 979 | echo "Seems like the downloaded version is older that the one installed." 980 | echo "It is basically a miracle or you have came from the future with this version!" 981 | echo "BE CAREFUL NOT TO DELETE THE BETA/ALPHA VERSION WITH THIS UPDATE!" 982 | else 983 | exitUpgrade "An unidentified error has ocurred. Exiting..." 984 | fi 985 | if assumeYes; then 986 | echo $'\n'"Assume yes activated, current version will be replaced with master branch" 987 | _answer="y" 988 | else 989 | echo $'\n'"Are you sure that you want to replace the current installation with the downloaded one?" 990 | read -p "Y/y to continue, anything else to cancel > " _answer 991 | _answer="$(lowercase $_answer)" 992 | fi 993 | echo 994 | if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then 995 | echo "Upgrading..." 996 | if cp "./pdfScale.sh" "$CURRENT_LOC" 2>/dev/null; then 997 | chmod +x "$CURRENT_LOC" 998 | exitUpgrade $'\n'"Success! Upgrade finished!"$'\n'" > $CURRENT_LOC" $EXIT_SUCCESS 999 | else 1000 | _answer="no" 1001 | echo $'\n'"There was an error when copying the new version." 1002 | if assumeYes; then 1003 | echo $'\nAssume yes activated, retrying with sudo.\nEnter password if needed > \n' 1004 | _answer="y" 1005 | else 1006 | echo "Do you want to retry using sudo (as root)?" 1007 | read -p "Y/y to continue, anything else to cancel > " _answer 1008 | fi 1009 | _answer="$(lowercase $_answer)" 1010 | if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then 1011 | echo "Upgrading with sudo..." 1012 | if sudo cp "./pdfScale.sh" "$CURRENT_LOC" 2>/dev/null; then 1013 | sudo chmod +x "$CURRENT_LOC" 1014 | exitUpgrade $'\n'"Success! Upgrade finished!"$'\n'" > $CURRENT_LOC" $EXIT_SUCCESS 1015 | else 1016 | exitUpgrade "There was an error when copying the new version." 1017 | fi 1018 | else 1019 | exitUpgrade "Exiting... (cancelled by user)" 1020 | fi 1021 | 1022 | fi 1023 | exitUpgrade "An unidentified error has ocurred. Exiting..." 1024 | else 1025 | exitUpgrade "Exiting... (cancelled by user)" 1026 | fi 1027 | exitUpgrade "An unidentified error has ocurred. Exiting..." 1028 | } 1029 | 1030 | # Compares versions with x.x.x format 1031 | isBiggerVersion() { 1032 | local OIFS=$IFS 1033 | 1034 | IFS='.' 1035 | local _first=($1) 1036 | local _second=($2) 1037 | local _ret=$FALSE 1038 | 1039 | if [[ ${_first[0]} -gt ${_second[0]} ]]; then 1040 | _ret=$TRUE 1041 | elif [[ ${_first[0]} -lt ${_second[0]} ]]; then 1042 | _ret=$FALSE 1043 | elif [[ ${_first[1]} -gt ${_second[1]} ]]; then 1044 | _ret=$TRUE 1045 | elif [[ ${_first[1]} -lt ${_second[1]} ]]; then 1046 | _ret=$FALSE 1047 | elif [[ ${_first[2]} -gt ${_second[2]} ]]; then 1048 | _ret=$TRUE 1049 | elif [[ ${_first[2]} -lt ${_second[2]} ]]; then 1050 | _ret=$FALSE 1051 | fi 1052 | 1053 | IFS=$OIFS 1054 | 1055 | return $_ret 1056 | } 1057 | 1058 | # Checks if output file is valid and writable 1059 | validateOutFile() { 1060 | local _tgtDir="$(dirname "$OUTFILEPDF")" 1061 | isDir "$_tgtDir" || initError "Output directory does not exist!"$'\n'"Target Dir: $_tgtDir" $EXIT_NOWRITE_PERMISSION 1062 | isNotExplodeMode && isAbortOnOverwrite && isFile "$OUTFILEPDF" && initError $'Output file already exists and --no-overwrite was used!\nRemove the "-n" or "--no-overwrite" option if you want to overwrite the file\n'"Target File: $OUTFILEPDF" $EXIT_NOWRITE_PERMISSION 1063 | isNotExplodeMode && isNotTouchable "$OUTFILEPDF" && initError "Could not get write permission for output file!"$'\n'"Target File: $OUTFILEPDF"$'\nPermission Denied' $EXIT_NOWRITE_PERMISSION 1064 | } 1065 | 1066 | # Returns $TRUE if we should not overwrite $OUTFILEPDF, $FALSE otherwise 1067 | isAbortOnOverwrite() { 1068 | return $ABORT_ON_OVERWRITE 1069 | } 1070 | 1071 | # Returns $TRUE if we should print the GS call to stdout 1072 | shouldPrintGSCall() { 1073 | return $PRINT_GS_CALL 1074 | } 1075 | 1076 | # Returns $TRUE if we are simulating, dry-run (no GS execution) 1077 | isDryRun() { 1078 | return $SIMULATE 1079 | } 1080 | 1081 | # Parses and validates the scaling factor 1082 | parseScale() { 1083 | AUTOMATIC_SCALING=$FALSE 1084 | if ! isFloatBiggerThanZero "$1"; then 1085 | printError "Invalid factor: $1" 1086 | printError "The factor must be a floating point number greater than 0" 1087 | printError "Example: for 80% use 0.8" 1088 | exit $EXIT_INVALID_SCALE 1089 | fi 1090 | SCALE="$1" 1091 | } 1092 | 1093 | # Parse a forced mode of operation 1094 | parseMode() { 1095 | local param="$(lowercase $1)" 1096 | case "${param}" in 1097 | c|catgrep|'cat+grep'|grep|g) 1098 | ADAPTIVE_MODE=$FALSE 1099 | MODE="CATGREP" 1100 | return $TRUE 1101 | ;; 1102 | i|imagemagick|identify) 1103 | ADAPTIVE_MODE=$FALSE 1104 | MODE="IDENTIFY" 1105 | return $TRUE 1106 | ;; 1107 | m|mdls|quartz|mac) 1108 | ADAPTIVE_MODE=$FALSE 1109 | MODE="MDLS" 1110 | return $TRUE 1111 | ;; 1112 | p|pdfinfo) 1113 | ADAPTIVE_MODE=$FALSE 1114 | MODE="PDFINFO" 1115 | return $TRUE 1116 | ;; 1117 | s|gs|ghostscript|ps|postscript) 1118 | ADAPTIVE_MODE=$FALSE 1119 | MODE="GS" 1120 | return $TRUE 1121 | ;; 1122 | a|auto|automatic|adaptive) 1123 | ADAPTIVE_MODE=$TRUE 1124 | MODE="" 1125 | return $TRUE 1126 | ;; 1127 | *) 1128 | initError "Invalid PDF Size Detection Mode: \"$1\"" $EXIT_INVALID_OPTION 1129 | return $FALSE 1130 | ;; 1131 | esac 1132 | 1133 | return $FALSE 1134 | } 1135 | 1136 | # Parses and validates the scaling factor 1137 | parseFlipDetectionMode() { 1138 | local param="$(lowercase $1)" 1139 | case "${param}" in 1140 | d|disable) 1141 | FLIP_DETECTION=$FALSE 1142 | FLIP_FORCE=$FALSE 1143 | ;; 1144 | f|force) 1145 | FLIP_DETECTION=$FALSE 1146 | FLIP_FORCE=$TRUE 1147 | ;; 1148 | a|auto|automatic) 1149 | FLIP_DETECTION=$TRUE 1150 | FLIP_FORCE=$FALSE 1151 | ;; 1152 | *) 1153 | initError "Invalid Flip Detection Mode: \"$1\"" $EXIT_INVALID_OPTION 1154 | return $FALSE 1155 | ;; 1156 | esac 1157 | } 1158 | 1159 | # Parses and validates the scaling factor 1160 | parseAutoRotationMode() { 1161 | local param="$(lowercase $1)" 1162 | case "${param}" in 1163 | n|none|'/none') 1164 | AUTO_ROTATION='/None' 1165 | ;; 1166 | a|all|'/all') 1167 | AUTO_ROTATION='/All' 1168 | ;; 1169 | p|pagebypage|'/pagebypage'|auto) 1170 | AUTO_ROTATION='/PageByPage' 1171 | ;; 1172 | *) 1173 | initError "Invalid Auto Rotation Mode: \"$1\"" $EXIT_INVALID_OPTION 1174 | return $FALSE 1175 | ;; 1176 | esac 1177 | } 1178 | 1179 | # Parses and validates the Print Mode (dPrinted parameter) 1180 | parsePrintMode() { 1181 | local param="$(lowercase $1)" 1182 | case "${param}" in 1183 | s|screen) 1184 | DPRINTED='-dPrinted=false' 1185 | ;; 1186 | p|print|printer) 1187 | DPRINTED='-dPrinted' 1188 | ;; 1189 | *) 1190 | initError "Invalid Print Mode (not s,screen,p,print): \"$1\"" $EXIT_INVALID_OPTION 1191 | return $FALSE 1192 | ;; 1193 | esac 1194 | } 1195 | 1196 | # Validades the a paper resize CLI option and sets the paper to $RESIZE_PAPER_TYPE 1197 | parsePaperResize() { 1198 | isEmpty "$1" && initError 'Invalid Paper Type: (empty)' $EXIT_INVALID_PAPER_SIZE 1199 | local lowercasePaper="$(lowercase $1)" 1200 | local customPaper=($lowercasePaper) 1201 | if [[ "$customPaper" = 'same' || "$customPaper" = 'keep' || "$customPaper" = 'source' ]]; then 1202 | RESIZE_PAPER_TYPE='source' 1203 | elif [[ "${customPaper[0]}" = 'custom' ]]; then 1204 | if isNotValidMeasure "${customPaper[1]}" || ! isFloatBiggerThanZero "${customPaper[2]}" || ! isFloatBiggerThanZero "${customPaper[3]}"; then 1205 | initError "Invalid Custom Paper Definition!"$'\n'"Use: -r 'custom '"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION 1206 | fi 1207 | RESIZE_PAPER_TYPE="custom" 1208 | CUSTOM_RESIZE_PAPER=$TRUE 1209 | if isMilimeter "${customPaper[1]}"; then 1210 | RESIZE_WIDTH="$(millimetersToPoints "${customPaper[2]}")" 1211 | RESIZE_HEIGHT="$(millimetersToPoints "${customPaper[3]}")" 1212 | elif isInch "${customPaper[1]}"; then 1213 | RESIZE_WIDTH="$(inchesToPoints "${customPaper[2]}")" 1214 | RESIZE_HEIGHT="$(inchesToPoints "${customPaper[3]}")" 1215 | elif isPoint "${customPaper[1]}"; then 1216 | RESIZE_WIDTH="${customPaper[2]}" 1217 | RESIZE_HEIGHT="${customPaper[3]}" 1218 | else 1219 | initError "Invalid Custom Paper Definition!"$'\n'"Use: -r 'custom '"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION 1220 | fi 1221 | else 1222 | isPaperName "$lowercasePaper" || initError "Invalid Paper Type: $1" $EXIT_INVALID_PAPER_SIZE 1223 | RESIZE_PAPER_TYPE="$lowercasePaper" 1224 | fi 1225 | } 1226 | 1227 | # Goes to GS -dColorImageResolution and -dGrayImageResolution parameters 1228 | parseImageResolution() { 1229 | if isNotAnInteger "$1"; then 1230 | printError "Invalid image resolution: $1" 1231 | printError "The image resolution must be an integer" 1232 | exit $EXIT_INVALID_IMAGE_RESOLUTION 1233 | fi 1234 | IMAGE_RESOLUTION="$1" 1235 | } 1236 | 1237 | # Goes to GS -dColorImageDownsampleType and -dGrayImageDownsampleType parameters 1238 | parseImageDownSample() { 1239 | local param="$(lowercase $1)" 1240 | case "${param}" in 1241 | s|subsample|'/subsample') 1242 | IMAGE_DOWNSAMPLE_TYPE='/Subsample' 1243 | ;; 1244 | a|average|'/average') 1245 | IMAGE_DOWNSAMPLE_TYPE='/Average' 1246 | ;; 1247 | b|bicubic|'/bicubic'|auto) 1248 | IMAGE_DOWNSAMPLE_TYPE='/Bicubic' 1249 | ;; 1250 | *) 1251 | initError "Invalid Image Downsample Mode: \"$1\"" $EXIT_INVALID_OPTION 1252 | return $FALSE 1253 | ;; 1254 | esac 1255 | } 1256 | 1257 | # Goes to GS -dColorImageDownsampleType and -dGrayImageDownsampleType parameters 1258 | parsePDFSettings() { 1259 | local param="$(lowercase $1)" 1260 | case "${param}" in 1261 | s|screen|'/screen') 1262 | PDF_SETTINGS='/screen' 1263 | ;; 1264 | e|ebook|'/ebook') 1265 | PDF_SETTINGS='/ebook' 1266 | ;; 1267 | p|printer|'/printer'|auto) 1268 | PDF_SETTINGS='/printer' 1269 | ;; 1270 | r|prepress|'/prepress') 1271 | PDF_SETTINGS='/prepress' 1272 | ;; 1273 | d|default|'/default') 1274 | PDF_SETTINGS='/default' 1275 | ;; 1276 | *) 1277 | initError "Invalid PDF Setting Profile: \"$1\""$'\nValid > printer, screen, ebook, prepress, default' $EXIT_INVALID_OPTION 1278 | return $FALSE 1279 | ;; 1280 | esac 1281 | } 1282 | 1283 | # How to position the resized pages (sets translation) 1284 | parseHorizontalAlignment() { 1285 | local param="$(lowercase $1)" 1286 | case "${param}" in 1287 | l|left) 1288 | HOR_ALIGN='LEFT' 1289 | ;; 1290 | r|right) 1291 | HOR_ALIGN='RIGHT' 1292 | ;; 1293 | c|center|middle) 1294 | HOR_ALIGN='CENTER' 1295 | ;; 1296 | *) 1297 | initError "Invalid Horizontal Alignment Setting: \"$1\""$'\nValid > left, right, center' $EXIT_INVALID_OPTION 1298 | return $FALSE 1299 | ;; 1300 | esac 1301 | } 1302 | 1303 | # How to position the resized pages (sets translation) 1304 | parseVerticalAlignment() { 1305 | local param="$(lowercase $1)" 1306 | case "${param}" in 1307 | t|top) 1308 | VERT_ALIGN='TOP' 1309 | ;; 1310 | b|bottom|bot) 1311 | VERT_ALIGN='BOTTOM' 1312 | ;; 1313 | c|center|middle) 1314 | VERT_ALIGN='CENTER' 1315 | ;; 1316 | *) 1317 | initError "Invalid Vertical Alignment Setting: \"$1\""$'\nValid > top, bottom, center' $EXIT_INVALID_OPTION 1318 | return $FALSE 1319 | ;; 1320 | esac 1321 | } 1322 | 1323 | # Set X Translation Offset 1324 | parseXTransOffset() { 1325 | if isFloat "$1"; then 1326 | XTRANSOFFSET="$1" 1327 | return $TRUE 1328 | fi 1329 | printError "Invalid X Translation Offset: $1" 1330 | printError "The X Translation Offset must be a floating point number" 1331 | exit $EXIT_INVALID_OPTION 1332 | } 1333 | 1334 | # Set Y Translation Offset 1335 | parseYTransOffset() { 1336 | if isFloat "$1"; then 1337 | YTRANSOFFSET="$1" 1338 | return $TRUE 1339 | fi 1340 | printError "Invalid Y Translation Offset: $1" 1341 | printError "The Y Translation Offset must be a floating point number" 1342 | exit $EXIT_INVALID_OPTION 1343 | } 1344 | 1345 | # Parse Gray Background color 1346 | parseGrayBackground() { 1347 | if isFloatPercentage "$1"; then 1348 | BACKGROUNDCOLOR="$1" 1349 | BACKGROUNDCALL="$BACKGROUNDCOLOR setgray clippath fill " # the space at the end is important! 1350 | BACKGROUNDTYPE="GRAY" 1351 | BACKGROUNDLOG="$GrayColor Mode > $BACKGROUNDCOLOR" 1352 | return $TRUE 1353 | fi 1354 | printError "Invalid Gray Background color." 1355 | printError "Need 1 floating point number between 0 and 1." 1356 | printError "Eg: --background-gray \"0.80\"" 1357 | printError "Invalid Param => $1" 1358 | exit $EXIT_INVALID_OPTION 1359 | } 1360 | 1361 | # Parse CMYK Background color 1362 | parseCMYKBackground() { 1363 | if isFloatPercentage "$1" && isFloatPercentage "$2" && isFloatPercentage "$3" && isFloatPercentage "$4"; then 1364 | BACKGROUNDCOLOR="$1 $2 $3 $4" 1365 | BACKGROUNDCALL="$BACKGROUNDCOLOR setcmykcolor clippath fill " # the space at the end is important! 1366 | BACKGROUNDTYPE="CMYK" 1367 | BACKGROUNDLOG="$BACKGROUNDTYPE Mode > $BACKGROUNDCOLOR" 1368 | return $TRUE 1369 | fi 1370 | printError "Invalid CMYK Background colors." 1371 | printError "Need 4 floating point numbers between 0 and 1 in CMYK order." 1372 | printError "Eg: --background-cmyk \"C M Y K\"" 1373 | printError " [C] => $1" 1374 | printError " [M] => $2" 1375 | printError " [Y] => $3" 1376 | printError " [K] => $4" 1377 | exit $EXIT_INVALID_OPTION 1378 | } 1379 | 1380 | # Just loads the RGB Vars (without testing anything) 1381 | loadRGBVars(){ 1382 | local rP="$(rgbToPercentage $1)" 1383 | local gP="$(rgbToPercentage $2)" 1384 | local bP="$(rgbToPercentage $3)" 1385 | BACKGROUNDCOLOR="$rP $gP $bP" 1386 | BACKGROUNDCALL="$BACKGROUNDCOLOR setrgbcolor clippath fill " # the space at the end is important! 1387 | BACKGROUNDTYPE="RGB" 1388 | BACKGROUNDLOG="$BACKGROUNDTYPE Mode > $1($(printf %.2f $rP)) $2($(printf %.2f $gP)) $3($(printf %.2f $bP))" 1389 | } 1390 | 1391 | # Converts 255-based RGB to Percentage 1392 | rgbToPercentage() { 1393 | local per=$(echo "scale=8; $1 / 255" | "$BCBIN") 1394 | printf '%.7f' "$per" # Print rounded conversion 1395 | } 1396 | 1397 | # Parse RGB Background color 1398 | parseRGBBackground() { 1399 | if isRGBInteger "$1" && isRGBInteger "$2" && isRGBInteger "$3" ; then 1400 | loadRGBVars "$1" "$2" "$3" 1401 | return $TRUE 1402 | fi 1403 | printError "Invalid RGB Background colors. Need 3 parameters in RGB order." 1404 | printError "Numbers must be RGB integers between 0 and 255." 1405 | printError "Eg: --background-rgb \"34 123 255\"" 1406 | printError " [R] => $1" 1407 | printError " [G] => $2" 1408 | printError " [B] => $3" 1409 | exit $EXIT_INVALID_OPTION 1410 | } 1411 | 1412 | 1413 | 1414 | 1415 | # Validades the a paper resize CLI option and sets the paper to $CROPBOX_PAPER_TYPE 1416 | parseCropbox() { 1417 | isEmpty "$1" && initError 'Invalid Cropbox: (empty)' $EXIT_INVALID_PAPER_SIZE 1418 | local lowercasePaper="$(lowercase $1)" 1419 | local customPaper=($lowercasePaper) 1420 | if [[ "$customPaper" = 'full' || "$customPaper" = 'fullsize' || "$customPaper" = 'mediabox' ]]; then 1421 | CROPBOX_PAPER_TYPE='fullsize' 1422 | elif [[ "${customPaper[0]}" = 'custom' ]]; then 1423 | if isNotValidMeasure "${customPaper[1]}" || ! isFloatBiggerThanZero "${customPaper[2]}" || ! isFloatBiggerThanZero "${customPaper[3]}"; then 1424 | initError "Invalid Custom Paper Definition!"$'\n'"Use: --cropbox 'custom '"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION 1425 | fi 1426 | CROPBOX_PAPER_TYPE="custom" 1427 | CUSTOM_CROPBOX_PAPER=$TRUE 1428 | if isMilimeter "${customPaper[1]}"; then 1429 | CROPBOX_WIDTH="$(millimetersToPoints "${customPaper[2]}")" 1430 | CROPBOX_HEIGHT="$(millimetersToPoints "${customPaper[3]}")" 1431 | elif isInch "${customPaper[1]}"; then 1432 | CROPBOX_WIDTH="$(inchesToPoints "${customPaper[2]}")" 1433 | CROPBOX_HEIGHT="$(inchesToPoints "${customPaper[3]}")" 1434 | elif isPoint "${customPaper[1]}"; then 1435 | CROPBOX_WIDTH="${customPaper[2]}" 1436 | CROPBOX_HEIGHT="${customPaper[3]}" 1437 | else 1438 | initError "Invalid Custom Paper Definition!"$'\n'"Use: --cropbox 'custom '"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION 1439 | fi 1440 | else 1441 | isPaperName "$lowercasePaper" || initError "Invalid Paper Type: $1" $EXIT_INVALID_PAPER_SIZE 1442 | CROPBOX_PAPER_TYPE="$lowercasePaper" 1443 | 1444 | fi 1445 | RESIZECOMMANDS='<> setpagedevice' 1446 | } 1447 | 1448 | 1449 | 1450 | ################## PDF PAGE COUNT DETECTION #################### 1451 | 1452 | # Gets page count from Ghostscript 1453 | getPageCount() { 1454 | PAGE_COUNT="$("$GSBIN" -dNOSAFER -dNODISPLAY -dBATCH -dQUIET -sFileName="$INFILEPDF" -c 'FileName (r) file runpdfbegin pdfpagecount = quit' 2>/dev/null)" 1455 | } 1456 | 1457 | # Gets page count and page sizes for all pages 1458 | getPageCountAndSizes() { 1459 | local _info="$("$GSBIN" -dNOSAFER -dNODISPLAY -dBATCH -dQUIET -sFileName="$INFILEPDF" -c 'FileName (r) file runpdfbegin pdfpagecount =print (\n) print 1 1 pdfpagecount {pdfgetpage /MediaBox get {=print ( ) print} forall (\n) print} for quit' 2>/dev/null)" 1460 | local t=0 1461 | local p=() 1462 | readarray -t _infoarr <<< "$_info" 1463 | PAGE_SIZES=() 1464 | for l in "${_infoarr[@]}" ; do 1465 | if [[ $t -eq 0 ]]; then 1466 | PAGE_COUNT=$l 1467 | else 1468 | p=($l) 1469 | PAGE_SIZES+=( "${p[2]} ${p[3]}" ) 1470 | fi 1471 | ((t++)) 1472 | done 1473 | } 1474 | 1475 | 1476 | 1477 | ################### PDF PAGE SIZE DETECTION #################### 1478 | 1479 | ################################################################ 1480 | # Detects operation mode and also runs the adaptive mode 1481 | # PAGESIZE LOGIC 1482 | # 1- Try to get Mediabox with GREP 1483 | # 2- MacOS => try to use mdls 1484 | # 3- Try to use pdfinfo 1485 | # 4- Try to use identify (imagemagick) 1486 | # 5- Fail 1487 | ################################################################ 1488 | getPageSize() { 1489 | if isNotAdaptiveMode; then 1490 | vprint " Get Page Size: Adaptive Disabled" 1491 | if [[ $MODE = "CATGREP" ]]; then 1492 | vprint " Method: Grep" 1493 | getPageSizeCatGrep 1494 | elif [[ $MODE = "MDLS" ]]; then 1495 | vprint " Method: Mac Quartz mdls" 1496 | getPageSizeMdls 1497 | elif [[ $MODE = "PDFINFO" ]]; then 1498 | vprint " Method: PDFInfo" 1499 | getPageSizePdfInfo 1500 | elif [[ $MODE = "IDENTIFY" ]]; then 1501 | vprint " Method: ImageMagick's Identify" 1502 | getPageSizeImagemagick 1503 | elif [[ $MODE = "GS" ]]; then 1504 | vprint " Method: Ghostscript PS Script" 1505 | getPageSizeGS 1506 | else 1507 | printError "Error! Invalid Mode: $MODE" 1508 | printError "Aborting execution..." 1509 | exit $EXIT_INVALID_OPTION 1510 | fi 1511 | return $TRUE 1512 | fi 1513 | 1514 | vprint " Get Page Size: Adaptive Enabled" 1515 | vprint " Method: Grep" 1516 | getPageSizeCatGrep 1517 | if pageSizeIsInvalid && [[ $OSNAME = "Darwin" ]]; then 1518 | vprint " Failed, trying next method" 1519 | vprint " Method: Mac Quartz mdls" 1520 | getPageSizeMdls 1521 | fi 1522 | 1523 | if pageSizeIsInvalid; then 1524 | vprint " Failed, trying next method" 1525 | vprint " Method: PDFInfo" 1526 | getPageSizePdfInfo 1527 | fi 1528 | 1529 | if pageSizeIsInvalid; then 1530 | vprint " Failed, trying next method" 1531 | vprint " Method: ImageMagick's Identify" 1532 | getPageSizeImagemagick 1533 | fi 1534 | 1535 | if pageSizeIsInvalid; then 1536 | vprint " Failed, trying next method" 1537 | vprint " Method: Ghostscript PS Script" 1538 | getPageSizeGS 1539 | fi 1540 | 1541 | if pageSizeIsInvalid; then 1542 | vprint " Failed all options" 1543 | printError "Error when detecting PDF paper size!" 1544 | printError "All methods of detection failed" 1545 | printError "You may want to install pdfinfo or imagemagick" 1546 | exit $EXIT_INVALID_PAGE_SIZE_DETECTED 1547 | fi 1548 | 1549 | return $TRUE 1550 | } 1551 | 1552 | # Gets page size using imagemagick's identify 1553 | getPageSizeImagemagick() { 1554 | # Sanity and Adaptive together 1555 | if isNotFile "$IDBIN" && isNotAdaptiveMode; then 1556 | notAdaptiveFailed "Make sure you installed ImageMagick and have identify on your \$PATH" "ImageMagick's Identify" 1557 | elif isNotFile "$IDBIN" && isAdaptiveMode; then 1558 | return $FALSE 1559 | fi 1560 | 1561 | # get data from image magick 1562 | local identify="$("$IDBIN" -format '%[fx:w] %[fx:h]BREAKME' "$INFILEPDF" 2>/dev/null)" 1563 | 1564 | if isEmpty "$identify" && isNotAdaptiveMode; then 1565 | notAdaptiveFailed "ImageMagicks's Identify returned an empty string!" 1566 | elif isEmpty "$identify" && isAdaptiveMode; then 1567 | return $FALSE 1568 | fi 1569 | 1570 | identify="${identify%%BREAKME*}" # get page size only for 1st page 1571 | identify=($identify) # make it an array 1572 | PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign 1573 | PGHEIGHT=$(printf '%.0f' "${identify[1]}") # assign 1574 | 1575 | return $TRUE 1576 | } 1577 | 1578 | # Gets page size using Mac Quarts mdls 1579 | getPageSizeMdls() { 1580 | # Sanity and Adaptive together 1581 | if isNotFile "$MDLSBIN" && isNotAdaptiveMode; then 1582 | notAdaptiveFailed "Are you even trying this on a Mac?" "Mac Quartz mdls" 1583 | elif isNotFile "$MDLSBIN" && isAdaptiveMode; then 1584 | return $FALSE 1585 | fi 1586 | 1587 | local identify="$("$MDLSBIN" -mdls -name kMDItemPageHeight -name kMDItemPageWidth "$INFILEPDF" 2>/dev/null)" 1588 | 1589 | if isEmpty "$identify" && isNotAdaptiveMode; then 1590 | notAdaptiveFailed "Mac Quartz mdls returned an empty string!" 1591 | elif isEmpty "$identify" && isAdaptiveMode; then 1592 | return $FALSE 1593 | fi 1594 | 1595 | identify=${identify//$'\t'/ } # change tab to space 1596 | identify=($identify) # make it an array 1597 | 1598 | if [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isNotAdaptiveMode; then 1599 | notAdaptiveFailed "There was no metadata to read from the file! Is Spotlight OFF?" 1600 | elif [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isAdaptiveMode; then 1601 | return $FALSE 1602 | fi 1603 | 1604 | PGWIDTH=$(printf '%.0f' "${identify[5]}") # assign 1605 | PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign 1606 | 1607 | return $TRUE 1608 | } 1609 | 1610 | # Gets page size using Linux PdfInfo 1611 | getPageSizePdfInfo() { 1612 | # Sanity and Adaptive together 1613 | if isNotFile "$PDFINFOBIN" && isNotAdaptiveMode; then 1614 | notAdaptiveFailed "Do you have pdfinfo installed and available on your \$PATH?" "Linux pdfinfo" 1615 | elif isNotFile "$PDFINFOBIN" && isAdaptiveMode; then 1616 | return $FALSE 1617 | fi 1618 | 1619 | # get data from image magick 1620 | local identify="$("$PDFINFOBIN" "$INFILEPDF" 2>/dev/null | "$GREPBIN" -i 'Page size:' )" 1621 | 1622 | if isEmpty "$identify" && isNotAdaptiveMode; then 1623 | notAdaptiveFailed "Linux PdfInfo returned an empty string!" 1624 | elif isEmpty "$identify" && isAdaptiveMode; then 1625 | return $FALSE 1626 | fi 1627 | 1628 | identify="${identify##*Page size:}" # remove stuff 1629 | identify=($identify) # make it an array 1630 | 1631 | PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign 1632 | PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign 1633 | 1634 | return $TRUE 1635 | } 1636 | 1637 | # Gets page size using Ghostscript and a PS script 1638 | getPageSizeGS() { 1639 | # Get MediaBox size using Ghostscript 1640 | local mediaBox="$("$GSBIN" -dNOSAFER -dNODISPLAY -dBATCH -dQUIET -sFileName="$INFILEPDF" -c 'FileName (r) file runpdfbegin 1 pdfgetpage /MediaBox get {=print ( ) print} forall' 2>/dev/null)" 1641 | 1642 | # No page size data available 1643 | if isEmpty "$mediaBox" && isNotAdaptiveMode; then 1644 | notAdaptiveFailed "There is no MediaBox in the pdf document!" 1645 | elif isEmpty "$mediaBox" && isAdaptiveMode; then 1646 | return $FALSE 1647 | fi 1648 | 1649 | mediaBox=($mediaBox) # make it an array 1650 | mbCount=${#mediaBox[@]} # array size 1651 | 1652 | # sanity 1653 | if [[ $mbCount -lt 4 ]] || ! isFloat "${mediaBox[2]}" || ! isFloat "${mediaBox[3]}" || isZero "${mediaBox[2]}" || isZero "${mediaBox[3]}"; then 1654 | if isNotAdaptiveMode; then 1655 | notAdaptiveFailed $'Error when reading the page size!\nThe page size information is invalid!' 1656 | fi 1657 | return $FALSE 1658 | fi 1659 | 1660 | # we are done 1661 | PGWIDTH=$(printf '%.0f' "${mediaBox[2]}") # Get Round Width 1662 | PGHEIGHT=$(printf '%.0f' "${mediaBox[3]}") # Get Round Height 1663 | 1664 | #echo "PGWIDTH=$PGWIDTH // PGHEIGHT=$PGHEIGHT" 1665 | return $TRUE 1666 | } 1667 | 1668 | # Gets page size using cat and grep 1669 | getPageSizeCatGrep() { 1670 | # get MediaBox info from PDF file using grep, these are all possible 1671 | # /MediaBox [0 0 595 841] 1672 | # /MediaBox [ 0 0 595.28 841.89] 1673 | # /MediaBox[ 0 0 595.28 841.89 ] 1674 | 1675 | # Get MediaBox data if possible 1676 | #local mediaBox="$("$GREPBIN" -a -e '/MediaBox' -m 1 "$INFILEPDF" 2>/dev/null)" 1677 | local mediaBox="$("$STRINGSBIN" "$INFILEPDF" | "$GREPBIN" -a -e '/MediaBox' -m 1 2>/dev/null)" 1678 | 1679 | mediaBox="${mediaBox##*/MediaBox}" 1680 | mediaBox="${mediaBox##*[}" 1681 | mediaBox="${mediaBox%%]*}" 1682 | #echo "mediaBox=$mediaBox" 1683 | 1684 | # No page size data available 1685 | if isEmpty "$mediaBox" && isNotAdaptiveMode; then 1686 | notAdaptiveFailed "There is no MediaBox in the pdf document!" 1687 | elif isEmpty "$mediaBox" && isAdaptiveMode; then 1688 | return $FALSE 1689 | fi 1690 | 1691 | mediaBox=($mediaBox) # make it an array 1692 | mbCount=${#mediaBox[@]} # array size 1693 | 1694 | # sanity 1695 | if [[ $mbCount -lt 4 ]] || ! isFloat "${mediaBox[2]}" || ! isFloat "${mediaBox[3]}" || isZero "${mediaBox[2]}" || isZero "${mediaBox[3]}"; then 1696 | if isNotAdaptiveMode; then 1697 | notAdaptiveFailed $'Error when reading the page size!\nThe page size information is invalid!' 1698 | fi 1699 | return $FALSE 1700 | fi 1701 | 1702 | # we are done 1703 | PGWIDTH=$(printf '%.0f' "${mediaBox[2]}") # Get Round Width 1704 | PGHEIGHT=$(printf '%.0f' "${mediaBox[3]}") # Get Round Height 1705 | 1706 | #echo "PGWIDTH=$PGWIDTH // PGHEIGHT=$PGHEIGHT" 1707 | return $TRUE 1708 | } 1709 | 1710 | isZero() { 1711 | [[ "$1" == "0" ]] && return $TRUE 1712 | [[ "$1" == "0.0" ]] && return $TRUE 1713 | [[ "$1" == "0.00" ]] && return $TRUE 1714 | [[ "$1" == "0.000" ]] && return $TRUE 1715 | return $FALSE 1716 | } 1717 | 1718 | # Prints error message and exits execution 1719 | notAdaptiveFailed() { 1720 | local errProgram="$2" 1721 | local errStr="$1" 1722 | if isEmpty "$2"; then 1723 | printError "Error when reading input file!" 1724 | printError "Could not determine the page size!" 1725 | else 1726 | printError "Error! $2 was not found!" 1727 | fi 1728 | isNotEmpty "$errStr" && printError "$errStr" 1729 | printError "Aborting! You may want to try the adaptive mode." 1730 | exit $EXIT_INVALID_PAGE_SIZE_DETECTED 1731 | } 1732 | 1733 | 1734 | 1735 | #################### GHOSTSCRIPT PAPER INFO #################### 1736 | 1737 | # Loads valid paper info to memory 1738 | getPaperInfo() { 1739 | # name inchesW inchesH mmW mmH pointsW pointsH 1740 | sizesUS="\ 1741 | 11x17 11.0 17.0 279 432 792 1224 1742 | ledger 17.0 11.0 432 279 1224 792 1743 | legal 8.5 14.0 216 356 612 1008 1744 | letter 8.5 11.0 216 279 612 792 1745 | lettersmall 8.5 11.0 216 279 612 792 1746 | arche 36.0 48.0 914 1219 2592 3456 1747 | archd 24.0 36.0 610 914 1728 2592 1748 | archc 18.0 24.0 457 610 1296 1728 1749 | archb 12.0 18.0 305 457 864 1296 1750 | archa 9.0 12.0 229 305 648 864" 1751 | 1752 | sizesISO="\ 1753 | a0 33.1 46.8 841 1189 2384 3370 1754 | a1 23.4 33.1 594 841 1684 2384 1755 | a2 16.5 23.4 420 594 1191 1684 1756 | a3 11.7 16.5 297 420 842 1191 1757 | a4 8.3 11.7 210 297 595 842 1758 | a4small 8.3 11.7 210 297 595 842 1759 | a5 5.8 8.3 148 210 420 595 1760 | a6 4.1 5.8 105 148 297 420 1761 | a7 2.9 4.1 74 105 210 297 1762 | a8 2.1 2.9 52 74 148 210 1763 | a9 1.5 2.1 37 52 105 148 1764 | a10 1.0 1.5 26 37 73 105 1765 | isob0 39.4 55.7 1000 1414 2835 4008 1766 | isob1 27.8 39.4 707 1000 2004 2835 1767 | isob2 19.7 27.8 500 707 1417 2004 1768 | isob3 13.9 19.7 353 500 1001 1417 1769 | isob4 9.8 13.9 250 353 709 1001 1770 | isob5 6.9 9.8 176 250 499 709 1771 | isob6 4.9 6.9 125 176 354 499 1772 | c0 36.1 51.1 917 1297 2599 3677 1773 | c1 25.5 36.1 648 917 1837 2599 1774 | c2 18.0 25.5 458 648 1298 1837 1775 | c3 12.8 18.0 324 458 918 1298 1776 | c4 9.0 12.8 229 324 649 918 1777 | c5 6.4 9.0 162 229 459 649 1778 | c6 4.5 6.4 114 162 323 459" 1779 | 1780 | sizesJIS="\ 1781 | jisb0 NA NA 1030 1456 2920 4127 1782 | jisb1 NA NA 728 1030 2064 2920 1783 | jisb2 NA NA 515 728 1460 2064 1784 | jisb3 NA NA 364 515 1032 1460 1785 | jisb4 NA NA 257 364 729 1032 1786 | jisb5 NA NA 182 257 516 729 1787 | jisb6 NA NA 128 182 363 516" 1788 | 1789 | sizesOther="\ 1790 | flsa 8.5 13.0 216 330 612 936 1791 | flse 8.5 13.0 216 330 612 936 1792 | halfletter 5.5 8.5 140 216 396 612 1793 | hagaki 3.9 5.8 100 148 283 420" 1794 | 1795 | sizesAll="\ 1796 | $sizesUS 1797 | $sizesISO 1798 | $sizesJIS 1799 | $sizesOther" 1800 | 1801 | } 1802 | 1803 | # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT 1804 | getGSPaperSize() { 1805 | isEmpty "$sizesall" && getPaperInfo 1806 | while read l; do 1807 | local cols=($l) 1808 | if [[ "$1" == ${cols[0]} ]]; then 1809 | RESIZE_WIDTH=${cols[5]} 1810 | RESIZE_HEIGHT=${cols[6]} 1811 | return $TRUE 1812 | fi 1813 | done <<< "$sizesAll" 1814 | } 1815 | 1816 | # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT 1817 | getCropboxPaperSize() { 1818 | isEmpty "$sizesall" && getPaperInfo 1819 | while read l; do 1820 | local cols=($l) 1821 | if [[ "$1" == ${cols[0]} ]]; then 1822 | CROPBOX_WIDTH=${cols[5]} 1823 | CROPBOX_HEIGHT=${cols[6]} 1824 | return $TRUE 1825 | fi 1826 | done <<< "$sizesAll" 1827 | } 1828 | 1829 | # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT 1830 | getGSPaperName() { 1831 | local w="$(printf "%.0f" $1)" 1832 | local h="$(printf "%.0f" $2)" 1833 | isEmpty "$sizesall" && getPaperInfo 1834 | # Because US Standard has inverted sizes, I need to scan 2 times 1835 | # instead of just testing if width is bigger than height 1836 | while read l; do 1837 | local cols=($l) 1838 | if [[ "$w" == ${cols[5]} && "$h" == ${cols[6]} ]]; then 1839 | printf "%s Portrait" $(uppercase ${cols[0]}) 1840 | return $TRUE 1841 | fi 1842 | done <<< "$sizesAll" 1843 | while read l; do 1844 | local cols=($l) 1845 | if [[ "$w" == ${cols[6]} && "$h" == ${cols[5]} ]]; then 1846 | printf "%s Landscape" $(uppercase ${cols[0]}) 1847 | return $TRUE 1848 | fi 1849 | done <<< "$sizesAll" 1850 | return $FALSE 1851 | } 1852 | 1853 | # Loads an array with paper names to memory 1854 | getPaperNames() { 1855 | paperNames=(a0 a1 a2 a3 a4 a4small a5 a6 a7 a8 a9 a10 isob0 isob1 isob2 isob3 isob4 isob5 isob6 c0 c1 c2 c3 c4 c5 c6 \ 1856 | 11x17 ledger legal letter lettersmall arche archd archc archb archa \ 1857 | jisb0 jisb1 jisb2 jisb3 jisb4 jisb5 jisb6 \ 1858 | flsa flse halfletter hagaki) 1859 | } 1860 | 1861 | # Prints uppercase paper names to screen (used in help) 1862 | printPaperNames() { 1863 | isEmpty "$paperNames" && getPaperNames 1864 | for i in "${!paperNames[@]}"; do 1865 | [[ $i -eq 0 ]] && echo -n -e ' ' 1866 | [[ $i -ne 0 && $((i % 5)) -eq 0 ]] && echo -n -e $'\n ' 1867 | ppN="$(uppercase ${paperNames[i]})" 1868 | printf "%-14s" "$ppN" 1869 | done 1870 | echo "" 1871 | } 1872 | 1873 | # Returns $TRUE if $! is a valid paper name, $FALSE otherwise 1874 | isPaperName() { 1875 | isEmpty "$1" && return $FALSE 1876 | isEmpty "$paperNames" && getPaperNames 1877 | for i in "${paperNames[@]}"; do 1878 | [[ "$i" = "$1" ]] && return $TRUE 1879 | done 1880 | return $FALSE 1881 | } 1882 | 1883 | # Prints all tables with ghostscript paper information 1884 | printPaperInfo() { 1885 | printVersion 3 1886 | echo $'\n'"Paper Sizes Information"$'\n' 1887 | getPaperInfo 1888 | printPaperTable "ISO STANDARD" "$sizesISO"; echo 1889 | printPaperTable "US STANDARD" "$sizesUS"; echo 1890 | printPaperTable "JIS STANDARD *Aproximated Points" "$sizesJIS"; echo 1891 | printPaperTable "OTHERS" "$sizesOther"; echo 1892 | } 1893 | 1894 | # GS paper table helper, prints a full line 1895 | printTableLine() { 1896 | echo '+-----------------------------------------------------------------+' 1897 | } 1898 | 1899 | # GS paper table helper, prints a line with dividers 1900 | printTableDivider() { 1901 | echo '+-----------------+-------+-------+-------+-------+-------+-------+' 1902 | } 1903 | 1904 | # GS paper table helper, prints a table header 1905 | printTableHeader() { 1906 | echo '| Name | inchW | inchH | mm W | mm H | pts W | pts H |' 1907 | } 1908 | 1909 | # GS paper table helper, prints a table title 1910 | printTableTitle() { 1911 | printf "| %-64s%s\n" "$1" '|' 1912 | } 1913 | 1914 | # GS paper table printer, prints a table for a paper variable 1915 | printPaperTable() { 1916 | printTableLine 1917 | printTableTitle "$1" 1918 | printTableLine 1919 | printTableHeader 1920 | printTableDivider 1921 | while read l; do 1922 | local cols=($l) 1923 | printf "| %-15s | %+5s | %+5s | %+5s | %+5s | %+5s | %+5s |\n" ${cols[*]}; 1924 | done <<< "$2" 1925 | printTableDivider 1926 | } 1927 | 1928 | # Returns $TRUE if $1 is a valid measurement for a custom paper, $FALSE otherwise 1929 | isNotValidMeasure() { 1930 | isMilimeter "$1" || isInch "$1" || isPoint "$1" && return $FALSE 1931 | return $TRUE 1932 | } 1933 | 1934 | # Returns $TRUE if $1 is a valid milimeter string, $FALSE otherwise 1935 | isMilimeter() { 1936 | [[ "$1" = 'mm' || "$1" = 'millimeters' || "$1" = 'milimeter' ]] && return $TRUE 1937 | return $FALSE 1938 | } 1939 | 1940 | # Returns $TRUE if $1 is a valid inch string, $FALSE otherwise 1941 | isInch() { 1942 | [[ "$1" = 'in' || "$1" = 'inch' || "$1" = 'inches' ]] && return $TRUE 1943 | return $FALSE 1944 | } 1945 | 1946 | # Returns $TRUE if $1 is a valid point string, $FALSE otherwise 1947 | isPoint() { 1948 | [[ "$1" = 'pt' || "$1" = 'pts' || "$1" = 'point' || "$1" = 'points' ]] && return $TRUE 1949 | return $FALSE 1950 | } 1951 | 1952 | # Returns $TRUE if a custom paper is being used, $FALSE otherwise 1953 | isCustomPaper() { 1954 | return $CUSTOM_RESIZE_PAPER 1955 | } 1956 | 1957 | # Returns $FALSE if a custom paper is being used, $TRUE otherwise 1958 | isNotCustomPaper() { 1959 | isCustomPaper && return $FALSE 1960 | return $TRUE 1961 | } 1962 | 1963 | # Returns $TRUE if a custom paper is being used, $FALSE otherwise 1964 | isCustomCropbox() { 1965 | return $CUSTOM_CROPBOX_PAPER 1966 | } 1967 | 1968 | # Returns $FALSE if a custom paper is being used, $TRUE otherwise 1969 | isNotCustomCropbox() { 1970 | isCustomCropbox && return $FALSE 1971 | return $TRUE 1972 | } 1973 | 1974 | 1975 | ######################### CONVERSIONS ########################## 1976 | 1977 | # Prints the lowercase char value for $1 1978 | lowercaseChar() { 1979 | case "$1" in 1980 | [A-Z]) 1981 | n=$(printf "%d" "'$1") 1982 | n=$((n+32)) 1983 | printf \\$(printf "%o" "$n") 1984 | ;; 1985 | *) 1986 | printf "%s" "$1" 1987 | ;; 1988 | esac 1989 | } 1990 | 1991 | # Prints the lowercase version of a string 1992 | lowercase() { 1993 | word="$@" 1994 | for((i=0;i<${#word};i++)) 1995 | do 1996 | ch="${word:$i:1}" 1997 | lowercaseChar "$ch" 1998 | done 1999 | } 2000 | 2001 | # Prints the uppercase char value for $1 2002 | uppercaseChar(){ 2003 | case "$1" in 2004 | [a-z]) 2005 | n=$(printf "%d" "'$1") 2006 | n=$((n-32)) 2007 | printf \\$(printf "%o" "$n") 2008 | ;; 2009 | *) 2010 | printf "%s" "$1" 2011 | ;; 2012 | esac 2013 | } 2014 | 2015 | # Prints the uppercase version of a string 2016 | uppercase() { 2017 | word="$@" 2018 | for((i=0;i<${#word};i++)) 2019 | do 2020 | ch="${word:$i:1}" 2021 | uppercaseChar "$ch" 2022 | done 2023 | } 2024 | 2025 | # Prints the postscript points rounded equivalent from $1 mm 2026 | millimetersToPoints() { 2027 | local pts=$(echo "scale=8; $1 * 72 / 25.4" | "$BCBIN") 2028 | printf '%.0f' "$pts" # Print rounded conversion 2029 | } 2030 | 2031 | # Prints the postscript points rounded equivalent from $1 inches 2032 | inchesToPoints() { 2033 | local pts=$(echo "scale=8; $1 * 72" | "$BCBIN") 2034 | printf '%.0f' "$pts" # Print rounded conversion 2035 | } 2036 | 2037 | # Prints the mm equivalent from $1 postscript points 2038 | pointsToMillimeters() { 2039 | local pts=$(echo "scale=8; $1 / 72 * 25.4" | "$BCBIN") 2040 | printf '%.0f' "$pts" # Print rounded conversion 2041 | } 2042 | 2043 | # Prints the inches equivalent from $1 postscript points 2044 | pointsToInches() { 2045 | local pts=$(echo "scale=8; $1 / 72" | "$BCBIN") 2046 | printf '%.1f' "$pts" # Print rounded conversion 2047 | } 2048 | 2049 | 2050 | ######################## MODE-DETECTION ######################## 2051 | 2052 | # Returns $TRUE if the scale was set manually, $FALSE if we are using automatic scaling 2053 | isManualScaledMode() { 2054 | [[ $AUTOMATIC_SCALING -eq $TRUE ]] && return $FALSE 2055 | return $TRUE 2056 | } 2057 | 2058 | # Returns true if we are resizing a paper (ignores scaling), false otherwise 2059 | isResizeMode() { 2060 | isEmpty $RESIZE_PAPER_TYPE && return $FALSE 2061 | return $TRUE 2062 | } 2063 | 2064 | # Returns true if we are reseting the cropboxes (ignores scaling), false otherwise 2065 | shouldSetCropbox() { 2066 | isEmpty $CROPBOX_PAPER_TYPE && return $FALSE 2067 | return $TRUE 2068 | } 2069 | 2070 | # Returns true if we are resizing a paper and the scale was manually set 2071 | isMixedMode() { 2072 | isResizeMode && isManualScaledMode && return $TRUE 2073 | return $FALSE 2074 | } 2075 | 2076 | # Return $TRUE if adaptive mode is enabled, $FALSE otherwise 2077 | isAdaptiveMode() { 2078 | return $ADAPTIVE_MODE 2079 | } 2080 | 2081 | # Return $TRUE if adaptive mode is disabled, $FALSE otherwise 2082 | isNotAdaptiveMode() { 2083 | isAdaptiveMode && return $FALSE 2084 | return $TRUE 2085 | } 2086 | 2087 | # Return $TRUE if explode mode is enabled, $FALSE otherwise 2088 | isExplodeMode() { 2089 | return $EXPLODE_MODE 2090 | } 2091 | 2092 | # Return $TRUE if explode mode is disabled, $FALSE otherwise 2093 | isNotExplodeMode() { 2094 | isExplodeMode && return $FALSE 2095 | return $TRUE 2096 | } 2097 | 2098 | # Return $TRUE if we should just print PDF info, $FALSE otherwise 2099 | isJustIdentify() { 2100 | return $JUST_IDENTIFY 2101 | } 2102 | 2103 | # Return $TRUE if we are not just printing PDF info, $FALSE otherwise 2104 | isNotJustIdentify() { 2105 | isJustIdentify && return $FALSE 2106 | return $TRUE 2107 | } 2108 | 2109 | 2110 | 2111 | ########################## VALIDATORS ########################## 2112 | 2113 | # Returns $TRUE if we don't need to create a background 2114 | noBackground() { 2115 | [[ "$BACKGROUNDTYPE" == "CMYK" ]] && return $FALSE 2116 | [[ "$BACKGROUNDTYPE" == "RGB" ]] && return $FALSE 2117 | [[ "$BACKGROUNDTYPE" == "GRAY" ]] && return $FALSE 2118 | return $TRUE 2119 | } 2120 | 2121 | # Returns $TRUE if we need to create a background 2122 | hasBackground() { 2123 | [[ "$BACKGROUNDTYPE" == "CMYK" ]] && return $TRUE 2124 | [[ "$BACKGROUNDTYPE" == "RGB" ]] && return $TRUE 2125 | [[ "$BACKGROUNDTYPE" == "GRAY" ]] && return $TRUE 2126 | return $FALSE 2127 | } 2128 | 2129 | # Returns $TRUE if $PGWIDTH OR $PGWIDTH are empty or NOT an Integer, $FALSE otherwise 2130 | pageSizeIsInvalid() { 2131 | if isNotAnInteger "$PGWIDTH" || isNotAnInteger "$PGHEIGHT"; then 2132 | return $TRUE 2133 | fi 2134 | return $FALSE 2135 | } 2136 | 2137 | # Return $TRUE if $1 is empty, $FALSE otherwise 2138 | isEmpty() { 2139 | [[ -z "$1" ]] && return $TRUE 2140 | return $FALSE 2141 | } 2142 | 2143 | # Return $TRUE if $1 is NOT empty, $FALSE otherwise 2144 | isNotEmpty() { 2145 | [[ -z "$1" ]] && return $FALSE 2146 | return $TRUE 2147 | } 2148 | 2149 | # Returns $TRUE if $1 is an integer, $FALSE otherwise 2150 | isAnInteger() { 2151 | case $1 in 2152 | ''|*[!0-9]*) return $FALSE ;; 2153 | *) return $TRUE ;; 2154 | esac 2155 | } 2156 | 2157 | # Returns $TRUE if $1 is NOT an integer, $FALSE otherwise 2158 | isNotAnInteger() { 2159 | case $1 in 2160 | ''|*[!0-9]*) return $TRUE ;; 2161 | *) return $FALSE ;; 2162 | esac 2163 | } 2164 | 2165 | # Returns $TRUE if $1 is an integer, $FALSE otherwise 2166 | isRGBInteger() { 2167 | isAnInteger "$1" && [[ $1 -ge 0 ]] && [[ $1 -le 255 ]] && return $TRUE 2168 | return $FALSE 2169 | } 2170 | 2171 | # Returns $TRUE if $1 is a floating point number (or an integer), $FALSE otherwise 2172 | isFloat() { 2173 | [[ -n "$1" && "$1" =~ ^-?[0-9]*([.][0-9]+)?$ ]] && return $TRUE 2174 | return $FALSE 2175 | } 2176 | 2177 | # Returns $TRUE if $1 is a floating point number bigger than zero, $FALSE otherwise 2178 | isFloatBiggerThanZero() { 2179 | isFloat "$1" && [[ "$1" =~ ^0*[1-9] || "$1" =~ ^0*[.]0*[1-9] ]] && return $TRUE 2180 | return $FALSE 2181 | } 2182 | 2183 | # Returns $TRUE if $1 is a floating point number between 0 and 1, $FALSE otherwise 2184 | isFloatPercentage() { 2185 | [[ -n "$1" && "$1" =~ ^-?[0]*([.][0-9]+)?$ ]] && return $TRUE 2186 | [[ "$1" == "1" ]] && return $TRUE 2187 | return $FALSE 2188 | } 2189 | 2190 | # Returns $TRUE if $1 is readable, $FALSE otherwise 2191 | isReadable() { 2192 | [[ -r "$1" ]] && return $TRUE 2193 | return $FALSE; 2194 | } 2195 | 2196 | # Returns $TRUE if $1 is a directory, $FALSE otherwise 2197 | isDir() { 2198 | [[ -d "$1" ]] && return $TRUE 2199 | return $FALSE; 2200 | } 2201 | 2202 | # Returns $FALSE if $1 is a directory, $TRUE otherwise 2203 | isNotDir() { 2204 | isDir "$1" && return $FALSE 2205 | return $TRUE; 2206 | } 2207 | 2208 | # Returns $TRUE if we could touch the file, $FALSE otherwise 2209 | isTouchable() { 2210 | touch "$1" 2>/dev/null && return $TRUE 2211 | return $FALSE 2212 | } 2213 | 2214 | # Returns $TRUE if we could NOT touch the file, $FALSE otherwise 2215 | isNotTouchable() { 2216 | isTouchable "$1" && return $FALSE 2217 | return $TRUE 2218 | } 2219 | 2220 | # Returns $TRUE if $1 has a .pdf extension, false otherwsie 2221 | isPDF() { 2222 | [[ "$(lowercase $1)" =~ ^..*\.pdf$ ]] && return $TRUE 2223 | return $FALSE 2224 | } 2225 | 2226 | # Returns $TRUE if $1 is a file, false otherwsie 2227 | isFile() { 2228 | [[ -f "$1" ]] && return $TRUE 2229 | return $FALSE 2230 | } 2231 | 2232 | # Returns $TRUE if $1 is NOT a file, false otherwsie 2233 | isNotFile() { 2234 | [[ -f "$1" ]] && return $FALSE 2235 | return $TRUE 2236 | } 2237 | 2238 | # Returns $TRUE if $1 is executable, false otherwsie 2239 | isExecutable() { 2240 | [[ -x "$1" ]] && return $TRUE 2241 | return $FALSE 2242 | } 2243 | 2244 | # Returns $TRUE if $1 is NOT executable, false otherwsie 2245 | isNotExecutable() { 2246 | [[ -x "$1" ]] && return $FALSE 2247 | return $TRUE 2248 | } 2249 | 2250 | # Returns $TRUE if $1 is a file and executable, false otherwsie 2251 | isAvailable() { 2252 | if isFile "$1" && isExecutable "$1"; then 2253 | return $TRUE 2254 | fi 2255 | return $FALSE 2256 | } 2257 | 2258 | # Returns $TRUE if $1 is NOT a file or NOT executable, false otherwsie 2259 | isNotAvailable() { 2260 | if isNotFile "$1" || isNotExecutable "$1"; then 2261 | return $TRUE 2262 | fi 2263 | return $FALSE 2264 | } 2265 | 2266 | # Returns $TRUE if we should avoid https certificate (on upgrade) 2267 | useInsecure() { 2268 | return $HTTPS_INSECURE 2269 | } 2270 | 2271 | # Returns $TRUE if we should not ask anything and assume yes as answer 2272 | assumeYes() { 2273 | return $ASSUME_YES 2274 | } 2275 | 2276 | # Returns $TRUE if we should ask the user for input 2277 | shouldAskUser() { 2278 | assumeYes && return $FALSE 2279 | return $TRUE 2280 | } 2281 | 2282 | 2283 | ###################### PRINTING TO SCREEN ###################### 2284 | 2285 | # Prints version 2286 | printVersion() { 2287 | local vStr="" 2288 | [[ "$2" = 'verbose' ]] && vStr=" - Verbose Execution" 2289 | local strBanner="$PDFSCALE_NAME v$VERSION$vStr" 2290 | if [[ $1 -eq 2 ]]; then 2291 | printError "$strBanner" 2292 | elif [[ $1 -eq 3 ]]; then 2293 | local extra="$(isNotEmpty "$2" && echo "$2")" 2294 | echo "$strBanner$extra" 2295 | else 2296 | vprint "$strBanner" 2297 | fi 2298 | } 2299 | 2300 | # Print page range verbose information 2301 | vPrintRange() { 2302 | local range="None (all pages)" 2303 | if isNotEmpty "$PAGE_RANGE"; then 2304 | local rangeP=(${PAGE_RANGE//=/ }) 2305 | range="${rangeP[1]}" 2306 | fi 2307 | vprint " Page Range: $range" 2308 | } 2309 | 2310 | # Verbose print of the Width and Height (Source or New) to screen 2311 | vPrintPageSizes() { 2312 | vprint " $1 Width: $PGWIDTH postscript-points" 2313 | vprint "$1 Height: $PGHEIGHT postscript-points" 2314 | } 2315 | 2316 | 2317 | # Prints input, output file info, if verbosing 2318 | vPrintFileInfo() { 2319 | vprint " Input File: $INFILEPDF" 2320 | vprint " Output File: $OUTFILEPDF" 2321 | } 2322 | 2323 | # Prints the scale factor to screen, or custom message 2324 | vPrintScaleFactor() { 2325 | local scaleMsg="$SCALE" 2326 | isNotEmpty "$1" && scaleMsg="$1" 2327 | vprint " Scale Factor: $scaleMsg" 2328 | } 2329 | 2330 | # Prints -dPrinted info to verbose log 2331 | vShowPrintMode() { 2332 | local pMode 2333 | pMode='Print ( -dPrinted )' 2334 | if [[ -z "$DPRINTED" ]]; then 2335 | pMode='Print ( auto/empty )' 2336 | elif [[ "$DPRINTED" = '-dPrinted=false' ]]; then 2337 | pMode='Screen ( -dPrinted=false )' 2338 | fi 2339 | vprint " Print Mode: $pMode" 2340 | } 2341 | 2342 | # Prints help info 2343 | printHelp() { 2344 | printVersion 3 2345 | #local paperList="$(printPaperNames)" 2346 | # echo " 2347 | printf "%s" " 2348 | Usage: $PDFSCALE_NAME 2349 | $PDFSCALE_NAME -i 2350 | $PDFSCALE_NAME [-v] [-s ] [-m ] [outfile.pdf] 2351 | $PDFSCALE_NAME [-v] [-r ] [-f ] [-a ] [outfile.pdf] 2352 | $PDFSCALE_NAME -p 2353 | $PDFSCALE_NAME -h 2354 | $PDFSCALE_NAME -V 2355 | 2356 | Parameters: 2357 | -v, --verbose 2358 | Verbose mode, prints extra information 2359 | Use twice for timestamp 2360 | -h, --help 2361 | Print this help to screen and exits 2362 | -V, --version 2363 | Prints version to screen and exits 2364 | --install, --self-install [target-path] 2365 | Install itself to [target-path] or /usr/local/bin/pdfscale if not specified 2366 | Should contain the full path with the desired executable name 2367 | --upgrade, --self-upgrade 2368 | Upgrades itself in-place (same path/name of the pdfScale.sh caller) 2369 | Downloads the master branch tarball and tries to self-upgrade 2370 | --insecure, --no-check-certificate 2371 | Use curl/wget without SSL library support 2372 | --yes, --assume-yes 2373 | Will answer yes to any prompt on install or upgrade, use with care 2374 | -n, --no-overwrite 2375 | Aborts execution if the output PDF file already exists 2376 | By default, the output file will be overwritten 2377 | Does NOT work if using --explode 2378 | -m, --mode 2379 | Paper size detection mode 2380 | Modes: a, adaptive Default mode, tries all the methods below 2381 | g, grep Forces the use of Grep method 2382 | m, mdls Forces the use of MacOS Quartz mdls 2383 | p, pdfinfo Forces the use of PDFInfo 2384 | i, identify Forces the use of ImageMagick's Identify 2385 | s, gs Forces the use of Ghostscript (PS script) 2386 | -i, --info 2387 | Prints Paper Size information to screen and exits 2388 | -e, --explode 2389 | Explode (split) outuput PDF into many files (one per page) 2390 | --range, --page-range 2391 | Defines the page range to be processed, using the -sPageList notation 2392 | Read below for more information on valid page ranges 2393 | -s, --scale 2394 | Changes the scaling factor or forces mixed mode 2395 | Defaults: $SCALE (scale mode) / Disabled (resize mode) 2396 | MUST be a number bigger than zero 2397 | Eg. -s 0.8 for 80% of the original size 2398 | -r, --resize 2399 | Triggers the Resize Paper Mode, disables auto-scaling of $SCALE 2400 | Resize PDF and fit-to-page 2401 | can be: source, custom or a valid std paper name, read below 2402 | -c, --cropbox 2403 | Resets Cropboxes on all pages to a specific paper size 2404 | Only applies to resize mode 2405 | can be: full | fullsize - Uses the same size as the main paper/mediabox 2406 | custom - Define a custom cropbox size in inches, mm or points 2407 | std paper name - Uses a paper size name (eg. a4, letter, etc) 2408 | -f, --flip-detect 2409 | Flip Detection Mode, defaults to 'auto' 2410 | Inverts Width <-> Height of a Resized PDF 2411 | Modes: a, auto Keeps source orientation, default 2412 | f, force Forces flip W <-> H 2413 | d, disable Disables flipping 2414 | -a, --auto-rotate 2415 | Setting for GS -dAutoRotatePages, defaults to 'PageByPage' 2416 | Uses text-orientation detection to set Portrait/Landscape 2417 | Modes: p, pagebypage Auto-rotates pages individually 2418 | n, none Retains orientation of each page 2419 | a, all Rotates all pages (or none) depending 2420 | on a kind of \"majority decision\" 2421 | --no-fit-to-page 2422 | Disables GS option dPDFFitPage (used when resizing) 2423 | --hor-align, --horizontal-alignment 2424 | Where to translate the scaled page 2425 | Default: center 2426 | Options: left, right, center 2427 | --vert-align, --vertical-alignment 2428 | Where to translate the scaled page 2429 | Default: center 2430 | Options: top, bottom, center 2431 | --xoffset, --xtrans-offset 2432 | Add/Subtract from the X translation (move left-right) 2433 | Default: 0.0 (zero) 2434 | Options: Positive or negative floating point number 2435 | --yoffset, --ytrans-offset 2436 | Add/Subtract from the Y translation (move top-bottom) 2437 | Default: 0.0 (zero) 2438 | Options: Positive or negative floating point number 2439 | --pdf-settings 2440 | Ghostscript PDF Profile to use in -dPDFSETTINGS 2441 | Default: printer 2442 | Options: screen, ebook, printer, prepress, default 2443 | --print-mode 2444 | Setting for GS -dPrinted, loads options for screen or printer 2445 | Defaults to nothing, which uses the print profile for files 2446 | The screen profile preserves URLs, but loses print annotations 2447 | Modes: s, screen Use screen options > '-dPrinted=false' 2448 | p, printer Use print options > '-dPrinted' 2449 | --image-downsample 2450 | Ghostscript Image Downsample Method 2451 | Default: bicubic 2452 | Options: subsample, average, bicubic 2453 | --image-resolution 2454 | Resolution in DPI of color and grayscale images in output 2455 | Default: 300 2456 | --background-gray 2457 | Creates a background with a gray color setting on PDF scaling 2458 | Percentage is a floating point percentage number between 0(black) and 1(white) 2459 | --background-cmyk <\"C M Y K\"> 2460 | Creates a background with a CMYK color setting on PDF scaling 2461 | Must be quoted into a single parameter as in \"0.2 0.2 0.2 0.2\" 2462 | Each color parameter is a floating point percentage number (between 0 and 1) 2463 | --background-rgb <\"R G B\"> 2464 | Creates a background with a RGB color setting on PDF scaling 2465 | Must be quoted into a single parameter as in \"100 100 200\" 2466 | RGB numbers are integers between 0 and 255 (255 122 50) 2467 | --newpdf Uses the -dNEWPDF flag in the GS Call (deprecated in new versions of GS) 2468 | --dry-run, --simulate 2469 | Just simulate execution. Will not run ghostscript 2470 | --print-gs-call, --gs-call 2471 | Print GS call to stdout. Will print at the very end between markers 2472 | -p, --print-papers 2473 | Prints Standard Paper info tables to screen and exits 2474 | 2475 | Scaling Mode: 2476 | - The default mode of operation is scaling mode with fixed paper 2477 | size and scaling pre-set to $SCALE 2478 | - By not using the resize mode you are using scaling mode 2479 | - Flip-Detection and Auto-Rotation are disabled in Scaling mode, 2480 | you can use '-r source -s ' to override. 2481 | - Ghostscript placement is from bottom-left position. This means that 2482 | a bottom-left placement has ZERO for both X and Y translations. 2483 | 2484 | Resize Paper Mode: 2485 | - Disables the default scaling factor! ($SCALE) 2486 | - Changes the PDF Paper Size in points. Will fit-to-page 2487 | 2488 | Mixed Mode: 2489 | - In mixed mode both the -s option and -r option must be specified 2490 | - The PDF will be first resized then scaled 2491 | 2492 | Page Ranges: 2493 | - Please refer to the Ghostscript manual on '-sPageList' for more info and examples. 2494 | - May cause execution warnings from Ghostscript if the PDF refences pages that were 2495 | removed. The output file should still be created, but with broken internal links. 2496 | - Using a range with an inexistant page will raise a warning from Ghostscript and 2497 | may also generate blank pages. 2498 | - Single page number | ex: --range 2 2499 | - Interval | ex: --range 2-4 2500 | - List of pages | ex: --range 1,3,6 2501 | - From page to end | ex: --range 3- 2502 | - odd/even specifier | ex: --range odd 2503 | - odd/even range | ex: --range even:1-4 2504 | - mixed entries | ex: --range 1,3-5,8- 2505 | 2506 | Output filename: 2507 | - Having the extension .pdf on the output file name is optional, 2508 | it will be added if not present. 2509 | - The output filename is optional. If no file name is passed 2510 | the output file will have the same name/destination of the 2511 | input file with added suffixes: 2512 | .SCALED.pdf is added to scaled files 2513 | ..pdf is added to resized files 2514 | ..SCALED.pdf is added in mixed mode 2515 | 2516 | Standard Paper Names: (case-insensitive) 2517 | A0 A1 A2 A3 A4 2518 | A4SMALL A5 A6 A7 A8 2519 | A9 A10 ISOB0 ISOB1 ISOB2 2520 | ISOB3 ISOB4 ISOB5 ISOB6 C0 2521 | C1 C2 C3 C4 C5 2522 | C6 11X17 LEDGER LEGAL LETTER 2523 | LETTERSMALL ARCHE ARCHD ARCHC ARCHB 2524 | ARCHA JISB0 JISB1 JISB2 JISB3 2525 | JISB4 JISB5 JISB6 FLSA FLSE 2526 | HALFLETTER HAGAKI 2527 | 2528 | Custom Paper Size: 2529 | - Paper size can be set manually in Millimeters, Inches or Points 2530 | - Custom paper definition MUST be quoted into a single parameter 2531 | - Actual size is applied in points (mms and inches are transformed) 2532 | - Measurements: mm, mms, millimeters 2533 | pt, pts, points 2534 | in, inch, inches 2535 | Use: $PDFSCALE_NAME -r 'custom ' 2536 | Ex: $PDFSCALE_NAME -r 'custom mm 300 300' 2537 | 2538 | Using Source Paper Size: (no-resizing) 2539 | - Wildcard 'source' is used to keep paper size the same as the input 2540 | - Useful to run Auto-Rotation without resizing 2541 | - Eg. $PDFSCALE_NAME -r source ./input.pdf 2542 | 2543 | Backgrounding: (paint a background) 2544 | - Backgrounding only happens when scaling 2545 | - Use a scale of 1.0 to force mixed mode and add background while resizing 2546 | 2547 | Options and Parameters Parsing: 2548 | - From v2.1.0 (long-opts) there is no need to pass file names at the end 2549 | - Anything that is not a short-option is case-insensitive 2550 | - Short-options: case-sensitive Eg. -v for Verbose, -V for Version 2551 | - Long-options: case-insensitive Eg. --SCALE and --scale are the same 2552 | - Subparameters: case-insensitive Eg. -m PdFinFo is valid 2553 | - Grouping short-options is not supported Eg. -vv, or -vs 0.9 2554 | 2555 | Additional Notes: 2556 | - File and folder names with spaces should be quoted or escaped 2557 | - Using a scale bigger than 1.0 may result on cropping parts of the PDF 2558 | - For detailed paper types information, use: $PDFSCALE_NAME -p 2559 | 2560 | Examples: 2561 | $PDFSCALE_NAME myPdfFile.pdf 2562 | $PDFSCALE_NAME -i '/home/My Folder/My PDF File.pdf' 2563 | $PDFSCALE_NAME myPdfFile.pdf \"My Scaled Pdf\" 2564 | $PDFSCALE_NAME -v -v myPdfFile.pdf 2565 | $PDFSCALE_NAME -s 0.85 myPdfFile.pdf My\\ Scaled\\ Pdf.pdf 2566 | $PDFSCALE_NAME -m pdfinfo -s 0.80 -v myPdfFile.pdf 2567 | $PDFSCALE_NAME -v -v -m i -s 0.7 myPdfFile.pdf 2568 | $PDFSCALE_NAME -r A4 myPdfFile.pdf 2569 | $PDFSCALE_NAME -v -v -r \"custom mm 252 356\" -s 0.9 -f \"../input file.pdf\" \"../my new pdf\" 2570 | " 2571 | } 2572 | 2573 | # Prints usage info 2574 | usage() { 2575 | [[ "$2" != 'nobanner' ]] && printVersion 2 2576 | isNotEmpty "$1" && printError $'\n'"$1" 2577 | printError $'\n'"Usage: $PDFSCALE_NAME [-v] [-s ] [-m ] [-r [-f ] [-a ]] [outfile.pdf]" 2578 | printError "Help : $PDFSCALE_NAME -h" 2579 | } 2580 | 2581 | # Prints Verbose information 2582 | vprint() { 2583 | [[ $VERBOSE -eq 0 ]] && return $TRUE 2584 | timestamp="" 2585 | [[ $VERBOSE -gt 1 ]] && timestamp="$(date +%Y-%m-%d:%H:%M:%S) | " 2586 | echo "$timestamp$1" 2587 | } 2588 | 2589 | # Prints dependency information and aborts execution 2590 | printDependency() { 2591 | printVersion 2 2592 | local brewName="$1" 2593 | [[ "$1" = 'pdfinfo' && "$OSNAME" = "Darwin" ]] && brewName="xpdf" 2594 | printError $'\n'"ERROR! You need to install the package '$1'"$'\n' 2595 | printError "Linux apt-get.: sudo apt-get install $1" 2596 | printError "Linux yum.....: sudo yum install $1" 2597 | printError "MacOS homebrew: brew install $brewName" 2598 | printError $'\n'"Aborting..." 2599 | exit $EXIT_MISSING_DEPENDENCY 2600 | } 2601 | 2602 | # Prints initialization errors and aborts execution 2603 | initError() { 2604 | local errStr="$1" 2605 | local exitStat=$2 2606 | isEmpty "$exitStat" && exitStat=$EXIT_ERROR 2607 | usage "ERROR! $errStr" "$3" 2608 | exit $exitStat 2609 | } 2610 | 2611 | # Prints to stderr 2612 | printError() { 2613 | echo >&2 "$@" 2614 | } 2615 | 2616 | 2617 | 2618 | ############################################################################## 2619 | ## REALPATH IMPLEMENTATION 2620 | ## https://github.com/mkropat/sh-realpath/blob/master/realpath.sh 2621 | 2622 | realpath() { 2623 | canonicalize_path "$(resolve_symlinks "$1")" 2624 | } 2625 | 2626 | resolve_symlinks() { 2627 | _resolve_symlinks "$1" 2628 | } 2629 | 2630 | _resolve_symlinks() { 2631 | _assert_no_path_cycles "$@" || return 2632 | 2633 | local dir_context path 2634 | path=$(readlink -- "$1") 2635 | if [ $? -eq 0 ]; then 2636 | dir_context=$(dirname -- "$1") 2637 | _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" 2638 | else 2639 | printf '%s\n' "$1" 2640 | fi 2641 | } 2642 | 2643 | _prepend_dir_context_if_necessary() { 2644 | if [ "$1" = . ]; then 2645 | printf '%s\n' "$2" 2646 | else 2647 | _prepend_path_if_relative "$1" "$2" 2648 | fi 2649 | } 2650 | 2651 | _prepend_path_if_relative() { 2652 | case "$2" in 2653 | /* ) printf '%s\n' "$2" ;; 2654 | * ) printf '%s\n' "$1/$2" ;; 2655 | esac 2656 | } 2657 | 2658 | _assert_no_path_cycles() { 2659 | local target path 2660 | 2661 | target=$1 2662 | shift 2663 | 2664 | for path in "$@"; do 2665 | if [ "$path" = "$target" ]; then 2666 | return 1 2667 | fi 2668 | done 2669 | } 2670 | 2671 | canonicalize_path() { 2672 | if [ -d "$1" ]; then 2673 | _canonicalize_dir_path "$1" 2674 | else 2675 | _canonicalize_file_path "$1" 2676 | fi 2677 | } 2678 | 2679 | _canonicalize_dir_path() { 2680 | (cd "$1" 2>/dev/null && pwd -P) 2681 | } 2682 | 2683 | _canonicalize_file_path() { 2684 | local dir file 2685 | dir=$(dirname -- "$1") 2686 | file=$(basename -- "$1") 2687 | (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") 2688 | } 2689 | 2690 | 2691 | ############################################################################## 2692 | ## READLINK -F IMPLEMENTATION 2693 | ## Running a native bash readlink -f substitute 2694 | 2695 | # Resolve symlinks 2696 | function tracelink() { 2697 | local link="$1" 2698 | while [ -L "$link" ]; do 2699 | lastLink="$link" 2700 | link=$(/bin/ls -ldq "$link") 2701 | link="${link##* -> }" 2702 | link=$(realpath "$link") 2703 | [ "$link" == "$lastlink" ] && echo -e "ERROR: link loop or inexistent target detected on $link" 1>&2 && break 2704 | done 2705 | echo -n "$link" 2706 | } 2707 | 2708 | # Traverse path 2709 | function abspath() { 2710 | pushd . > /dev/null; 2711 | if [ -d "$1" ]; then 2712 | cd "$1"; 2713 | dirs -l +0; 2714 | else 2715 | cd "`dirname \"$1\"`"; 2716 | cur_dir=`dirs -l +0`; 2717 | if [ "$cur_dir" == "/" ]; then 2718 | echo -n "$cur_dir`basename \"$1\"`"; 2719 | else 2720 | echo -n "$cur_dir/`basename \"$1\"`"; 2721 | fi; 2722 | fi; 2723 | popd > /dev/null; 2724 | } 2725 | 2726 | # Uses tracelink and abspath to emulate readlink -f 2727 | function readlinkf() { 2728 | echo -n "$(tracelink "$(abspath "$1")")" 2729 | } 2730 | 2731 | 2732 | 2733 | 2734 | 2735 | ########################## EXECUTION ########################### 2736 | 2737 | initDeps 2738 | getOptions "${@}" 2739 | main 2740 | exit $? 2741 | --------------------------------------------------------------------------------