#!/usr/bin/perl
use strict ;
use warnings ;
use Term::ANSIColor ;
use File::Temp ;
use v5.40 ;
#use feature 'signatures' ;
#use experimental 'refaliasing', 'signatures';
#use feature 'signatures' ;
#use Unicode::GCString ;
# sudo dnf install "perl(Unicode::LineBreak)"

# *************************************************************** #
# Convert .heic (iPhone) images to other formats.                 #
# This is a test and demonstration app for Perl Piggies.          #
# The Perl examples are functional as written; however the actual #
# image conversion requires the external 'heif-dec' utility       #
# which is distributed as part of the libheif plug-in library.    #
# *************************************************************** #
# Author : Mahlon R. Smith - The Software Samurai                 #
#   Copyright (c) 2026-2027 Mahlon R. Smith, The Software Samurai #
# Updated: 14-Jun-2026                                            #
#     GNU GPL copyright notice in README file.                    #
# Developed under: Perl 5.40.3                                    #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# Please see the README files for program details.                #
#                                                                 #
#                                                                 #
# *************************************************************** #

   my $HEIC_VERSION = "0.0.01" ;
   my $HEIC_CRYEARS = "2026-2027" ;
   use Cwd qw ( getcwd ) ;
   my $BWD=getcwd() ;
   my $argCnt = @ARGV ;

   #--------------------------------------#
   # Supported Target Filename Extensions #
   #--------------------------------------#
   my @TrgEncoding =
   (
      "jpg",
      "jpeg",
      "png",
      "tif",
      "tiff",
      "y4m",
   ) ;
   #---------------#
   #-- Constants --#
   #---------------#
   my $defaultEncoding = 0 ;     # default encoding format (index)
   my $defaultQuality  = 92 ;    # default encoding quality
   my $MinQuality      = 10 ;    # minimum quality percentage
   my $MaxQuality      = 100 ;   # maximum quality percentage
   my $magicBox = 12 ;           # chars to read from source magic box
   my $magicHeic = "ftypheic" ;  # expected token in source magic box
   my $magicHeix = "ftypheix" ;  # alternate token in source magic box
   my $magicMif1 = "ftypmif1" ;  # (generic codec)
   my $magicStrLength = length ( $magicHeic) ; # chars in magic tokens
   my $magicStrOffset = 4 ;      # leading chars to ignore in source magic box
   my $hdecName = "heif-dec" ;   # name of external conversion application
   my $SPAD = "-" ;              # padding character
   my $DPAD = "=" ;              # padding character
   my $FALSE = 0 ;               # 'boolean' values
   my $TRUE  = 1 ;

   #----------------------#
   #-- Global Variables --#
   #----------------------#
   my $gsOut ;                   # output buffer
   my $gsIn ;                    # input buffer
   my $uLine ;                   # formatted divider lines
   my $slen ;                    # length of divider lines
   my $verFlag  = $FALSE ;       # set if user requests Version number
   my $hlpFlag  = $FALSE ;       # set if user requests Help
   my $redFlag  = $FALSE ;       # set if Help redirection specified
   my $extFlag  = $FALSE ;       # set if '-e' option has been processed
   my $validSrc = $FALSE ;       # set if source file verified
   my $validTrg = $FALSE ;       # set if target file verified
   my $fatalErr = $FALSE ;       # set if fatal error encountered
   my $verbMode = $FALSE ;       # set if verbose output output
   my @srcList = "" ;            # list of source files in CWD ('-all' option only)
   my $srcName = "" ;            # source file - full name
   my $srcBase = "" ;            # source file - base name
   my $srcExt  = "" ;            # source file - filename extension
   my $trgName = "" ;            # target file - full name
   my $trgBase = "" ;            # target file - base name
   my $trgExt  = @TrgEncoding[$defaultEncoding] ; # target file - filename extension
   my $trgQuality = $defaultQuality ;             # encoding quality (JPEG encoding only)
   my $fatalErrMsg ;                              # message to display in case of fatal error

   # Say Hello. If no arguments specified, #
   # Give user a clue, then exit.          #
   Title ( $gsOut, $uLine, $slen ) ;
   if ( $argCnt == 0 )
   { userClue ( $uLine, $slen ) ; exit ; }

   # If first argument is not an option switch, then capture   #
   # the ARGV[0] (source filename); AND if ARGV[1] is not an   #
   # option switch, then capture the ARGV[1] (target filename),#
   # Programmer's Note; Yes, user friendliness is often messy. #
   $srcName = @ARGV[0] ;
   if ( (index ( $srcName, "-" )) != 0 )
   {
      @srcList[0] = $srcName ;

      # If target filename specified, (ARGV[1]), separate into   #
      # component base name and extension/encoding.              #
      # If encoding specified, verify that it is one of the      #
      # supported formats. If encoding not specified, retain     #
      # default encoding. (may be overwritten by '-e' option)    #
      if ( ($argCnt > 1) && ((index (@ARGV[1], "-")) != 0) )
      {
         $trgName = @ARGV[1] ;
         if ( (ValidateTrgName ( \$trgName, \$trgBase, \$trgExt )) == $FALSE )
         {
            $fatalErrMsg = "Error! Target file '$trgName'\n" ;
            $fatalErrMsg = $fatalErrMsg . "       specifies an unsupported encoding: '$trgExt'" ;
            $fatalErr = $TRUE ;
         }
      }
   }

   # Process the list of switch '-x' options, excluding any   #
   # previously processed arguments.                          #
   foreach my $i ( @ARGV )
   {
      # If parameter is a switch (begins with a '-' character) #
      if ( (index ( $i, "-" )) == 0 )
      {
         if ( $i eq "--version" )                   # version/copyright request
         { $verFlag = $TRUE ; }
         elsif ( ((index ( $i, "--help")) == 0) ||  # help request
                 ((index ( $i, "-h")) == 0) )
         {
            $hlpFlag = $TRUE ;
            if ( ((index ( $i, "less" )) > 0) ||    # help redirection
                 ((index ( $i, "-hl" )) == 0) )
            { $redFlag = $TRUE ; }
         }
         elsif ( (index ( $i, "-src=" )) == 0 )     # source file option
         {
            @srcList = () ;               # Empty the array
            @srcList[0] = substr ( $i, 5, 99 ) ;
         }
         elsif ( (index ( $i, "-trg=" )) == 0 )     # target file option
         {
            my $tmp_trgExt = $trgExt ;
            $trgName = substr ( $i, 5, 99 ) ;
            if ( (ValidateTrgName ( \$trgName, \$trgBase, \$trgExt )) == $FALSE )
            {
               $fatalErrMsg = "Error! Target file '$trgName'\n" ;
               $fatalErrMsg = $fatalErrMsg . "       specifies an unsupported encoding: '$trgExt'" ;
               $fatalErr = $TRUE ;
            }

            # Programmer's Note: If this parameter is encountered AFTER an 
            # '-e=nnn' parameter, the target extension (if any) must not be
            # allowed to overwrite the option specified by '-e'.
            elsif ( $extFlag != $FALSE )
            { $trgExt = $tmp_trgExt ; }
         }
         elsif ( (index ( $i, "-all" )) == 0 )      # source == all HEIC-encoded file in CWD
         {
            @srcList = () ;               # Empty the array
            captureAll ( \@srcList ) ;    # Capture all source names in CWD
         }
         elsif ( (index ( $i, "-e=" )) == 0 )       # target encoding specified
         {
            if ( (captureEncoding ( \$i, \$trgExt )) != $FALSE )
            { $extFlag = $TRUE ; }
            else
            {
               $fatalErrMsg = "Error! Unsupported encoding '$trgExt' specified." ;
               $fatalErr = $TRUE ;
            }
         }
         elsif ( ((index ( $i, "-q=" )) == 0) ||    # target quality specified
                 ((index ( $i, "-p=" )) == 0) )     # (note user-friendly programmming)
         {
            if ( (captureQuality ( \$i, \$trgQuality )) == $FALSE )
            {
               $fatalErrMsg = "Error! Quality percentage '$trgQuality' out of range.\n" ;
               $fatalErrMsg = $fatalErrMsg . "       Range: 10 <= Quality <= 100.\n\n" ;
               $fatalErr = $TRUE ;
            }
         }
         elsif ( (index ( $i, "-v" )) == 0 )
         {
            $verbMode = $TRUE ;
         }
         else
         {
            $fatalErrMsg = "Error! Unrecognized option: '$i'" ;
            $fatalErr = $TRUE ;
         }
      }
   }

   if ( $verFlag  )                       # version overrides all
   { Version ( $uLine, $slen ) ; exit ; }
   elsif ( $hlpFlag )                     # help overrides all except version
   { Help ( $uLine, $redFlag ) ; exit ; }
   elsif ( $fatalErr != $FALSE )          # invalid arguments
   { print colored ( "$fatalErrMsg\n\n", 'red' ) ; exit ; }


   # --------------------------------------------------------------------- #
   # All arguments have been captured without any fatal errors (so far).   #
   # 1) Validate the source file(s):                                       #
   #    a) each source file must exist                                     #
   #    b) each source file must be HEIC encoded                           #
   # 2) Verify that target file(s) do not exist:                           #
   #    a) For a single source file, IF a target filename was specified,   #
   #       verify that it does not exist.                                  #
   #    b) For multiple source files, ignore target filename specification #
   #       and construct each target name using base name of source file   #
   #       and the specified encoding/extension.                           #
   # --------------------------------------------------------------------- #
   my $srcCnt = scalar @srcList ;         # number of elements in source list
   my $cwid = columnWidth ( \@srcList ) ; # width for filename column
   if ( $srcCnt > 0 )
   {
      $validSrc = $TRUE ;                 # hope for the best
      print colored ( "Source Candidates:\n", 'blue underline' ) ;
      foreach my $i ( @srcList )
      {
         $srcName = $i ;
         $srcBase = "" ;
         $srcExt  = "" ;
         $gsOut = "$srcName" ;
         $gsOut .= (' ' x ($cwid - length ( $gsOut ))) ;
         print colored ( "$gsOut", 'blue' ) ;
         if ( (ValidateSrc ( $srcName, $srcBase, $srcExt )) != $FALSE )
         { print colored ( "verified\n", 'green' ) ; }
         else
         { print colored ( "error!\n", 'red' ) ; $validSrc = $FALSE ; }
      }

      if ( $validSrc == $FALSE )          # if source validation  failed
      {
         $fatalErrMsg = "\nError! Source file does not exist, or\n" ;
         $fatalErrMsg = $fatalErrMsg . "       magic number indicates non-HEIC encoding." ;
         print colored ( "$fatalErrMsg\n\n", 'red' ) ;
         exit ;
      }

      # Debugging Only: enable pre-processing report. #
      #debugReport () ;

      # Verify that the external application used for #
      # conversion is installed and accessible.       #
      if ( (appInstalled ()) == $FALSE )
      {
         print colored ( "\nSorry, the $hdecName utility is not available; please\n", 'blue' ) ;
         print colored ( "see the README file for installation instructions.\n\n", 'blue' ) ;
         exit ;
      }

      # Source files and target file information have been  #
      # validated. Perform conversion to target encoding.   #
      print colored ( "\nTarget Filenames :\n", 'blue underline' ) ;
      if ( ($srcCnt > 1) || (length($trgBase) == 0) )
      {
         foreach my $i ( @srcList )
         {
            $trgName = "" ;
            Convert ( $i, $trgName ) ;
         }
      }
      # Special Case: If base name of target file specified #
      # explicitly, use it when performing conversion.      #
      else
      { Convert ( @srcList[0], $trgBase ) ; }

      print "\n" ;
      exit ;   # Bye-bye!
   }  #( $srcCnt > 0)

 ###========================= END OF MAIN SEQUENCE =========================###


