1

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

不幸的是,尽管我的代码告诉我我正在更改 PDF 对象 ($pdf->{changes}) 并提供 pdf 文档试图更改最大可访问性(任何人都可以访问、读取、写入)pdf 的输出从未似乎随着这些变化而实现。我也一直在 grepping 我批量输出的对象节点 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');

任何帮助或建议将不胜感激

4

3 回答 3

3

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

  • 创立日期
  • 修改日期

您可以在下面找到使用 CAM::PDF 设置/修改ModDate与当前日期的快速脚本,从而产生“修改”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";
}

于 2020-02-23T22:15:35.473 回答
3

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

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

于 2020-03-02T02:36:17.743 回答
0

我发现我试图编辑的行实际上并不是 pdf 中的一组连续字符,而是它位于 PDF 中 BT 行中的 TJ 运算符内。我看不到任何处理所需文本在 CAM::PDF 库中的 TJ 行中的情况的任何规定(尽管可能有 @ChrisDolan ?)因此它无法被 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 线

对于任何想快速解决同样问题的人,这个“脏”脚本在这种情况下对我有用:

#!/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 = $1;
    }
    if ($x =~ /FlateDecode\/Length (\d+)\//) {
        print "Warn Object $i has len/len what the even is this?\n";
        $stream_len = $1;
    }
    if ($x =~ /XML\/Length (\d+)>>/) {
        $deflate = 0;
        $stream_len = $1;
    }
    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/([\(\)])/\\\1/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 字符换成了我希望将其更改为的日期,这意味着我必须遵守 TJ 运算符行遵守的“不友好”语法

于 2020-03-06T13:16:30.713 回答