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.

Другие вопросы по тегам