# --------------------------------------------------------------------- #
# Convert the specified source file to target encoding.                 #
#                                                                       #
# Inputs (by-value) :                                                   #
#    $src  : name of a source (.heic) file in CWD                       #
#    $trg  : if specified, base name for target file.                   #
#            Ignored, if empty string. Use Base source name instead.    #
#                                                                       #
# Returns: $TRUE  if target successfully created                        #
#          $FALSE if target already exists or other conversion error.   #
#                                                                       #
# Note that the functionality depends on specific global variables:     #
#  1) $trgExt     : encoding and filename extension                     #
#  2) $trgQuality : for JPEG only, percentage (lossy compression)       #
#  3) $verbMode   : determines amount of data reported per conversion   #
#  4) $gsOut      : format output to terminal window                    #
#  4) $gsIn       : capturrd output from external application           #
#                       - - - - - - - - - -                             #
#                                                                       #
# 1) Construct the target filename and verify that target does not      #
#    already exist. If it does, return failure.                         #
# 2) Call the external application to perform the conversion.           #
#    a) Output from the called utility is captured, and is discarded    #
#       by default. however, if the $verbMode flag is set, the output   #
#       will be echoed to the display.                                  #
#    b) If JPEG target, include the '--quality' parameter               #
#       This parameter does not apply to other encodings.               #
#                                                                       #
# 3) Source file is not modified.                                       #
#                                                                       #
# heif-dec return value:                                                #
#   0 == success                                                        #
#   1 == syntax/usage error, or loading error, error while converting   #
#        writing target image                                           #
#                                                                       #
#                       - - - - - - - - - -                             #
# Capturing stdout - From perldoc -f qx                                 #
# -------------------------------------                                 #
# $output = `cmd 2>&1`;             Capture stdout and stderr together  #
# $output = `cmd 2>/dev/null`;      Capture stdout, discard stderr      #
# $output = `cmd 2>&1 1>/dev/null`; Capture stderr, discard stdout      #
# system("program args 1>program.stdout 2>program.stderr");             #
#    Capture stdout && stderr separately                                #
#                                                                       #
#                                                                       #
# --------------------------------------------------------------------- #
sub Convert ( $src, $trg )
{
   my $quietToken   = "--quiet" ;
   my $qualityToken = "--quality" ;
   my $outputToken  = "--output" ;
   my $tName = "" ;                       # temporary buffers for parsing
   my $tBase = "" ;
   my $tExt  = "" ;
   my $cmd   = $hdecName ;                # construct command
   my $status = $FALSE ;                  # return value

   parseFilename ( \$src, \$tBase, \$tExt ) ;
   if ( (length ( $trg )) > 0 )
   { $tBase = $trg ; }
   $gsOut = $tName = "$tBase.$trgExt" ;
   $gsOut .= (' ' x ($cwid - length ( $gsOut ))) ;
   print colored ( "$gsOut", 'blue' ) ;

   # If target DOES NOT exist, create the command  #
   # to call the external utility.                 #
   if ( !-e $tName )
   {
      # If JPEG target, add the quality percentage. #
      if ( ($trgExt eq @TrgEncoding[$defaultEncoding]) ||
           ($trgExt eq @TrgEncoding[$defaultEncoding + 1]) )
      { $cmd .= " $qualityToken=$trgQuality" ; }

      # Add the source and target filenames. #
      $cmd .= " $src $outputToken=$tName" ;

      # Execute the command #
      $gsIn = qx ( $cmd ) ;

      # Verify that target file was created.#
      if ( -e $tName )
      {
         print colored ( "verified\n", 'green' ) ;
         $status = $TRUE ;

         # If verbose output specified,display the command     #
         # and interim results.                                #
         # NOTE: The captured data is not a simple text string.#
         # It includes a large number of escape sequences      #
         # and/or ASCII command codes (approximately 1KB).     #
         # The following sequence normalizes the captured data #
         # before writing it to the terminal window.           #
         if ( $verbMode )
         {
            my $indx = index ( $gsIn, "\n" ) + 1 ;
            $gsOut = substr ( $gsIn, 0, $indx ) ;
            $indx = rindex ( $gsIn, "decoding" ) ;
            $gsOut .= substr ( $gsIn, $indx, 100 ) ;
            my $ilen = length ( $gsIn ) ;
            my $olen = length ( $gsOut ) ;
            print "gsIn len:$ilen gsOut len:$olen\n" ;
            print "Command Structure: '$cmd'\n$gsOut\n" ;
         }
      }
      else
      { print colored ( "error - target not created.\n", 'red' ) ; }
   }
   else
   {
      print colored ( "error - target already exists.\n", 'red' ) ;
   }
}  # Convert() #

