Bash Script to scale and/or resize PDFs from the command line.
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

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