Perl:CAM::PDF 进行了更改,但这些更改并未在最终文档中实现

Perl: CAM::PDF makes changes, but these don't materialise in final doc

我一直在使用 CAM::PDF 模块尝试在工作中编辑 pdf 文档 - 本质上只是尝试自动更改文档上的日期以显示它们最近已被审阅

不幸的是,尽管我的代码告诉我我正在对 PDF 对象进行更改 ($pdf->{changes}) 并给 pdf 文档试图改变最大的可访问性(任何人都可以访问、阅读、写入) pdf 的输出似乎从未随着这些更改而实现。我也一直在 grep 大量输出的对象节点 tmp 文件,发现所有这些在 运行 代码之后都没有显示旧日期的迹象;然而,当我在 运行 之后查看 pdf 时,旧日期仍在 pdf 上。有没有人以前遇到过这个或可以提出任何建议?

仅手动执行此操作不是一种选择;我想编写这个脚本,这样我就可以有一个脚本,我只是 运行 一次针对多个文件(我有很多这些文件需要在工作中整理)但除了更改文档上写的日期之外,文档必须保持相同的外观(我的意思是,如果它们的尺寸稍微改变一下就可以了,但如果它们的外观完全改变了就不行了)

我严格遵循模块 CAM::PDF 作者的示例 changepdfstring.pl (https://metacpan.org/pod/distribution/CAM-PDF/bin/changepdfstring.pl) 如何为我的代码执行此操作,然后尝试了不同的变体来尝试并让事情发挥作用 - 所以我很困惑最终没有任何效果

#!/usr/bin/perl
use strict;
use warnings;
use CAM::PDF;
use Data::Dumper;

my $pdf = CAM::PDF->new('Order fulfilment process flowchart.pdf');
if (!$pdf->canModify())
   {
      die "This PDF forbids modification\n";
   }
my $olddate = "15.02.2019";
my $newdate = "22.02.2022";
foreach my $objectnumber (keys %{$pdf->{xref}}){
        my $objectnode = $pdf->dereference($objectnumber);
        $pdf->changeString($objectnode, {$olddate=>$newdate});
                }


my $change = $pdf->{changes};
print Dumper($change);
my $count = 0;
foreach my $objectnumber (keys %{$pdf->{xref}}){
        my $objectnode = $pdf->dereference($objectnumber);
        $count++;
        open (ONO, ">tmp.objectnode.$count");
        print ONO Dumper($objectnode);
        close (ONO);}

if (!scalar %{$pdf->{changes}})
{
   die "no changes were made :(";
}
$pdf->preserveOrder();

$pdf->cleanoutput('pleasework.pdf');

如有任何帮助或建议,我们将不胜感激

在 PDF 规范[1]的第 145 页快速搜索显示有 2 个元数据字段应该允许简单的更改来实现您正在尝试做的事情。

  • 创建日期
  • 修改日期

您可以在下面找到一个快速脚本,使用 CAM::PDF 将 set/modify ModDate 与当前日期,从而给出 [=36= 的错觉] PDF.

如果需要,可以修改脚本以使用特定日期而不是当前时间来设置修改日期。

请注意,我不确定 CAM::PDF 是否是完成此任务的最佳选择。

该脚本只是在 CAM::PDF 的限制和简单性范围内可以完成的示例。

[1] https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf

#!/usr/bin/env perl
use strict;
use warnings;

use Time::Local;
use CAM::PDF;
use CAM::PDF::Node;

my $infile = shift || die 'syntax...';
my $outfile = shift || die 'syntax...';
my $pdf = CAM::PDF->new($infile) || die;
my $info = $pdf->getValue($pdf->{trailer}->{Info});
if ($info) {
    my @time = localtime(time);
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @time;
    $year += 1900;
    $mon++;
    my $gmt_offset_in_seconds = timegm(@time) - timelocal(@time);
    my $gmt_offset_min = ($gmt_offset_in_seconds / 60) % 60;
    my $gmt_offset_hour = abs(int($gmt_offset_in_seconds / (60*60)));
    my $offset_char = "";
    if ($gmt_offset_in_seconds < 0) {
        $offset_char = "-";
    } else {
        $offset_char = "+";
    }
    my $date = sprintf("D:%04d%02d%02d%02d%02d%02d%s%02d'%02d'", $year, $mon, $mday, $hour, $min, $sec, $offset_char, $gmt_offset_hour, $gmt_offset_min);
    my $objnum = undef;
    for my $obj ('Creator', 'Producer', 'CreationDate') {
        if (exists $info->{$obj} and exists $info->{$obj}->{objnum}) {
            $objnum = $info->{$obj}->{objnum};
            last;
        }
    }
    die "Cannot find objnum, halting..." if not defined $objnum;
    my $mod_date = $info->{ModDate};
    if ($mod_date) {
        $mod_date->{value} = $date;
    } else {
        my $mod_date = new CAM::PDF::Node('string',$date);
        $mod_date->{gennum} = 0;
        $mod_date->{objnum} = $objnum;
        $info->{ModDate} = $mod_date;
    }
    $pdf->preserveOrder();
    $pdf->cleanoutput($outfile);
} else {
    print "Cannot find PDF info section, doing nothing!\n";
}

我是 CAM::PDF 的作者。没有看到 PDF,我只能猜测,但我敢打赌问题是 $olddate 根本不匹配文档中的任何文本。例如,字距调整可以将字符串分成多个部分。此外,有几种不同的方法可以对在生成的文档中显示相同的字符串进行编码。因此,您的诀窍是弄清楚特定文档中的日期模式是什么。

也就是说,我也喜欢@Bruce Ramos 在单独的回答中提出的聪明想法。该方法不会更改呈现的 PDF 中可见的日期(例如,如果您打印它),但它应该在几乎所有 PDF 查看器中显示为元数据。

我发现我试图编辑的行实际上不是 pdf 中的一组连续字符,而是在 PDF 中 BT 行中的 TJ 运算符内。我看不到任何处理所需文本在 CAM::PDF 库中的 TJ 行中的情况的规定(尽管可能有 @ChrisDolan?)因此无法对 "swapped out" 进行操作 CAM::PDF。解压缩所有流(如果适用)后,我发现此 'TJ' 行包含我希望操作的文本:

[(D)-20(a)24(t)62(e)-46(:)86( )-46(1)52(5)-37(.)70(0)-37(2)52(.)-20(2)52(0)-37(1)52(9)] TJ

我不相信CAM::PDF可以作用于TJ线,也许它只能作用于Tj线

对于寻找同一问题的快速答案的任何人,这个 "dirty" 脚本在这种情况下对我有用:

#!/usr/bin/perl

use strict;
use Compress::Raw::Zlib;
use bytes;

open(OUT,'>', "newfromoldscript.pdf");

my $fname = 'Order fulfilment process flowchart.pdf';
open(FILE, '<:raw', $fname) || die("can't open($fname): $!");
$/ = undef;
my $file = <FILE>;

my $file_len = length($file);
my $i = 0;
my $offset;

my $offset;
my $o;
do {
    $o = doX(substr($file, $offset, $file_len), $i);
    $offset+=$o;
    $i++;
} while($o  && $i< 100);    

sub doX {
    my $file = shift;
    my $i = shift;

    my $stream = index($file, "\nstream");

    if ($stream < 0) {
        print OUT $file;
        return 0;
    }
    $stream++;
    my $deflate = 1;

    my $line_before = rindex(substr($file,0,$stream), "<<");
    print OUT substr($file,0,$line_before);

    my $x = substr($file, $line_before,$stream-$line_before);

    if ($i == 22) {
        print "";
    }

    my $stream_len;
    if ($x =~ /FlateDecode\/Length (\d+)>>/) {
        $stream_len = ;
    }
    if ($x =~ /FlateDecode\/Length (\d+)\//) {
        print "Warn Object $i has len/len what the even is this?\n";
        $stream_len = ;
    }
    if ($x =~ /XML\/Length (\d+)>>/) {
        $deflate = 0;
        $stream_len = ;
    }
    if (!$stream_len) { 
        die("I fail with no stream len : $x");
    }


    print "-->$line_before,$i,$stream=$stream_len=$x<--\n";
    my $bytes = substr($file, $stream+8,$stream_len);

    my $orig_bytes = $bytes;    # inflate seems to mangle bytes, so take a copy

    my $o;
    my $d=new Compress::Raw::Zlib::Inflate();

    if ($deflate) {
        $d->inflate($bytes,$o);
    } else {
        $o = $bytes;
    }
    my $orig_x = $x;

    my $changes;
    my %change = (
        '-20(2)52(0)-37(.)52(.)' => '-20(2)52(0)-37(2)52(0)', #trialling different reg ex's here 
                '-37(1)52(9)'=>'-37(2)52(0)', #reg ex's
                'Date: 15.02.2019'=>'Date: 12.02.2020', 
                '[(A)[\d-]+(p)[\d-]+(p)[\d-]+(r)[\d-]+(o)[\d-]+(ve)[\d-]+(d)[\d-]+( )[\d-]+(B[^\]]+\] TJ'=>'(Approved By: George W) Tj??G-TAG??' #scrap the whole TJ, replace for Tj
    );
    foreach my $re (keys %change) {
                my $to = $change{$re};
                $re =~ s/([\(\)])/\/g;     # escape round brackets
        print $re;

        open (GW, ">tmp.gw");
                print GW $re;
                close (GW);
                if ($o=~/$re/m) {
                        $o =~ s/$re/$to/mg;
                        print $o;
                        $changes++;
                }
        }
        if ($changes) {

        print "\n MADE CHANGES\n";  
        #split, get rid of the ? mark tag
        my @remains = split('\?\?G-TAG\?\?', $o); 
        my $firsthalf = $remains[0];
        my $secondhalf = $remains[1];

        #reverse the string
        $firsthalf = scalar reverse ($firsthalf);
                if ($firsthalf =~ m/fT 52\.8 2F/){print "FOUND THE REVERSE"}
        $firsthalf =~ s/fT 52\.8 2F/fT 52\.8 0F/;
        #reg ex to back track to the nearest and thus relevant Font/F and set it to F0 


        #put it back in correct orientation
        $firsthalf = scalar reverse ($firsthalf);
        $o = join("", $firsthalf, $secondhalf);
        open (WEIRD, ">tmp.weird");
        print WEIRD $firsthalf;
        close (WEIRD);

        $changes++;
        my $d = new Compress::Raw::Zlib::Deflate();
        my $obytes;
        my $obytes2;
        my $status = $d->deflate($o, $obytes);
        $d->flush($obytes2);
        $bytes = $obytes . $obytes2;

        if (length($bytes) != $stream_len) {
            my $l = length($bytes);
            print "-->$x<--\n";
            warn("what do we do here $l != $stream_len");
            $orig_x =~ s/$stream_len/$l/;
        }
        print OUT $orig_x . "stream\r\n";
        print OUT $bytes . "\r";
    } else {
        print OUT $orig_x . "stream\r\n";
        print OUT $orig_bytes . "\r";
    }





    open(TMP,">out/tmp.$i.bytes");
    print TMP $o;
    close(TMP);

    return $stream + 8 + $stream_len + 1;
}

基本上我将 TJ 换成了 Tj,以便将文档上其他人的名字更改为我的名字,这使得插入我的更改更简单(但可能会造成混乱)。为了使其能够以大写字母显示,我必须反转字符串并将 (F2) 下的字体 (F) 换成 F0

对于与日期相关的 TJ 行,我将 TJ 字符换成了我希望将其更改为的日期,这意味着我必须遵守 "unfriendly" 语法 TJ operator lines above by