# --------------------------------------------------------------------- #
# Validate the source file.                                             #
# -------------------------                                             #
# Returns $TRUE if: a) file exists and                                  #
#                   b) HEIC-encoding verified.                          #
#                                                                       #
#                       - - - - - - - - - -                             #
# Note that this subroutine is declared without arguments.              #
# This means that any arguments passed are delivered as an array of     #
# pass-by-reference pointers which are accessed by anonymous references.#
# 1st Parm: $_[0] contains full source filename                         #
# 2nd Parm: $_[1] receives base filename (excludes extension)           #
# 3rd Parm: $_[2] receives filename extension                           #
#                                                                       #
# This is the old-school Perl, easy to implement, simple to maintain,   #
# but which violates the number one principle of software design,       #
# which is: Self Documenting Code!  Bad Perl Punks - Go to your room!   #
# --------------------------------------------------------------------- #
sub ValidateSrc
{
   my $indx ;                             # character index
   my $status = $FALSE ;                  # return value

   # Extract filename extension #
   # (Index the last '.'        #
   $indx = rindex ( $_[0], '.' ) + 1 ;
   if ( $indx > 0 )
   {
      $_[2] = substr ( $_[0], $indx, 4 ) ; # 4 characters max
      $indx -= 1 ;
      $_[1] = substr ( $_[0], 0, $indx ) ;

      # Verify that source file exists #
      if ( -e $srcName )
      {
         # Verify the source file signature.                            #
         # Open the file in binary mode and read the magic data.        #
         # Bytes 0-3  is a numeric value indicating size of magic box.  #
         # Bytes 4-7  should be the constant string: "ftyp"             #
         # Bytes 8-11 should be a constant string: "heic" or "mif1"     #
         my $buffer ;
         open (my $fh, '<', $srcName) or die $! ;
         binmode ( $fh ) ;
         read ( $fh, $buffer, $magicBox ) ;
         close ( $fh ) ;
         $buffer = substr( $buffer, $magicStrOffset, $magicStrLength ) ;

         # Debugging Only: display the captured magic-number sequence.  #
         #print "magic: '$buffer' len:$magicStrLength\n" ;

         if ( ((index ( $buffer, $magicHeic )) == 0) ||
              ((index ( $buffer, $magicMif1 )) == 0) ||
              ((index ( $buffer, $magicHeix )) == 0) )
         {
            $status = $TRUE ;
         }
      }
   }
   return $status ;

}  # ValidateSrc() #

