#!/bin/sh

# https://github.com/aklomp/shrinkpdf
# Licensed under the 3-clause BSD license:
#
# Copyright (c) 2014-2025, Alfred Klomp
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


shrink ()
{
	if [ "$grayscale" = "YES" ]; then
		gray_params="-sProcessColorModel=DeviceGray \
		             -sColorConversionStrategy=Gray \
		             -dOverrideICC"
	else
		gray_params=""
	fi

	# Allow unquoted variables; we want word splitting for $gray_params.
	# shellcheck disable=SC2086
	gs					\
	  -q -dNOPAUSE -dBATCH -dSAFER		\
	  -sDEVICE=pdfwrite			\
	  -dCompatibilityLevel="$4"		\
	  -dPDFSETTINGS=/screen			\
	  -dEmbedAllFonts=true			\
	  -dSubsetFonts=true			\
	  -dAutoRotatePages=/None		\
	  -dColorImageDownsampleType=/Bicubic	\
	  -dColorImageResolution="$3"		\
	  -dColorImageDownsampleThreshold="$5"	\
	  -dGrayImageDownsampleType=/Bicubic	\
	  -dGrayImageResolution="$3"		\
	  -dGrayImageDownsampleThreshold="$5"	\
	  -dMonoImageDownsampleType=/Subsample	\
	  -dMonoImageResolution="$3"		\
	  -dMonoImageDownsampleThreshold="$5"	\
	  -sOutputFile="$2"			\
	  ${gray_params}			\
	  "$1"
}

get_pdf_version ()
{
	# $1 is the input file. The PDF version is contained in the
	# first 1024 bytes and will be extracted from the PDF file.
	pdf_version=$(head -c 1024 "$1" | LC_ALL=C awk 'BEGIN { found=0 }{ if (match($0, "%PDF-[0-9]\\.[0-9]") && ! found) { print substr($0, RSTART + 5, 3); found=1 } }')
	if [ -z "$pdf_version" ] || [ "${#pdf_version}" != "3" ]; then
		return 1
	fi
}

check_input_file ()
{
	# Check if the given file exists.
	if [ ! -f "$1" ]; then
		echo "Error: Input file does not exist." >&2
		return 1
	fi
}

check_smaller ()
{
	# If $1 and $2 are regular files, we can compare file sizes to
	# see if we succeeded in shrinking. If not, we copy $1 over $2:
	if [ ! -f "$1" ] || [ ! -f "$2" ]; then
		return 0;
	fi
	ISIZE="$(wc -c "$1" | awk '{ print $1 }')"
	OSIZE="$(wc -c "$2" | awk '{ print $1 }')"
	if [ "$ISIZE" -lt "$OSIZE" ]; then
		echo "Input smaller than output, doing straight copy" >&2
		cp "$1" "$2"
	fi
}

check_overwrite ()
{
	# If $1 and $2 refer to the same file, then the file would get
	# truncated to zero, which is unexpected. Abort the operation.
	# Unfortunately the stronger `-ef` test is not in POSIX.
	if [ "$1" = "$2" ]; then
		echo "The output file is the same as the input file. This would truncate the file." >&2
		echo "Use a temporary file as an intermediate step." >&2
		return 1
	fi
}

usage ()
{
	echo "Reduces PDF filesize by lossy recompressing with Ghostscript."
	echo "Not guaranteed to succeed, but usually works."
	echo
	echo "Usage: $1 [-g] [-h] [-o output] [-r res] [-t threshold] infile"
	echo
	echo "Options:"
	echo " -g  Enable grayscale conversion which can further reduce output size."
	echo " -h  Show this help text."
	echo " -o  Output file, default is standard output."
	echo " -r  Resolution in DPI, default is 72."
	echo " -t  Threshold multiplier for an image to qualify for downsampling, default is 1.5"
}

# Set default option values.
grayscale=""
ofile="-"
res="72"
threshold="1.5"

# Parse command line options.
while getopts ':hgo:r:t:' flag; do
  case $flag in
    h)
      usage "$0"
      exit 0
      ;;
    g)
      grayscale="YES"
      ;;
    o)
      ofile="${OPTARG}"
      ;;
    r)
      res="${OPTARG}"
      ;;
    t)
      threshold="${OPTARG}"
      ;;
    \?)
      echo "invalid option (use -h for help)"
      exit 1
      ;;
  esac
done
shift $((OPTIND - 1))

# An input file is required.
if [ -z "$1" ]; then
	usage "$0"
	exit 1
else
	ifile="$1"
fi

# Check if input file exists
check_input_file "$ifile" || exit $?

# Check that the output file is not the same as the input file.
check_overwrite "$ifile" "$ofile" || exit $?

# Get the PDF version of the input file.
get_pdf_version "$ifile" || pdf_version="1.5"

# Shrink the PDF.
shrink "$ifile" "$ofile" "$res" "$pdf_version" "$threshold" || exit $?

# Check that the output is actually smaller.
check_smaller "$ifile" "$ofile"
