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.
 
 

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