# --------------------------------------------------------------------- #
# Capture names of all HEIC files in the current working directory.     #
# Each captured filename is stored in an element of caller's array.     #
#                                                                       #
#                       - - - - - - - - - -                             #
# Note that a pointer to caller's array is passed anonymously, but is   #
# given a name within this subroutine to provide clarity.               #
# --------------------------------------------------------------------- #

sub captureAll
{
   my ($srcPtr) = @_ ;                    # pointer to caller's data array
   my $raw = qx (ls -1 *[.]heic ) ;       # capture list of filenames matching regex expression
   my $name ;                             # isolated filename
   my $indx ;                             # character index
   my $item = 0 ;                         # loop control

   while ( ($indx = index ( "$raw", "\n" )) > 0 )  # index of newline character
   {
      $indx++ ;                           # convert index to char count
      $name = substr ( $raw, 0, $indx ) ; # get the row (incl. newline)
      chomp $name ;                       # discard the newline
      $srcPtr->[$item++] = $name ;        # save to caller's array
      substr ( $raw, 0, $indx ) = "" ;    # shift out the processed item
   }

}  # captureAll() #

# --------------------------------------------------------------------- #
# Validate the target filename:                                         #
# -----------------------------                                         #
# Separate into components: base + extension. Then validate that the    #
# extension (if provided) is included in the list of supported          #
# encodings/extensions.                                                 #
#                                                                       #
#                       - - - - - - - - - -                             #
# Note that this subroutine is declared using alias arguments which     #
# identify pass-by-reference variables.                                 #
# Note the special syntax used above when invoking the call.            #
#                                                                       #
# --------------------------------------------------------------------- #
sub ValidateTrgName ( $name, $base, $ext )
{
   my $status = $FALSE ;
   my $indx = rindex ( $$name, '.' ) + 1 ;

   # Parse filename into base and extension components,  #
   # and if filename extension, verify that extension    #
   # references a supported target encoding.             #
   parseFilename ( $name, $base, $ext ) ;
   if ( (length ( $$ext )) > 0 )
   {
      $status = ValidateEncoding ( $ext ) ;
   }

   # If no filename extension, assume default encoding.#
   #        (May be overridden by '-e' option.)        #
   else
   {
      $$ext = $TrgEncoding[$defaultEncoding] ;
      $status = $TRUE ;
   }
   return $status ;

}  # ValidateTrgName()

