Bash Script to scale and/or resize PDFs from the command line.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 

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