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.
 
 

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