# --------------------------------------------------------------------- #
# Parse a filename into base and extension components.                  #
#                                                                       #
#                       - - - - - - - - - -                             #
# Note that the arguments are named, pass-by-reference variables.       #
# The syntax used by the caller(s) varies based on whether the          #
# calling parameters have already been de-referenced.                   #
# a) In the main sequence, the parameters reference global variables.   #
# b) From within the 'ValidateTrgName()' subroutine the parameters      #
#    are simple pointers to global variables.                           #
# --------------------------------------------------------------------- #
sub parseFilename ( $name, $base, $ext )
{
   $$base = "" ;                             # initialize caller's buffers
   $$ext  = "" ;
   my $indx = rindex ( $$name, '.' ) + 1 ;   #locate last full-stop character

   if ( $indx > 0 )
   {
      $$ext = substr ( $$name, $indx, 4 ) ;  # filename extension (4 characters max)
      $indx -= 1 ;
      $$base = substr ( $$name, 0, $indx ) ; # base filename
   }
   else
   { $$base = $$name ; }

}  # parseFilename

# --------------------------------------------------------------------- #
# Validate Target Encoding Option:                                      #
# --------------------------------                                      #
# Input : $ext (by-reference) filename extension from user              #
#                                                                       #
# Verify that specified encoding is one of the supported type by        #
# comparing user input with the items in the @TrgEncoding array.        #
#                                                                       #
# Note that the test is case-insensitive; however, the conversion       #
# utility ignores case of input and always outputs lowercase. For       #
# consistency, the matching value from the array replaces user input.   #
# --------------------------------------------------------------------- #
sub ValidateEncoding ( $ext )
{
   my $status = $FALSE ;

   # case-insensitive comparison #
   foreach my $i ( @TrgEncoding )
   {
      if ( fc($$ext) eq fc($i) )
      { $$ext = $i ; $status = $TRUE ; last ; }
   }
   return $status ;

}

# --------------------------------------------------------------------- #
# Extract specified encoding from command-line parameter.               #
# and verify that it is a supported encoding type.                      #
#                                                                       #
#                       - - - - - - - - - -                             #
# Note that because caller passed the parameters by-reference,          #
# calling a subroutine from within this subroutine does not require     #
# additional dereferencing.                                             #
# --------------------------------------------------------------------- #
sub captureEncoding ( $raw, $ext )
{
   my $status = $FALSE ;

   # Extract encoding token from parameter.#
   $$ext = substr ( $$raw, 3, 4 ) ;

   # verify that specified encoding is #
   # one of the supported types.       #
   $status = ValidateEncoding ( $ext ) ;
#   if ( (ValidateEncoding ( $ext )) != $FALSE )
#   { $status = $TRUE ; }
   return $status ; 

}  # captureEncoding() #

