Как удалить все изображения из PDF, не повреждая его с помощью CAM::PDF?

Сценарий ниже может удалить все изображения из файла PDF, используя CAM::PDF, Вывод, однако, поврежден. Тем не менее, читатели PDF могут открыть его, но жалуются на ошибки. Например, mupdf говорит:

error: no XObject subtype specified
error: cannot draw xobject/image
warning: Ignoring errors during rendering
mupdf: warning: Errors found on page

Сейчас, CAM::PDF на странице CPAN ( здесь) перечислены deleteObject() метод в разделе "Более глубокие утилиты", что, вероятно, означает, что он не предназначен для публичного использования. Кроме того, он предупреждает, что:

Эта функция НЕ заботится о зависимостях от этого объекта.

Мой вопрос: как правильно удалить объекты из файла PDF, используя CAM::PDF? Если проблема связана с зависимостями, как я могу удалить объект, заботясь о его зависимостях?

Чтобы узнать, как удалить изображения из PDF-файла с помощью других инструментов, см. Соответствующий вопрос здесь.

use CAM::PDF;    
my $pdf = new CAM::PDF ( shift ) or die $CAM::PDF::errstr;

foreach my $objnum ( sort { $a <=> $b } keys %{ $pdf->{xref} } ) {
  my $xobj = $pdf->dereference ( $objnum );

  if ( $xobj->{value}->{type} eq 'dictionary' ) {
    my $im = $xobj->{value}->{value};
    if
    (
      defined $im->{Type} and defined $im->{Subtype}
      and $pdf->getValue ( $im->{Type}    ) eq 'XObject'
      and $pdf->getValue ( $im->{Subtype} ) eq 'Image'
    )
    {
      $pdf->deleteObject ( $objnum );
    }
  }
}

$pdf->cleanoutput ( '-' );

2 ответа

Это использует CAM::PDF, но использует немного другой подход. Вместо того, чтобы пытаться удалить изображения, что довольно сложно, оно заменяет каждое изображение прозрачным.

Во-первых, обратите внимание, что мы можем использовать магию изображения для создания пустого PDF, который содержит только прозрачное изображение:

% convert  -size 200x100 xc:none transparent.pdf

Если мы просмотрим сгенерированный PDF в текстовом редакторе, мы можем найти основной объект изображения:

8 0 obj
<<
/Type /XObject
/Subtype /Image
/Name /Im0
...

Здесь важно отметить, что мы создали прозрачное изображение как объект № 8.

Затем возникает вопрос импорта этого объекта и использования его для замены каждого из реальных изображений в PDF-файле, эффективно удаляя их.

use warnings; use strict;
use CAM::PDF;    
my $pdf = new CAM::PDF ( shift ) or die $CAM::PDF::errstr;

my $trans_pdf = CAM::PDF->new("transparent.pdf") || die "$CAM::PDF::errstr\n";
my $trans_objnum = 8; # object number of transparent image

foreach my $objnum ( sort { $a <=> $b } keys %{ $pdf->{xref} } ) {
  my $xobj = $pdf->dereference ( $objnum );

  if ( $xobj->{value}->{type} eq 'dictionary' ) {
    my $im = $xobj->{value}->{value};
    if
    (
      defined $im->{Type} and defined $im->{Subtype}
      and $pdf->getValue ( $im->{Type}    ) eq 'XObject'
      and $pdf->getValue ( $im->{Subtype} ) eq 'Image'
    ) {
        $pdf->replaceObject ( $objnum, $trans_pdf, $trans_objnum, 1 );
    }
  }
}

$pdf->cleanoutput ( '-' );

Сценарий теперь заменяет каждое изображение в PDF импортированным прозрачным объектом изображения (объект № 8 из transparent.pdf).

Другой подход, который действительно удаляет изображения:

  1. найти и удалить изображения XObjects в списках ресурсов,
  2. сохранить массив с именами удаленных ресурсов,
  3. заменить одинаковые пробелы на соответствующие Do операторы в каждом содержании страницы,
  4. очистить и распечатать.

Обратите внимание, что подход дварринга более безопасен, так как он не должен вызывать $doc->cleanse в конце. Согласно CAM::PDF документация ( здесь), cleanse метод

Удалить неиспользуемые предметы. ВНИМАНИЕ: эта функция разбивает некоторые документы PDF, потому что она удаляет объекты, которые строго являются частью иерархии модели страницы, но которые в любом случае необходимы (например, некоторые объекты определения шрифта).

Я не знаю, сколько проблем с использованием cleanse может быть.

use CAM::PDF;
my $doc = new CAM::PDF ( shift ) or die $CAM::PDF::errstr;

# delete image XObjects among resources
# but keep their names

my @names;

foreach my $objnum ( sort { $a <=> $b } keys %{ $doc->{xref} } ) {
  my $obj = $doc->dereference( $objnum );
  next unless $obj->{value}->{type} eq 'dictionary';

  my $n = $obj->{value}->{value};

  my $resources = $doc->getValue ( $n->{Resources}       ) or next;
  my $resource  = $doc->getValue ( $resources->{XObject} ) or next;

  foreach my $name ( sort keys $resource ) {
    my $im = $doc->getValue ( $resource->{$name} ) or next;

    next unless defined $im->{Type}
            and defined $im->{Subtype}
            and $doc->getValue ( $im->{Type}    ) eq 'XObject'
            and $doc->getValue ( $im->{Subtype} ) eq 'Image';

    delete $resource->{$name};                                                                                                           
    push @names, $name;                                                                                                                  
  }                                                                                                                                      
}                                                                                                                                        


# delete the corresponding Do operators                                                                                                                        

if ( @names ) {                                                                                                                                                               
  foreach my $p ( 1 .. $doc->numPages ) {                                                                                                                                     
    my $content = $doc->getPageContent ( $p );
    my $s;
    foreach my $name ( @names ) {
      ++$s if $content =~ s{( / \Q$name\E \s+ Do \b )} { ' ' x length $1 }xeg;
    }
    $doc->setPageContent ( $p, $content ) if $s;
  }
}

$doc->cleanse;
$doc->cleanoutput;
Другие вопросы по тегам