I just figured someone could be interested in the script I had lying around my computer for quite some time now. I use it to crop PDFs for the reader. Example invocation
:/croppdf -o outfile.pdf infile.pdf 10 20. 500. 700 20 30. 500. 700
If you just run it without arguments, it gives a command summary for all features. Runs on Linux/Bash with ghostscript (command gs) installed.
Code:
#!/bin/bash
page_dimension=2
testmode=0
viewer=xdgopen
first_page=0
last_page=0
usage=0
for (( i = 0; i<page_dimension; i++ ))
do
ratio[ i ]=1
done
ratio[ 0 ]=596
ratio[ 1 ]=842
while getopts :tf:l:v:a:o:d: name
do
case "$name" in
t)
testmode=1
;;
f)
first_page=$(( OPTARG*1 ))
;;
l)
last_page=$(( OPTARG*1 ))
;;
o)
outfile="$OPTARG"
;;
v)
viewer="$OPTARG"
;;
d)
page_dimension=$((OPTARG))
;;
a)
remainder="${OPTARG}"
i=0
while echo "$remainder" | grep -oq '^[[:digit:]]*'
do
value=$(echo "$remainder" | grep -o '^[[:digit:]]*')
ratio[ i ]=$value
remainder=${remainder#${value},}
(( i++ ))
done
;;
?)
usage=1
;;
esac
done
shift $((OPTIND - 1))
edge_count=$((2*page_dimension))
if (( usage || $#<5 || ( $#-1 )%edge_count ))
then
echo "Usage $0 \\"
echo " (-t)? \\"
echo " (-a <Width>,<Height>)? \\"
echo " (-f <First Page>)? (-l <Last Page>)? \\"
echo " (-o <Out File>)? \\"
echo " (-d <Dimension>)? \\"
echo " (-v <Viewer>)? \\"
echo " <In File> (<Left> <Bottom> ... <Right> <Top> ... )+"
echo ""
printf "Crop pages of PDF document <In File> to remove margins. If <First Page> is specified, will only output pages after that page. If <Last Page> is specified, will only output pages before that page.\nThe first group of <Left>, <Bottom>, <Right>, and <Top> specify the desired area to be cropped to on the first page of <In File>. The desired area for the n-th page after that page is obtained from the according group, with the groups being cyclically continued.\n\nTESTMODE\nIf -t is given, all outputted pages are cropped to their respective, desired areas and the resulting pdf is shown with the command specified by <Viewer> or \`xdgopen\`, if <Viewer> is not specified. This allows to tweak the areas to the exact desired amount.\n\nNORMAL MODE\nIf -t is not given, the smallest possible area with an optimal aspect ratio of width and height that contains the desired area is calculated. The outputted pages are then cropped to that optimal area with the optimal aspect ratio. If any of the values of <Left>, <Bottom>, <Right> or <Bottom> were suffixed by a period sign (.), the desired area will be aligned with the according edge of the optimal area. Otherwise, the desired area is centered within the optimal area. If no <Out File> is given, a filename is guessed based upon the <In File>. Otherwise, the output will be written to <Out File>.\nThe optimal aspect ratio can be specified by passing <Width> and <Height> and defaults to portrait oriented DIN A4. The dimensionality of pages can be specified by <Dimension>. Currently, the Ghostscript backend only supports 2 dimensional pages." | fmt -s
exit 1
fi
infile=$1
shift 1
loop_count=$(($#/edge_count))
if [[ "$outfile" == "" ]]
then
if echo "$infile" | grep -q '\.[^\/]*$'
then
outfile="${infile%.*}_cropped.pdf"
else
outfile="${infile}_cropped"
fi
fi
optimal_ratio=$(echo "scale=3;${ratio[ 1 ]}/${ratio[ 0 ]}" | bc)
echo "Optimal ratio set to $optimal_ratio"
if (( testmode ))
then
outfile="$(mktemp)"
else
echo "Cropping '$infile' to '$outfile'"
if [[ -f "$outfile" ]]
then
echo "'$outfile' already exists. Overwrite? y/[n]"
read confirm
if [[ "$confirm" == "y" ]]
then
rm "$outfile"
else
exit 1
fi
fi
fi
# GHOST SCRIPT DATA
declare -a lefts bottoms rights tops transx transy
for (( i = 0; i < loop_count; i++ ))
do
echo "CropBox #$i:"
declare -a desired
aligns=(0 0)
for (( edge = 0; edge<edge_count; edge++ ))
do
index=$((1+edge+i*edge_count))
desired_value=${!index}
if [[ "$desired_value" == *'.' ]]
then
desired_value=${desired_value:0:-1}
(( aligns[ edge%page_dimension ]+=2*( edge/page_dimension )-1 ))
fi
desired[ edge ]=$((desired_value))
done
echo " Desired box: ${desired[ 0 ]} ${desired[ 1 ]} ${desired[ 2 ]} ${desired[ 3 ]}"
echo " H-Alignment: ${aligns[ 0 ]}"
echo " V-Alignment: ${aligns[ 1 ]}"
if ! (( testmode ))
then
# Find constraining dimension, expand page in all other dimensions
normalization=1
for (( axis = 0; axis<page_dimension; axis++ ))
do
(( normalization*=ratio[ axis ] ))
done
constraining_axis=0
constraining_factor=0
for (( axis = 0; axis<page_dimension; axis++ ))
do
desired_distance=$((desired[ axis+page_dimension ]-desired[ axis ]))
testfactor=$((( desired_distance*normalization )/ratio[ axis ]))
if (( testfactor>constraining_factor ))
then
constraining_axis=$axis
constraining_factor=$testfactor
fi
done
constraining_distance=$((desired[ constraining_axis+page_dimension ]-desired[ constraining_axis ]))
echo " Fit axis: $constraining_axis"
# Now expand all other directions
for (( align_dir = 0; align_dir<page_dimension; align_dir++ ))
do
if (( align_dir != constraining_axis ))
then
desired_distance=$((desired[ align_dir+page_dimension ]-desired[ align_dir ]))
needed_distance=$((( constraining_distance*ratio[ align_dir ] )/ratio[ constraining_axis ]))
margin=$((needed_distance-desired_distance))
echo " Margin along axis $align_dir: $margin"
for side in {0..1}
do
factor=$((( (-1)+2*side )-aligns[ align_dir ] ))
(( desired[ align_dir+2*side ]+=( margin*factor )/2 ))
done
fi
done
fi
echo " Effective box: ${desired[ 0 ]} ${desired[ 1 ]} ${desired[ 2 ]} ${desired[ 3 ]}"
# BEGIN GHOSTSCRIPT SPECIFIC DATA PREPARATION
if (( desired[ 0 ]>=0 ))
then
lefts[ i ]=${desired[ 0 ]}
rights[ i ]=${desired[ 2 ]}
transx[ i ]=0
else
lefts[ i ]=0
rights[ i ]=$((desired[ 2 ]-desired[ 0 ]))
transx[ i ]=$((-desired[ 0 ]))
fi
if (( desired[ 1 ]>=0 ))
then
bottoms[ i ]=${desired[ 1 ]}
tops[ i ]=${desired[ 3 ]}
transy[ i ]=0
else
bottoms[ i ]=0
tops[ i ]=$((desired[ 3 ]-desired[ 1 ]))
transy[ i ]=$((-desired[ 1 ]))
fi
# END GHOSTSCRIPT SPECIFIC DATA PREPARATION
done
pages_string=''
if (( $first_page ))
then
pages_string=" -dFirstPage=$first_page"
fi
if (( $last_page ))
then
pages_string="$pages_string -dLastPage=$last_page"
fi
if (( first_page ))
then
page_offset=$((first_page-1))
else
page_offset=0
fi
read -d '' code <<endcode
<<
/BeginPage
{
${page_offset} add
${loop_count} mod
[ ${transx[@]} ]
1 index
get
[ ${transy[@]} ]
2 index
get
translate
}
/EndPage
{
0 eq
{
${page_offset} add
${loop_count} mod
[
/CropBox
[
[ ${lefts[@]} ]
4 index
get
[ ${bottoms[@]} ]
5 index
get
[ ${rights[@]} ]
6 index
get
[ ${tops[@]} ]
7 index
get
]
/PAGE
pdfmark
[
/MediaBox
[
0
0
[ ${rights[@]} ]
6 index
get
[ ${tops[@]} ]
7 index
get
]
/PAGE
pdfmark
true
}
{false}
ifelse
}
>>
setpagedevice
endcode
gs -o "$outfile"$pages_string -sDEVICE=pdfwrite -c "$code" -f "$infile"
if (( testmode ))
then
$viewer "$outfile"
rm "$outfile"
fi