# --------------------------------------------------------------------- #
# Extract specified quality percentage from command-line                #
# argument, and verify that it is in range.                             #
#                                                                       #
#                       - - - - - - - - - -                             #
# The parameters are passed in as named, pass-by-reference variables,   #
# which means that changes made locally will be seen by caller.         #
# --------------------------------------------------------------------- #
sub captureQuality ( $raw, $qual )
{
   my $status = $FALSE ;
   $$qual = substr ( $$raw, 3, 3 ) ;
   if ( ($$qual >= $MinQuality) && ($$qual <= $MaxQuality) )
   { $status = $TRUE ; }

   return $status ;

}  # captureQuality() *

# --------------------------------------------------------------------- #
# Verify that the target application, is installed on the system and    #
# is accessible. Optionally, extract the application version number.    #
# Note the redirection of 'stderr' to the bit bucket.                   #
#                       - - - - - - - - - -                             #
#                                                                       #
# This subroutine accepts an optional argument (by-reference).          #
# The number of arguments passed is indicated by the scalar @_          #
# which indicates the number of elements in the argument array.         #
#  a) When referenced as a scalar value, or as a pointer to the array,  #
#     it performs as expected:                                          #
#        if ( @_ > 0 ) { print "'$_[0]'\n" ; }                          #
#  b) @_ SHOULD NOT be used as a reference to the first element of an   #
#     array, even though the syntax is accepted:                        #
#        if ( @_ == 1 ) { print "'@_'\n" ; }                            #
#                                                                       #
#                       - - - - - - - - - -                             #
# The data returned by request for version number takes the form:       #
# [heic]$ heif-dec --version                                            #
#         1.19.8                                                        #
#         libheif: 1.19.8                                               #
#         plugin path: /usr/lib64/libheif                               #
# --------------------------------------------------------------------- #
sub appInstalled
{
   my $status = $FALSE ;
   $gsIn = qx ( 'heif-dec' '--version' 2>/dev/null ) ;
   if ( (index ( $gsIn, "libheif:" )) > 0 )
   {
      $status = $TRUE ;

      # If specified, extract version number to caller's variable.#
      if ( @_ > 0 )
      {
         my $indx = index ( $gsIn, "\n" ) ;
         $_[0] = substr ( $gsIn, 0, $indx ) ;
      }
   }
   return $status ;

}  # appInstalled()

# --------------------------------------------------------------------- #
# Format and display the application title.                             #
#                       - - - - - - - - - -                             #
# Three arguments are passed by anonymous reference, where:             #
#  $_[0] receives the formatted title text                              #
#  $_[1] receives a dividing line with the same number of characters    #
#        as the title text.                                             #
#  $_[2] receives the width of the title text as a character count      #
# --------------------------------------------------------------------- #
sub Title
{
   $_[0] = sprintf ( "heic.pl: v:%s  Copyright (c) %s The Software Samurai", 
                     $HEIC_VERSION, $HEIC_CRYEARS ) ;
   $_[2] = length ( $_[0] ) ;
   padLine ( $_[1], $_[2], $DPAD ) ;
   system ( "clear" ) ;
   print colored ( "$_[0]\n", 'blue' ) ;
   print colored ( "$_[1]\n", 'blue' ) ;
}

# --------------------------------------------------------------------- #
# Create a row of text of the specified length, using the specified     #
# character as the fill character.                                      #
#                       - - - - - - - - - -                             #
# Two arguments are passed by anonymous reference, where:               #
#  $_[0] receives the formatted divider text                            #
#  $_[1] specifies the character to be used as the fill character.      #
# --------------------------------------------------------------------- #
sub padLine
{
   $_[0] = "" ;
   $_[0] = ($_[2] x ($_[1]) ) ;
}

# --------------------------------------------------------------------- #
# Calculate the width of filename column to ensure that next column     #
# is properly aligned.                                                  #
#                       - - - - - - - - - -                             #
# A pointer to the array of filenames is passed by anonymous            #
# pass-by-reference; however, the array is assigned a name within this  #
# subroutine to provide clarity.                                        #
# --------------------------------------------------------------------- #
sub columnWidth
{
   my ($srcPtr) = @_ ;                    # pointer to caller's data array
   my $itemLen ;                          # character count
   my $colWidth = 0 ;                     # return value

   foreach my $itemText ( $srcPtr )
   {
      if ( ($itemLen = length ( $itemText )) > $colWidth )
      { $colWidth = $itemLen ; }
   }
   $colWidth += (4 + $colWidth % 4) ;
   return $colWidth ;

}  # columnWidth() #


