Bash Script to scale and/or resize PDFs from the command line.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 

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