Bash Script to scale and/or resize PDFs from the command line.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 

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