# --------------------------------------------------------------------- #
# For Debugging Only - Report the validated call parameters.            #
# --------------------------------------------------------------------- #
sub debugReport
{
   $gsOut = sprintf ( "   ENCODING: %-6s QUALITY: $trgQuality%%    VERBOSE: %-5s\n", 
                      $trgExt, $verbMode ) ;
   print colored ( "$gsOut", 'cyan' ) ;
}  # debugReport() #

# --------------------------------------------------------------------- #
# Quick help: Give user a clue.                                         #
#                       - - - - - - - - - -                             #
# Two arguments are passed by anonymous reference, where:               #
#  $_[0] receives the formatted dividing line text                      #
#  $_[1] specifies the character count                                  #
# --------------------------------------------------------------------- #
sub userClue
{
   padLine ( $_[0], $_[1], $SPAD ) ;
   print colored ( "Convert HEIC Image To Alternate Format. Type: heic.pl --help\n", 'blue' ) ;
   print colored( "$_[0]\n\n", 'blue' ) ;

}  # userClue() #

# --------------------------------------------------------------------- #
# Display application version and copyright info:                       #
#                       - - - - - - - - - -                             #
# Two arguments are passed, and are referenced by anonymous             #
# pass-by-reference pointers:                                           #
#   $_[0] - caller's buffer to contain text of a dividing line.         #
#   $_[1] - length (characters/columns) for the divider.                #
#                                                                       #
#                       - - - - - - - - - -                             #
# Note that characters and columns are not guaranteed to be the same;   #
# however, in this simple example, only ASCII characters are used, so   #
# the calculation works automagically.                                  #
#                       - - - - - - - - - -                             #
# A paragraph of text could be structured as a single string, or as     #
# an array of strings. Either way, the Perl compiler does some strange  #
# things to the text.                                                   #
# a) Newline characters which are not explicitly included in the text   #
#    but are simply part of the text editor's page formatting, are      #
#    still rendered as part of the output to the terminal window:       #
#    Example: The following are equivalent:                             #
#             my $textA = "Hello\nWorld!" ;                             #
#             my $textB =                                               #
#             "Hello                                                    #
#             World!" ;                                                 #
#             print "$textA\n" ;    -->  Hello                          #
#                                        World!                         #
#             print "$textB\n" ;    -->  Hello                          #
#                                        World!                         #
# b) If newline characters are embedded in the elements of an array,    #
#    the position of the newline within each element is significant.    #
#    -- For some unexplained reason, if the newline character is at     #
#       the end of the element, the compiler INCORRECTLY appends a      #
#       space character after the newline character                     #
#       (but strangely, not after the last element)                     #
#       my @textA =                                                     #
#       (                 print "@textA" ;                              #
#       "Abcde\n",              yields:|Abcde                           #
#       "Fghij\n",                     | Fghij                          #
#       "Klmno\n",                     | Klmno                          #
#       ) ;                                                             #
#    -- To get correct left alignment of the output, place the newline  #
#       characters at the beginning of each element except the first:   #
#       my @textB =                                                     #
#       (                 print "@textA" ;                              #
#       "Abcde",                yields:|Abcde                           #
#       "\nFghij",                     |Fghij                           #
#       "\nKlmno",                     |Klmno                           #
#       ) ;                                                             #
#                                                                       #
# --------------------------------------------------------------------- #
sub Version
{
   my @vMsg =
   (
   "License GPLv3+: GNU GPL version 3",
   "\n<http://gnu.org/licenses/gpl.html>",
   "\nThis is free software: you are free to modify and/or",
   "\nredistribute it under the terms set out in the license.",
   "\nThere is NO WARRANTY, to the extent permitted by law."
   ) ;

   # Create dividing line #
   padLine ( $_[0], $_[1], $SPAD ) ;

   # Report version number of called app, if available.#
   my $appver = "" ;
   appInstalled ( $appver ) ;
   if ( (length ( $appver )) > 0 )
   { print colored ( " libheif/heif-dec v:$appver\n$_[0]\n", 'blue' ) ; }

   print colored ( "@vMsg\n$_[0]\n\n", 'blue' ) ;

}

