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.
 
 

1649 lines
58 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 - 2018 / 04 / 01
  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. VERSION="2.2.0"
  13. ###################### EXTERNAL PROGRAMS #######################
  14. GSBIN="" # GhostScript Binary
  15. BCBIN="" # BC Math Binary
  16. IDBIN="" # Identify Binary
  17. PDFINFOBIN="" # PDF Info Binary
  18. MDLSBIN="" # MacOS mdls Binary
  19. ##################### ENVIRONMENT SET-UP #######################
  20. LC_MEASUREMENT="C" # To make sure our numbers have .decimals
  21. LC_ALL="C" # Some languages use , as decimal token
  22. LC_CTYPE="C"
  23. LC_NUMERIC="C"
  24. TRUE=0 # Silly stuff
  25. FALSE=1
  26. ########################### GLOBALS ############################
  27. SCALE="0.95" # scaling factor (0.95 = 95%, e.g.)
  28. VERBOSE=0 # verbosity Level
  29. PDFSCALE_NAME="$(basename $0)" # simplified name of this script
  30. OSNAME="$(uname 2>/dev/null)" # Check where we are running
  31. GS_RUN_STATUS="" # Holds GS error messages, signals errors
  32. INFILEPDF="" # Input PDF file name
  33. OUTFILEPDF="" # Output PDF file name
  34. JUST_IDENTIFY=$FALSE # Flag to just show PDF info
  35. ABORT_ON_OVERWRITE=$FALSE # Flag to abort if OUTFILEPDF already exists
  36. ADAPTIVEMODE=$TRUE # Automatically try to guess best mode
  37. AUTOMATIC_SCALING=$TRUE # Default scaling in $SCALE, disabled in resize mode
  38. MODE="" # Which page size detection to use
  39. RESIZE_PAPER_TYPE="" # Pre-defined paper to use
  40. CUSTOM_RESIZE_PAPER=$FALSE # If we are using a custom-defined paper
  41. FLIP_DETECTION=$TRUE # If we should run the Flip-detection
  42. FLIP_FORCE=$FALSE # If we should force Flipping
  43. AUTO_ROTATION='/PageByPage' # GS call auto-rotation setting
  44. PGWIDTH="" # Input PDF Page Width
  45. PGHEIGHT="" # Input PDF Page Height
  46. RESIZE_WIDTH="" # Resized PDF Page Width
  47. RESIZE_HEIGHT="" # Resized PDF Page Height
  48. ############################# Image resolution (dpi)
  49. IMAGE_RESOLUTION=300 # 300 is /Printer default
  50. ############################# Image compression setting
  51. # default screen ebook printer prepress
  52. # ColorImageDownsampleType /Subsample /Average /Bicubic /Bicubic /Bicubic
  53. IMAGE_DOWNSAMPLE_TYPE='/Bicubic'
  54. ############################# default PDF profile
  55. # /screen /ebook /printer /prepress /default
  56. # -dPDFSETTINGS=/screen (screen-view-only quality, 72 dpi images)
  57. # -dPDFSETTINGS=/ebook (low quality, 150 dpi images)
  58. # -dPDFSETTINGS=/printer (high quality, 300 dpi images)
  59. # -dPDFSETTINGS=/prepress (high quality, color preserving, 300 dpi imgs)
  60. # -dPDFSETTINGS=/default (almost identical to /screen)
  61. PDF_SETTINGS='/printer'
  62. ############################# default Scaling alignment
  63. VERT_ALIGN="CENTER"
  64. HOR_ALIGN="CENTER"
  65. ############################# Translation Offset to apply
  66. XTRANSOFFSET=0.0
  67. YTRANSOFFSET=0.0
  68. ########################## EXIT FLAGS ##########################
  69. EXIT_SUCCESS=0
  70. EXIT_ERROR=1
  71. EXIT_INVALID_PAGE_SIZE_DETECTED=10
  72. EXIT_FILE_NOT_FOUND=20
  73. EXIT_INPUT_NOT_PDF=21
  74. EXIT_INVALID_OPTION=22
  75. EXIT_NO_INPUT_FILE=23
  76. EXIT_INVALID_SCALE=24
  77. EXIT_MISSING_DEPENDENCY=25
  78. EXIT_IMAGEMAGIK_NOT_FOUND=26
  79. EXIT_MAC_MDLS_NOT_FOUND=27
  80. EXIT_PDFINFO_NOT_FOUND=28
  81. EXIT_NOWRITE_PERMISSION=29
  82. EXIT_NOREAD_PERMISSION=30
  83. EXIT_TEMP_FILE_EXISTS=40
  84. EXIT_INVALID_PAPER_SIZE=50
  85. EXIT_INVALID_IMAGE_RESOLUTION=51
  86. ############################# MAIN #############################
  87. # Main function called at the end
  88. main() {
  89. printPDFSizes # may exit here
  90. local finalRet=$EXIT_ERROR
  91. if isMixedMode; then
  92. initMain " Mixed Tasks: Resize & Scale"
  93. local tempFile=""
  94. local tempSuffix="$RANDOM$RANDOM""_TEMP_$RANDOM$RANDOM.pdf"
  95. outputFile="$OUTFILEPDF" # backup outFile name
  96. tempFile="${OUTFILEPDF%.pdf}.$tempSuffix" # set a temp file name
  97. if isFile "$tempFile"; then
  98. printError $'Error! Temporary file name already exists!\n'"File: $tempFile"$'\nAborting execution to avoid overwriting the file.\nPlease Try again...'
  99. exit $EXIT_TEMP_FILE_EXISTS
  100. fi
  101. OUTFILEPDF="$tempFile" # set output to tmp file
  102. pageResize # resize to tmp file
  103. finalRet=$?
  104. INFILEPDF="$tempFile" # get tmp file as input
  105. OUTFILEPDF="$outputFile" # reset final target
  106. PGWIDTH=$RESIZE_WIDTH # we already know the new page size
  107. PGHEIGHT=$RESIZE_HEIGHT # from the last command (Resize)
  108. vPrintPageSizes ' New'
  109. vPrintScaleFactor
  110. pageScale # scale the resized pdf
  111. finalRet=$(($finalRet+$?))
  112. # remove tmp file
  113. isFile "$tempFile" && rm "$tempFile" >/dev/null 2>&1 || printError "Error when removing temporary file: $tempFile"
  114. elif isResizeMode; then
  115. initMain " Single Task: Resize PDF Paper"
  116. vPrintScaleFactor "Disabled (resize only)"
  117. pageResize
  118. finalRet=$?
  119. else
  120. initMain " Single Task: Scale PDF Contents"
  121. local scaleMode=""
  122. isManualScaledMode && scaleMode='(manual)' || scaleMode='(auto)'
  123. vPrintScaleFactor "$SCALE $scaleMode"
  124. pageScale
  125. finalRet=$?
  126. fi
  127. if [[ $finalRet -eq $EXIT_SUCCESS ]] && isEmpty "$GS_RUN_STATUS"; then
  128. vprint " Final Status: File created successfully"
  129. else
  130. vprint " Final Status: Error detected. Exit status: $finalRet"
  131. printError "PdfScale: ERROR!"$'\n'"Ghostscript Debug Info:"$'\n'"$GS_RUN_STATUS"
  132. fi
  133. return $finalRet
  134. }
  135. # Initializes PDF processing for all modes of operation
  136. initMain() {
  137. printVersion 1 'verbose'
  138. isNotEmpty "$1" && vprint "$1"
  139. vPrintFileInfo
  140. getPageSize
  141. vPrintPageSizes ' Source'
  142. }
  143. # Prints PDF Info and exits with $EXIT_SUCCESS, but only if $JUST_IDENTIFY is $TRUE
  144. printPDFSizes() {
  145. if [[ $JUST_IDENTIFY -eq $TRUE ]]; then
  146. VERBOSE=0
  147. printVersion 3 " - Paper Sizes"
  148. getPageSize || initError "Could not get pagesize!"
  149. local paperType="$(getGSPaperName $PGWIDTH $PGHEIGHT)"
  150. isEmpty "$paperType" && paperType="Custom Paper Size"
  151. printf '%s\n' "------------+-----------------------------"
  152. printf " File | %s\n" "$(basename "$INFILEPDF")"
  153. printf " Paper Type | %s\n" "$paperType"
  154. printf '%s\n' "------------+-----------------------------"
  155. printf '%s\n' " | WIDTH x HEIGHT"
  156. printf " Points | %+8s x %-8s\n" "$PGWIDTH" "$PGHEIGHT"
  157. printf " Milimeters | %+8s x %-8s\n" "$(pointsToMilimeters $PGWIDTH)" "$(pointsToMilimeters $PGHEIGHT)"
  158. printf " Inches | %+8s x %-8s\n" "$(pointsToInches $PGWIDTH)" "$(pointsToInches $PGHEIGHT)"
  159. exit $EXIT_SUCCESS
  160. fi
  161. return $EXIT_SUCCESS
  162. }
  163. ###################### GHOSTSCRIPT CALLS #######################
  164. # Runs the ghostscript scaling script
  165. pageScale() {
  166. # Compute translation factors to position pages
  167. CENTERXTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGWIDTH" | "$BCBIN")
  168. CENTERYTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGHEIGHT" | "$BCBIN")
  169. BXTRANS=$CENTERXTRANS
  170. BYTRANS=$CENTERYTRANS
  171. if [[ "$VERT_ALIGN" = "TOP" ]]; then
  172. BYTRANS=$(echo "scale=6; 2*$CENTERYTRANS" | "$BCBIN")
  173. elif [[ "$VERT_ALIGN" = "BOTTOM" ]]; then
  174. BYTRANS=0
  175. fi
  176. if [[ "$HOR_ALIGN" = "LEFT" ]]; then
  177. BXTRANS=0
  178. elif [[ "$HOR_ALIGN" = "RIGHT" ]]; then
  179. BXTRANS=$(echo "scale=6; 2*$CENTERXTRANS" | "$BCBIN")
  180. fi
  181. vprint " Vert-Align: $VERT_ALIGN"
  182. vprint " Hor-Align: $HOR_ALIGN"
  183. XTRANS=$(echo "scale=6; $BXTRANS + $XTRANSOFFSET" | "$BCBIN")
  184. YTRANS=$(echo "scale=6; $BYTRANS + $YTRANSOFFSET" | "$BCBIN")
  185. vprint "$(printf ' Translation X: %.2f = %.2f + %.2f (offset)' $XTRANS $BXTRANS $XTRANSOFFSET)"
  186. vprint "$(printf ' Translation Y: %.2f = %.2f + %.2f (offset)' $YTRANS $BYTRANS $YTRANSOFFSET)"
  187. local increase=$(echo "scale=0; (($SCALE - 1) * 100)/1" | "$BCBIN")
  188. vprint " Run Scaling: $increase %"
  189. GS_RUN_STATUS="$GS_RUN_STATUS""$(gsPageScale 2>&1)"
  190. return $? # Last command is always returned I think
  191. }
  192. # Runs GS call for scaling, nothing else should run here
  193. gsPageScale() {
  194. # Scale page
  195. "$GSBIN" \
  196. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  197. -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \
  198. -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \
  199. -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \
  200. -dColorConversionStrategy=/LeaveColorUnchanged \
  201. -dSubsetFonts=true -dEmbedAllFonts=true \
  202. -dDEVICEWIDTHPOINTS=$PGWIDTH -dDEVICEHEIGHTPOINTS=$PGHEIGHT \
  203. -sOutputFile="$OUTFILEPDF" \
  204. -c "<</BeginPage{$SCALE $SCALE scale $XTRANS $YTRANS translate}>> setpagedevice" \
  205. -f "$INFILEPDF"
  206. return $?
  207. }
  208. # Runs the ghostscript paper resize script
  209. pageResize() {
  210. # Get paper sizes from source if not resizing
  211. isResizePaperSource && { RESIZE_WIDTH=$PGWIDTH; RESIZE_HEIGHT=$PGHEIGHT; }
  212. # Get new paper sizes if not custom or source paper
  213. isNotCustomPaper && ! isResizePaperSource && getGSPaperSize "$RESIZE_PAPER_TYPE"
  214. vprint " Auto Rotate: $(basename $AUTO_ROTATION)"
  215. runFlipDetect
  216. vprint " Run Resizing: $(uppercase "$RESIZE_PAPER_TYPE") ( "$RESIZE_WIDTH" x "$RESIZE_HEIGHT" ) pts"
  217. GS_RUN_STATUS="$GS_RUN_STATUS""$(gsPageResize 2>&1)"
  218. return $?
  219. }
  220. # Runs GS call for resizing, nothing else should run here
  221. gsPageResize() {
  222. # Change page size
  223. "$GSBIN" \
  224. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  225. -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \
  226. -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \
  227. -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \
  228. -dColorConversionStrategy=/LeaveColorUnchanged \
  229. -dSubsetFonts=true -dEmbedAllFonts=true \
  230. -dDEVICEWIDTHPOINTS=$RESIZE_WIDTH -dDEVICEHEIGHTPOINTS=$RESIZE_HEIGHT \
  231. -dAutoRotatePages=$AUTO_ROTATION \
  232. -dFIXEDMEDIA -dPDFFitPage \
  233. -sOutputFile="$OUTFILEPDF" \
  234. -f "$INFILEPDF"
  235. return $?
  236. }
  237. # Returns $TRUE if we should use the source paper size, $FALSE otherwise
  238. isResizePaperSource() {
  239. [[ "$RESIZE_PAPER_TYPE" = 'source' ]] && return $TRUE
  240. return $FALSE
  241. }
  242. # Filp-Detect Logic
  243. runFlipDetect() {
  244. if isFlipForced; then
  245. vprint " Flip Detect: Forced Mode!"
  246. applyFlipRevert
  247. elif isFlipDetectionEnabled && shouldFlip; then
  248. vprint " Flip Detect: Wrong orientation detected!"
  249. applyFlipRevert
  250. elif ! isFlipDetectionEnabled; then
  251. vprint " Flip Detect: Disabled"
  252. else
  253. vprint " Flip Detect: No change needed"
  254. fi
  255. }
  256. # Inverts $RESIZE_HEIGHT with $RESIZE_WIDTH
  257. applyFlipRevert() {
  258. local tmpInverter=""
  259. tmpInverter=$RESIZE_HEIGHT
  260. RESIZE_HEIGHT=$RESIZE_WIDTH
  261. RESIZE_WIDTH=$tmpInverter
  262. vprint " Inverting Width <-> Height"
  263. }
  264. # Returns the $FLIP_DETECTION flag
  265. isFlipDetectionEnabled() {
  266. return $FLIP_DETECTION
  267. }
  268. # Returns the $FLIP_FORCE flag
  269. isFlipForced() {
  270. return $FLIP_FORCE
  271. }
  272. # Returns $TRUE if the the paper size will invert orientation from source, $FALSE otherwise
  273. shouldFlip() {
  274. [[ $PGWIDTH -gt $PGHEIGHT && $RESIZE_WIDTH -lt $RESIZE_HEIGHT ]] || [[ $PGWIDTH -lt $PGHEIGHT && $RESIZE_WIDTH -gt $RESIZE_HEIGHT ]] && return $TRUE
  275. return $FALSE
  276. }
  277. ########################## INITIALIZERS #########################
  278. # Loads external dependencies and checks for errors
  279. initDeps() {
  280. GREPBIN="$(which grep 2>/dev/null)"
  281. GSBIN="$(which gs 2>/dev/null)"
  282. BCBIN="$(which bc 2>/dev/null)"
  283. IDBIN=$(which identify 2>/dev/null)
  284. MDLSBIN="$(which mdls 2>/dev/null)"
  285. PDFINFOBIN="$(which pdfinfo 2>/dev/null)"
  286. vprint "Checking for basename, grep, ghostscript and bcmath"
  287. basename "" >/dev/null 2>&1 || printDependency 'basename'
  288. isNotAvailable "$GREPBIN" && printDependency 'grep'
  289. isNotAvailable "$GSBIN" && printDependency 'ghostscript'
  290. isNotAvailable "$BCBIN" && printDependency 'bc'
  291. return $TRUE
  292. }
  293. # Checks for dependencies errors, run after getting options
  294. checkDeps() {
  295. if [[ $MODE = "IDENTIFY" ]]; then
  296. vprint "Checking for imagemagick's identify"
  297. if isNotAvailable "$IDBIN"; then printDependency 'imagemagick'; fi
  298. fi
  299. if [[ $MODE = "PDFINFO" ]]; then
  300. vprint "Checking for pdfinfo"
  301. if isNotAvailable "$PDFINFOBIN"; then printDependency 'pdfinfo'; fi
  302. fi
  303. if [[ $MODE = "MDLS" ]]; then
  304. vprint "Checking for MacOS mdls"
  305. if isNotAvailable "$MDLSBIN"; then
  306. initError 'mdls executable was not found! Is this even MacOS?' $EXIT_MAC_MDLS_NOT_FOUND
  307. fi
  308. fi
  309. return $TRUE
  310. }
  311. ######################### CLI OPTIONS ##########################
  312. # Parse options
  313. getOptions() {
  314. local _optArgs=() # things that do not start with a '-'
  315. local _tgtFile="" # to set $OUTFILEPDF
  316. local _currParam="" # to enable case-insensitiveness
  317. while [ ${#} -gt 0 ]; do
  318. if [[ "${1:0:2}" = '--' ]]; then
  319. # Long Option, get lowercase version
  320. _currParam="$(lowercase ${1})"
  321. elif [[ "${1:0:1}" = '-' ]]; then
  322. # short Option, just assign
  323. _currParam="${1}"
  324. else
  325. # file name arguments, store as is and reset loop
  326. _optArgs+=("$1")
  327. shift
  328. continue
  329. fi
  330. case "$_currParam" in
  331. -v|--verbose)
  332. ((VERBOSE++))
  333. shift
  334. ;;
  335. -n|--no-overwrite|--nooverwrite)
  336. ABORT_ON_OVERWRITE=$TRUE
  337. shift
  338. ;;
  339. -h|--help)
  340. printHelp
  341. exit $EXIT_SUCCESS
  342. ;;
  343. -V|--version)
  344. printVersion 3
  345. exit $EXIT_SUCCESS
  346. ;;
  347. -i|--identify|--info)
  348. JUST_IDENTIFY=$TRUE
  349. shift
  350. ;;
  351. -s|--scale|--setscale|--set-scale)
  352. shift
  353. parseScale "$1"
  354. shift
  355. ;;
  356. -m|--mode|--paperdetect|--paper-detect|--pagesizemode|--page-size-mode)
  357. shift
  358. parseMode "$1"
  359. shift
  360. ;;
  361. -r|--resize)
  362. shift
  363. parsePaperResize "$1"
  364. shift
  365. ;;
  366. -p|--printpapers|--print-papers|--listpapers|--list-papers)
  367. printPaperInfo
  368. exit $EXIT_SUCCESS
  369. ;;
  370. -f|--flipdetection|--flip-detection|--flip-mode|--flipmode|--flipdetect|--flip-detect)
  371. shift
  372. parseFlipDetectionMode "$1"
  373. shift
  374. ;;
  375. -a|--autorotation|--auto-rotation|--autorotate|--auto-rotate)
  376. shift
  377. parseAutoRotationMode "$1"
  378. shift
  379. ;;
  380. --pdf-settings)
  381. shift
  382. parsePDFSettings "$1"
  383. shift
  384. ;;
  385. --image-downsample)
  386. shift
  387. parseImageDownSample "$1"
  388. shift
  389. ;;
  390. --image-resolution)
  391. shift
  392. parseImageResolution "$1"
  393. shift
  394. ;;
  395. --horizontal-alignment|--hor-align|--xalign|--x-align)
  396. shift
  397. parseHorizontalAlignment "$1"
  398. shift
  399. ;;
  400. --vertical-alignment|--ver-align|--vert-align|--yalign|--y-align)
  401. shift
  402. parseVerticalAlignment "$1"
  403. shift
  404. ;;
  405. --xtrans|--xtrans-offset|--xoffset)
  406. shift
  407. parseXTransOffset "$1"
  408. shift
  409. ;;
  410. --ytrans|--ytrans-offset|--yoffset)
  411. shift
  412. parseYTransOffset "$1"
  413. shift
  414. ;;
  415. *)
  416. initError "Invalid Parameter: \"$1\"" $EXIT_INVALID_OPTION
  417. ;;
  418. esac
  419. done
  420. isEmpty "${_optArgs[2]}" || initError "Seems like you passed an extra file name?"$'\n'"Invalid option: ${_optArgs[2]}" $EXIT_INVALID_OPTION
  421. if [[ $JUST_IDENTIFY -eq $TRUE ]]; then
  422. isEmpty "${_optArgs[1]}" || initError "Seems like you passed an extra file name?"$'\n'"Invalid option: ${_optArgs[1]}" $EXIT_INVALID_OPTION
  423. VERBOSE=0 # remove verboseness if present
  424. fi
  425. # Validate input PDF file
  426. INFILEPDF="${_optArgs[0]}"
  427. isEmpty "$INFILEPDF" && initError "Input file is empty!" $EXIT_NO_INPUT_FILE
  428. isPDF "$INFILEPDF" || initError "Input file is not a PDF file: $INFILEPDF" $EXIT_INPUT_NOT_PDF
  429. isFile "$INFILEPDF" || initError "Input file not found: $INFILEPDF" $EXIT_FILE_NOT_FOUND
  430. isReadable "$INFILEPDF" || initError "No read access to input file: $INFILEPDF"$'\nPermission Denied' $EXIT_NOREAD_PERMISSION
  431. checkDeps
  432. if [[ $JUST_IDENTIFY -eq $TRUE ]]; then
  433. return $TRUE # no need to get output file, so return already
  434. fi
  435. _tgtFile="${_optArgs[1]}"
  436. local _autoName="${INFILEPDF%.*}" # remove possible stupid extension, like .pDF
  437. if isMixedMode; then
  438. isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.$(uppercase $RESIZE_PAPER_TYPE).SCALED.pdf"
  439. elif isResizeMode; then
  440. isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.$(uppercase $RESIZE_PAPER_TYPE).pdf"
  441. else
  442. isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.SCALED.pdf"
  443. fi
  444. isNotEmpty "$_tgtFile" && OUTFILEPDF="${_tgtFile%.pdf}.pdf"
  445. validateOutFile
  446. }
  447. # Checks if output file is valid and writable
  448. validateOutFile() {
  449. local _tgtDir="$(dirname "$OUTFILEPDF")"
  450. isDir "$_tgtDir" || initError "Output directory does not exist!"$'\n'"Target Dir: $_tgtDir" $EXIT_NOWRITE_PERMISSION
  451. isAbortOnOverwrite && isFile "$OUTFILEPDF" && initError $'Output file already exists and --no-overwrite was used!\nRemove the "-n" or "--no-overwrite" option if you want to overwrite the file\n'"Target File: $OUTFILEPDF" $EXIT_NOWRITE_PERMISSION
  452. isTouchable "$OUTFILEPDF" || initError "Could not get write permission for output file!"$'\n'"Target File: $OUTFILEPDF"$'\nPermission Denied' $EXIT_NOWRITE_PERMISSION
  453. }
  454. # Returns $TRUE if we should not overwrite $OUTFILEPDF, $FALSE otherwise
  455. isAbortOnOverwrite() {
  456. return $ABORT_ON_OVERWRITE
  457. }
  458. # Parses and validates the scaling factor
  459. parseScale() {
  460. AUTOMATIC_SCALING=$FALSE
  461. if ! isFloatBiggerThanZero "$1"; then
  462. printError "Invalid factor: $1"
  463. printError "The factor must be a floating point number greater than 0"
  464. printError "Example: for 80% use 0.8"
  465. exit $EXIT_INVALID_SCALE
  466. fi
  467. SCALE="$1"
  468. }
  469. # Parse a forced mode of operation
  470. parseMode() {
  471. local param="$(lowercase $1)"
  472. case "${param}" in
  473. c|catgrep|'cat+grep'|grep|g)
  474. ADAPTIVEMODE=$FALSE
  475. MODE="CATGREP"
  476. return $TRUE
  477. ;;
  478. i|imagemagick|identify)
  479. ADAPTIVEMODE=$FALSE
  480. MODE="IDENTIFY"
  481. return $TRUE
  482. ;;
  483. m|mdls|quartz|mac)
  484. ADAPTIVEMODE=$FALSE
  485. MODE="MDLS"
  486. return $TRUE
  487. ;;
  488. p|pdfinfo)
  489. ADAPTIVEMODE=$FALSE
  490. MODE="PDFINFO"
  491. return $TRUE
  492. ;;
  493. a|auto|automatic|adaptive)
  494. ADAPTIVEMODE=$TRUE
  495. MODE=""
  496. return $TRUE
  497. ;;
  498. *)
  499. initError "Invalid PDF Size Detection Mode: \"$1\"" $EXIT_INVALID_OPTION
  500. return $FALSE
  501. ;;
  502. esac
  503. return $FALSE
  504. }
  505. # Parses and validates the scaling factor
  506. parseFlipDetectionMode() {
  507. local param="$(lowercase $1)"
  508. case "${param}" in
  509. d|disable)
  510. FLIP_DETECTION=$FALSE
  511. FLIP_FORCE=$FALSE
  512. ;;
  513. f|force)
  514. FLIP_DETECTION=$FALSE
  515. FLIP_FORCE=$TRUE
  516. ;;
  517. a|auto|automatic)
  518. FLIP_DETECTION=$TRUE
  519. FLIP_FORCE=$FALSE
  520. ;;
  521. *)
  522. initError "Invalid Flip Detection Mode: \"$1\"" $EXIT_INVALID_OPTION
  523. return $FALSE
  524. ;;
  525. esac
  526. }
  527. # Parses and validates the scaling factor
  528. parseAutoRotationMode() {
  529. local param="$(lowercase $1)"
  530. case "${param}" in
  531. n|none|'/none')
  532. AUTO_ROTATION='/None'
  533. ;;
  534. a|all|'/all')
  535. AUTO_ROTATION='/All'
  536. ;;
  537. p|pagebypage|'/pagebypage'|auto)
  538. AUTO_ROTATION='/PageByPage'
  539. ;;
  540. *)
  541. initError "Invalid Auto Rotation Mode: \"$1\"" $EXIT_INVALID_OPTION
  542. return $FALSE
  543. ;;
  544. esac
  545. }
  546. # Validades the a paper resize CLI option and sets the paper to $RESIZE_PAPER_TYPE
  547. parsePaperResize() {
  548. isEmpty "$1" && initError 'Invalid Paper Type: (empty)' $EXIT_INVALID_PAPER_SIZE
  549. local lowercasePaper="$(lowercase $1)"
  550. local customPaper=($lowercasePaper)
  551. if [[ "$customPaper" = 'same' || "$customPaper" = 'keep' || "$customPaper" = 'source' ]]; then
  552. RESIZE_PAPER_TYPE='source'
  553. elif [[ "${customPaper[0]}" = 'custom' ]]; then
  554. if isNotValidMeasure "${customPaper[1]}" || ! isFloatBiggerThanZero "${customPaper[2]}" || ! isFloatBiggerThanZero "${customPaper[3]}"; then
  555. initError "Invalid Custom Paper Definition!"$'\n'"Use: -r 'custom <measurement> <width> <height>'"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION
  556. fi
  557. RESIZE_PAPER_TYPE="custom"
  558. CUSTOM_RESIZE_PAPER=$TRUE
  559. if isMilimeter "${customPaper[1]}"; then
  560. RESIZE_WIDTH="$(milimetersToPoints "${customPaper[2]}")"
  561. RESIZE_HEIGHT="$(milimetersToPoints "${customPaper[3]}")"
  562. elif isInch "${customPaper[1]}"; then
  563. RESIZE_WIDTH="$(inchesToPoints "${customPaper[2]}")"
  564. RESIZE_HEIGHT="$(inchesToPoints "${customPaper[3]}")"
  565. elif isPoint "${customPaper[1]}"; then
  566. RESIZE_WIDTH="${customPaper[2]}"
  567. RESIZE_HEIGHT="${customPaper[3]}"
  568. else
  569. initError "Invalid Custom Paper Definition!"$'\n'"Use: -r 'custom <measurement> <width> <height>'"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION
  570. fi
  571. else
  572. isPaperName "$lowercasePaper" || initError "Invalid Paper Type: $1" $EXIT_INVALID_PAPER_SIZE
  573. RESIZE_PAPER_TYPE="$lowercasePaper"
  574. fi
  575. }
  576. # Goes to GS -dColorImageResolution and -dGrayImageResolution parameters
  577. parseImageResolution() {
  578. if isNotAnInteger "$1"; then
  579. printError "Invalid image resolution: $1"
  580. printError "The image resolution must be an integer"
  581. exit $EXIT_INVALID_IMAGE_RESOLUTION
  582. fi
  583. IMAGE_RESOLUTION="$1"
  584. }
  585. # Goes to GS -dColorImageDownsampleType and -dGrayImageDownsampleType parameters
  586. parseImageDownSample() {
  587. local param="$(lowercase $1)"
  588. case "${param}" in
  589. s|subsample|'/subsample')
  590. IMAGE_DOWNSAMPLE_TYPE='/Subsample'
  591. ;;
  592. a|average|'/average')
  593. IMAGE_DOWNSAMPLE_TYPE='/Average'
  594. ;;
  595. b|bicubic|'/bicubic'|auto)
  596. IMAGE_DOWNSAMPLE_TYPE='/Bicubic'
  597. ;;
  598. *)
  599. initError "Invalid Image Downsample Mode: \"$1\"" $EXIT_INVALID_OPTION
  600. return $FALSE
  601. ;;
  602. esac
  603. }
  604. # Goes to GS -dColorImageDownsampleType and -dGrayImageDownsampleType parameters
  605. parsePDFSettings() {
  606. local param="$(lowercase $1)"
  607. case "${param}" in
  608. s|screen|'/screen')
  609. PDF_SETTINGS='/screen'
  610. ;;
  611. e|ebook|'/ebook')
  612. PDF_SETTINGS='/ebook'
  613. ;;
  614. p|printer|'/printer'|auto)
  615. PDF_SETTINGS='/printer'
  616. ;;
  617. r|prepress|'/prepress')
  618. PDF_SETTINGS='/prepress'
  619. ;;
  620. d|default|'/default')
  621. PDF_SETTINGS='/default'
  622. ;;
  623. *)
  624. initError "Invalid PDF Setting Profile: \"$1\""$'\nValid > printer, screen, ebook, prepress, default' $EXIT_INVALID_OPTION
  625. return $FALSE
  626. ;;
  627. esac
  628. }
  629. # How to position the resized pages (sets translation)
  630. parseHorizontalAlignment() {
  631. local param="$(lowercase $1)"
  632. case "${param}" in
  633. l|left)
  634. HOR_ALIGN='LEFT'
  635. ;;
  636. r|right)
  637. HOR_ALIGN='RIGHT'
  638. ;;
  639. c|center|middle)
  640. HOR_ALIGN='CENTER'
  641. ;;
  642. *)
  643. initError "Invalid Horizontal Alignment Setting: \"$1\""$'\nValid > left, right, center' $EXIT_INVALID_OPTION
  644. return $FALSE
  645. ;;
  646. esac
  647. }
  648. # How to position the resized pages (sets translation)
  649. parseVerticalAlignment() {
  650. local param="$(lowercase $1)"
  651. case "${param}" in
  652. t|top)
  653. VERT_ALIGN='TOP'
  654. ;;
  655. b|bottom|bot)
  656. VERT_ALIGN='BOTTOM'
  657. ;;
  658. c|center|middle)
  659. VERT_ALIGN='CENTER'
  660. ;;
  661. *)
  662. initError "Invalid Vertical Alignment Setting: \"$1\""$'\nValid > top, bottom, center' $EXIT_INVALID_OPTION
  663. return $FALSE
  664. ;;
  665. esac
  666. }
  667. # Set X Translation Offset
  668. parseXTransOffset() {
  669. if isFloat "$1"; then
  670. XTRANSOFFSET="$1"
  671. return $TRUE
  672. fi
  673. printError "Invalid X Translation Offset: $1"
  674. printError "The X Translation Offset must be a floating point number"
  675. exit $EXIT_INVALID_OPTION
  676. }
  677. # Set Y Translation Offset
  678. parseYTransOffset() {
  679. if isFloat "$1"; then
  680. YTRANSOFFSET="$1"
  681. return $TRUE
  682. fi
  683. printError "Invalid Y Translation Offset: $1"
  684. printError "The Y Translation Offset must be a floating point number"
  685. exit $EXIT_INVALID_OPTION
  686. }
  687. ################### PDF PAGE SIZE DETECTION ####################
  688. ################################################################
  689. # Detects operation mode and also runs the adaptive mode
  690. # PAGESIZE LOGIC
  691. # 1- Try to get Mediabox with GREP
  692. # 2- MacOS => try to use mdls
  693. # 3- Try to use pdfinfo
  694. # 4- Try to use identify (imagemagick)
  695. # 5- Fail
  696. ################################################################
  697. getPageSize() {
  698. if isNotAdaptiveMode; then
  699. vprint " Get Page Size: Adaptive Disabled"
  700. if [[ $MODE = "CATGREP" ]]; then
  701. vprint " Method: Grep"
  702. getPageSizeCatGrep
  703. elif [[ $MODE = "MDLS" ]]; then
  704. vprint " Method: Mac Quartz mdls"
  705. getPageSizeMdls
  706. elif [[ $MODE = "PDFINFO" ]]; then
  707. vprint " Method: PDFInfo"
  708. getPageSizePdfInfo
  709. elif [[ $MODE = "IDENTIFY" ]]; then
  710. vprint " Method: ImageMagick's Identify"
  711. getPageSizeImagemagick
  712. else
  713. printError "Error! Invalid Mode: $MODE"
  714. printError "Aborting execution..."
  715. exit $EXIT_INVALID_OPTION
  716. fi
  717. return $TRUE
  718. fi
  719. vprint " Get Page Size: Adaptive Enabled"
  720. vprint " Method: Grep"
  721. getPageSizeCatGrep
  722. if pageSizeIsInvalid && [[ $OSNAME = "Darwin" ]]; then
  723. vprint " Failed"
  724. vprint " Method: Mac Quartz mdls"
  725. getPageSizeMdls
  726. fi
  727. if pageSizeIsInvalid; then
  728. vprint " Failed"
  729. vprint " Method: PDFInfo"
  730. getPageSizePdfInfo
  731. fi
  732. if pageSizeIsInvalid; then
  733. vprint " Failed"
  734. vprint " Method: ImageMagick's Identify"
  735. getPageSizeImagemagick
  736. fi
  737. if pageSizeIsInvalid; then
  738. vprint " Failed"
  739. printError "Error when detecting PDF paper size!"
  740. printError "All methods of detection failed"
  741. printError "You may want to install pdfinfo or imagemagick"
  742. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  743. fi
  744. return $TRUE
  745. }
  746. # Gets page size using imagemagick's identify
  747. getPageSizeImagemagick() {
  748. # Sanity and Adaptive together
  749. if isNotFile "$IDBIN" && isNotAdaptiveMode; then
  750. notAdaptiveFailed "Make sure you installed ImageMagick and have identify on your \$PATH" "ImageMagick's Identify"
  751. elif isNotFile "$IDBIN" && isAdaptiveMode; then
  752. return $FALSE
  753. fi
  754. # get data from image magick
  755. local identify="$("$IDBIN" -format '%[fx:w] %[fx:h]BREAKME' "$INFILEPDF" 2>/dev/null)"
  756. if isEmpty "$identify" && isNotAdaptiveMode; then
  757. notAdaptiveFailed "ImageMagicks's Identify returned an empty string!"
  758. elif isEmpty "$identify" && isAdaptiveMode; then
  759. return $FALSE
  760. fi
  761. identify="${identify%%BREAKME*}" # get page size only for 1st page
  762. identify=($identify) # make it an array
  763. PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign
  764. PGHEIGHT=$(printf '%.0f' "${identify[1]}") # assign
  765. return $TRUE
  766. }
  767. # Gets page size using Mac Quarts mdls
  768. getPageSizeMdls() {
  769. # Sanity and Adaptive together
  770. if isNotFile "$MDLSBIN" && isNotAdaptiveMode; then
  771. notAdaptiveFailed "Are you even trying this on a Mac?" "Mac Quartz mdls"
  772. elif isNotFile "$MDLSBIN" && isAdaptiveMode; then
  773. return $FALSE
  774. fi
  775. local identify="$("$MDLSBIN" -mdls -name kMDItemPageHeight -name kMDItemPageWidth "$INFILEPDF" 2>/dev/null)"
  776. if isEmpty "$identify" && isNotAdaptiveMode; then
  777. notAdaptiveFailed "Mac Quartz mdls returned an empty string!"
  778. elif isEmpty "$identify" && isAdaptiveMode; then
  779. return $FALSE
  780. fi
  781. identify=${identify//$'\t'/ } # change tab to space
  782. identify=($identify) # make it an array
  783. if [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isNotAdaptiveMode; then
  784. notAdaptiveFailed "There was no metadata to read from the file! Is Spotlight OFF?"
  785. elif [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isAdaptiveMode; then
  786. return $FALSE
  787. fi
  788. PGWIDTH=$(printf '%.0f' "${identify[5]}") # assign
  789. PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign
  790. return $TRUE
  791. }
  792. # Gets page size using Linux PdfInfo
  793. getPageSizePdfInfo() {
  794. # Sanity and Adaptive together
  795. if isNotFile "$PDFINFOBIN" && isNotAdaptiveMode; then
  796. notAdaptiveFailed "Do you have pdfinfo installed and available on your \$PATH?" "Linux pdfinfo"
  797. elif isNotFile "$PDFINFOBIN" && isAdaptiveMode; then
  798. return $FALSE
  799. fi
  800. # get data from image magick
  801. local identify="$("$PDFINFOBIN" "$INFILEPDF" 2>/dev/null | "$GREPBIN" -i 'Page size:' )"
  802. if isEmpty "$identify" && isNotAdaptiveMode; then
  803. notAdaptiveFailed "Linux PdfInfo returned an empty string!"
  804. elif isEmpty "$identify" && isAdaptiveMode; then
  805. return $FALSE
  806. fi
  807. identify="${identify##*Page size:}" # remove stuff
  808. identify=($identify) # make it an array
  809. PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign
  810. PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign
  811. return $TRUE
  812. }
  813. # Gets page size using cat and grep
  814. getPageSizeCatGrep() {
  815. # get MediaBox info from PDF file using grep, these are all possible
  816. # /MediaBox [0 0 595 841]
  817. # /MediaBox [ 0 0 595.28 841.89]
  818. # /MediaBox[ 0 0 595.28 841.89 ]
  819. # Get MediaBox data if possible
  820. local mediaBox="$("$GREPBIN" -a -e '/MediaBox' -m 1 "$INFILEPDF" 2>/dev/null)"
  821. mediaBox="${mediaBox##*/MediaBox}"
  822. # No page size data available
  823. if isEmpty "$mediaBox" && isNotAdaptiveMode; then
  824. notAdaptiveFailed "There is no MediaBox in the pdf document!"
  825. elif isEmpty "$mediaBox" && isAdaptiveMode; then
  826. return $FALSE
  827. fi
  828. # remove chars [ and ]
  829. mediaBox="${mediaBox//[}"
  830. mediaBox="${mediaBox//]}"
  831. mediaBox=($mediaBox) # make it an array
  832. mbCount=${#mediaBox[@]} # array size
  833. # sanity
  834. if [[ $mbCount -lt 4 ]]; then
  835. printError "Error when reading the page size!"
  836. printError "The page size information is invalid!"
  837. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  838. fi
  839. # we are done
  840. PGWIDTH=$(printf '%.0f' "${mediaBox[2]}") # Get Round Width
  841. PGHEIGHT=$(printf '%.0f' "${mediaBox[3]}") # Get Round Height
  842. return $TRUE
  843. }
  844. # Prints error message and exits execution
  845. notAdaptiveFailed() {
  846. local errProgram="$2"
  847. local errStr="$1"
  848. if isEmpty "$2"; then
  849. printError "Error when reading input file!"
  850. printError "Could not determine the page size!"
  851. else
  852. printError "Error! $2 was not found!"
  853. fi
  854. isNotEmpty "$errStr" && printError "$errStr"
  855. printError "Aborting! You may want to try the adaptive mode."
  856. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  857. }
  858. # Verbose print of the Width and Height (Source or New) to screen
  859. vPrintPageSizes() {
  860. vprint " $1 Width: $PGWIDTH postscript-points"
  861. vprint "$1 Height: $PGHEIGHT postscript-points"
  862. }
  863. #################### GHOSTSCRIPT PAPER INFO ####################
  864. # Loads valid paper info to memory
  865. getPaperInfo() {
  866. # name inchesW inchesH mmW mmH pointsW pointsH
  867. sizesUS="\
  868. 11x17 11.0 17.0 279 432 792 1224
  869. ledger 17.0 11.0 432 279 1224 792
  870. legal 8.5 14.0 216 356 612 1008
  871. letter 8.5 11.0 216 279 612 792
  872. lettersmall 8.5 11.0 216 279 612 792
  873. archE 36.0 48.0 914 1219 2592 3456
  874. archD 24.0 36.0 610 914 1728 2592
  875. archC 18.0 24.0 457 610 1296 1728
  876. archB 12.0 18.0 305 457 864 1296
  877. archA 9.0 12.0 229 305 648 864"
  878. sizesISO="\
  879. a0 33.1 46.8 841 1189 2384 3370
  880. a1 23.4 33.1 594 841 1684 2384
  881. a2 16.5 23.4 420 594 1191 1684
  882. a3 11.7 16.5 297 420 842 1191
  883. a4 8.3 11.7 210 297 595 842
  884. a4small 8.3 11.7 210 297 595 842
  885. a5 5.8 8.3 148 210 420 595
  886. a6 4.1 5.8 105 148 297 420
  887. a7 2.9 4.1 74 105 210 297
  888. a8 2.1 2.9 52 74 148 210
  889. a9 1.5 2.1 37 52 105 148
  890. a10 1.0 1.5 26 37 73 105
  891. isob0 39.4 55.7 1000 1414 2835 4008
  892. isob1 27.8 39.4 707 1000 2004 2835
  893. isob2 19.7 27.8 500 707 1417 2004
  894. isob3 13.9 19.7 353 500 1001 1417
  895. isob4 9.8 13.9 250 353 709 1001
  896. isob5 6.9 9.8 176 250 499 709
  897. isob6 4.9 6.9 125 176 354 499
  898. c0 36.1 51.1 917 1297 2599 3677
  899. c1 25.5 36.1 648 917 1837 2599
  900. c2 18.0 25.5 458 648 1298 1837
  901. c3 12.8 18.0 324 458 918 1298
  902. c4 9.0 12.8 229 324 649 918
  903. c5 6.4 9.0 162 229 459 649
  904. c6 4.5 6.4 114 162 323 459"
  905. sizesJIS="\
  906. jisb0 NA NA 1030 1456 2920 4127
  907. jisb1 NA NA 728 1030 2064 2920
  908. jisb2 NA NA 515 728 1460 2064
  909. jisb3 NA NA 364 515 1032 1460
  910. jisb4 NA NA 257 364 729 1032
  911. jisb5 NA NA 182 257 516 729
  912. jisb6 NA NA 128 182 363 516"
  913. sizesOther="\
  914. flsa 8.5 13.0 216 330 612 936
  915. flse 8.5 13.0 216 330 612 936
  916. halfletter 5.5 8.5 140 216 396 612
  917. hagaki 3.9 5.8 100 148 283 420"
  918. sizesAll="\
  919. $sizesUS
  920. $sizesISO
  921. $sizesJIS
  922. $sizesOther"
  923. }
  924. # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT
  925. getGSPaperSize() {
  926. isEmpty "$sizesall" && getPaperInfo
  927. while read l; do
  928. local cols=($l)
  929. if [[ "$1" == ${cols[0]} ]]; then
  930. RESIZE_WIDTH=${cols[5]}
  931. RESIZE_HEIGHT=${cols[6]}
  932. return $TRUE
  933. fi
  934. done <<< "$sizesAll"
  935. }
  936. # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT
  937. getGSPaperName() {
  938. local w="$(printf "%.0f" $1)"
  939. local h="$(printf "%.0f" $2)"
  940. isEmpty "$sizesall" && getPaperInfo
  941. # Because US Standard has inverted sizes, I need to scan 2 times
  942. # instead of just testing if width is bigger than height
  943. while read l; do
  944. local cols=($l)
  945. if [[ "$w" == ${cols[5]} && "$h" == ${cols[6]} ]]; then
  946. printf "%s Portrait" $(uppercase ${cols[0]})
  947. return $TRUE
  948. fi
  949. done <<< "$sizesAll"
  950. while read l; do
  951. local cols=($l)
  952. if [[ "$w" == ${cols[6]} && "$h" == ${cols[5]} ]]; then
  953. printf "%s Landscape" $(uppercase ${cols[0]})
  954. return $TRUE
  955. fi
  956. done <<< "$sizesAll"
  957. return $FALSE
  958. }
  959. # Loads an array with paper names to memory
  960. getPaperNames() {
  961. 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 \
  962. 11x17 ledger legal letter lettersmall archE archD archC archB archA \
  963. jisb0 jisb1 jisb2 jisb3 jisb4 jisb5 jisb6 \
  964. flsa flse halfletter hagaki)
  965. }
  966. # Prints uppercase paper names to screen (used in help)
  967. printPaperNames() {
  968. isEmpty "$paperNames" && getPaperNames
  969. for i in "${!paperNames[@]}"; do
  970. [[ $i -eq 0 ]] && echo -n -e ' '
  971. [[ $i -ne 0 && $((i % 5)) -eq 0 ]] && echo -n -e $'\n '
  972. ppN="$(uppercase ${paperNames[i]})"
  973. printf "%-14s" "$ppN"
  974. done
  975. echo ""
  976. }
  977. # Returns $TRUE if $! is a valid paper name, $FALSE otherwise
  978. isPaperName() {
  979. isEmpty "$1" && return $FALSE
  980. isEmpty "$paperNames" && getPaperNames
  981. for i in "${paperNames[@]}"; do
  982. [[ "$i" = "$1" ]] && return $TRUE
  983. done
  984. return $FALSE
  985. }
  986. # Prints all tables with ghostscript paper information
  987. printPaperInfo() {
  988. printVersion 3
  989. echo $'\n'"Paper Sizes Information"$'\n'
  990. getPaperInfo
  991. printPaperTable "ISO STANDARD" "$sizesISO"; echo
  992. printPaperTable "US STANDARD" "$sizesUS"; echo
  993. printPaperTable "JIS STANDARD *Aproximated Points" "$sizesJIS"; echo
  994. printPaperTable "OTHERS" "$sizesOther"; echo
  995. }
  996. # GS paper table helper, prints a full line
  997. printTableLine() {
  998. echo '+-----------------------------------------------------------------+'
  999. }
  1000. # GS paper table helper, prints a line with dividers
  1001. printTableDivider() {
  1002. echo '+-----------------+-------+-------+-------+-------+-------+-------+'
  1003. }
  1004. # GS paper table helper, prints a table header
  1005. printTableHeader() {
  1006. echo '| Name | inchW | inchH | mm W | mm H | pts W | pts H |'
  1007. }
  1008. # GS paper table helper, prints a table title
  1009. printTableTitle() {
  1010. printf "| %-64s%s\n" "$1" '|'
  1011. }
  1012. # GS paper table printer, prints a table for a paper variable
  1013. printPaperTable() {
  1014. printTableLine
  1015. printTableTitle "$1"
  1016. printTableLine
  1017. printTableHeader
  1018. printTableDivider
  1019. while read l; do
  1020. local cols=($l)
  1021. printf "| %-15s | %+5s | %+5s | %+5s | %+5s | %+5s | %+5s |\n" ${cols[*]};
  1022. done <<< "$2"
  1023. printTableDivider
  1024. }
  1025. # Returns $TRUE if $1 is a valid measurement for a custom paper, $FALSE otherwise
  1026. isNotValidMeasure() {
  1027. isMilimeter "$1" || isInch "$1" || isPoint "$1" && return $FALSE
  1028. return $TRUE
  1029. }
  1030. # Returns $TRUE if $1 is a valid milimeter string, $FALSE otherwise
  1031. isMilimeter() {
  1032. [[ "$1" = 'mm' || "$1" = 'milimeters' || "$1" = 'milimeter' ]] && return $TRUE
  1033. return $FALSE
  1034. }
  1035. # Returns $TRUE if $1 is a valid inch string, $FALSE otherwise
  1036. isInch() {
  1037. [[ "$1" = 'in' || "$1" = 'inch' || "$1" = 'inches' ]] && return $TRUE
  1038. return $FALSE
  1039. }
  1040. # Returns $TRUE if $1 is a valid point string, $FALSE otherwise
  1041. isPoint() {
  1042. [[ "$1" = 'pt' || "$1" = 'pts' || "$1" = 'point' || "$1" = 'points' ]] && return $TRUE
  1043. return $FALSE
  1044. }
  1045. # Returns $TRUE if a custom paper is being used, $FALSE otherwise
  1046. isCustomPaper() {
  1047. return $CUSTOM_RESIZE_PAPER
  1048. }
  1049. # Returns $FALSE if a custom paper is being used, $TRUE otherwise
  1050. isNotCustomPaper() {
  1051. isCustomPaper && return $FALSE
  1052. return $TRUE
  1053. }
  1054. ######################### CONVERSIONS ##########################
  1055. # Prints the lowercase char value for $1
  1056. lowercaseChar() {
  1057. case "$1" in
  1058. [A-Z])
  1059. n=$(printf "%d" "'$1")
  1060. n=$((n+32))
  1061. printf \\$(printf "%o" "$n")
  1062. ;;
  1063. *)
  1064. printf "%s" "$1"
  1065. ;;
  1066. esac
  1067. }
  1068. # Prints the lowercase version of a string
  1069. lowercase() {
  1070. word="$@"
  1071. for((i=0;i<${#word};i++))
  1072. do
  1073. ch="${word:$i:1}"
  1074. lowercaseChar "$ch"
  1075. done
  1076. }
  1077. # Prints the uppercase char value for $1
  1078. uppercaseChar(){
  1079. case "$1" in
  1080. [a-z])
  1081. n=$(printf "%d" "'$1")
  1082. n=$((n-32))
  1083. printf \\$(printf "%o" "$n")
  1084. ;;
  1085. *)
  1086. printf "%s" "$1"
  1087. ;;
  1088. esac
  1089. }
  1090. # Prints the uppercase version of a string
  1091. uppercase() {
  1092. word="$@"
  1093. for((i=0;i<${#word};i++))
  1094. do
  1095. ch="${word:$i:1}"
  1096. uppercaseChar "$ch"
  1097. done
  1098. }
  1099. # Prints the postscript points rounded equivalent from $1 mm
  1100. milimetersToPoints() {
  1101. local pts=$(echo "scale=8; $1 * 72 / 25.4" | "$BCBIN")
  1102. printf '%.0f' "$pts" # Print rounded conversion
  1103. }
  1104. # Prints the postscript points rounded equivalent from $1 inches
  1105. inchesToPoints() {
  1106. local pts=$(echo "scale=8; $1 * 72" | "$BCBIN")
  1107. printf '%.0f' "$pts" # Print rounded conversion
  1108. }
  1109. # Prints the mm equivalent from $1 postscript points
  1110. pointsToMilimeters() {
  1111. local pts=$(echo "scale=8; $1 / 72 * 25.4" | "$BCBIN")
  1112. printf '%.0f' "$pts" # Print rounded conversion
  1113. }
  1114. # Prints the inches equivalent from $1 postscript points
  1115. pointsToInches() {
  1116. local pts=$(echo "scale=8; $1 / 72" | "$BCBIN")
  1117. printf '%.1f' "$pts" # Print rounded conversion
  1118. }
  1119. ######################## MODE-DETECTION ########################
  1120. # Returns $TRUE if the scale was set manually, $FALSE if we are using automatic scaling
  1121. isManualScaledMode() {
  1122. [[ $AUTOMATIC_SCALING -eq $TRUE ]] && return $FALSE
  1123. return $TRUE
  1124. }
  1125. # Returns true if we are resizing a paper (ignores scaling), false otherwise
  1126. isResizeMode() {
  1127. isEmpty $RESIZE_PAPER_TYPE && return $FALSE
  1128. return $TRUE
  1129. }
  1130. # Returns true if we are resizing a paper and the scale was manually set
  1131. isMixedMode() {
  1132. isResizeMode && isManualScaledMode && return $TRUE
  1133. return $FALSE
  1134. }
  1135. # Return $TRUE if adaptive mode is enabled, $FALSE otherwise
  1136. isAdaptiveMode() {
  1137. return $ADAPTIVEMODE
  1138. }
  1139. # Return $TRUE if adaptive mode is disabled, $FALSE otherwise
  1140. isNotAdaptiveMode() {
  1141. isAdaptiveMode && return $FALSE
  1142. return $TRUE
  1143. }
  1144. ########################## VALIDATORS ##########################
  1145. # Returns $TRUE if $PGWIDTH OR $PGWIDTH are empty or NOT an Integer, $FALSE otherwise
  1146. pageSizeIsInvalid() {
  1147. if isNotAnInteger "$PGWIDTH" || isNotAnInteger "$PGHEIGHT"; then
  1148. return $TRUE
  1149. fi
  1150. return $FALSE
  1151. }
  1152. # Return $TRUE if $1 is empty, $FALSE otherwise
  1153. isEmpty() {
  1154. [[ -z "$1" ]] && return $TRUE
  1155. return $FALSE
  1156. }
  1157. # Return $TRUE if $1 is NOT empty, $FALSE otherwise
  1158. isNotEmpty() {
  1159. [[ -z "$1" ]] && return $FALSE
  1160. return $TRUE
  1161. }
  1162. # Returns $TRUE if $1 is an integer, $FALSE otherwise
  1163. isAnInteger() {
  1164. case $1 in
  1165. ''|*[!0-9]*) return $FALSE ;;
  1166. *) return $TRUE ;;
  1167. esac
  1168. }
  1169. # Returns $TRUE if $1 is NOT an integer, $FALSE otherwise
  1170. isNotAnInteger() {
  1171. case $1 in
  1172. ''|*[!0-9]*) return $TRUE ;;
  1173. *) return $FALSE ;;
  1174. esac
  1175. }
  1176. # Returns $TRUE if $1 is a floating point number (or an integer), $FALSE otherwise
  1177. isFloat() {
  1178. [[ -n "$1" && "$1" =~ ^-?[0-9]*([.][0-9]+)?$ ]] && return $TRUE
  1179. return $FALSE
  1180. }
  1181. # Returns $TRUE if $1 is a floating point number bigger than zero, $FALSE otherwise
  1182. isFloatBiggerThanZero() {
  1183. isFloat "$1" && [[ (( $1 > 0 )) ]] && return $TRUE
  1184. return $FALSE
  1185. }
  1186. # Returns $TRUE if $1 is readable, $FALSE otherwise
  1187. isReadable() {
  1188. [[ -r "$1" ]] && return $TRUE
  1189. return $FALSE;
  1190. }
  1191. # Returns $TRUE if $1 is a directory, $FALSE otherwise
  1192. isDir() {
  1193. [[ -d "$1" ]] && return $TRUE
  1194. return $FALSE;
  1195. }
  1196. # Returns 0 if succeded, other integer otherwise
  1197. isTouchable() {
  1198. touch "$1" 2>/dev/null
  1199. }
  1200. # Returns $TRUE if $1 has a .pdf extension, false otherwsie
  1201. isPDF() {
  1202. [[ "$(lowercase $1)" =~ ^..*\.pdf$ ]] && return $TRUE
  1203. return $FALSE
  1204. }
  1205. # Returns $TRUE if $1 is a file, false otherwsie
  1206. isFile() {
  1207. [[ -f "$1" ]] && return $TRUE
  1208. return $FALSE
  1209. }
  1210. # Returns $TRUE if $1 is NOT a file, false otherwsie
  1211. isNotFile() {
  1212. [[ -f "$1" ]] && return $FALSE
  1213. return $TRUE
  1214. }
  1215. # Returns $TRUE if $1 is executable, false otherwsie
  1216. isExecutable() {
  1217. [[ -x "$1" ]] && return $TRUE
  1218. return $FALSE
  1219. }
  1220. # Returns $TRUE if $1 is NOT executable, false otherwsie
  1221. isNotExecutable() {
  1222. [[ -x "$1" ]] && return $FALSE
  1223. return $TRUE
  1224. }
  1225. # Returns $TRUE if $1 is a file and executable, false otherwsie
  1226. isAvailable() {
  1227. if isFile "$1" && isExecutable "$1"; then
  1228. return $TRUE
  1229. fi
  1230. return $FALSE
  1231. }
  1232. # Returns $TRUE if $1 is NOT a file or NOT executable, false otherwsie
  1233. isNotAvailable() {
  1234. if isNotFile "$1" || isNotExecutable "$1"; then
  1235. return $TRUE
  1236. fi
  1237. return $FALSE
  1238. }
  1239. ###################### PRINTING TO SCREEN ######################
  1240. # Prints version
  1241. printVersion() {
  1242. local vStr=""
  1243. [[ "$2" = 'verbose' ]] && vStr=" - Verbose Execution"
  1244. local strBanner="$PDFSCALE_NAME v$VERSION$vStr"
  1245. if [[ $1 -eq 2 ]]; then
  1246. printError "$strBanner"
  1247. elif [[ $1 -eq 3 ]]; then
  1248. local extra="$(isNotEmpty "$2" && echo "$2")"
  1249. echo "$strBanner$extra"
  1250. else
  1251. vprint "$strBanner"
  1252. fi
  1253. }
  1254. # Prints input, output file info, if verbosing
  1255. vPrintFileInfo() {
  1256. vprint " Input File: $INFILEPDF"
  1257. vprint " Output File: $OUTFILEPDF"
  1258. }
  1259. # Prints the scale factor to screen, or custom message
  1260. vPrintScaleFactor() {
  1261. local scaleMsg="$SCALE"
  1262. isNotEmpty "$1" && scaleMsg="$1"
  1263. vprint " Scale Factor: $scaleMsg"
  1264. }
  1265. # Prints help info
  1266. printHelp() {
  1267. printVersion 3
  1268. local paperList="$(printPaperNames)"
  1269. echo "
  1270. Usage: $PDFSCALE_NAME <inFile.pdf>
  1271. $PDFSCALE_NAME -i <inFile.pdf>
  1272. $PDFSCALE_NAME [-v] [-s <factor>] [-m <page-detection>] <inFile.pdf> [outfile.pdf]
  1273. $PDFSCALE_NAME [-v] [-r <paper>] [-f <flip-detection>] [-a <auto-rotation>] <inFile.pdf> [outfile.pdf]
  1274. $PDFSCALE_NAME -p
  1275. $PDFSCALE_NAME -h
  1276. $PDFSCALE_NAME -V
  1277. Parameters:
  1278. -v, --verbose
  1279. Verbose mode, prints extra information
  1280. Use twice for timestamp
  1281. -h, --help
  1282. Print this help to screen and exits
  1283. -V, --version
  1284. Prints version to screen and exits
  1285. -n, --no-overwrite
  1286. Aborts execution if the output PDF file already exists
  1287. By default, the output file will be overwritten
  1288. -m, --mode <mode>
  1289. Paper size detection mode
  1290. Modes: a, adaptive Default mode, tries all the methods below
  1291. g, grep Forces the use of Grep method
  1292. m, mdls Forces the use of MacOS Quartz mdls
  1293. p, pdfinfo Forces the use of PDFInfo
  1294. i, identify Forces the use of ImageMagick's Identify
  1295. -i, --info <file>
  1296. Prints <file> Paper Size information to screen and exits
  1297. -s, --scale <factor>
  1298. Changes the scaling factor or forces mixed mode
  1299. Defaults: $SCALE (scale mode) / Disabled (resize mode)
  1300. MUST be a number bigger than zero
  1301. Eg. -s 0.8 for 80% of the original size
  1302. -r, --resize <paper>
  1303. Triggers the Resize Paper Mode, disables auto-scaling of $SCALE
  1304. Resize PDF and fit-to-page
  1305. <paper> can be: source, custom or a valid std paper name, read below
  1306. -f, --flip-detect <mode>
  1307. Flip Detection Mode, defaults to 'auto'
  1308. Inverts Width <-> Height of a Resized PDF
  1309. Modes: a, auto Keeps source orientation, default
  1310. f, force Forces flip W <-> H
  1311. d, disable Disables flipping
  1312. -a, --auto-rotate <mode>
  1313. Setting for GS -dAutoRotatePages, defaults to 'PageByPage'
  1314. Uses text-orientation detection to set Portrait/Landscape
  1315. Modes: p, pagebypage Auto-rotates pages individually
  1316. n, none Retains orientation of each page
  1317. a, all Rotates all pages (or none) depending
  1318. on a kind of \"majority decision\"
  1319. --hor-align,--horizontal-alignment <left|center|right>
  1320. Where to translate the scaled page
  1321. Default: center
  1322. Options: left, right, center
  1323. --vert-align,--vertical-alignment <top|center|bottom>
  1324. Where to translate the scaled page
  1325. Default: center
  1326. Options: top, bootom, center
  1327. --xoffset,--xtrans-offset <FloatNumber>
  1328. Add/Subtract from the X translation (move left-right)
  1329. Default: 0.0 (zero)
  1330. Options: Positive or negative floating point number
  1331. --yoffset,--ytrans-offset <FloatNumber>
  1332. Add/Subtract from the Y translation (move top-bottim)
  1333. Default: 0.0 (zero)
  1334. Options: Positive or negative floating point number
  1335. --pdf-settings <gs-pdf-profile>
  1336. Ghostscript PDF Profile to use in -dPDFSETTINGS
  1337. Default: printer
  1338. Options: screen, ebook, printer, prepress, default
  1339. --image-downsample <gs-downsample-method>
  1340. Ghostscript Image Downsample Method
  1341. Default: bicubic
  1342. Options: subsample, average, bicubic
  1343. --image-resolution <dpi>
  1344. Resolution in DPI of color and grayscale images in output
  1345. Default: 300
  1346. -p, --print-papers
  1347. Prints Standard Paper info tables to screen and exits
  1348. Scaling Mode:
  1349. - The default mode of operation is scaling mode with fixed paper
  1350. size and scaling pre-set to $SCALE
  1351. - By not using the resize mode you are using scaling mode
  1352. - Flip-Detection and Auto-Rotation are disabled in Scaling mode,
  1353. you can use '-r source -s <scale>' to override.
  1354. - Ghostscript placement is from bottom-left position. This means that
  1355. a bottom-left placement has ZERO for both X and Y translations.
  1356. Resize Paper Mode:
  1357. - Disables the default scaling factor! ($SCALE)
  1358. - Changes the PDF Paper Size in points. Will fit-to-page
  1359. Mixed Mode:
  1360. - In mixed mode both the -s option and -r option must be specified
  1361. - The PDF will be first resized then scaled
  1362. Output filename:
  1363. - Having the extension .pdf on the output file name is optional,
  1364. it will be added if not present.
  1365. - The output filename is optional. If no file name is passed
  1366. the output file will have the same name/destination of the
  1367. input file with added suffixes:
  1368. .SCALED.pdf is added to scaled files
  1369. .<PAPERSIZE>.pdf is added to resized files
  1370. .<PAPERSIZE>.SCALED.pdf is added in mixed mode
  1371. Standard Paper Names: (case-insensitive)
  1372. $paperList
  1373. Custom Paper Size:
  1374. - Paper size can be set manually in Milimeters, Inches or Points
  1375. - Custom paper definition MUST be quoted into a single parameter
  1376. - Actual size is applied in points (mms and inches are transformed)
  1377. - Measurements: mm, mms, milimeters
  1378. pt, pts, points
  1379. in, inch, inches
  1380. Use: $PDFSCALE_NAME -r 'custom <measurement> <width> <height>'
  1381. Ex: $PDFSCALE_NAME -r 'custom mm 300 300'
  1382. Using Source Paper Size: (no-resizing)
  1383. - Wildcard 'source' is used used to keep paper size the same as the input
  1384. - Usefull to run Auto-Rotation without resizing
  1385. - Eg. $PDFSCALE_NAME -r source ./input.dpf
  1386. Options and Parameters Parsing:
  1387. - From v2.1.0 (long-opts) there is no need to pass file names at the end
  1388. - Anything that is not a short-option is case-insensitive
  1389. - Short-options: case-sensitive Eg. -v for Verbose, -V for Version
  1390. - Long-options: case-insensitive Eg. --SCALE and --scale are the same
  1391. - Subparameters: case-insensitive Eg. -m PdFinFo is valid
  1392. - Grouping short-options is not supported Eg. -vv, or -vs 0.9
  1393. Additional Notes:
  1394. - File and folder names with spaces should be quoted or escaped
  1395. - The scaling is centered and using a scale bigger than 1.0 may
  1396. result on cropping parts of the PDF
  1397. - For detailed paper types information, use: $PDFSCALE_NAME -p
  1398. Examples:
  1399. $PDFSCALE_NAME myPdfFile.pdf
  1400. $PDFSCALE_NAME -i '/home/My Folder/My PDF File.pdf'
  1401. $PDFSCALE_NAME myPdfFile.pdf \"My Scaled Pdf\"
  1402. $PDFSCALE_NAME -v -v myPdfFile.pdf
  1403. $PDFSCALE_NAME -s 0.85 myPdfFile.pdf My\\ Scaled\\ Pdf.pdf
  1404. $PDFSCALE_NAME -m pdfinfo -s 0.80 -v myPdfFile.pdf
  1405. $PDFSCALE_NAME -v -v -m i -s 0.7 myPdfFile.pdf
  1406. $PDFSCALE_NAME -r A4 myPdfFile.pdf
  1407. $PDFSCALE_NAME -v -v -r \"custom mm 252 356\" -s 0.9 -f \"../input file.pdf\" \"../my new pdf\"
  1408. "
  1409. }
  1410. # Prints usage info
  1411. usage() {
  1412. [[ "$2" != 'nobanner' ]] && printVersion 2
  1413. isNotEmpty "$1" && printError $'\n'"$1"
  1414. printError $'\n'"Usage: $PDFSCALE_NAME [-v] [-s <factor>] [-m <mode>] [-r <paper> [-f <mode>] [-a <mode>]] <inFile.pdf> [outfile.pdf]"
  1415. printError "Help : $PDFSCALE_NAME -h"
  1416. }
  1417. # Prints Verbose information
  1418. vprint() {
  1419. [[ $VERBOSE -eq 0 ]] && return $TRUE
  1420. timestamp=""
  1421. [[ $VERBOSE -gt 1 ]] && timestamp="$(date +%Y-%m-%d:%H:%M:%S) | "
  1422. echo "$timestamp$1"
  1423. }
  1424. # Prints dependency information and aborts execution
  1425. printDependency() {
  1426. printVersion 2
  1427. local brewName="$1"
  1428. [[ "$1" = 'pdfinfo' && "$OSNAME" = "Darwin" ]] && brewName="xpdf"
  1429. printError $'\n'"ERROR! You need to install the package '$1'"$'\n'
  1430. printError "Linux apt-get.: sudo apt-get install $1"
  1431. printError "Linux yum.....: sudo yum install $1"
  1432. printError "MacOS homebrew: brew install $brewName"
  1433. printError $'\n'"Aborting..."
  1434. exit $EXIT_MISSING_DEPENDENCY
  1435. }
  1436. # Prints initialization errors and aborts execution
  1437. initError() {
  1438. local errStr="$1"
  1439. local exitStat=$2
  1440. isEmpty "$exitStat" && exitStat=$EXIT_ERROR
  1441. usage "ERROR! $errStr" "$3"
  1442. exit $exitStat
  1443. }
  1444. # Prints to stderr
  1445. printError() {
  1446. echo >&2 "$@"
  1447. }
  1448. ########################## EXECUTION ###########################
  1449. initDeps
  1450. getOptions "${@}"
  1451. main
  1452. exit $?