Perl: CAM::PDF вносит изменения, но они не отражаются в окончательном документе.
Я использую модуль CAM::PDF, чтобы попробовать редактировать документы pdf на работе - по сути, просто пытаюсь автоматически изменить дату в документах, чтобы показать, что они недавно были просмотрены.
к сожалению, несмотря на то, что мой код сообщает мне, что я вношу изменения в объекты PDF ($pdf->{changes}) и предоставляю файлы PDF, документ пытается изменить максимальную доступность (любой может получить доступ, читать, писать) PDF-файл никогда не выводится похоже, материализуются с этими изменениями. Я также просматривал tmp-файлы объектных узлов, которые я выводил массово, и обнаружил, что все они не показывают признаков старой даты после запуска кода; тем не менее, когда я просматриваю PDF-файл после его запуска, старая дата все еще находится в PDF-файле. Кто-нибудь сталкивался с этим раньше или может что-нибудь подсказать?
просто делать это вручную - не вариант; Я хочу создать сценарий, чтобы у меня был сценарий, который я запускаю для нескольких файлов одновременно (у меня есть МНОГО этих файлов, которые нужно разобрать на работе), но кроме изменения дат, написанных в документе, документ должен оставаться таким же (я имею в виду, было бы хорошо, если бы они немного изменились в размере, но не хорошо, если бы они полностью изменились по внешнему виду)
Я строго следовал примеру changepdfstring.pl (https://metacpan.org/pod/distribution/CAM-PDF/bin/changepdfstring.pl) от автора модуля CAM::PDF о том, как это сделать для моего кода, затем попробовал разные его варианты, чтобы заставить все работать - так что меня смущает, что в итоге ничего не сработало
#!/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');
Любая помощь или совет будут очень благодарны
3 ответа
Я обнаружил, что строка, которую я пытался отредактировать, на самом деле не была непрерывным набором символов в PDF-файле, а скорее находилась внутри оператора TJ в строке BT в PDF. Я не вижу каких-либо условий для обработки случаев, когда желаемый текст находится в строках TJ в библиотеке CAM::PDF (хотя, возможно, есть @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 для замены чьего-либо имени в документе на мое имя, что упрощает вставку моего изменения (но потенциально беспорядочно). Чтобы это отображалось заглавными буквами, мне пришлось перевернуть строку и поменять шрифт (F), который находился под (F2), на F0
Для строки TJ, относящейся к дате, я заменил символы TJ на дату, на которую я хотел изменить ее, это означало, что я должен был соблюдать "недружественный" синтаксис, строки оператора TJ соблюдают
Быстрый поиск на странице 145 спецификации PDF [1] показывает, что есть 2 поля метаданных, которые должны позволить простое изменение для достижения того, что вы пытаетесь сделать.
- Дата создания
- ModDate
Ниже вы можете найти быстрый скрипт, использующий 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";
}
Я автор CAM::PDF. Не видя PDF-файла, я могу только догадываться, но держу пари, что проблема в том, что$olddate
просто не соответствует ни одному тексту в документе. Например, кернинг может разбивать строки на несколько частей. Кроме того, существует несколько различных способов кодирования строк, которые выглядят одинаково в итоговом документе. Таким образом, уловка для вас будет заключаться в том, чтобы выяснить, каков шаблон для дат в ваших конкретных документах.
Тем не менее, мне также нравится умная идея, которую @Bruce Ramos предложил в отдельном ответе. Такой подход не изменит дату, которая отображается в обработанном PDF-файле (например, если вы его распечатаете), но она должна отображаться как метаданные почти в любом средстве просмотра PDF.