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.
 
 

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