# --------------------------------------------------------------------- #
# Display application help:                                             #
# -------------------------                                             #
# Two arguments are passed by value:                                    #
#   $uline    - dividing line has been initialized by caller.           #
#   $redirect - flag indicating whether ouput is directly to stdout or  #
#               is to be redirected through the 'less' utility.         #
#                                                                       #
# Please see the Version() subroutine header above for a discussion     #
# of the differences between defining a paragraph of text using a       #
# single text string and an array of text rows.                         #
#                                                                       #
#                       - - - - - - - - - -                             #
# If redirection through 'less' specified, create a temporary file      #
# in the system temp directory, and write the help text to the file.    #
# The 'system' function accepts either a single argument or a list      #
# of arguments.                                                         #
#    Single Arg: system ( "less filename" );                            #
#    Arg List  : system ( "less", "filename" );                         #
# The difference is that the shell parses the single-string, so if      #
# there are "special" characters which could be interpreted as shell    #
# instructions, Passing the arguments as a list means that Perl         #
# handles the parsing, so the shell doesn't interfere.                  #
#                                                                       #
# NOTE: If the file is closed, it is immediately deleted (which is a    #
#       stupid design decision). To be sure the full text is displayed  #
#       by the 'less' utility, the output stream must be flushed before #
#       sending the file.                                               #
# NOTE: The temp file is automatically deleted when handle goes         #
#       out-of-scope. (This is actually a _good_ design decision).      #
# --------------------------------------------------------------------- #
sub Help ( $uline, $redirect )
{
my $helpMsg = 
"Invoking: heic.pl SOURCEFILE(s) [TARGETFILE] [OPTIONS]
* Specify source file(s):
  * Enter name of a file in current working directory (CWD).
    Source file must be in High-Efficiency-Image-Container
    'HEIC' format. If specified without the '-src' switch,
    it must be the first argument.
    > heic.pl Orange.heic
    > heic.pl -e=jpg -src=Orange.heic
  * '-all' : Specify that all HEIC-encoded files in
    the CWD are to be processed. A list of verified source
    files will be displayed, and confirmation will be 
    requested before processing the list.
    > heic.pl -all
* Target filename. (optional)
  * Target filename not specified: Filename of target 
    taken from source filename, with encoding specified
    by '-e' option (or default).
    > heic.pl Orange.heic -e=png --> Orange.png
    > heic.pl Orange.heic        --> Orange.jpg
  * Target filename with extension: Extension specifies
    target encoding.
    > heic.pl Orange.heic Citrus.png --> CitrusTree.png
  * Target filename with no extension: encoding 
    specified by '-e' option (or default).
    > heic.pl Orange.heic Citrus -e=png --> Citrus.png
    > heic.pl Orange.heic Citrus        --> Citrus.jpg
* -e=ENCODING  Target encoding. (optional)
  * Supported encoding options (either upper/lowercase):
    'jpg' (default), 'jpeg', 'tif', 'tiff', 'png', 'y4m'
    (Note that 'jpg'=='jpeg' and 'tif'=='tiff'.)
    > heic.pl Orange.heic -e=png --> Orange.png
  * If target format is not specified, (neither '-e' option
    nor by target filename extension), then default target
    format is JPEG.
    > heic.pl Orange.heic        --> Orange.jpg
    > heic.pl Orange.heic Citrus --> Citrus.jpg
  * If both target filename extension and '-e' option
    specified, '-e' overrides target filename extension.
    > heic.pl Orange.heic Citrus.jpg -e=png --> Citrus.png
* -q=QUALITY  Quality Percentage. (optional)
  * HEIC encoding is highly compressed (nearly) lossless
    format. The quality of the data for target encoding, and
    consequently the size of the target file is specified
    using this option. (for JPEG 'lossy' compression only)
    Range: 10 <= quality <= 100 (default: 92% approx.)
    > heic.pl Orange.heic Orange.jpg -q=100 --> 'lossless'
    > heic.pl Orange.heic Orange.jpg -q=80  --> 80% quality
* -v Verbose Output (optional)
  * By default, the heif-dec' application is invoked in
    silent mode: output is produced only if an error occurs.
  * If verbose output is specified, invoke the application
    to report interim conversion results.
    > heic.pl Orange.heic Orange.jpg -v
* --version  Version number and copyright info. (optional)
    Overrides all other command line arguments.
    > heic.pl --version
* --help[less] (-h[l]) Command-line Help. (optional)
    Overrides all command line arguments except '--version'.
    The '--helpless' and '-hl' options will redirect the
    output through the 'less' utility.
    > heic.pl --help    heic.pl --helpless
" ;

   if ( $redirect == $TRUE )
   {
      my $tmp = File::Temp->new( TEMPLATE => 'heicpl_XXXXXX', DIR => '/tmp', SUFFIX => '.txt' ) ;
      my $tmpName = $tmp->filename ;
      print $tmp "$gsOut\n" ;
      print $tmp "$uline\n" ;
      print $tmp "$helpMsg" ;
      print $tmp "$uline\n" ;
      $tmp->flush() ;
      system ( "less", "$tmpName" ) ;
      userClue ( $uline, $slen ) ;
   }
   else
   {
      print colored ( "$helpMsg$uline\n", 'blue' ) ;
   }

}  # Help() #
