Bash Script to scale and/or resize PDFs from the command line.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1008 line
31 KiB

  1. #!/usr/bin/env bash
  2. # pdfScale.sh
  3. #
  4. # Scale PDF to specified percentage of original size.
  5. #
  6. # Gustavo Arnosti Neves - 2016 / 07 / 10
  7. # Latest Version - 2017 / 05 / 14
  8. #
  9. # This script: https://github.com/tavinus/pdfScale
  10. # Based on: http://ma.juii.net/blog/scale-page-content-of-pdf-files
  11. # And: https://gist.github.com/MichaelJCole/86e4968dbfc13256228a
  12. ###################################################
  13. # PAGESIZE LOGIC
  14. # 1- Try to get Mediabox with CAT/GREP
  15. # 2- MacOS => try to use mdls
  16. # 3- Try to use pdfinfo
  17. # 4- Try to use identify (imagemagick)
  18. # 5- Fail
  19. ###################################################
  20. VERSION="2.0.0"
  21. SCALE="0.95" # scaling factor (0.95 = 95%, e.g.)
  22. VERBOSE=0 # verbosity Level
  23. PDFSCALE_NAME="$(basename $0)" # simplified name of this script
  24. # SET AND VALIDATED LATER
  25. GSBIN="" # GhostScript Binary
  26. BCBIN="" # BC Math Binary
  27. IDBIN="" # Identify Binary
  28. PDFINFOBIN="" # PDF Info Binary
  29. MDLSBIN="" # MacOS mdls Binary
  30. OSNAME="$(uname 2>/dev/null)" # Check where we are running
  31. LC_MEASUREMENT="C" # To make sure our numbers have .decimals
  32. LC_ALL="C" # Some languages use , as decimal token
  33. LC_CTYPE="C"
  34. LC_NUMERIC="C"
  35. TRUE=0 # Silly stuff
  36. FALSE=1
  37. ADAPTIVEMODE=$TRUE # Automatically try to guess best mode
  38. AUTOMATIC_SCALING=$TRUE # Default scaling in $SCALE, override by resize mode
  39. MODE=""
  40. RESIZE_PAPER_TYPE=""
  41. PGWIDTH=""
  42. PGHEIGHT=""
  43. RESIZE_WIDTH=""
  44. RESIZE_HEIGHT=""
  45. ### EXIT FLAGS
  46. EXIT_SUCCESS=0
  47. EXIT_ERROR=1
  48. EXIT_INVALID_PAGE_SIZE_DETECTED=10
  49. EXIT_FILE_NOT_FOUND=20
  50. EXIT_INPUT_NOT_PDF=21
  51. EXIT_INVALID_OPTION=22
  52. EXIT_NO_INPUT_FILE=23
  53. EXIT_INVALID_SCALE=24
  54. EXIT_MISSING_DEPENDENCY=25
  55. EXIT_IMAGEMAGIK_NOT_FOUND=26
  56. EXIT_MAC_MDLS_NOT_FOUND=27
  57. EXIT_PDFINFO_NOT_FOUND=28
  58. EXIT_INVALID_PAPER_SIZE=50
  59. ############################# MAIN #############################
  60. # Main function called at the end
  61. main() {
  62. printVersion 1 'verbose'
  63. loadDeps
  64. vprint " Input file: $INFILEPDF"
  65. vprint " Output file: $OUTFILEPDF"
  66. getPageSize
  67. if isMixedMode; then
  68. vprint " Mixed Tasks: Resize & Scale"
  69. vprint " Scale factor: $SCALE"
  70. vPrintSourcePageSizes ' Source'
  71. outputFile="$OUTFILEPDF" # backup outFile name
  72. tempFile="${OUTFILEPDF%.pdf}.__TEMP__.pdf" # set a temp file name
  73. OUTFILEPDF="$tempFile" # set output to tmp file
  74. pageResize # resize to tmp file
  75. INFILEPDF="$tempFile" # get tmp file as input
  76. OUTFILEPDF="$outputFile" # reset final target
  77. PGWIDTH=$RESIZE_WIDTH # we already know the new page size
  78. PGHEIGHT=$RESIZE_HEIGHT # from the last command (Resize)
  79. vPrintSourcePageSizes ' New'
  80. pageScale # scale the resized pdf
  81. # remove tmp file
  82. rm "$tempFile" >/dev/null 2>&1 || printError "Error when removing temporary file: $tempFile"
  83. elif isResizeMode; then
  84. vprint " Single Task: Resize PDF Paper"
  85. vprint " Scale factor: Disabled (resize only)"
  86. vPrintSourcePageSizes ' Source'
  87. pageResize
  88. else
  89. local scaleMode=""
  90. vprint " Single Task: Scale PDF Contents"
  91. isManualScaledMode && scaleMode='(manual)' || scaleMode='(auto)'
  92. vprint " Scale factor: $SCALE $scaleMode"
  93. vPrintSourcePageSizes ' Source'
  94. pageScale
  95. fi
  96. return $EXIT_SUCCESS
  97. }
  98. ########################## INITIALIZER ##########################
  99. # Loads external dependencies and checks for errors
  100. loadDeps() {
  101. GSBIN="$(which gs 2>/dev/null)"
  102. BCBIN="$(which bc 2>/dev/null)"
  103. IDBIN=$(which identify 2>/dev/null)
  104. MDLSBIN="$(which mdls 2>/dev/null)"
  105. PDFINFOBIN="$(which pdfinfo 2>/dev/null)"
  106. vprint "Checking for ghostscript and bcmath"
  107. if notIsAvailable "$GSBIN"; then printDependency 'ghostscript'; fi
  108. if notIsAvailable "$BCBIN"; then printDependency 'bc'; fi
  109. if [[ $MODE = "IDENTIFY" ]]; then
  110. vprint "Checking for imagemagick's identify"
  111. if notIsAvailable "$IDBIN"; then printDependency 'imagemagick'; fi
  112. fi
  113. if [[ $MODE = "PDFINFO" ]]; then
  114. vprint "Checking for pdfinfo"
  115. if notIsAvailable "$PDFINFOBIN"; then printDependency 'pdfinfo'; fi
  116. fi
  117. if [[ $MODE = "MDLS" ]]; then
  118. vprint "Checking for MacOS mdls"
  119. if notIsAvailable "$MDLSBIN"; then
  120. initError 'mdls executable was not found! Is this even MacOS?' $EXIT_MAC_MDLS_NOT_FOUND 'nobanner'
  121. fi
  122. fi
  123. }
  124. ######################### CLI OPTIONS ##########################
  125. # Parse options
  126. getOptions() {
  127. while getopts ":vhVs:m:r:p" o; do
  128. case "${o}" in
  129. v)
  130. ((VERBOSE++))
  131. ;;
  132. h)
  133. printHelp
  134. exit $EXIT_SUCCESS
  135. ;;
  136. V)
  137. printVersion
  138. exit $EXIT_SUCCESS
  139. ;;
  140. s)
  141. parseScale ${OPTARG}
  142. ;;
  143. m)
  144. parseMode ${OPTARG}
  145. ;;
  146. r)
  147. parsePaperResize ${OPTARG}
  148. ;;
  149. p)
  150. printPaperInfo
  151. exit $EXIT_SUCCESS
  152. ;;
  153. *)
  154. initError "Invalid Option: -$OPTARG" $EXIT_INVALID_OPTION
  155. ;;
  156. esac
  157. done
  158. shift $((OPTIND-1))
  159. # Validate input PDF file
  160. INFILEPDF="$1"
  161. isEmpty "$INFILEPDF" && initError "Input file is empty!" $EXIT_NO_INPUT_FILE
  162. isPDF "$INFILEPDF" || initError "Input file is not a PDF file: $INFILEPDF" $EXIT_INPUT_NOT_PDF
  163. isFile "$INFILEPDF" || initError "Input file not found: $INFILEPDF" $EXIT_FILE_NOT_FOUND
  164. if isEmpty "$2"; then
  165. if isMixedMode; then
  166. OUTFILEPDF="${INFILEPDF%.pdf}.$(uppercase $RESIZE_PAPER_TYPE).SCALED.pdf"
  167. elif isResizeMode; then
  168. OUTFILEPDF="${INFILEPDF%.pdf}.$(uppercase $RESIZE_PAPER_TYPE).pdf"
  169. else
  170. OUTFILEPDF="${INFILEPDF%.pdf}.SCALED.pdf"
  171. fi
  172. else
  173. OUTFILEPDF="${2%.pdf}.pdf"
  174. fi
  175. }
  176. # Parses and validates the scaling factor
  177. parseScale() {
  178. AUTOMATIC_SCALING=$FALSE
  179. if ! [[ -n "$1" && "$1" =~ ^-?[0-9]*([.][0-9]+)?$ && (($1 > 0 )) ]] ; then
  180. printError "Invalid factor: $1"
  181. printError "The factor must be a floating point number greater than 0"
  182. printError "Example: for 80% use 0.8"
  183. exit $EXIT_INVALID_SCALE
  184. fi
  185. SCALE=$1
  186. }
  187. ###################### GHOSTSCRIPT CALLS #######################
  188. # Runs the ghostscript scaling script
  189. pageScale() {
  190. # Compute translation factors (to center page).
  191. XTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGWIDTH" | "$BCBIN")
  192. YTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGHEIGHT" | "$BCBIN")
  193. vprint " Translation X: $XTRANS"
  194. vprint " Translation Y: $YTRANS"
  195. # Do it.
  196. "$GSBIN" \
  197. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  198. -dCompatibilityLevel="1.5" -dPDFSETTINGS="/printer" \
  199. -dColorConversionStrategy=/LeaveColorUnchanged \
  200. -dSubsetFonts=true -dEmbedAllFonts=true \
  201. -dDEVICEWIDTHPOINTS=$PGWIDTH -dDEVICEHEIGHTPOINTS=$PGHEIGHT \
  202. -sOutputFile="$OUTFILEPDF" \
  203. -c "<</BeginPage{$SCALE $SCALE scale $XTRANS $YTRANS translate}>> setpagedevice" \
  204. -f "$INFILEPDF" &
  205. wait ${!}
  206. }
  207. # Runs the ghostscript paper resize script
  208. pageResize() {
  209. getGSPaperSize "$RESIZE_PAPER_TYPE"
  210. local tmpInverter=""
  211. if [[ $PGWIDTH -gt $PGHEIGHT && $RESIZE_WIDTH -lt $RESIZE_HEIGHT ]]; then
  212. vprint " Flip Detect: Wrong orientation!"
  213. vprint " Inverting Width <-> Height"
  214. tmpInverter=$RESIZE_HEIGHT
  215. RESIZE_HEIGHT=$RESIZE_WIDTH
  216. RESIZE_WIDTH=$tmpInverter
  217. fi
  218. vprint " Resizing to: $(uppercase $RESIZE_PAPER_TYPE) ( $RESIZE_WIDTH x $RESIZE_HEIGHT )"
  219. # Change page size
  220. "$GSBIN" \
  221. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  222. -dCompatibilityLevel="1.5" -dPDFSETTINGS="/printer" \
  223. -dColorConversionStrategy=/LeaveColorUnchanged \
  224. -dSubsetFonts=true -dEmbedAllFonts=true \
  225. -dDEVICEWIDTHPOINTS=$RESIZE_WIDTH -dDEVICEHEIGHTPOINTS=$RESIZE_HEIGHT \
  226. -dFIXEDMEDIA -dPDFFitPage \
  227. -sOutputFile="$OUTFILEPDF" \
  228. -f "$INFILEPDF" &
  229. wait ${!}
  230. }
  231. ################### PDF PAGE SIZE DETECTION ####################
  232. # Parse a forced mode of operation
  233. parseMode() {
  234. if isEmpty "$1"; then
  235. printError "Mode is empty, please specify the desired mode"
  236. printError "Falling back to adaptive mode!"
  237. ADAPTIVEMODE=$TRUE
  238. MODE=""
  239. return $FALSE
  240. fi
  241. if [[ $1 = 'c' || $1 = 'catgrep' || $1 = 'cat+grep' || $1 = 'CatGrep' || $1 = 'C' || $1 = 'CATGREP' ]]; then
  242. ADAPTIVEMODE=$FALSE
  243. MODE="CATGREP"
  244. return $TRUE
  245. elif [[ $1 = 'i' || $1 = 'imagemagick' || $1 = 'identify' || $1 = 'ImageMagick' || $1 = 'Identify' || $1 = 'I' || $1 = 'IDENTIFY' ]]; then
  246. ADAPTIVEMODE=$FALSE
  247. MODE="IDENTIFY"
  248. return $TRUE
  249. elif [[ $1 = 'm' || $1 = 'mdls' || $1 = 'MDLS' || $1 = 'quartz' || $1 = 'mac' || $1 = 'M' ]]; then
  250. ADAPTIVEMODE=$FALSE
  251. MODE="MDLS"
  252. return $TRUE
  253. elif [[ $1 = 'p' || $1 = 'pdfinfo' || $1 = 'PDFINFO' || $1 = 'PdfInfo' || $1 = 'P' ]]; then
  254. ADAPTIVEMODE=$FALSE
  255. MODE="PDFINFO"
  256. return $TRUE
  257. elif [[ $1 = 'a' || $1 = 'adaptive' || $1 = 'automatic' || $1 = 'A' || $1 = 'ADAPTIVE' || $1 = 'AUTOMATIC' ]]; then
  258. ADAPTIVEMODE=$TRUE
  259. MODE=""
  260. return $TRUE
  261. else
  262. printError "Invalid mode: $1"
  263. printError "Falling back to adaptive mode!"
  264. ADAPTIVEMODE=$TRUE
  265. MODE=""
  266. return $FALSE
  267. fi
  268. return $FALSE
  269. }
  270. # Gets page size using imagemagick's identify
  271. getPageSizeImagemagick() {
  272. # Sanity and Adaptive together
  273. if notIsFile "$IDBIN" && isNotAdaptiveMode; then
  274. notAdaptiveFailed "Make sure you installed ImageMagick and have identify on your \$PATH" "ImageMagick's Identify"
  275. elif notIsFile "$IDBIN" && isAdaptiveMode; then
  276. return $FALSE
  277. fi
  278. # get data from image magick
  279. local identify="$("$IDBIN" -format '%[fx:w] %[fx:h]BREAKME' "$INFILEPDF" 2>/dev/null)"
  280. if isEmpty "$identify" && isNotAdaptiveMode; then
  281. notAdaptiveFailed "ImageMagicks's Identify returned an empty string!"
  282. elif isEmpty "$identify" && isAdaptiveMode; then
  283. return $FALSE
  284. fi
  285. identify="${identify%%BREAKME*}" # get page size only for 1st page
  286. identify=($identify) # make it an array
  287. PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign
  288. PGHEIGHT=$(printf '%.0f' "${identify[1]}") # assign
  289. return $TRUE
  290. }
  291. # Gets page size using Mac Quarts mdls
  292. getPageSizeMdls() {
  293. # Sanity and Adaptive together
  294. if notIsFile "$MDLSBIN" && isNotAdaptiveMode; then
  295. notAdaptiveFailed "Are you even trying this on a Mac?" "Mac Quartz mdls"
  296. elif notIsFile "$MDLSBIN" && isAdaptiveMode; then
  297. return $FALSE
  298. fi
  299. local identify="$("$MDLSBIN" -mdls -name kMDItemPageHeight -name kMDItemPageWidth "$INFILEPDF" 2>/dev/null)"
  300. if isEmpty "$identify" && isNotAdaptiveMode; then
  301. notAdaptiveFailed "Mac Quartz mdls returned an empty string!"
  302. elif isEmpty "$identify" && isAdaptiveMode; then
  303. return $FALSE
  304. fi
  305. identify=${identify//$'\t'/ } # change tab to space
  306. identify=($identify) # make it an array
  307. if [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isNotAdaptiveMode; then
  308. notAdaptiveFailed "There was no metadata to read from the file! Is Spotlight OFF?"
  309. elif [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isAdaptiveMode; then
  310. return $FALSE
  311. fi
  312. PGWIDTH=$(printf '%.0f' "${identify[5]}") # assign
  313. PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign
  314. return $TRUE
  315. }
  316. # Gets page size using Linux PdfInfo
  317. getPageSizePdfInfo() {
  318. # Sanity and Adaptive together
  319. if notIsFile "$PDFINFOBIN" && isNotAdaptiveMode; then
  320. notAdaptiveFailed "Do you have pdfinfo installed and available on your \$PATH?" "Linux pdfinfo"
  321. elif notIsFile "$PDFINFOBIN" && isAdaptiveMode; then
  322. return $FALSE
  323. fi
  324. # get data from image magick
  325. local identify="$("$PDFINFOBIN" "$INFILEPDF" 2>/dev/null | grep -i 'Page size:' )"
  326. if isEmpty "$identify" && isNotAdaptiveMode; then
  327. notAdaptiveFailed "Linux PdfInfo returned an empty string!"
  328. elif isEmpty "$identify" && isAdaptiveMode; then
  329. return $FALSE
  330. fi
  331. identify="${identify##*Page size:}" # remove stuff
  332. identify=($identify) # make it an array
  333. PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign
  334. PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign
  335. return $TRUE
  336. }
  337. # Gets page size using cat and grep
  338. getPageSizeCatGrep() {
  339. # get MediaBox info from PDF file using cat and grep, these are all possible
  340. # /MediaBox [0 0 595 841]
  341. # /MediaBox [ 0 0 595.28 841.89]
  342. # /MediaBox[ 0 0 595.28 841.89 ]
  343. # Get MediaBox data if possible
  344. local mediaBox="$(cat "$INFILEPDF" | grep -a '/MediaBox' | head -n1)"
  345. mediaBox="${mediaBox##*/MediaBox}"
  346. # No page size data available
  347. if isEmpty "$mediaBox" && isNotAdaptiveMode; then
  348. notAdaptiveFailed "There is no MediaBox in the pdf document!"
  349. elif isEmpty "$mediaBox" && isAdaptiveMode; then
  350. return $FALSE
  351. fi
  352. # remove chars [ and ]
  353. mediaBox="${mediaBox//[}"
  354. mediaBox="${mediaBox//]}"
  355. mediaBox=($mediaBox) # make it an array
  356. mbCount=${#mediaBox[@]} # array size
  357. # sanity
  358. if [[ $mbCount -lt 4 ]]; then
  359. printError "Error when reading the page size!"
  360. printError "The page size information is invalid!"
  361. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  362. fi
  363. # we are done
  364. PGWIDTH=$(printf '%.0f' "${mediaBox[2]}") # Get Round Width
  365. PGHEIGHT=$(printf '%.0f' "${mediaBox[3]}") # Get Round Height
  366. return $TRUE
  367. }
  368. # Detects operation mode and also runs the adaptive mode
  369. getPageSize() {
  370. if isNotAdaptiveMode; then
  371. vprint " Get Page Size: Adaptive Disabled"
  372. if [[ $MODE = "CATGREP" ]]; then
  373. vprint " Method: Cat + Grep"
  374. getPageSizeCatGrep
  375. elif [[ $MODE = "MDLS" ]]; then
  376. vprint " Method: Mac Quartz mdls"
  377. getPageSizeMdls
  378. elif [[ $MODE = "PDFINFO" ]]; then
  379. vprint " Method: PDFInfo"
  380. getPageSizePdfInfo
  381. elif [[ $MODE = "IDENTIFY" ]]; then
  382. vprint " Method: ImageMagick's Identify"
  383. getPageSizeImagemagick
  384. else
  385. printError "Error! Invalid Mode: $MODE"
  386. printError "Aborting execution..."
  387. exit $EXIT_INVALID_OPTION
  388. fi
  389. return $TRUE
  390. fi
  391. vprint " Get Page Size: Adaptive Enabled"
  392. vprint " Method: Cat + Grep"
  393. getPageSizeCatGrep
  394. if pageSizeIsInvalid && [[ $OSNAME = "Darwin" ]]; then
  395. vprint " Failed"
  396. vprint " Method: Mac Quartz mdls"
  397. getPageSizeMdls
  398. fi
  399. if pageSizeIsInvalid; then
  400. vprint " Failed"
  401. vprint " Method: PDFInfo"
  402. getPageSizePdfInfo
  403. fi
  404. if pageSizeIsInvalid; then
  405. vprint " Failed"
  406. vprint " Method: ImageMagick's Identify"
  407. getPageSizeImagemagick
  408. fi
  409. if pageSizeIsInvalid; then
  410. vprint " Failed"
  411. printError "Error when detecting PDF paper size!"
  412. printError "All methods of detection failed"
  413. printError "You may want to install pdfinfo or imagemagick"
  414. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  415. fi
  416. return $TRUE
  417. }
  418. # Prints error message and exits execution
  419. notAdaptiveFailed() {
  420. local errProgram="$2"
  421. local errStr="$1"
  422. if isEmpty "$2"; then
  423. printError "Error when reading input file!"
  424. printError "Could not determine the page size!"
  425. else
  426. printError "Error! $2 was not found!"
  427. fi
  428. isNotEmpty "$errStr" && printError "$errStr"
  429. printError "Aborting! You may want to try the adaptive mode."
  430. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  431. }
  432. # Verbose print of the Width and Height (Source or New) to screen
  433. vPrintSourcePageSizes() {
  434. vprint " $1 Width: $PGWIDTH postscript-points"
  435. vprint "$1 Height: $PGHEIGHT postscript-points"
  436. }
  437. #################### GHOSTSCRIPT PAPER INFO ####################
  438. # Loads GS paper info to memory
  439. getPaperInfo() {
  440. # name inchesW inchesH mmW mmH pointsW pointsH
  441. sizesUS="\
  442. 11x17 11.0 17.0 279 432 792 1224
  443. ledger 17.0 11.0 432 279 1224 792
  444. legal 8.5 14.0 216 356 612 1008
  445. letter 8.5 11.0 216 279 612 792
  446. lettersmall 8.5 11.0 216 279 612 792
  447. archE 36.0 48.0 914 1219 2592 3456
  448. archD 24.0 36.0 610 914 1728 2592
  449. archC 18.0 24.0 457 610 1296 1728
  450. archB 12.0 18.0 305 457 864 1296
  451. archA 9.0 12.0 229 305 648 864"
  452. sizesISO="\
  453. a0 33.1 46.8 841 1189 2384 3370
  454. a1 23.4 33.1 594 841 1684 2384
  455. a2 16.5 23.4 420 594 1191 1684
  456. a3 11.7 16.5 297 420 842 1191
  457. a4 8.3 11.7 210 297 595 842
  458. a4small 8.3 11.7 210 297 595 842
  459. a5 5.8 8.3 148 210 420 595
  460. a6 4.1 5.8 105 148 297 420
  461. a7 2.9 4.1 74 105 210 297
  462. a8 2.1 2.9 52 74 148 210
  463. a9 1.5 2.1 37 52 105 148
  464. a10 1.0 1.5 26 37 73 105
  465. isob0 39.4 55.7 1000 1414 2835 4008
  466. isob1 27.8 39.4 707 1000 2004 2835
  467. isob2 19.7 27.8 500 707 1417 2004
  468. isob3 13.9 19.7 353 500 1001 1417
  469. isob4 9.8 13.9 250 353 709 1001
  470. isob5 6.9 9.8 176 250 499 709
  471. isob6 4.9 6.9 125 176 354 499
  472. c0 36.1 51.1 917 1297 2599 3677
  473. c1 25.5 36.1 648 917 1837 2599
  474. c2 18.0 25.5 458 648 1298 1837
  475. c3 12.8 18.0 324 458 918 1298
  476. c4 9.0 12.8 229 324 649 918
  477. c5 6.4 9.0 162 229 459 649
  478. c6 4.5 6.4 114 162 323 459"
  479. sizesJIS="\
  480. jisb0 NA NA 1030 1456 2920 4127
  481. jisb1 NA NA 728 1030 2064 2920
  482. jisb2 NA NA 515 728 1460 2064
  483. jisb3 NA NA 364 515 1032 1460
  484. jisb4 NA NA 257 364 729 1032
  485. jisb5 NA NA 182 257 516 729
  486. jisb6 NA NA 128 182 363 516"
  487. sizesOther="\
  488. flsa 8.5 13.0 216 330 612 936
  489. flse 8.5 13.0 216 330 612 936
  490. halfletter 5.5 8.5 140 216 396 612
  491. hagaki 3.9 5.8 100 148 283 420"
  492. sizesAll="\
  493. $sizesUS
  494. $sizesISO
  495. $sizesJIS
  496. $sizesOther"
  497. }
  498. # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT
  499. getGSPaperSize() {
  500. isEmpty "$sizesall" && getPaperInfo
  501. while read l; do
  502. local cols=($l)
  503. if [[ "$1" == ${cols[0]} ]]; then
  504. RESIZE_WIDTH=${cols[5]}
  505. RESIZE_HEIGHT=${cols[6]}
  506. return $TRUE
  507. fi
  508. done <<< "$sizesAll"
  509. }
  510. # Loads an array with paper names to memory
  511. getPaperNames() {
  512. 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 \
  513. 11x17 ledger legal letter lettersmall archE archD archC archB archA \
  514. jisb0 jisb1 jisb2 jisb3 jisb4 jisb5 jisb6 \
  515. flsa flse halfletter hagaki)
  516. }
  517. # Prints uppercase paper names to screen (used in help)
  518. printPaperNames() {
  519. isEmpty "$paperNames" && getPaperNames
  520. for i in "${!paperNames[@]}"; do
  521. [[ $i -ne 0 && $((i % 5)) -eq 0 ]] && echo ""
  522. ppN="$(uppercase ${paperNames[i]})"
  523. printf "%-14s" "$ppN"
  524. done
  525. echo ""
  526. }
  527. # Returns $TRUE if $! is a valid paper name, $FALSE otherwise
  528. isPaperName() {
  529. isEmpty "$1" && return $FALSE
  530. isEmpty "$paperNames" && getPaperNames
  531. for i in "${paperNames[@]}"; do
  532. [[ "$i" = "$1" ]] && return $TRUE
  533. done
  534. return $FALSE
  535. }
  536. # Prints all tables with ghostscript paper information
  537. printPaperInfo() {
  538. printVersion
  539. echo $'\n'"Valid Ghostscript Paper Sizes accepted"$'\n'
  540. getPaperInfo
  541. printPaperTable "ISO STANDARD" "$sizesISO"; echo
  542. printPaperTable "US STANDARD" "$sizesUS"; echo
  543. printPaperTable "JIS STANDARD *Aproximated Points" "$sizesJIS"; echo
  544. printPaperTable "OTHERS" "$sizesOther"; echo
  545. }
  546. # GS paper table helper, prints a full line
  547. printTableLine() {
  548. echo '+-----------------------------------------------------------------+'
  549. }
  550. # GS paper table helper, prints a line with dividers
  551. printTableDivider() {
  552. echo '+-----------------+-------+-------+-------+-------+-------+-------+'
  553. }
  554. # GS paper table helper, prints a table header
  555. printTableHeader() {
  556. echo '| Name | inchW | inchH | mm W | mm H | pts W | pts H |'
  557. }
  558. # GS paper table helper, prints a table title
  559. printTableTitle() {
  560. printf "| %-64s%s\n" "$1" '|'
  561. }
  562. # GS paper table printer, prints a table for a paper variable
  563. printPaperTable() {
  564. printTableLine
  565. printTableTitle "$1"
  566. printTableLine
  567. printTableHeader
  568. printTableDivider
  569. while read l; do
  570. local cols=($l)
  571. printf "| %-15s | %+5s | %+5s | %+5s | %+5s | %+5s | %+5s |\n" ${cols[*]};
  572. done <<< "$2"
  573. printTableDivider
  574. }
  575. # Validades the a paper resize CLI option and sets the paper to $RESIZE_PAPER_TYPE
  576. parsePaperResize() {
  577. isEmpty "$1" && initError 'Invalid Paper Type: (empty)' $EXIT_INVALID_PAPER_SIZE
  578. local lowercasePaper="$(lowercase $1)"
  579. isPaperName "$lowercasePaper" || initError "Invalid Paper Type: $1" $EXIT_INVALID_PAPER_SIZE
  580. RESIZE_PAPER_TYPE="$lowercasePaper"
  581. }
  582. # Returns $TRUE if the scale was set manually, $FALSE if we are using automatic scaling
  583. isManualScaledMode() {
  584. [[ $AUTOMATIC_SCALING -eq $TRUE ]] && return $FALSE
  585. return $TRUE
  586. }
  587. # Returns true if we are resizing a paper (ignores scaling), false otherwise
  588. isResizeMode() {
  589. isEmpty $RESIZE_PAPER_TYPE && return $FALSE
  590. return $TRUE
  591. }
  592. # Returns true if we are resizing a paper and the scale was manually set
  593. isMixedMode() {
  594. isResizeMode && isManualScaledMode && return $TRUE
  595. return $FALSE
  596. }
  597. # Prints the lowercase char value for $1
  598. lowercaseChar() {
  599. case "$1" in
  600. [A-Z])
  601. n=$(printf "%d" "'$1")
  602. n=$((n+32))
  603. printf \\$(printf "%o" "$n")
  604. ;;
  605. *)
  606. printf "%s" "$1"
  607. ;;
  608. esac
  609. }
  610. # Prints the lowercase version of a string
  611. lowercase() {
  612. word="$@"
  613. for((i=0;i<${#word};i++))
  614. do
  615. ch="${word:$i:1}"
  616. lowercaseChar "$ch"
  617. done
  618. }
  619. # Prints the uppercase char value for $1
  620. uppercaseChar(){
  621. case "$1" in
  622. [a-z])
  623. n=$(printf "%d" "'$1")
  624. n=$((n-32))
  625. printf \\$(printf "%o" "$n")
  626. ;;
  627. *)
  628. printf "%s" "$1"
  629. ;;
  630. esac
  631. }
  632. # Prints the uppercase version of a string
  633. uppercase() {
  634. word="$@"
  635. for((i=0;i<${#word};i++))
  636. do
  637. ch="${word:$i:1}"
  638. uppercaseChar "$ch"
  639. done
  640. }
  641. ########################## VALIDATORS ##########################
  642. # Returns $TRUE if $PGWIDTH OR $PGWIDTH are empty or NOT an Integer, false otherwise
  643. pageSizeIsInvalid() {
  644. if isNotAnInteger "$PGWIDTH" || isNotAnInteger "$PGHEIGHT"; then
  645. return $TRUE
  646. fi
  647. return $FALSE
  648. }
  649. # Return $TRUE if adaptive mode is enabled, false otherwise
  650. isAdaptiveMode() {
  651. return $ADAPTIVEMODE
  652. }
  653. # Return $TRUE if adaptive mode is disabled, false otherwise
  654. isNotAdaptiveMode() {
  655. isAdaptiveMode && return $FALSE
  656. return $TRUE
  657. }
  658. # Return $TRUE if $1 is empty, false otherwise
  659. isEmpty() {
  660. [[ -z "$1" ]] && return $TRUE
  661. return $FALSE
  662. }
  663. # Return $TRUE if $1 is NOT empty, false otherwise
  664. isNotEmpty() {
  665. [[ -z "$1" ]] && return $FALSE
  666. return $TRUE
  667. }
  668. isAnInteger() {
  669. case $1 in
  670. ''|*[!0-9]*) return $FALSE ;;
  671. *) return $TRUE ;;
  672. esac
  673. }
  674. isNotAnInteger() {
  675. case $1 in
  676. ''|*[!0-9]*) return $TRUE ;;
  677. *) return $FALSE ;;
  678. esac
  679. }
  680. # Returns $TRUE if $1 has a .pdf extension, false otherwsie
  681. isPDF() {
  682. [[ "$1" =~ ^..*\.pdf$ ]] && return $TRUE
  683. return $FALSE
  684. }
  685. # Returns $TRUE if $1 is a file, false otherwsie
  686. isFile() {
  687. [[ -f "$1" ]] && return $TRUE
  688. return $FALSE
  689. }
  690. # Returns $TRUE if $1 is NOT a file, false otherwsie
  691. notIsFile() {
  692. [[ -f "$1" ]] && return $FALSE
  693. return $TRUE
  694. }
  695. # Returns $TRUE if $1 is executable, false otherwsie
  696. isExecutable() {
  697. [[ -x "$1" ]] && return $TRUE
  698. return $FALSE
  699. }
  700. # Returns $TRUE if $1 is NOT executable, false otherwsie
  701. notIsExecutable() {
  702. [[ -x "$1" ]] && return $FALSE
  703. return $TRUE
  704. }
  705. # Returns $TRUE if $1 is a file and executable, false otherwsie
  706. isAvailable() {
  707. if isFile "$1" && isExecutable "$1"; then
  708. return $TRUE
  709. fi
  710. return $FALSE
  711. }
  712. # Returns $TRUE if $1 is NOT a file or NOT executable, false otherwsie
  713. notIsAvailable() {
  714. if notIsFile "$1" || notIsExecutable "$1"; then
  715. return $TRUE
  716. fi
  717. return $FALSE
  718. }
  719. ###################### PRINTING TO SCREEN ######################
  720. # Prints version
  721. printVersion() {
  722. local vStr=""
  723. [[ "$2" = 'verbose' ]] && vStr=" - Verbose Execution"
  724. if [[ $1 -eq 2 ]]; then
  725. printError "$PDFSCALE_NAME v$VERSION$vStr"
  726. else
  727. echo "$PDFSCALE_NAME v$VERSION$vStr"
  728. fi
  729. }
  730. # Prints help info
  731. printHelp() {
  732. printVersion
  733. local paperList="$(printPaperNames)"
  734. echo "
  735. Usage: $PDFSCALE_NAME [-v] [-s <factor>] [-m <mode>] [-r <paper>] <inFile.pdf> [outfile.pdf]
  736. $PDFSCALE_NAME -p
  737. $PDFSCALE_NAME -h
  738. $PDFSCALE_NAME -V
  739. Parameters:
  740. -v Verbose mode, prints extra information
  741. Use twice for timestamp
  742. -h Print this help to screen and exits
  743. -V Prints version to screen and exits
  744. -m <mode> Page size Detection mode
  745. May disable the Adaptive Mode
  746. -s <factor> Changes the scaling factor or forces scaling
  747. Defaults: $SCALE / no scaling (resize mode)
  748. MUST be a number bigger than zero
  749. Eg. -s 0.8 for 80% of the original size
  750. -r <paper> Triggers the Resize Paper Mode
  751. Resize PDF paper proportionally
  752. Must be a valid Ghostscript paper name
  753. -p Prints Ghostscript paper info tables to screen
  754. Scaling Mode:
  755. The default mode of operation is scaling mode with fixed paper
  756. size and scaling pre-set to $SCALE. By not using the resize mode
  757. you are using scaling mode.
  758. Resize Paper Mode:
  759. Disables the default scaling factor! ($SCALE)
  760. Alternative mode of operation to change the PDF paper
  761. proportionally. Will fit-to-page.
  762. Mixed Mode:
  763. In mixed mode both the -s option and -r option must be specified.
  764. The PDF will be both scaled and have the paper type changed.
  765. Output filename:
  766. The output filename is optional. If no file name is passed
  767. the output file will have the same name/destination of the
  768. input file with added suffixes:
  769. .SCALED.pdf is added to scaled files
  770. .<PAPERSIZE>.pdf is added to resized files
  771. .<PAPERSIZE>.SCALED.pdf is added in mixed mode
  772. Page Detection Modes:
  773. a, adaptive Default mode, tries all the methods below
  774. c, cat+grep Forces the use of the cat + grep method
  775. m, mdls Forces the use of MacOS Quartz mdls
  776. p, pdfinfo Forces the use of PDFInfo
  777. i, identify Forces the use of ImageMagick's Identify
  778. Valid Ghostscript Paper Names:
  779. $paperList
  780. Notes:
  781. - Adaptive Page size detection will try different modes until
  782. it gets a page size. You can force a mode with -m 'mode'.
  783. - Options must be passed before the file names to be parsed.
  784. - Having the extension .pdf on the output file name is optional,
  785. it will be added if not present.
  786. - File and folder names with spaces should be quoted or escaped.
  787. - The scaling is centered and using a scale bigger than 1 may
  788. result on cropping parts of the pdf.
  789. Examples:
  790. $PDFSCALE_NAME myPdfFile.pdf
  791. $PDFSCALE_NAME myPdfFile.pdf myScaledPdf
  792. $PDFSCALE_NAME -v -v myPdfFile.pdf
  793. $PDFSCALE_NAME -s 0.85 myPdfFile.pdf myScaledPdf.pdf
  794. $PDFSCALE_NAME -m pdfinfo -s 0.80 -v myPdfFile.pdf
  795. $PDFSCALE_NAME -v -v -m i -s 0.7 myPdfFile.pdf
  796. $PDFSCALE_NAME -h
  797. "
  798. }
  799. # Prints usage info
  800. usage() {
  801. [[ "$2" != 'nobanner' ]] && printVersion 2
  802. isNotEmpty "$1" && printError "$1"
  803. printError "Usage: $PDFSCALE_NAME [-v] [-s <factor>] [-m <mode>] <inFile.pdf> [outfile.pdf]"
  804. printError "Try: $PDFSCALE_NAME -h # for help"
  805. }
  806. # Prints Verbose information
  807. vprint() {
  808. [[ $VERBOSE -eq 0 ]] && return $TRUE
  809. timestamp=""
  810. [[ $VERBOSE -gt 1 ]] && timestamp="$(date +%Y-%m-%d:%H:%M:%S) | "
  811. echo "$timestamp$1"
  812. }
  813. # Prints dependency information and aborts execution
  814. printDependency() {
  815. #printVersion 2
  816. local brewName="$1"
  817. [[ "$1" = 'pdfinfo' && "$OSNAME" = "Darwin" ]] && brewName="xpdf"
  818. printError $'\n'"ERROR! You need to install the package '$1'"$'\n'
  819. printError "Linux apt-get.: sudo apt-get install $1"
  820. printError "Linux yum.....: sudo yum install $1"
  821. printError "MacOS homebrew: brew install $brewName"
  822. printError $'\n'"Aborting..."
  823. exit $EXIT_MISSING_DEPENDENCY
  824. }
  825. # Prints initialization errors and aborts execution
  826. initError() {
  827. local errStr="$1"
  828. local exitStat=$2
  829. isEmpty "$exitStat" && exitStat=$EXIT_ERROR
  830. usage "ERROR! $errStr" "$3"
  831. exit $exitStat
  832. }
  833. # Prints to stderr
  834. printError() {
  835. echo >&2 "$@"
  836. }
  837. ########################## EXECUTION ###########################
  838. getOptions "${@}"
  839. main
  840. exit $?