# Configuration of latexmk so that it works with the hyperxmp package.
# Hyperxmp puts document xmp metadata in the pdf file, and one of the
# fields in that metadata is byteCount, which is the size of the pdf file.
# This configuration ensures that the byteCount is present and correctly
# equals the pdf file's size.
#
# Method of operation:
# For any command that might produce a pdf file, prepend "internal mycmd3 "
# to the previously configured command, so that a subroutine mycmd3 is
# called.  It runs the expected command and then does post-processing if
# the output file is a pdf file.

foreach my $cmd ( "latex", "lualatex", "pdflatex", "xelatex",
                  "dvipdf", "xdvipdfmx", "ps2pdf" ) {
    ${$cmd} = "internal mycmd ${$cmd}";
}

#======================================================

sub mycmd {
  # Run command, and do extra processing of output file, if it is a pdf
  # file.
  # This subroutine is run in a rule context, so that $rule is the name of
  # the rule and $$Pdest is the destination file name. 

  # Identification string for messages:
  local $name = "$My_name.mycmd";

  my $retval = system @_;

  if ( $$Pdest =~ /\.pdf$/ ) {
     fix_pdf( $$Pdest );
  }
  return $retval;
}

#======================================================

sub fix_pdf {
  # Change/insert byteCount field with correct file length, while preserving
  # the file size and the length of the stream containing xmp metadata.
  # Return 1 on success, else 0.

  local $pdf_name = shift;
  local $tmp_name = "$pdf_name.new.pdf";
  local $pdf_size  = (stat($pdf_name))[7];
  warn "Inserting/correcting byteCount field in '$pdf_name' ...\n";

  # Strings surrounding (and identifying) the byteCount field, and other
  # parts of the xmp packet:
  local $xmp_start = '<x:xmpmeta xmlns:x="adobe:ns:meta/">';
  local $decl_bC = '<pdfaProperty:name>byteCount</pdfaProperty:name>';
  local $pre_bC = '<prism:byteCount>';
  local $post_bC = '</prism:byteCount>';
  local $pC = '<prism:pageCount>';
  local $rd_end = '</rdf:Description>';
  local $xmp_end = '</x:xmpmeta>';

  local *PDF;
  local *TMP;

  if (! open PDF, "<", $pdf_name ) {
      warn "  Cannot read '$pdf_name'\n";
      return 0;
  }
  if ( ! open TMP, ">", $tmp_name ) {
      warn "  Cannot write temporary file '$tmp_name'\n";
      close PDF;
      return 0;
  }
  local $status = 0;  # 0 = no XMP packet, 1 = success, >= errors
  while ( <PDF> ) {
      # Only examine first XMP packet:
      if ( ($status == 0)  &&  /^\s*\Q$xmp_start\E/ ) {
         local @xmp = $_;
         local $len_padding = 0;
         local $xmp_after_line = '';
         &xmp_get_mod;
         print TMP @xmp;
         # Insert correct padding to leave file size unchanged:
         while ( $len_padding > 0 ) {
             my $len_line = 64;
             if ( $len_line > $len_padding ) { $len_line = $len_padding; }
             $len_padding -= $len_line;
             print TMP (' ' x ($len_line - 1) ), "\n";
         }
         print TMP $xmp_after_line;
         $xmp_after_line = '';
     }
     else {
         print TMP "$_";
     }
  }
  close PDF;
  close TMP;

  if ($status == 0) {
      warn "  Could not insert/modify byteCount, since no XMP packet was found.\n";
      warn "  So '$pdf_name' is unchanged,\n",
           "  and I will delete temporary file '$tmp_name'.\n";
      unlink $tmp_name;
  } elsif ($status == 1)  {
      rename $tmp_name, $pdf_name
        or die "  Cannot move temporary file '$tmp_name' to '$pdf_name'.\n",
               "  Error is '$!'\n";
  } else {
      warn "  Could not insert correct byteCount. See above for reason.\n";
      warn "  So '$pdf_name' is unchanged,\n",
           "  and I will delete temporary file '$tmp_name'.\n";
      unlink $tmp_name;
  }
  return ($status == 1);
}

#======================================================

sub xmp_get_mod {
    # Get xmp packet, given that @xmp contains its first line.
    # Get amount of trailing padding, and line after that.
    # If possible, insert a byteCount field:
    #    Either replace existing specification, if it exists,
    #    or insert one in expected place for hyperxmp, if the XMP packet
    #      matches what hyperxmp would produce.
    # Return xmp packet in @xmp, amount of padding needed in $len_padding,
    # line after that in $xmp_after_line, and error code in $error.
    # Set $status appropriately: 1 for success; >=1 for failure.

    $len_padding = 0;
    $xmp_after_line = '';
    
    my $bC_index = -1;
    my $xmp_end_found = 0;
    my $decl_bC_found = 0;
    while ( <PDF> ) {
        push @xmp, $_;
        if ( /^\s*\Q$xmp_end\E/ ) {
            $xmp_end_found = 1;
            # Get amount of padding;
            while (<PDF>) {
                if ( /^\s*$/ ) {
                    $len_padding += length($_);
                } else {
                    $xmp_after_line = $_;
                    last;
                }
            }
            last;
        }
        elsif ( $bC_index >= 0 ){
            next;
        }
        # Rest of conditions only apply if no place yet found for byteCount
        # specification.
        elsif ( /^(\s*)\Q$pre_bC\E.*?\Q$post_bC\E\s*$/ ) {
            $bC_index = $#xmp;
        }
        elsif ( /^\s*\Q$decl_bC\E/ ) {
            $decl_bC_found = 1;
        }
        elsif ( /^(\s*)\Q$rd_end\E/ ){
            # End of rdf:Description block.
            # So having previous declaration of byteCount is irrelevant.
            $decl_bC_found = 0;
        }
        elsif ( $decl_bC_found  &&  /^(\s*)\Q$pC\E/ ){
            $bC_index = $#xmp;
            pop @xmp;
            push @xmp, '', $_;
        }

    } # End reading of XMP

    if ($bC_index < 0) {
        if ( ! $xmp_end_found ) {
            warn "  End of XMP packet not found.\n";
            $status = 2;
        }
        elsif ( ! $decl_bC_found ) {
            warn "  XMP packet not in appropriate hyperxmp-compatible format.\n";
            $status = 3;
        }
        return;
    }
    my $new_line = '      ' . $pre_bC . $pdf_size . $post_bC . "\n";
    my $old_line = $xmp[$bC_index];
    my $delta_len = length($new_line) - length($old_line);
    if ($delta_len > $len_padding) {
        warn "  Cannot get padding correct for '$pdf_name'.\n",
             "    Length change of bC line = $delta_len; ",
             "    Padding bytes available = $len_padding.\n";
        $status = 4;
        return;
    } else {
        $len_padding -= $delta_len;
        $xmp[$bC_index] = $new_line;
        $status = 1;
    }
}

#======================================================