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.
 
 

2132 line
77 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 / 12
  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.3.7"
  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. ############################# Execution Flags
  69. SIMULATE=$FALSE # Avoid execution
  70. PRINT_GS_CALL=$FALSE # Print GS Call to stdout
  71. GS_CALL_STRING="" # Buffer
  72. ############################# Project Info
  73. PROJECT_NAME="pdfScale"
  74. PROJECT_URL="https://github.com/tavinus/$PROJECT_NAME"
  75. PROJECT_BRANCH='master'
  76. HTTPS_INSECURE=$FALSE
  77. ASSUME_YES=$FALSE
  78. RUN_SELF_INSTALL=$FALSE
  79. TARGET_LOC="/usr/local/bin/pdfscale"
  80. ########################## EXIT FLAGS ##########################
  81. EXIT_SUCCESS=0
  82. EXIT_ERROR=1
  83. EXIT_INVALID_PAGE_SIZE_DETECTED=10
  84. EXIT_FILE_NOT_FOUND=20
  85. EXIT_INPUT_NOT_PDF=21
  86. EXIT_INVALID_OPTION=22
  87. EXIT_NO_INPUT_FILE=23
  88. EXIT_INVALID_SCALE=24
  89. EXIT_MISSING_DEPENDENCY=25
  90. EXIT_IMAGEMAGIK_NOT_FOUND=26
  91. EXIT_MAC_MDLS_NOT_FOUND=27
  92. EXIT_PDFINFO_NOT_FOUND=28
  93. EXIT_NOWRITE_PERMISSION=29
  94. EXIT_NOREAD_PERMISSION=30
  95. EXIT_TEMP_FILE_EXISTS=40
  96. EXIT_INVALID_PAPER_SIZE=50
  97. EXIT_INVALID_IMAGE_RESOLUTION=51
  98. ############################# MAIN #############################
  99. # Main function called at the end
  100. main() {
  101. printPDFSizes # may exit here
  102. local finalRet=$EXIT_ERROR
  103. if isMixedMode; then
  104. initMain " Mixed Tasks: Resize & Scale"
  105. local tempFile=""
  106. local tempSuffix="$RANDOM$RANDOM""_TEMP_$RANDOM$RANDOM.pdf"
  107. outputFile="$OUTFILEPDF" # backup outFile name
  108. tempFile="${OUTFILEPDF%.pdf}.$tempSuffix" # set a temp file name
  109. if isFile "$tempFile"; then
  110. printError $'Error! Temporary file name already exists!\n'"File: $tempFile"$'\nAborting execution to avoid overwriting the file.\nPlease Try again...'
  111. exit $EXIT_TEMP_FILE_EXISTS
  112. fi
  113. OUTFILEPDF="$tempFile" # set output to tmp file
  114. pageResize # resize to tmp file
  115. finalRet=$?
  116. INFILEPDF="$tempFile" # get tmp file as input
  117. OUTFILEPDF="$outputFile" # reset final target
  118. PGWIDTH=$RESIZE_WIDTH # we already know the new page size
  119. PGHEIGHT=$RESIZE_HEIGHT # from the last command (Resize)
  120. vPrintPageSizes ' New'
  121. vPrintScaleFactor
  122. pageScale # scale the resized pdf
  123. finalRet=$(($finalRet+$?))
  124. # remove tmp file
  125. if isFile "$tempFile"; then
  126. rm "$tempFile" >/dev/null 2>&1 || printError "Error when removing temporary file: $tempFile"
  127. fi
  128. elif isResizeMode; then
  129. initMain " Single Task: Resize PDF Paper"
  130. vPrintScaleFactor "Disabled (resize only)"
  131. pageResize
  132. finalRet=$?
  133. else
  134. initMain " Single Task: Scale PDF Contents"
  135. local scaleMode=""
  136. isManualScaledMode && scaleMode='(manual)' || scaleMode='(auto)'
  137. vPrintScaleFactor "$SCALE $scaleMode"
  138. pageScale
  139. finalRet=$?
  140. fi
  141. if [[ $finalRet -eq $EXIT_SUCCESS ]] && isEmpty "$GS_RUN_STATUS"; then
  142. if isDryRun; then
  143. vprint " Final Status: Simulation completed successfully"
  144. else
  145. vprint " Final Status: File created successfully"
  146. fi
  147. else
  148. vprint " Final Status: Error detected. Exit status: $finalRet"
  149. printError "PdfScale: ERROR!"$'\n'"Ghostscript Debug Info:"$'\n'"$GS_RUN_STATUS"
  150. fi
  151. if isNotEmpty "$GS_CALL_STRING" && shouldPrintGSCall; then
  152. printf "%s" "$GS_CALL_STRING"
  153. fi
  154. return $finalRet
  155. }
  156. # Initializes PDF processing for all modes of operation
  157. initMain() {
  158. printVersion 1 'verbose'
  159. isNotEmpty "$1" && vprint "$1"
  160. local sim="FALSE"
  161. isDryRun && sim="TRUE (Simulating)"
  162. vprint " Dry-Run: $sim"
  163. vPrintFileInfo
  164. getPageSize
  165. vPrintPageSizes ' Source'
  166. }
  167. # Prints PDF Info and exits with $EXIT_SUCCESS, but only if $JUST_IDENTIFY is $TRUE
  168. printPDFSizes() {
  169. if [[ $JUST_IDENTIFY -eq $TRUE ]]; then
  170. VERBOSE=0
  171. printVersion 3 " - Paper Sizes"
  172. getPageSize || initError "Could not get pagesize!"
  173. local paperType="$(getGSPaperName $PGWIDTH $PGHEIGHT)"
  174. isEmpty "$paperType" && paperType="Custom Paper Size"
  175. printf '%s\n' "------------+-----------------------------"
  176. printf " File | %s\n" "$(basename "$INFILEPDF")"
  177. printf " Paper Type | %s\n" "$paperType"
  178. printf '%s\n' "------------+-----------------------------"
  179. printf '%s\n' " | WIDTH x HEIGHT"
  180. printf " Points | %+8s x %-8s\n" "$PGWIDTH" "$PGHEIGHT"
  181. printf " Milimeters | %+8s x %-8s\n" "$(pointsToMilimeters $PGWIDTH)" "$(pointsToMilimeters $PGHEIGHT)"
  182. printf " Inches | %+8s x %-8s\n" "$(pointsToInches $PGWIDTH)" "$(pointsToInches $PGHEIGHT)"
  183. exit $EXIT_SUCCESS
  184. fi
  185. return $EXIT_SUCCESS
  186. }
  187. ###################### GHOSTSCRIPT CALLS #######################
  188. # Runs the ghostscript scaling script
  189. pageScale() {
  190. # Compute translation factors to position pages
  191. CENTERXTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGWIDTH" | "$BCBIN")
  192. CENTERYTRANS=$(echo "scale=6; 0.5*(1.0-$SCALE)/$SCALE*$PGHEIGHT" | "$BCBIN")
  193. BXTRANS=$CENTERXTRANS
  194. BYTRANS=$CENTERYTRANS
  195. if [[ "$VERT_ALIGN" = "TOP" ]]; then
  196. BYTRANS=$(echo "scale=6; 2*$CENTERYTRANS" | "$BCBIN")
  197. elif [[ "$VERT_ALIGN" = "BOTTOM" ]]; then
  198. BYTRANS=0
  199. fi
  200. if [[ "$HOR_ALIGN" = "LEFT" ]]; then
  201. BXTRANS=0
  202. elif [[ "$HOR_ALIGN" = "RIGHT" ]]; then
  203. BXTRANS=$(echo "scale=6; 2*$CENTERXTRANS" | "$BCBIN")
  204. fi
  205. vprint " Vert-Align: $VERT_ALIGN"
  206. vprint " Hor-Align: $HOR_ALIGN"
  207. XTRANS=$(echo "scale=6; $BXTRANS + $XTRANSOFFSET" | "$BCBIN")
  208. YTRANS=$(echo "scale=6; $BYTRANS + $YTRANSOFFSET" | "$BCBIN")
  209. vprint "$(printf ' Translation X: %.2f = %.2f + %.2f (offset)' $XTRANS $BXTRANS $XTRANSOFFSET)"
  210. vprint "$(printf ' Translation Y: %.2f = %.2f + %.2f (offset)' $YTRANS $BYTRANS $YTRANSOFFSET)"
  211. local increase=$(echo "scale=0; (($SCALE - 1) * 100)/1" | "$BCBIN")
  212. vprint " Run Scaling: $increase %"
  213. GS_RUN_STATUS="$GS_RUN_STATUS""$(gsPageScale 2>&1)"
  214. GS_CALL_STRING="$GS_CALL_STRING"$'[GS SCALE CALL STARTS]\n'"$(gsPrintPageScale)"$'\n[GS SCALE CALL ENDS]\n'
  215. return $? # Last command is always returned I think
  216. }
  217. # Runs GS call for scaling, nothing else should run here
  218. gsPageScale() {
  219. if isDryRun; then
  220. return $TRUE
  221. fi
  222. # Scale page
  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=$PGWIDTH -dDEVICEHEIGHTPOINTS=$PGHEIGHT \
  231. -sOutputFile="$OUTFILEPDF" \
  232. -c "<</BeginPage{$SCALE $SCALE scale $XTRANS $YTRANS translate}>> setpagedevice" \
  233. -f "$INFILEPDF"
  234. }
  235. # Prints GS call for scaling
  236. gsPrintPageScale() {
  237. local _call_str=""
  238. # Print Scale page command
  239. read -d '' _call_str<< _EOF_
  240. "$GSBIN" \
  241. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  242. -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \
  243. -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \
  244. -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \
  245. -dColorConversionStrategy=/LeaveColorUnchanged \
  246. -dSubsetFonts=true -dEmbedAllFonts=true \
  247. -dDEVICEWIDTHPOINTS=$PGWIDTH -dDEVICEHEIGHTPOINTS=$PGHEIGHT \
  248. -sOutputFile="$OUTFILEPDF" \
  249. -c "<</BeginPage{$SCALE $SCALE scale $XTRANS $YTRANS translate}>> setpagedevice" \
  250. -f "$INFILEPDF"
  251. _EOF_
  252. echo -ne "$_call_str"
  253. }
  254. # Runs the ghostscript paper resize script
  255. pageResize() {
  256. # Get paper sizes from source if not resizing
  257. isResizePaperSource && { RESIZE_WIDTH=$PGWIDTH; RESIZE_HEIGHT=$PGHEIGHT; }
  258. # Get new paper sizes if not custom or source paper
  259. isNotCustomPaper && ! isResizePaperSource && getGSPaperSize "$RESIZE_PAPER_TYPE"
  260. vprint " Auto Rotate: $(basename $AUTO_ROTATION)"
  261. runFlipDetect
  262. vprint " Run Resizing: $(uppercase "$RESIZE_PAPER_TYPE") ( "$RESIZE_WIDTH" x "$RESIZE_HEIGHT" ) pts"
  263. GS_RUN_STATUS="$GS_RUN_STATUS""$(gsPageResize 2>&1)"
  264. GS_CALL_STRING="$GS_CALL_STRING"$'[GS RESIZE CALL STARTS]\n'"$(gsPrintPageResize)"$'\n[GS RESIZE CALL ENDS]\n'
  265. return $?
  266. }
  267. # Runs GS call for resizing, nothing else should run here
  268. gsPageResize() {
  269. if isDryRun; then
  270. return $TRUE
  271. fi
  272. # Change page size
  273. "$GSBIN" \
  274. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  275. -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \
  276. -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \
  277. -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \
  278. -dColorConversionStrategy=/LeaveColorUnchanged \
  279. -dSubsetFonts=true -dEmbedAllFonts=true \
  280. -dDEVICEWIDTHPOINTS=$RESIZE_WIDTH -dDEVICEHEIGHTPOINTS=$RESIZE_HEIGHT \
  281. -dAutoRotatePages=$AUTO_ROTATION \
  282. -dFIXEDMEDIA -dPDFFitPage \
  283. -sOutputFile="$OUTFILEPDF" \
  284. -f "$INFILEPDF"
  285. return $?
  286. }
  287. # Prints GS call for resizing
  288. gsPrintPageResize() {
  289. # Print Resize page command
  290. local _call_str=""
  291. # Print Scale page command
  292. read -d '' _call_str<< _EOF_
  293. "$GSBIN" \
  294. -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER \
  295. -dCompatibilityLevel="1.5" -dPDFSETTINGS="$PDF_SETTINGS" \
  296. -dColorImageResolution=$IMAGE_RESOLUTION -dGrayImageResolution=$IMAGE_RESOLUTION \
  297. -dColorImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" -dGrayImageDownsampleType="$IMAGE_DOWNSAMPLE_TYPE" \
  298. -dColorConversionStrategy=/LeaveColorUnchanged \
  299. -dSubsetFonts=true -dEmbedAllFonts=true \
  300. -dDEVICEWIDTHPOINTS=$RESIZE_WIDTH -dDEVICEHEIGHTPOINTS=$RESIZE_HEIGHT \
  301. -dAutoRotatePages=$AUTO_ROTATION \
  302. -dFIXEDMEDIA -dPDFFitPage \
  303. -sOutputFile="$OUTFILEPDF" \
  304. -f "$INFILEPDF"
  305. _EOF_
  306. echo -ne "$_call_str"
  307. }
  308. # Returns $TRUE if we should use the source paper size, $FALSE otherwise
  309. isResizePaperSource() {
  310. [[ "$RESIZE_PAPER_TYPE" = 'source' ]] && return $TRUE
  311. return $FALSE
  312. }
  313. # Filp-Detect Logic
  314. runFlipDetect() {
  315. if isFlipForced; then
  316. vprint " Flip Detect: Forced Mode!"
  317. applyFlipRevert
  318. elif isFlipDetectionEnabled && shouldFlip; then
  319. vprint " Flip Detect: Wrong orientation detected!"
  320. applyFlipRevert
  321. elif ! isFlipDetectionEnabled; then
  322. vprint " Flip Detect: Disabled"
  323. else
  324. vprint " Flip Detect: No change needed"
  325. fi
  326. }
  327. # Inverts $RESIZE_HEIGHT with $RESIZE_WIDTH
  328. applyFlipRevert() {
  329. local tmpInverter=""
  330. tmpInverter=$RESIZE_HEIGHT
  331. RESIZE_HEIGHT=$RESIZE_WIDTH
  332. RESIZE_WIDTH=$tmpInverter
  333. vprint " Inverting Width <-> Height"
  334. }
  335. # Returns the $FLIP_DETECTION flag
  336. isFlipDetectionEnabled() {
  337. return $FLIP_DETECTION
  338. }
  339. # Returns the $FLIP_FORCE flag
  340. isFlipForced() {
  341. return $FLIP_FORCE
  342. }
  343. # Returns $TRUE if the the paper size will invert orientation from source, $FALSE otherwise
  344. shouldFlip() {
  345. [[ $PGWIDTH -gt $PGHEIGHT && $RESIZE_WIDTH -lt $RESIZE_HEIGHT ]] || [[ $PGWIDTH -lt $PGHEIGHT && $RESIZE_WIDTH -gt $RESIZE_HEIGHT ]] && return $TRUE
  346. return $FALSE
  347. }
  348. ########################## INITIALIZERS #########################
  349. # Loads external dependencies and checks for errors
  350. initDeps() {
  351. GREPBIN="$(which grep 2>/dev/null)"
  352. GSBIN="$(which gs 2>/dev/null)"
  353. BCBIN="$(which bc 2>/dev/null)"
  354. IDBIN=$(which identify 2>/dev/null)
  355. MDLSBIN="$(which mdls 2>/dev/null)"
  356. PDFINFOBIN="$(which pdfinfo 2>/dev/null)"
  357. vprint "Checking for basename, grep, ghostscript and bcmath"
  358. basename "" >/dev/null 2>&1 || printDependency 'basename'
  359. isNotAvailable "$GREPBIN" && printDependency 'grep'
  360. isNotAvailable "$GSBIN" && printDependency 'ghostscript'
  361. isNotAvailable "$BCBIN" && printDependency 'bc'
  362. return $TRUE
  363. }
  364. # Checks for dependencies errors, run after getting options
  365. checkDeps() {
  366. if [[ $MODE = "IDENTIFY" ]]; then
  367. vprint "Checking for imagemagick's identify"
  368. if isNotAvailable "$IDBIN"; then printDependency 'imagemagick'; fi
  369. fi
  370. if [[ $MODE = "PDFINFO" ]]; then
  371. vprint "Checking for pdfinfo"
  372. if isNotAvailable "$PDFINFOBIN"; then printDependency 'pdfinfo'; fi
  373. fi
  374. if [[ $MODE = "MDLS" ]]; then
  375. vprint "Checking for MacOS mdls"
  376. if isNotAvailable "$MDLSBIN"; then
  377. initError 'mdls executable was not found! Is this even MacOS?' $EXIT_MAC_MDLS_NOT_FOUND
  378. fi
  379. fi
  380. return $TRUE
  381. }
  382. ######################### CLI OPTIONS ##########################
  383. # Parse options
  384. getOptions() {
  385. local _optArgs=() # things that do not start with a '-'
  386. local _tgtFile="" # to set $OUTFILEPDF
  387. local _currParam="" # to enable case-insensitiveness
  388. while [ ${#} -gt 0 ]; do
  389. if [[ "${1:0:2}" = '--' ]]; then
  390. # Long Option, get lowercase version
  391. _currParam="$(lowercase ${1})"
  392. elif [[ "${1:0:1}" = '-' ]]; then
  393. # short Option, just assign
  394. _currParam="${1}"
  395. else
  396. # file name arguments, store as is and reset loop
  397. _optArgs+=("$1")
  398. shift
  399. continue
  400. fi
  401. case "$_currParam" in
  402. -v|--verbose)
  403. ((VERBOSE++))
  404. shift
  405. ;;
  406. -n|--no-overwrite|--nooverwrite)
  407. ABORT_ON_OVERWRITE=$TRUE
  408. shift
  409. ;;
  410. -h|--help)
  411. printHelp
  412. exit $EXIT_SUCCESS
  413. ;;
  414. -V|--version)
  415. printVersion 3
  416. exit $EXIT_SUCCESS
  417. ;;
  418. -i|--identify|--info)
  419. JUST_IDENTIFY=$TRUE
  420. shift
  421. ;;
  422. -s|--scale|--setscale|--set-scale)
  423. shift
  424. parseScale "$1"
  425. shift
  426. ;;
  427. -m|--mode|--paperdetect|--paper-detect|--pagesizemode|--page-size-mode)
  428. shift
  429. parseMode "$1"
  430. shift
  431. ;;
  432. -r|--resize)
  433. shift
  434. parsePaperResize "$1"
  435. shift
  436. ;;
  437. -p|--printpapers|--print-papers|--listpapers|--list-papers)
  438. printPaperInfo
  439. exit $EXIT_SUCCESS
  440. ;;
  441. -f|--flipdetection|--flip-detection|--flip-mode|--flipmode|--flipdetect|--flip-detect)
  442. shift
  443. parseFlipDetectionMode "$1"
  444. shift
  445. ;;
  446. -a|--autorotation|--auto-rotation|--autorotate|--auto-rotate)
  447. shift
  448. parseAutoRotationMode "$1"
  449. shift
  450. ;;
  451. --pdf-settings)
  452. shift
  453. parsePDFSettings "$1"
  454. shift
  455. ;;
  456. --image-downsample)
  457. shift
  458. parseImageDownSample "$1"
  459. shift
  460. ;;
  461. --image-resolution)
  462. shift
  463. parseImageResolution "$1"
  464. shift
  465. ;;
  466. --horizontal-alignment|--hor-align|--xalign|--x-align)
  467. shift
  468. parseHorizontalAlignment "$1"
  469. shift
  470. ;;
  471. --vertical-alignment|--ver-align|--vert-align|--yalign|--y-align)
  472. shift
  473. parseVerticalAlignment "$1"
  474. shift
  475. ;;
  476. --xtrans|--xtrans-offset|--xoffset)
  477. shift
  478. parseXTransOffset "$1"
  479. shift
  480. ;;
  481. --ytrans|--ytrans-offset|--yoffset)
  482. shift
  483. parseYTransOffset "$1"
  484. shift
  485. ;;
  486. --simulate|--dry-run)
  487. SIMULATE=$TRUE
  488. shift
  489. ;;
  490. --install|--self-install)
  491. RUN_SELF_INSTALL=$TRUE
  492. shift
  493. if [[ ${1:0:1} != "-" ]]; then
  494. TARGET_LOC="$1"
  495. shift
  496. fi
  497. ;;
  498. --upgrade|--self-upgrade)
  499. RUN_SELF_UPGRADE=$TRUE
  500. shift
  501. ;;
  502. --insecure|--no-check-certificate)
  503. HTTPS_INSECURE=$TRUE
  504. shift
  505. ;;
  506. --yes|--assume-yes)
  507. ASSUME_YES=$TRUE
  508. shift
  509. ;;
  510. --print-gs-call|--gs-call)
  511. PRINT_GS_CALL=$TRUE
  512. shift
  513. ;;
  514. *)
  515. initError "Invalid Parameter: \"$1\"" $EXIT_INVALID_OPTION
  516. ;;
  517. esac
  518. done
  519. shouldInstall && selfInstall "$TARGET_LOC" # WILL EXIT HERE
  520. shouldUpgrade && selfUpgrade # WILL EXIT HERE
  521. isEmpty "${_optArgs[2]}" || initError "Seems like you passed an extra file name?"$'\n'"Invalid option: ${_optArgs[2]}" $EXIT_INVALID_OPTION
  522. if [[ $JUST_IDENTIFY -eq $TRUE ]]; then
  523. isEmpty "${_optArgs[1]}" || initError "Seems like you passed an extra file name?"$'\n'"Invalid option: ${_optArgs[1]}" $EXIT_INVALID_OPTION
  524. VERBOSE=0 # remove verboseness if present
  525. fi
  526. # Validate input PDF file
  527. INFILEPDF="${_optArgs[0]}"
  528. isEmpty "$INFILEPDF" && initError "Input file is empty!" $EXIT_NO_INPUT_FILE
  529. isPDF "$INFILEPDF" || initError "Input file is not a PDF file: $INFILEPDF" $EXIT_INPUT_NOT_PDF
  530. isFile "$INFILEPDF" || initError "Input file not found: $INFILEPDF" $EXIT_FILE_NOT_FOUND
  531. isReadable "$INFILEPDF" || initError "No read access to input file: $INFILEPDF"$'\nPermission Denied' $EXIT_NOREAD_PERMISSION
  532. checkDeps
  533. if [[ $JUST_IDENTIFY -eq $TRUE ]]; then
  534. return $TRUE # no need to get output file, so return already
  535. fi
  536. _tgtFile="${_optArgs[1]}"
  537. local _autoName="${INFILEPDF%.*}" # remove possible stupid extension, like .pDF
  538. if isMixedMode; then
  539. isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.$(uppercase $RESIZE_PAPER_TYPE).SCALED.pdf"
  540. elif isResizeMode; then
  541. isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.$(uppercase $RESIZE_PAPER_TYPE).pdf"
  542. else
  543. isEmpty "$_tgtFile" && OUTFILEPDF="${_autoName}.SCALED.pdf"
  544. fi
  545. isNotEmpty "$_tgtFile" && OUTFILEPDF="${_tgtFile%.pdf}.pdf"
  546. validateOutFile
  547. }
  548. # Returns $TRUE if the install flag is set
  549. shouldInstall() {
  550. return $RUN_SELF_INSTALL
  551. }
  552. # Returns $TRUE if the upgrade flag is set
  553. shouldUpgrade() {
  554. return $RUN_SELF_UPGRADE
  555. }
  556. # Install pdfScale
  557. selfInstall() {
  558. CURRENT_LOC="$(readlink -f $0)"
  559. TARGET_LOC="$1"
  560. isEmpty "$TARGET_LOC" && TARGET_LOC="/usr/local/bin/pdfscale"
  561. VERBOSE=0
  562. NEED_SUDO=$FALSE
  563. printVersion 3 " - Self Install"
  564. echo ""
  565. echo "Current location : $CURRENT_LOC"
  566. echo "Target location : $TARGET_LOC"
  567. if [[ "$CURRENT_LOC" = "$TARGET_LOC" ]]; then
  568. echo $'\n'"Error! Source and Target locations are the same!"
  569. echo "Cannot copy to itself..."
  570. exit $EXIT_INVALID_OPTION
  571. fi
  572. TARGET_FOLDER="$(dirname $TARGET_LOC)"
  573. local _answer="NO"
  574. if isNotDir "$TARGET_FOLDER"; then
  575. echo $'\nThe target folder does not exist\n > '"$TARGET_FOLDER"
  576. if assumeYes; then
  577. echo ''
  578. _answer="y"
  579. else
  580. read -p $'\nCreate the target folder? Y/y to continue > ' _answer
  581. _answer="$(lowercase $_answer)"
  582. fi
  583. if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then
  584. _answer="no"
  585. if mkdir -p "$TARGET_FOLDER" 2>/dev/null; then
  586. echo " > Folder Created!"
  587. else
  588. echo $'\n'"There was an error when trying to create the folder."
  589. if assumeYes; then
  590. echo $'\nTrying again with sudo, enter password if needed > '
  591. _answer="y"
  592. else
  593. read -p $'\nDo you want to try again with sudo (as root)? Y/y to continue > ' _answer
  594. _answer="$(lowercase $_answer)"
  595. fi
  596. if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then
  597. NEED_SUDO=$TRUE
  598. if sudo mkdir -p "$TARGET_FOLDER" 2>/dev/null; then
  599. echo "Folder Created!"
  600. else
  601. echo "There was an error when trying to create the folder."
  602. exit $EXIT_ERROR
  603. fi
  604. else
  605. echo "Exiting..."
  606. exit $EXIT_ERROR
  607. fi
  608. fi
  609. else
  610. echo "Exiting... (cancelled by user)"
  611. exit $EXIT_ERROR
  612. fi
  613. fi
  614. _answer="no"
  615. if isFile "$TARGET_LOC"; then
  616. echo $'\n'"The target file already exists: $TARGET_LOC"
  617. if assumeYes; then
  618. _answer="y"
  619. else
  620. read -p "Y/y to overwrite, anything else to cancel > " _answer
  621. _answer="$(lowercase $_answer)"
  622. fi
  623. if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then
  624. echo "Target will be replaced!"
  625. else
  626. echo "Exiting... (cancelled by user)"
  627. exit $EXIT_ERROR
  628. fi
  629. fi
  630. if [[ $NEED_SUDO -eq $TRUE ]]; then
  631. if sudo cp "$CURRENT_LOC" "$TARGET_LOC"; then
  632. echo $'\nSuccess! Program installed!'
  633. echo " > $TARGET_LOC"
  634. exit $EXIT_SUCCESS
  635. else
  636. echo "There was an error when trying to install the program."
  637. exit $EXIT_ERROR
  638. fi
  639. fi
  640. if cp "$CURRENT_LOC" "$TARGET_LOC"; then
  641. echo $'\nSuccess! Program installed!'
  642. echo " > $TARGET_LOC"
  643. exit $EXIT_SUCCESS
  644. else
  645. _answer="no"
  646. echo "There was an error when trying to install pdfScale."
  647. if assumeYes; then
  648. echo $'\nTrying again with sudo, enter password if needed > '
  649. _answer="y"
  650. else
  651. read -p $'Do you want to try again with sudo (as root)? Y/y to continue > ' _answer
  652. _answer="$(lowercase $_answer)"
  653. fi
  654. if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then
  655. NEED_SUDO=$TRUE
  656. if sudo cp "$CURRENT_LOC" "$TARGET_LOC"; then
  657. echo $'\nSuccess! Program installed!'
  658. echo " > $TARGET_LOC"
  659. exit $EXIT_SUCCESS
  660. else
  661. echo "There was an error when trying to install the program."
  662. exit $EXIT_ERROR
  663. fi
  664. else
  665. echo "Exiting... (cancelled by user)"
  666. exit $EXIT_ERROR
  667. fi
  668. fi
  669. exit $EXIT_ERROR
  670. }
  671. # Tries to download with curl or wget
  672. getUrl() {
  673. useInsecure && echo $'\nHTTPS Insecure flag is enabled!\nCertificates will be ignored by curl/wget\n'
  674. local url="$1"
  675. local target="$2"
  676. local _stat=""
  677. if isEmpty "$url" || isEmpty "$target"; then
  678. echo "Error! Invalid parameters for download."
  679. echo "URL > $url"
  680. echo "TARGET > $target"
  681. exit $EXIT_INVALID_OPTION
  682. fi
  683. WGET_BIN="$(which wget 2>/dev/null)"
  684. CURL_BIN="$(which curl 2>/dev/null)"
  685. if isExecutable "$WGET_BIN"; then
  686. useInsecure && WGET_BIN="$WGET_BIN --no-check-certificate"
  687. echo "Downloading file with wget"
  688. _stat="$($WGET_BIN -O "$target" "$url" 2>&1)"
  689. if [[ $? -eq 0 ]]; then
  690. return $TRUE
  691. else
  692. echo "Error when downloading file!"
  693. echo " > $url"
  694. echo "Status:"
  695. echo "$_stat"
  696. exit $EXIT_ERROR
  697. fi
  698. elif isExecutable "$CURL_BIN"; then
  699. useInsecure && CURL_BIN="$CURL_BIN --insecure"
  700. echo "Downloading file with curl"
  701. _stat="$($CURL_BIN -o "$target" "$url" 2>&1)"
  702. if [[ $? -eq 0 ]]; then
  703. return $TRUE
  704. else
  705. echo "Error when downloading file!"
  706. echo " > $url"
  707. echo "Status:"
  708. echo "$_stat"
  709. exit $EXIT_ERROR
  710. fi
  711. else
  712. echo "Error! Could not find Wget or Curl to perform download."
  713. echo "Please install either curl or wget and try again."
  714. exit $EXIT_FILE_NOT_FOUND
  715. fi
  716. }
  717. # Tries to remove temporary files from upgrade
  718. clearUpgrade() {
  719. echo $'\nCleaning up downloaded files from /tmp'
  720. if isFile "$TMP_TARGET"; then
  721. echo -n " > $TMP_TARGET > "
  722. rm "$TMP_TARGET" 2>/dev/null && echo "Ok" || echo "Fail"
  723. else
  724. echo " > no temporary tarball was found to remove"
  725. fi
  726. if isDir "$TMP_EXTRACTED"; then
  727. echo -n " > $TMP_EXTRACTED > "
  728. rm -rf "$TMP_EXTRACTED" 2>/dev/null && echo "Ok" || echo "Fail"
  729. else
  730. echo " > no temporary master folder was found to remove"
  731. fi
  732. }
  733. # Exit upgrade with message and status code
  734. # $1 Mensagem (printed if not empty)
  735. # $2 Status (defaults to $EXIT_ERROR)
  736. exitUpgrade() {
  737. isDir "$_cwd" && cd "$_cwd"
  738. isNotEmpty "$1" && echo "$1"
  739. clearUpgrade
  740. isNotEmpty "$2" && exit $2
  741. exit $EXIT_ERROR
  742. }
  743. # Downloads current version from github's MASTER branch
  744. selfUpgrade() {
  745. CURRENT_LOC="$(readlink -f $0)"
  746. _cwd="$(pwd)"
  747. local _cur_tstamp="$(date '+%Y%m%d-%H%M%S')"
  748. TMP_DIR='/tmp'
  749. TMP_TARGET="$TMP_DIR/pdfScale_$_cur_tstamp.tar.gz"
  750. TMP_EXTRACTED="$TMP_DIR/$PROJECT_NAME-$PROJECT_BRANCH"
  751. local _answer="no"
  752. printVersion 3 " - Self Upgrade"
  753. echo $'\n'"Preparing download to temp folder"
  754. echo " > $TMP_TARGET"
  755. getUrl "$PROJECT_URL/archive/$PROJECT_BRANCH.tar.gz" "$TMP_TARGET"
  756. if isNotFile "$TMP_TARGET"; then
  757. echo "Error! Could not find downloaded file!"
  758. exit $EXIT_FILE_NOT_FOUND
  759. fi
  760. echo $'\n'"Extracting compressed file"
  761. cd "$TMP_DIR"
  762. if ! (tar xzf "$TMP_TARGET" 2>/dev/null || gtar xzf "$TMP_TARGET" 2>/dev/null); then
  763. exitUpgrade "Extraction error."
  764. fi
  765. if ! cd "$TMP_EXTRACTED" 2>/dev/null; then
  766. exitUpgrade $'Error when accessing temporary folder\n > '"$TMP_EXTRACTED"
  767. fi
  768. if ! chmod +x pdfScale.sh; then
  769. exitUpgrade $'Error when setting new pdfScale to executable\n > '"$TMP_EXTRACTED/pdfScale.sh"
  770. fi
  771. local newver="$(./pdfScale.sh --version 2>/dev/null)"
  772. local curver="$(printVersion 3 2>/dev/null)"
  773. newver=($newver)
  774. curver=($curver)
  775. newver=${newver[1]#v}
  776. curver=${curver[1]#v}
  777. echo $'\n'" Current Version is: $curver"
  778. echo "Downloaded Version is: $newver"$'\n'
  779. if [[ "$newver" = "$curver" ]]; then
  780. echo "Seems like we have downloaded the same version that is installed."
  781. elif isBiggerVersion "$newver" "$curver"; then
  782. echo "Seems like the downloaded version is newer that the one installed."
  783. elif isBiggerVersion "$curver" "$newver"; then
  784. echo "Seems like the downloaded version is older that the one installed."
  785. echo "It is basically a miracle or you have came from the future with this version!"
  786. echo "BE CAREFUL NOT TO DELETE THE BETA/ALPHA VERSION WITH THIS UPDATE!"
  787. else
  788. exitUpgrade "An unidentified error has ocurred. Exiting..."
  789. fi
  790. if assumeYes; then
  791. echo $'\n'"Assume yes activated, current version will be replaced with master branch"
  792. _answer="y"
  793. else
  794. echo $'\n'"Are you sure that you want to replace the current installation with the downloaded one?"
  795. read -p "Y/y to continue, anything else to cancel > " _answer
  796. _answer="$(lowercase $_answer)"
  797. fi
  798. echo
  799. if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then
  800. echo "Upgrading..."
  801. if cp "./pdfScale.sh" "$CURRENT_LOC" 2>/dev/null; then
  802. exitUpgrade $'\n'"Success! Upgrade finished!"$'\n'" > $CURRENT_LOC" $EXIT_SUCCESS
  803. else
  804. _answer="no"
  805. echo $'\n'"There was an error when copying the new version."
  806. if assumeYes; then
  807. echo $'\nAssume yes activated, retrying with sudo.\nEnter password if needed > \n'
  808. _answer="y"
  809. else
  810. echo "Do you want to retry using sudo (as root)?"
  811. read -p "Y/y to continue, anything else to cancel > " _answer
  812. fi
  813. _answer="$(lowercase $_answer)"
  814. if [[ "$_answer" = "y" || "$_answer" = "yes" ]]; then
  815. echo "Upgrading with sudo..."
  816. if sudo cp "./pdfScale.sh" "$CURRENT_LOC" 2>/dev/null; then
  817. exitUpgrade $'\n'"Success! Upgrade finished!"$'\n'" > $CURRENT_LOC" $EXIT_SUCCESS
  818. else
  819. exitUpgrade "There was an error when copying the new version."
  820. fi
  821. else
  822. exitUpgrade "Exiting... (cancelled by user)"
  823. fi
  824. fi
  825. exitUpgrade "An unidentified error has ocurred. Exiting..."
  826. else
  827. exitUpgrade "Exiting... (cancelled by user)"
  828. fi
  829. exitUpgrade "An unidentified error has ocurred. Exiting..."
  830. }
  831. # Compares versions with x.x.x format
  832. isBiggerVersion() {
  833. local OIFS=$IFS
  834. IFS='.'
  835. local _first=($1)
  836. local _second=($2)
  837. local _ret=$FALSE
  838. if [[ ${_first[0]} -gt ${_second[0]} ]]; then
  839. _ret=$TRUE
  840. elif [[ ${_first[0]} -lt ${_second[0]} ]]; then
  841. _ret=$FALSE
  842. elif [[ ${_first[1]} -gt ${_second[1]} ]]; then
  843. _ret=$TRUE
  844. elif [[ ${_first[1]} -lt ${_second[1]} ]]; then
  845. _ret=$FALSE
  846. elif [[ ${_first[2]} -gt ${_second[2]} ]]; then
  847. _ret=$TRUE
  848. elif [[ ${_first[2]} -lt ${_second[2]} ]]; then
  849. _ret=$FALSE
  850. fi
  851. IFS=$OIFS
  852. return $_ret
  853. }
  854. # Checks if output file is valid and writable
  855. validateOutFile() {
  856. local _tgtDir="$(dirname "$OUTFILEPDF")"
  857. isDir "$_tgtDir" || initError "Output directory does not exist!"$'\n'"Target Dir: $_tgtDir" $EXIT_NOWRITE_PERMISSION
  858. 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
  859. isTouchable "$OUTFILEPDF" || initError "Could not get write permission for output file!"$'\n'"Target File: $OUTFILEPDF"$'\nPermission Denied' $EXIT_NOWRITE_PERMISSION
  860. }
  861. # Returns $TRUE if we should not overwrite $OUTFILEPDF, $FALSE otherwise
  862. isAbortOnOverwrite() {
  863. return $ABORT_ON_OVERWRITE
  864. }
  865. # Returns $TRUE if we should print the GS call to stdout
  866. shouldPrintGSCall() {
  867. return $PRINT_GS_CALL
  868. }
  869. # Returns $TRUE if we are simulating, dry-run (no GS execution)
  870. isDryRun() {
  871. return $SIMULATE
  872. }
  873. # Parses and validates the scaling factor
  874. parseScale() {
  875. AUTOMATIC_SCALING=$FALSE
  876. if ! isFloatBiggerThanZero "$1"; then
  877. printError "Invalid factor: $1"
  878. printError "The factor must be a floating point number greater than 0"
  879. printError "Example: for 80% use 0.8"
  880. exit $EXIT_INVALID_SCALE
  881. fi
  882. SCALE="$1"
  883. }
  884. # Parse a forced mode of operation
  885. parseMode() {
  886. local param="$(lowercase $1)"
  887. case "${param}" in
  888. c|catgrep|'cat+grep'|grep|g)
  889. ADAPTIVEMODE=$FALSE
  890. MODE="CATGREP"
  891. return $TRUE
  892. ;;
  893. i|imagemagick|identify)
  894. ADAPTIVEMODE=$FALSE
  895. MODE="IDENTIFY"
  896. return $TRUE
  897. ;;
  898. m|mdls|quartz|mac)
  899. ADAPTIVEMODE=$FALSE
  900. MODE="MDLS"
  901. return $TRUE
  902. ;;
  903. p|pdfinfo)
  904. ADAPTIVEMODE=$FALSE
  905. MODE="PDFINFO"
  906. return $TRUE
  907. ;;
  908. a|auto|automatic|adaptive)
  909. ADAPTIVEMODE=$TRUE
  910. MODE=""
  911. return $TRUE
  912. ;;
  913. *)
  914. initError "Invalid PDF Size Detection Mode: \"$1\"" $EXIT_INVALID_OPTION
  915. return $FALSE
  916. ;;
  917. esac
  918. return $FALSE
  919. }
  920. # Parses and validates the scaling factor
  921. parseFlipDetectionMode() {
  922. local param="$(lowercase $1)"
  923. case "${param}" in
  924. d|disable)
  925. FLIP_DETECTION=$FALSE
  926. FLIP_FORCE=$FALSE
  927. ;;
  928. f|force)
  929. FLIP_DETECTION=$FALSE
  930. FLIP_FORCE=$TRUE
  931. ;;
  932. a|auto|automatic)
  933. FLIP_DETECTION=$TRUE
  934. FLIP_FORCE=$FALSE
  935. ;;
  936. *)
  937. initError "Invalid Flip Detection Mode: \"$1\"" $EXIT_INVALID_OPTION
  938. return $FALSE
  939. ;;
  940. esac
  941. }
  942. # Parses and validates the scaling factor
  943. parseAutoRotationMode() {
  944. local param="$(lowercase $1)"
  945. case "${param}" in
  946. n|none|'/none')
  947. AUTO_ROTATION='/None'
  948. ;;
  949. a|all|'/all')
  950. AUTO_ROTATION='/All'
  951. ;;
  952. p|pagebypage|'/pagebypage'|auto)
  953. AUTO_ROTATION='/PageByPage'
  954. ;;
  955. *)
  956. initError "Invalid Auto Rotation Mode: \"$1\"" $EXIT_INVALID_OPTION
  957. return $FALSE
  958. ;;
  959. esac
  960. }
  961. # Validades the a paper resize CLI option and sets the paper to $RESIZE_PAPER_TYPE
  962. parsePaperResize() {
  963. isEmpty "$1" && initError 'Invalid Paper Type: (empty)' $EXIT_INVALID_PAPER_SIZE
  964. local lowercasePaper="$(lowercase $1)"
  965. local customPaper=($lowercasePaper)
  966. if [[ "$customPaper" = 'same' || "$customPaper" = 'keep' || "$customPaper" = 'source' ]]; then
  967. RESIZE_PAPER_TYPE='source'
  968. elif [[ "${customPaper[0]}" = 'custom' ]]; then
  969. if isNotValidMeasure "${customPaper[1]}" || ! isFloatBiggerThanZero "${customPaper[2]}" || ! isFloatBiggerThanZero "${customPaper[3]}"; then
  970. initError "Invalid Custom Paper Definition!"$'\n'"Use: -r 'custom <measurement> <width> <height>'"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION
  971. fi
  972. RESIZE_PAPER_TYPE="custom"
  973. CUSTOM_RESIZE_PAPER=$TRUE
  974. if isMilimeter "${customPaper[1]}"; then
  975. RESIZE_WIDTH="$(milimetersToPoints "${customPaper[2]}")"
  976. RESIZE_HEIGHT="$(milimetersToPoints "${customPaper[3]}")"
  977. elif isInch "${customPaper[1]}"; then
  978. RESIZE_WIDTH="$(inchesToPoints "${customPaper[2]}")"
  979. RESIZE_HEIGHT="$(inchesToPoints "${customPaper[3]}")"
  980. elif isPoint "${customPaper[1]}"; then
  981. RESIZE_WIDTH="${customPaper[2]}"
  982. RESIZE_HEIGHT="${customPaper[3]}"
  983. else
  984. initError "Invalid Custom Paper Definition!"$'\n'"Use: -r 'custom <measurement> <width> <height>'"$'\n'"Measurements: mm, in, pts" $EXIT_INVALID_OPTION
  985. fi
  986. else
  987. isPaperName "$lowercasePaper" || initError "Invalid Paper Type: $1" $EXIT_INVALID_PAPER_SIZE
  988. RESIZE_PAPER_TYPE="$lowercasePaper"
  989. fi
  990. }
  991. # Goes to GS -dColorImageResolution and -dGrayImageResolution parameters
  992. parseImageResolution() {
  993. if isNotAnInteger "$1"; then
  994. printError "Invalid image resolution: $1"
  995. printError "The image resolution must be an integer"
  996. exit $EXIT_INVALID_IMAGE_RESOLUTION
  997. fi
  998. IMAGE_RESOLUTION="$1"
  999. }
  1000. # Goes to GS -dColorImageDownsampleType and -dGrayImageDownsampleType parameters
  1001. parseImageDownSample() {
  1002. local param="$(lowercase $1)"
  1003. case "${param}" in
  1004. s|subsample|'/subsample')
  1005. IMAGE_DOWNSAMPLE_TYPE='/Subsample'
  1006. ;;
  1007. a|average|'/average')
  1008. IMAGE_DOWNSAMPLE_TYPE='/Average'
  1009. ;;
  1010. b|bicubic|'/bicubic'|auto)
  1011. IMAGE_DOWNSAMPLE_TYPE='/Bicubic'
  1012. ;;
  1013. *)
  1014. initError "Invalid Image Downsample Mode: \"$1\"" $EXIT_INVALID_OPTION
  1015. return $FALSE
  1016. ;;
  1017. esac
  1018. }
  1019. # Goes to GS -dColorImageDownsampleType and -dGrayImageDownsampleType parameters
  1020. parsePDFSettings() {
  1021. local param="$(lowercase $1)"
  1022. case "${param}" in
  1023. s|screen|'/screen')
  1024. PDF_SETTINGS='/screen'
  1025. ;;
  1026. e|ebook|'/ebook')
  1027. PDF_SETTINGS='/ebook'
  1028. ;;
  1029. p|printer|'/printer'|auto)
  1030. PDF_SETTINGS='/printer'
  1031. ;;
  1032. r|prepress|'/prepress')
  1033. PDF_SETTINGS='/prepress'
  1034. ;;
  1035. d|default|'/default')
  1036. PDF_SETTINGS='/default'
  1037. ;;
  1038. *)
  1039. initError "Invalid PDF Setting Profile: \"$1\""$'\nValid > printer, screen, ebook, prepress, default' $EXIT_INVALID_OPTION
  1040. return $FALSE
  1041. ;;
  1042. esac
  1043. }
  1044. # How to position the resized pages (sets translation)
  1045. parseHorizontalAlignment() {
  1046. local param="$(lowercase $1)"
  1047. case "${param}" in
  1048. l|left)
  1049. HOR_ALIGN='LEFT'
  1050. ;;
  1051. r|right)
  1052. HOR_ALIGN='RIGHT'
  1053. ;;
  1054. c|center|middle)
  1055. HOR_ALIGN='CENTER'
  1056. ;;
  1057. *)
  1058. initError "Invalid Horizontal Alignment Setting: \"$1\""$'\nValid > left, right, center' $EXIT_INVALID_OPTION
  1059. return $FALSE
  1060. ;;
  1061. esac
  1062. }
  1063. # How to position the resized pages (sets translation)
  1064. parseVerticalAlignment() {
  1065. local param="$(lowercase $1)"
  1066. case "${param}" in
  1067. t|top)
  1068. VERT_ALIGN='TOP'
  1069. ;;
  1070. b|bottom|bot)
  1071. VERT_ALIGN='BOTTOM'
  1072. ;;
  1073. c|center|middle)
  1074. VERT_ALIGN='CENTER'
  1075. ;;
  1076. *)
  1077. initError "Invalid Vertical Alignment Setting: \"$1\""$'\nValid > top, bottom, center' $EXIT_INVALID_OPTION
  1078. return $FALSE
  1079. ;;
  1080. esac
  1081. }
  1082. # Set X Translation Offset
  1083. parseXTransOffset() {
  1084. if isFloat "$1"; then
  1085. XTRANSOFFSET="$1"
  1086. return $TRUE
  1087. fi
  1088. printError "Invalid X Translation Offset: $1"
  1089. printError "The X Translation Offset must be a floating point number"
  1090. exit $EXIT_INVALID_OPTION
  1091. }
  1092. # Set Y Translation Offset
  1093. parseYTransOffset() {
  1094. if isFloat "$1"; then
  1095. YTRANSOFFSET="$1"
  1096. return $TRUE
  1097. fi
  1098. printError "Invalid Y Translation Offset: $1"
  1099. printError "The Y Translation Offset must be a floating point number"
  1100. exit $EXIT_INVALID_OPTION
  1101. }
  1102. ################### PDF PAGE SIZE DETECTION ####################
  1103. ################################################################
  1104. # Detects operation mode and also runs the adaptive mode
  1105. # PAGESIZE LOGIC
  1106. # 1- Try to get Mediabox with GREP
  1107. # 2- MacOS => try to use mdls
  1108. # 3- Try to use pdfinfo
  1109. # 4- Try to use identify (imagemagick)
  1110. # 5- Fail
  1111. ################################################################
  1112. getPageSize() {
  1113. if isNotAdaptiveMode; then
  1114. vprint " Get Page Size: Adaptive Disabled"
  1115. if [[ $MODE = "CATGREP" ]]; then
  1116. vprint " Method: Grep"
  1117. getPageSizeCatGrep
  1118. elif [[ $MODE = "MDLS" ]]; then
  1119. vprint " Method: Mac Quartz mdls"
  1120. getPageSizeMdls
  1121. elif [[ $MODE = "PDFINFO" ]]; then
  1122. vprint " Method: PDFInfo"
  1123. getPageSizePdfInfo
  1124. elif [[ $MODE = "IDENTIFY" ]]; then
  1125. vprint " Method: ImageMagick's Identify"
  1126. getPageSizeImagemagick
  1127. else
  1128. printError "Error! Invalid Mode: $MODE"
  1129. printError "Aborting execution..."
  1130. exit $EXIT_INVALID_OPTION
  1131. fi
  1132. return $TRUE
  1133. fi
  1134. vprint " Get Page Size: Adaptive Enabled"
  1135. vprint " Method: Grep"
  1136. getPageSizeCatGrep
  1137. if pageSizeIsInvalid && [[ $OSNAME = "Darwin" ]]; then
  1138. vprint " Failed"
  1139. vprint " Method: Mac Quartz mdls"
  1140. getPageSizeMdls
  1141. fi
  1142. if pageSizeIsInvalid; then
  1143. vprint " Failed"
  1144. vprint " Method: PDFInfo"
  1145. getPageSizePdfInfo
  1146. fi
  1147. if pageSizeIsInvalid; then
  1148. vprint " Failed"
  1149. vprint " Method: ImageMagick's Identify"
  1150. getPageSizeImagemagick
  1151. fi
  1152. if pageSizeIsInvalid; then
  1153. vprint " Failed"
  1154. printError "Error when detecting PDF paper size!"
  1155. printError "All methods of detection failed"
  1156. printError "You may want to install pdfinfo or imagemagick"
  1157. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  1158. fi
  1159. return $TRUE
  1160. }
  1161. # Gets page size using imagemagick's identify
  1162. getPageSizeImagemagick() {
  1163. # Sanity and Adaptive together
  1164. if isNotFile "$IDBIN" && isNotAdaptiveMode; then
  1165. notAdaptiveFailed "Make sure you installed ImageMagick and have identify on your \$PATH" "ImageMagick's Identify"
  1166. elif isNotFile "$IDBIN" && isAdaptiveMode; then
  1167. return $FALSE
  1168. fi
  1169. # get data from image magick
  1170. local identify="$("$IDBIN" -format '%[fx:w] %[fx:h]BREAKME' "$INFILEPDF" 2>/dev/null)"
  1171. if isEmpty "$identify" && isNotAdaptiveMode; then
  1172. notAdaptiveFailed "ImageMagicks's Identify returned an empty string!"
  1173. elif isEmpty "$identify" && isAdaptiveMode; then
  1174. return $FALSE
  1175. fi
  1176. identify="${identify%%BREAKME*}" # get page size only for 1st page
  1177. identify=($identify) # make it an array
  1178. PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign
  1179. PGHEIGHT=$(printf '%.0f' "${identify[1]}") # assign
  1180. return $TRUE
  1181. }
  1182. # Gets page size using Mac Quarts mdls
  1183. getPageSizeMdls() {
  1184. # Sanity and Adaptive together
  1185. if isNotFile "$MDLSBIN" && isNotAdaptiveMode; then
  1186. notAdaptiveFailed "Are you even trying this on a Mac?" "Mac Quartz mdls"
  1187. elif isNotFile "$MDLSBIN" && isAdaptiveMode; then
  1188. return $FALSE
  1189. fi
  1190. local identify="$("$MDLSBIN" -mdls -name kMDItemPageHeight -name kMDItemPageWidth "$INFILEPDF" 2>/dev/null)"
  1191. if isEmpty "$identify" && isNotAdaptiveMode; then
  1192. notAdaptiveFailed "Mac Quartz mdls returned an empty string!"
  1193. elif isEmpty "$identify" && isAdaptiveMode; then
  1194. return $FALSE
  1195. fi
  1196. identify=${identify//$'\t'/ } # change tab to space
  1197. identify=($identify) # make it an array
  1198. if [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isNotAdaptiveMode; then
  1199. notAdaptiveFailed "There was no metadata to read from the file! Is Spotlight OFF?"
  1200. elif [[ "${identify[5]}" = "(null)" || "${identify[2]}" = "(null)" ]] && isAdaptiveMode; then
  1201. return $FALSE
  1202. fi
  1203. PGWIDTH=$(printf '%.0f' "${identify[5]}") # assign
  1204. PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign
  1205. return $TRUE
  1206. }
  1207. # Gets page size using Linux PdfInfo
  1208. getPageSizePdfInfo() {
  1209. # Sanity and Adaptive together
  1210. if isNotFile "$PDFINFOBIN" && isNotAdaptiveMode; then
  1211. notAdaptiveFailed "Do you have pdfinfo installed and available on your \$PATH?" "Linux pdfinfo"
  1212. elif isNotFile "$PDFINFOBIN" && isAdaptiveMode; then
  1213. return $FALSE
  1214. fi
  1215. # get data from image magick
  1216. local identify="$("$PDFINFOBIN" "$INFILEPDF" 2>/dev/null | "$GREPBIN" -i 'Page size:' )"
  1217. if isEmpty "$identify" && isNotAdaptiveMode; then
  1218. notAdaptiveFailed "Linux PdfInfo returned an empty string!"
  1219. elif isEmpty "$identify" && isAdaptiveMode; then
  1220. return $FALSE
  1221. fi
  1222. identify="${identify##*Page size:}" # remove stuff
  1223. identify=($identify) # make it an array
  1224. PGWIDTH=$(printf '%.0f' "${identify[0]}") # assign
  1225. PGHEIGHT=$(printf '%.0f' "${identify[2]}") # assign
  1226. return $TRUE
  1227. }
  1228. # Gets page size using cat and grep
  1229. getPageSizeCatGrep() {
  1230. # get MediaBox info from PDF file using grep, these are all possible
  1231. # /MediaBox [0 0 595 841]
  1232. # /MediaBox [ 0 0 595.28 841.89]
  1233. # /MediaBox[ 0 0 595.28 841.89 ]
  1234. # Get MediaBox data if possible
  1235. local mediaBox="$("$GREPBIN" -a -e '/MediaBox' -m 1 "$INFILEPDF" 2>/dev/null)"
  1236. mediaBox="${mediaBox##*/MediaBox}"
  1237. mediaBox="${mediaBox##*[}"
  1238. mediaBox="${mediaBox%%]*}"
  1239. #echo "mediaBox=$mediaBox"
  1240. # No page size data available
  1241. if isEmpty "$mediaBox" && isNotAdaptiveMode; then
  1242. notAdaptiveFailed "There is no MediaBox in the pdf document!"
  1243. elif isEmpty "$mediaBox" && isAdaptiveMode; then
  1244. return $FALSE
  1245. fi
  1246. mediaBox=($mediaBox) # make it an array
  1247. mbCount=${#mediaBox[@]} # array size
  1248. # sanity
  1249. if [[ $mbCount -lt 4 ]] || ! isFloat "${mediaBox[2]}" || ! isFloat "${mediaBox[3]}" || isZero "${mediaBox[2]}" || isZero "${mediaBox[3]}"; then
  1250. if isNotAdaptiveMode; then
  1251. notAdaptiveFailed $'Error when reading the page size!\nThe page size information is invalid!'
  1252. fi
  1253. return $FALSE
  1254. fi
  1255. # we are done
  1256. PGWIDTH=$(printf '%.0f' "${mediaBox[2]}") # Get Round Width
  1257. PGHEIGHT=$(printf '%.0f' "${mediaBox[3]}") # Get Round Height
  1258. #echo "PGWIDTH=$PGWIDTH // PGHEIGHT=$PGHEIGHT"
  1259. return $TRUE
  1260. }
  1261. isZero() {
  1262. [[ "$1" == "0" ]] && return $TRUE
  1263. [[ "$1" == "0.0" ]] && return $TRUE
  1264. [[ "$1" == "0.00" ]] && return $TRUE
  1265. [[ "$1" == "0.000" ]] && return $TRUE
  1266. return $FALSE
  1267. }
  1268. # Prints error message and exits execution
  1269. notAdaptiveFailed() {
  1270. local errProgram="$2"
  1271. local errStr="$1"
  1272. if isEmpty "$2"; then
  1273. printError "Error when reading input file!"
  1274. printError "Could not determine the page size!"
  1275. else
  1276. printError "Error! $2 was not found!"
  1277. fi
  1278. isNotEmpty "$errStr" && printError "$errStr"
  1279. printError "Aborting! You may want to try the adaptive mode."
  1280. exit $EXIT_INVALID_PAGE_SIZE_DETECTED
  1281. }
  1282. # Verbose print of the Width and Height (Source or New) to screen
  1283. vPrintPageSizes() {
  1284. vprint " $1 Width: $PGWIDTH postscript-points"
  1285. vprint "$1 Height: $PGHEIGHT postscript-points"
  1286. }
  1287. #################### GHOSTSCRIPT PAPER INFO ####################
  1288. # Loads valid paper info to memory
  1289. getPaperInfo() {
  1290. # name inchesW inchesH mmW mmH pointsW pointsH
  1291. sizesUS="\
  1292. 11x17 11.0 17.0 279 432 792 1224
  1293. ledger 17.0 11.0 432 279 1224 792
  1294. legal 8.5 14.0 216 356 612 1008
  1295. letter 8.5 11.0 216 279 612 792
  1296. lettersmall 8.5 11.0 216 279 612 792
  1297. archE 36.0 48.0 914 1219 2592 3456
  1298. archD 24.0 36.0 610 914 1728 2592
  1299. archC 18.0 24.0 457 610 1296 1728
  1300. archB 12.0 18.0 305 457 864 1296
  1301. archA 9.0 12.0 229 305 648 864"
  1302. sizesISO="\
  1303. a0 33.1 46.8 841 1189 2384 3370
  1304. a1 23.4 33.1 594 841 1684 2384
  1305. a2 16.5 23.4 420 594 1191 1684
  1306. a3 11.7 16.5 297 420 842 1191
  1307. a4 8.3 11.7 210 297 595 842
  1308. a4small 8.3 11.7 210 297 595 842
  1309. a5 5.8 8.3 148 210 420 595
  1310. a6 4.1 5.8 105 148 297 420
  1311. a7 2.9 4.1 74 105 210 297
  1312. a8 2.1 2.9 52 74 148 210
  1313. a9 1.5 2.1 37 52 105 148
  1314. a10 1.0 1.5 26 37 73 105
  1315. isob0 39.4 55.7 1000 1414 2835 4008
  1316. isob1 27.8 39.4 707 1000 2004 2835
  1317. isob2 19.7 27.8 500 707 1417 2004
  1318. isob3 13.9 19.7 353 500 1001 1417
  1319. isob4 9.8 13.9 250 353 709 1001
  1320. isob5 6.9 9.8 176 250 499 709
  1321. isob6 4.9 6.9 125 176 354 499
  1322. c0 36.1 51.1 917 1297 2599 3677
  1323. c1 25.5 36.1 648 917 1837 2599
  1324. c2 18.0 25.5 458 648 1298 1837
  1325. c3 12.8 18.0 324 458 918 1298
  1326. c4 9.0 12.8 229 324 649 918
  1327. c5 6.4 9.0 162 229 459 649
  1328. c6 4.5 6.4 114 162 323 459"
  1329. sizesJIS="\
  1330. jisb0 NA NA 1030 1456 2920 4127
  1331. jisb1 NA NA 728 1030 2064 2920
  1332. jisb2 NA NA 515 728 1460 2064
  1333. jisb3 NA NA 364 515 1032 1460
  1334. jisb4 NA NA 257 364 729 1032
  1335. jisb5 NA NA 182 257 516 729
  1336. jisb6 NA NA 128 182 363 516"
  1337. sizesOther="\
  1338. flsa 8.5 13.0 216 330 612 936
  1339. flse 8.5 13.0 216 330 612 936
  1340. halfletter 5.5 8.5 140 216 396 612
  1341. hagaki 3.9 5.8 100 148 283 420"
  1342. sizesAll="\
  1343. $sizesUS
  1344. $sizesISO
  1345. $sizesJIS
  1346. $sizesOther"
  1347. }
  1348. # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT
  1349. getGSPaperSize() {
  1350. isEmpty "$sizesall" && getPaperInfo
  1351. while read l; do
  1352. local cols=($l)
  1353. if [[ "$1" == ${cols[0]} ]]; then
  1354. RESIZE_WIDTH=${cols[5]}
  1355. RESIZE_HEIGHT=${cols[6]}
  1356. return $TRUE
  1357. fi
  1358. done <<< "$sizesAll"
  1359. }
  1360. # Gets a paper size in points and sets it to RESIZE_WIDTH and RESIZE_HEIGHT
  1361. getGSPaperName() {
  1362. local w="$(printf "%.0f" $1)"
  1363. local h="$(printf "%.0f" $2)"
  1364. isEmpty "$sizesall" && getPaperInfo
  1365. # Because US Standard has inverted sizes, I need to scan 2 times
  1366. # instead of just testing if width is bigger than height
  1367. while read l; do
  1368. local cols=($l)
  1369. if [[ "$w" == ${cols[5]} && "$h" == ${cols[6]} ]]; then
  1370. printf "%s Portrait" $(uppercase ${cols[0]})
  1371. return $TRUE
  1372. fi
  1373. done <<< "$sizesAll"
  1374. while read l; do
  1375. local cols=($l)
  1376. if [[ "$w" == ${cols[6]} && "$h" == ${cols[5]} ]]; then
  1377. printf "%s Landscape" $(uppercase ${cols[0]})
  1378. return $TRUE
  1379. fi
  1380. done <<< "$sizesAll"
  1381. return $FALSE
  1382. }
  1383. # Loads an array with paper names to memory
  1384. getPaperNames() {
  1385. 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 \
  1386. 11x17 ledger legal letter lettersmall archE archD archC archB archA \
  1387. jisb0 jisb1 jisb2 jisb3 jisb4 jisb5 jisb6 \
  1388. flsa flse halfletter hagaki)
  1389. }
  1390. # Prints uppercase paper names to screen (used in help)
  1391. printPaperNames() {
  1392. isEmpty "$paperNames" && getPaperNames
  1393. for i in "${!paperNames[@]}"; do
  1394. [[ $i -eq 0 ]] && echo -n -e ' '
  1395. [[ $i -ne 0 && $((i % 5)) -eq 0 ]] && echo -n -e $'\n '
  1396. ppN="$(uppercase ${paperNames[i]})"
  1397. printf "%-14s" "$ppN"
  1398. done
  1399. echo ""
  1400. }
  1401. # Returns $TRUE if $! is a valid paper name, $FALSE otherwise
  1402. isPaperName() {
  1403. isEmpty "$1" && return $FALSE
  1404. isEmpty "$paperNames" && getPaperNames
  1405. for i in "${paperNames[@]}"; do
  1406. [[ "$i" = "$1" ]] && return $TRUE
  1407. done
  1408. return $FALSE
  1409. }
  1410. # Prints all tables with ghostscript paper information
  1411. printPaperInfo() {
  1412. printVersion 3
  1413. echo $'\n'"Paper Sizes Information"$'\n'
  1414. getPaperInfo
  1415. printPaperTable "ISO STANDARD" "$sizesISO"; echo
  1416. printPaperTable "US STANDARD" "$sizesUS"; echo
  1417. printPaperTable "JIS STANDARD *Aproximated Points" "$sizesJIS"; echo
  1418. printPaperTable "OTHERS" "$sizesOther"; echo
  1419. }
  1420. # GS paper table helper, prints a full line
  1421. printTableLine() {
  1422. echo '+-----------------------------------------------------------------+'
  1423. }
  1424. # GS paper table helper, prints a line with dividers
  1425. printTableDivider() {
  1426. echo '+-----------------+-------+-------+-------+-------+-------+-------+'
  1427. }
  1428. # GS paper table helper, prints a table header
  1429. printTableHeader() {
  1430. echo '| Name | inchW | inchH | mm W | mm H | pts W | pts H |'
  1431. }
  1432. # GS paper table helper, prints a table title
  1433. printTableTitle() {
  1434. printf "| %-64s%s\n" "$1" '|'
  1435. }
  1436. # GS paper table printer, prints a table for a paper variable
  1437. printPaperTable() {
  1438. printTableLine
  1439. printTableTitle "$1"
  1440. printTableLine
  1441. printTableHeader
  1442. printTableDivider
  1443. while read l; do
  1444. local cols=($l)
  1445. printf "| %-15s | %+5s | %+5s | %+5s | %+5s | %+5s | %+5s |\n" ${cols[*]};
  1446. done <<< "$2"
  1447. printTableDivider
  1448. }
  1449. # Returns $TRUE if $1 is a valid measurement for a custom paper, $FALSE otherwise
  1450. isNotValidMeasure() {
  1451. isMilimeter "$1" || isInch "$1" || isPoint "$1" && return $FALSE
  1452. return $TRUE
  1453. }
  1454. # Returns $TRUE if $1 is a valid milimeter string, $FALSE otherwise
  1455. isMilimeter() {
  1456. [[ "$1" = 'mm' || "$1" = 'milimeters' || "$1" = 'milimeter' ]] && return $TRUE
  1457. return $FALSE
  1458. }
  1459. # Returns $TRUE if $1 is a valid inch string, $FALSE otherwise
  1460. isInch() {
  1461. [[ "$1" = 'in' || "$1" = 'inch' || "$1" = 'inches' ]] && return $TRUE
  1462. return $FALSE
  1463. }
  1464. # Returns $TRUE if $1 is a valid point string, $FALSE otherwise
  1465. isPoint() {
  1466. [[ "$1" = 'pt' || "$1" = 'pts' || "$1" = 'point' || "$1" = 'points' ]] && return $TRUE
  1467. return $FALSE
  1468. }
  1469. # Returns $TRUE if a custom paper is being used, $FALSE otherwise
  1470. isCustomPaper() {
  1471. return $CUSTOM_RESIZE_PAPER
  1472. }
  1473. # Returns $FALSE if a custom paper is being used, $TRUE otherwise
  1474. isNotCustomPaper() {
  1475. isCustomPaper && return $FALSE
  1476. return $TRUE
  1477. }
  1478. ######################### CONVERSIONS ##########################
  1479. # Prints the lowercase char value for $1
  1480. lowercaseChar() {
  1481. case "$1" in
  1482. [A-Z])
  1483. n=$(printf "%d" "'$1")
  1484. n=$((n+32))
  1485. printf \\$(printf "%o" "$n")
  1486. ;;
  1487. *)
  1488. printf "%s" "$1"
  1489. ;;
  1490. esac
  1491. }
  1492. # Prints the lowercase version of a string
  1493. lowercase() {
  1494. word="$@"
  1495. for((i=0;i<${#word};i++))
  1496. do
  1497. ch="${word:$i:1}"
  1498. lowercaseChar "$ch"
  1499. done
  1500. }
  1501. # Prints the uppercase char value for $1
  1502. uppercaseChar(){
  1503. case "$1" in
  1504. [a-z])
  1505. n=$(printf "%d" "'$1")
  1506. n=$((n-32))
  1507. printf \\$(printf "%o" "$n")
  1508. ;;
  1509. *)
  1510. printf "%s" "$1"
  1511. ;;
  1512. esac
  1513. }
  1514. # Prints the uppercase version of a string
  1515. uppercase() {
  1516. word="$@"
  1517. for((i=0;i<${#word};i++))
  1518. do
  1519. ch="${word:$i:1}"
  1520. uppercaseChar "$ch"
  1521. done
  1522. }
  1523. # Prints the postscript points rounded equivalent from $1 mm
  1524. milimetersToPoints() {
  1525. local pts=$(echo "scale=8; $1 * 72 / 25.4" | "$BCBIN")
  1526. printf '%.0f' "$pts" # Print rounded conversion
  1527. }
  1528. # Prints the postscript points rounded equivalent from $1 inches
  1529. inchesToPoints() {
  1530. local pts=$(echo "scale=8; $1 * 72" | "$BCBIN")
  1531. printf '%.0f' "$pts" # Print rounded conversion
  1532. }
  1533. # Prints the mm equivalent from $1 postscript points
  1534. pointsToMilimeters() {
  1535. local pts=$(echo "scale=8; $1 / 72 * 25.4" | "$BCBIN")
  1536. printf '%.0f' "$pts" # Print rounded conversion
  1537. }
  1538. # Prints the inches equivalent from $1 postscript points
  1539. pointsToInches() {
  1540. local pts=$(echo "scale=8; $1 / 72" | "$BCBIN")
  1541. printf '%.1f' "$pts" # Print rounded conversion
  1542. }
  1543. ######################## MODE-DETECTION ########################
  1544. # Returns $TRUE if the scale was set manually, $FALSE if we are using automatic scaling
  1545. isManualScaledMode() {
  1546. [[ $AUTOMATIC_SCALING -eq $TRUE ]] && return $FALSE
  1547. return $TRUE
  1548. }
  1549. # Returns true if we are resizing a paper (ignores scaling), false otherwise
  1550. isResizeMode() {
  1551. isEmpty $RESIZE_PAPER_TYPE && return $FALSE
  1552. return $TRUE
  1553. }
  1554. # Returns true if we are resizing a paper and the scale was manually set
  1555. isMixedMode() {
  1556. isResizeMode && isManualScaledMode && return $TRUE
  1557. return $FALSE
  1558. }
  1559. # Return $TRUE if adaptive mode is enabled, $FALSE otherwise
  1560. isAdaptiveMode() {
  1561. return $ADAPTIVEMODE
  1562. }
  1563. # Return $TRUE if adaptive mode is disabled, $FALSE otherwise
  1564. isNotAdaptiveMode() {
  1565. isAdaptiveMode && return $FALSE
  1566. return $TRUE
  1567. }
  1568. ########################## VALIDATORS ##########################
  1569. # Returns $TRUE if $PGWIDTH OR $PGWIDTH are empty or NOT an Integer, $FALSE otherwise
  1570. pageSizeIsInvalid() {
  1571. if isNotAnInteger "$PGWIDTH" || isNotAnInteger "$PGHEIGHT"; then
  1572. return $TRUE
  1573. fi
  1574. return $FALSE
  1575. }
  1576. # Return $TRUE if $1 is empty, $FALSE otherwise
  1577. isEmpty() {
  1578. [[ -z "$1" ]] && return $TRUE
  1579. return $FALSE
  1580. }
  1581. # Return $TRUE if $1 is NOT empty, $FALSE otherwise
  1582. isNotEmpty() {
  1583. [[ -z "$1" ]] && return $FALSE
  1584. return $TRUE
  1585. }
  1586. # Returns $TRUE if $1 is an integer, $FALSE otherwise
  1587. isAnInteger() {
  1588. case $1 in
  1589. ''|*[!0-9]*) return $FALSE ;;
  1590. *) return $TRUE ;;
  1591. esac
  1592. }
  1593. # Returns $TRUE if $1 is NOT an integer, $FALSE otherwise
  1594. isNotAnInteger() {
  1595. case $1 in
  1596. ''|*[!0-9]*) return $TRUE ;;
  1597. *) return $FALSE ;;
  1598. esac
  1599. }
  1600. # Returns $TRUE if $1 is a floating point number (or an integer), $FALSE otherwise
  1601. isFloat() {
  1602. [[ -n "$1" && "$1" =~ ^-?[0-9]*([.][0-9]+)?$ ]] && return $TRUE
  1603. return $FALSE
  1604. }
  1605. # Returns $TRUE if $1 is a floating point number bigger than zero, $FALSE otherwise
  1606. isFloatBiggerThanZero() {
  1607. isFloat "$1" && [[ (( $1 > 0 )) ]] && return $TRUE
  1608. return $FALSE
  1609. }
  1610. # Returns $TRUE if $1 is readable, $FALSE otherwise
  1611. isReadable() {
  1612. [[ -r "$1" ]] && return $TRUE
  1613. return $FALSE;
  1614. }
  1615. # Returns $TRUE if $1 is a directory, $FALSE otherwise
  1616. isDir() {
  1617. [[ -d "$1" ]] && return $TRUE
  1618. return $FALSE;
  1619. }
  1620. # Returns $FALSE if $1 is a directory, $TRUE otherwise
  1621. isNotDir() {
  1622. isDir "$1" && return $FALSE
  1623. return $TRUE;
  1624. }
  1625. # Returns 0 if succeded, other integer otherwise
  1626. isTouchable() {
  1627. touch "$1" 2>/dev/null
  1628. }
  1629. # Returns $TRUE if $1 has a .pdf extension, false otherwsie
  1630. isPDF() {
  1631. [[ "$(lowercase $1)" =~ ^..*\.pdf$ ]] && return $TRUE
  1632. return $FALSE
  1633. }
  1634. # Returns $TRUE if $1 is a file, false otherwsie
  1635. isFile() {
  1636. [[ -f "$1" ]] && return $TRUE
  1637. return $FALSE
  1638. }
  1639. # Returns $TRUE if $1 is NOT a file, false otherwsie
  1640. isNotFile() {
  1641. [[ -f "$1" ]] && return $FALSE
  1642. return $TRUE
  1643. }
  1644. # Returns $TRUE if $1 is executable, false otherwsie
  1645. isExecutable() {
  1646. [[ -x "$1" ]] && return $TRUE
  1647. return $FALSE
  1648. }
  1649. # Returns $TRUE if $1 is NOT executable, false otherwsie
  1650. isNotExecutable() {
  1651. [[ -x "$1" ]] && return $FALSE
  1652. return $TRUE
  1653. }
  1654. # Returns $TRUE if $1 is a file and executable, false otherwsie
  1655. isAvailable() {
  1656. if isFile "$1" && isExecutable "$1"; then
  1657. return $TRUE
  1658. fi
  1659. return $FALSE
  1660. }
  1661. # Returns $TRUE if $1 is NOT a file or NOT executable, false otherwsie
  1662. isNotAvailable() {
  1663. if isNotFile "$1" || isNotExecutable "$1"; then
  1664. return $TRUE
  1665. fi
  1666. return $FALSE
  1667. }
  1668. # Returns $TRUE if we should avoid https certificate (on upgrade)
  1669. useInsecure() {
  1670. return $HTTPS_INSECURE
  1671. }
  1672. # Returns $TRUE if we should not ask anything and assume yes as answer
  1673. assumeYes() {
  1674. return $ASSUME_YES
  1675. }
  1676. # Returns $TRUE if we should ask the user for input
  1677. shouldAskUser() {
  1678. assumeYes && return $FALSE
  1679. return $TRUE
  1680. }
  1681. ###################### PRINTING TO SCREEN ######################
  1682. # Prints version
  1683. printVersion() {
  1684. local vStr=""
  1685. [[ "$2" = 'verbose' ]] && vStr=" - Verbose Execution"
  1686. local strBanner="$PDFSCALE_NAME v$VERSION$vStr"
  1687. if [[ $1 -eq 2 ]]; then
  1688. printError "$strBanner"
  1689. elif [[ $1 -eq 3 ]]; then
  1690. local extra="$(isNotEmpty "$2" && echo "$2")"
  1691. echo "$strBanner$extra"
  1692. else
  1693. vprint "$strBanner"
  1694. fi
  1695. }
  1696. # Prints input, output file info, if verbosing
  1697. vPrintFileInfo() {
  1698. vprint " Input File: $INFILEPDF"
  1699. vprint " Output File: $OUTFILEPDF"
  1700. }
  1701. # Prints the scale factor to screen, or custom message
  1702. vPrintScaleFactor() {
  1703. local scaleMsg="$SCALE"
  1704. isNotEmpty "$1" && scaleMsg="$1"
  1705. vprint " Scale Factor: $scaleMsg"
  1706. }
  1707. # Prints help info
  1708. printHelp() {
  1709. printVersion 3
  1710. local paperList="$(printPaperNames)"
  1711. echo "
  1712. Usage: $PDFSCALE_NAME <inFile.pdf>
  1713. $PDFSCALE_NAME -i <inFile.pdf>
  1714. $PDFSCALE_NAME [-v] [-s <factor>] [-m <page-detection>] <inFile.pdf> [outfile.pdf]
  1715. $PDFSCALE_NAME [-v] [-r <paper>] [-f <flip-detection>] [-a <auto-rotation>] <inFile.pdf> [outfile.pdf]
  1716. $PDFSCALE_NAME -p
  1717. $PDFSCALE_NAME -h
  1718. $PDFSCALE_NAME -V
  1719. Parameters:
  1720. -v, --verbose
  1721. Verbose mode, prints extra information
  1722. Use twice for timestamp
  1723. -h, --help
  1724. Print this help to screen and exits
  1725. -V, --version
  1726. Prints version to screen and exits
  1727. --install, --self-install [target-path]
  1728. Install itself to [target-path] or /usr/local/bin/pdfscale if not specified
  1729. Should contain the full path with the desired executable name
  1730. --upgrade, --self-upgrade
  1731. Upgrades itself in-place (same path/name of the pdfScale.sh caller)
  1732. Downloads the master branch tarball and tries to self-upgrade
  1733. --insecure, --no-check-certificate
  1734. Use curl/wget without SSL library support
  1735. --yes, --assume-yes
  1736. Will answer yes to any prompt on install or upgrade, use with care
  1737. -n, --no-overwrite
  1738. Aborts execution if the output PDF file already exists
  1739. By default, the output file will be overwritten
  1740. -m, --mode <mode>
  1741. Paper size detection mode
  1742. Modes: a, adaptive Default mode, tries all the methods below
  1743. g, grep Forces the use of Grep method
  1744. m, mdls Forces the use of MacOS Quartz mdls
  1745. p, pdfinfo Forces the use of PDFInfo
  1746. i, identify Forces the use of ImageMagick's Identify
  1747. -i, --info <file>
  1748. Prints <file> Paper Size information to screen and exits
  1749. -s, --scale <factor>
  1750. Changes the scaling factor or forces mixed mode
  1751. Defaults: $SCALE (scale mode) / Disabled (resize mode)
  1752. MUST be a number bigger than zero
  1753. Eg. -s 0.8 for 80% of the original size
  1754. -r, --resize <paper>
  1755. Triggers the Resize Paper Mode, disables auto-scaling of $SCALE
  1756. Resize PDF and fit-to-page
  1757. <paper> can be: source, custom or a valid std paper name, read below
  1758. -f, --flip-detect <mode>
  1759. Flip Detection Mode, defaults to 'auto'
  1760. Inverts Width <-> Height of a Resized PDF
  1761. Modes: a, auto Keeps source orientation, default
  1762. f, force Forces flip W <-> H
  1763. d, disable Disables flipping
  1764. -a, --auto-rotate <mode>
  1765. Setting for GS -dAutoRotatePages, defaults to 'PageByPage'
  1766. Uses text-orientation detection to set Portrait/Landscape
  1767. Modes: p, pagebypage Auto-rotates pages individually
  1768. n, none Retains orientation of each page
  1769. a, all Rotates all pages (or none) depending
  1770. on a kind of \"majority decision\"
  1771. --hor-align, --horizontal-alignment <left|center|right>
  1772. Where to translate the scaled page
  1773. Default: center
  1774. Options: left, right, center
  1775. --vert-align, --vertical-alignment <top|center|bottom>
  1776. Where to translate the scaled page
  1777. Default: center
  1778. Options: top, bootom, center
  1779. --xoffset, --xtrans-offset <FloatNumber>
  1780. Add/Subtract from the X translation (move left-right)
  1781. Default: 0.0 (zero)
  1782. Options: Positive or negative floating point number
  1783. --yoffset, --ytrans-offset <FloatNumber>
  1784. Add/Subtract from the Y translation (move top-bottom)
  1785. Default: 0.0 (zero)
  1786. Options: Positive or negative floating point number
  1787. --pdf-settings <gs-pdf-profile>
  1788. Ghostscript PDF Profile to use in -dPDFSETTINGS
  1789. Default: printer
  1790. Options: screen, ebook, printer, prepress, default
  1791. --image-downsample <gs-downsample-method>
  1792. Ghostscript Image Downsample Method
  1793. Default: bicubic
  1794. Options: subsample, average, bicubic
  1795. --image-resolution <dpi>
  1796. Resolution in DPI of color and grayscale images in output
  1797. Default: 300
  1798. --dry-run, --simulate
  1799. Just simulate execution. Will not run ghostscript
  1800. --print-gs-call, --gs-call
  1801. Print GS call to stdout. Will print at the very end between markers
  1802. -p, --print-papers
  1803. Prints Standard Paper info tables to screen and exits
  1804. Scaling Mode:
  1805. - The default mode of operation is scaling mode with fixed paper
  1806. size and scaling pre-set to $SCALE
  1807. - By not using the resize mode you are using scaling mode
  1808. - Flip-Detection and Auto-Rotation are disabled in Scaling mode,
  1809. you can use '-r source -s <scale>' to override.
  1810. - Ghostscript placement is from bottom-left position. This means that
  1811. a bottom-left placement has ZERO for both X and Y translations.
  1812. Resize Paper Mode:
  1813. - Disables the default scaling factor! ($SCALE)
  1814. - Changes the PDF Paper Size in points. Will fit-to-page
  1815. Mixed Mode:
  1816. - In mixed mode both the -s option and -r option must be specified
  1817. - The PDF will be first resized then scaled
  1818. Output filename:
  1819. - Having the extension .pdf on the output file name is optional,
  1820. it will be added if not present.
  1821. - The output filename is optional. If no file name is passed
  1822. the output file will have the same name/destination of the
  1823. input file with added suffixes:
  1824. .SCALED.pdf is added to scaled files
  1825. .<PAPERSIZE>.pdf is added to resized files
  1826. .<PAPERSIZE>.SCALED.pdf is added in mixed mode
  1827. Standard Paper Names: (case-insensitive)
  1828. $paperList
  1829. Custom Paper Size:
  1830. - Paper size can be set manually in Milimeters, Inches or Points
  1831. - Custom paper definition MUST be quoted into a single parameter
  1832. - Actual size is applied in points (mms and inches are transformed)
  1833. - Measurements: mm, mms, milimeters
  1834. pt, pts, points
  1835. in, inch, inches
  1836. Use: $PDFSCALE_NAME -r 'custom <measurement> <width> <height>'
  1837. Ex: $PDFSCALE_NAME -r 'custom mm 300 300'
  1838. Using Source Paper Size: (no-resizing)
  1839. - Wildcard 'source' is used used to keep paper size the same as the input
  1840. - Usefull to run Auto-Rotation without resizing
  1841. - Eg. $PDFSCALE_NAME -r source ./input.dpf
  1842. Options and Parameters Parsing:
  1843. - From v2.1.0 (long-opts) there is no need to pass file names at the end
  1844. - Anything that is not a short-option is case-insensitive
  1845. - Short-options: case-sensitive Eg. -v for Verbose, -V for Version
  1846. - Long-options: case-insensitive Eg. --SCALE and --scale are the same
  1847. - Subparameters: case-insensitive Eg. -m PdFinFo is valid
  1848. - Grouping short-options is not supported Eg. -vv, or -vs 0.9
  1849. Additional Notes:
  1850. - File and folder names with spaces should be quoted or escaped
  1851. - The scaling is centered and using a scale bigger than 1.0 may
  1852. result on cropping parts of the PDF
  1853. - For detailed paper types information, use: $PDFSCALE_NAME -p
  1854. Examples:
  1855. $PDFSCALE_NAME myPdfFile.pdf
  1856. $PDFSCALE_NAME -i '/home/My Folder/My PDF File.pdf'
  1857. $PDFSCALE_NAME myPdfFile.pdf \"My Scaled Pdf\"
  1858. $PDFSCALE_NAME -v -v myPdfFile.pdf
  1859. $PDFSCALE_NAME -s 0.85 myPdfFile.pdf My\\ Scaled\\ Pdf.pdf
  1860. $PDFSCALE_NAME -m pdfinfo -s 0.80 -v myPdfFile.pdf
  1861. $PDFSCALE_NAME -v -v -m i -s 0.7 myPdfFile.pdf
  1862. $PDFSCALE_NAME -r A4 myPdfFile.pdf
  1863. $PDFSCALE_NAME -v -v -r \"custom mm 252 356\" -s 0.9 -f \"../input file.pdf\" \"../my new pdf\"
  1864. "
  1865. }
  1866. # Prints usage info
  1867. usage() {
  1868. [[ "$2" != 'nobanner' ]] && printVersion 2
  1869. isNotEmpty "$1" && printError $'\n'"$1"
  1870. printError $'\n'"Usage: $PDFSCALE_NAME [-v] [-s <factor>] [-m <mode>] [-r <paper> [-f <mode>] [-a <mode>]] <inFile.pdf> [outfile.pdf]"
  1871. printError "Help : $PDFSCALE_NAME -h"
  1872. }
  1873. # Prints Verbose information
  1874. vprint() {
  1875. [[ $VERBOSE -eq 0 ]] && return $TRUE
  1876. timestamp=""
  1877. [[ $VERBOSE -gt 1 ]] && timestamp="$(date +%Y-%m-%d:%H:%M:%S) | "
  1878. echo "$timestamp$1"
  1879. }
  1880. # Prints dependency information and aborts execution
  1881. printDependency() {
  1882. printVersion 2
  1883. local brewName="$1"
  1884. [[ "$1" = 'pdfinfo' && "$OSNAME" = "Darwin" ]] && brewName="xpdf"
  1885. printError $'\n'"ERROR! You need to install the package '$1'"$'\n'
  1886. printError "Linux apt-get.: sudo apt-get install $1"
  1887. printError "Linux yum.....: sudo yum install $1"
  1888. printError "MacOS homebrew: brew install $brewName"
  1889. printError $'\n'"Aborting..."
  1890. exit $EXIT_MISSING_DEPENDENCY
  1891. }
  1892. # Prints initialization errors and aborts execution
  1893. initError() {
  1894. local errStr="$1"
  1895. local exitStat=$2
  1896. isEmpty "$exitStat" && exitStat=$EXIT_ERROR
  1897. usage "ERROR! $errStr" "$3"
  1898. exit $exitStat
  1899. }
  1900. # Prints to stderr
  1901. printError() {
  1902. echo >&2 "$@"
  1903. }
  1904. ########################## EXECUTION ###########################
  1905. initDeps
  1906. getOptions "${@}"
  1907. main
  1908. exit $?