Статья размещена автором Бетке Сергей Сергеевич

PowerShell: пакетная генерация .pdf файлов (или tiff-to-pdf своими силами)

Продолжая тему пакетной обработки изображений (для электронного архива документации), пришёл к необходимости для некоторых подрядчиков генерировать .pdf (ну – для тех, кто в .tiff ничего, кроме первой страницы увидеть не может). Использовать для этого всевозможные принтеры не очень хочется по ряду причин (основная – разворачивать подобное решение по сети не особо удобно, хотя GPP в этом могли бы помочь). Поэтому искал и нашёл решение на powershell + .net.

Пересмотрел массу библиотек под dotNet:

Но остановился только на PDFSharp. Библиотека сопровождается, позволяет как “читать”, так и “писать” .pdf документы. И требуется только один .dll файл для использования функций библиотеки в сценарии. В общем, минусы можно везде найти, но для моей простой задачи вполне подойдёт.

Нашёл типичный пример (Hello, world) на C#. Ну и переписал его на powershell:

add-type -path 'PDFsharp\PdfSharp.dll';

$pdf = new-object -typeName PdfSharp.Pdf.PdfDocument; 
$pdf.Info.Title = 'Created with PDFsharp'; 
[PdfSharp.Pdf.PdfPage] $page = $pdf.AddPage(); 
[PdfSharp.Drawing.XGraphics] $gfx = [PdfSharp.Drawing.XGraphics]::FromPdfPage( $page ); 
[PdfSharp.Drawing.XFont] $font = new-object -typeName PdfSharp.Drawing.XFont -argumentList `
    'Verdana' `
    , 20 `
    , ( [PdfSharp.Drawing.XFontStyle]::BoldItalic ) `
;
$gfx.DrawString( `
    'Hello, World!' `
    , $font `
    , ( [PdfSharp.Drawing.XBrushes]::Black ) `
    , ( new-object -typeName PdfSharp.Drawing.XRect -argumentList 0, 0, ($page.Width), ($page.Height) ) `
    , ( [PdfSharp.Drawing.XStringFormats]::Center ) `
); 
$pdf.Save( 'dest.pdf' ); 

Предварительную сборку PDFSharp выкладываем в папку PDFSharp. И этот код прекрасно генерирует простейший одностраничный .pdf файл с надписью по центру – Hello, world.

Но это был только пример. Я то хочу из полученного в предыдущей статье монохромного многостраничного .tiff файла получить .pdf. Попробуем.

Import-Module '.\ITG.Imaging\ITG.Imaging.PDF' -force

get-childItem `
    -path 'X:\Порталы\Аутсорсинг\Процессы\Изготовление офисов\Чертежи' `
    -filter '*.tif' `
| Copy-Tiff2PDF -passThru `
;    

Выше – пример использования модуля и “командлеты” из него, а вот и сам модуль:

add-type -assembly 'System.Drawing';
add-type -assembly 'PresentationCore';
add-type -path (join-path -path $PSScriptRoot -childPath 'PDFsharp\PdfSharp-WPF.dll');

function Copy-Tiff2PDF {
    <#
        .Synopsis
            Преобразует TIFF файл в PDF
        .Description
            Преобразует TIFF файл в PDF
        .Parameter sourceFile
            Исходный файл (объект).
        .Parameter destination
            Полный путь (к папке), в который будет сохранён полученный файл.
            Если параметр не указан, будет создан файл с тем же именем
            в каталоге исходного файла
        .Parameter newName
            Имя создаваемого файла.
            Если параметр не указан, будет создан файл с тем же именем
        .Example
            Обработка файлов каталога:
            dir 'c:\temp\*.tif' | Copy-Tiff2PDF
    #>
    
    param (
        [Parameter(
            Mandatory=$true,
            Position=0,
            ValueFromPipeline=$true,
            HelpMessage='Исходный файл (объект).'
        )]
        [System.IO.FileInfo] $sourceFile
        , [Parameter(
            Mandatory=$false,
            Position=1,
            ValueFromPipeline=$false,
            HelpMessage='Полный путь (к папке), в который будет сохранён полученный файл.'
        )]
        [System.IO.DirectoryInfo] $destination = ($sourceFile.Directory)
        , [Parameter(
            Mandatory=$false,
            Position=2,
            ValueFromPipeline=$false,
            HelpMessage='Имя создаваемого файла.'
        )]
          [string] $newName = ([System.IO.Path]::GetFileNameWithoutExtension($sourceFile.name) + '.pdf')
        , [switch] $PassThru
    )
    
    process {
        $sourceStream = new-object -typeName System.IO.FileStream -argumentList `
            ( $sourceFile.FullName ) `
            , ( [System.IO.FileMode]::Open ) `
            , ( [System.IO.FileAccess]::Read  ) `
            , ( [System.IO.FileShare]::Read  ) `
        ;
        $decoder = new-object -typeName System.Windows.Media.Imaging.TiffBitmapDecoder -argumentList `
            ( $sourceStream ) `
            , ( [System.Windows.Media.Imaging.BitmapCreateOptions]::None ) `
            , ( [System.Windows.Media.Imaging.BitmapCacheOption]::None ) `
        ;
        [System.IO.FileInfo] $destFile = ( join-path -path $destination.Fullname -childPath $newName );
        $pdf = new-object -typeName PdfSharp.Pdf.PdfDocument; 
        
        foreach ($frame in $decoder.Frames) {
            [System.IO.FileInfo] $tempFile = [System.IO.Path]::GetTempFileName();
            $encoder = new-object -typeName System.Windows.Media.Imaging.TiffBitmapEncoder;
            $encoder.Frames.Add( $frame.Clone() );
            $tempStream = new-object -typeName System.IO.FileStream -argumentList `
                ( $tempFile ) `
                , ( [System.IO.FileMode]::Create ) `
            ;
            $encoder.Save( $tempStream );
            $tempStream.Close();

            [PdfSharp.Pdf.PdfPage] $page = $pdf.AddPage(); 
            $page.Height = [PdfSharp.Drawing.XUnit]::FromPresentation( $frame.Height );
            $page.Width = [PdfSharp.Drawing.XUnit]::FromPresentation( $frame.Width );
            [PdfSharp.Drawing.XGraphics] $gfx = [PdfSharp.Drawing.XGraphics]::FromPdfPage( $page ); 
            $gfx.DrawImage( `
                ( [PdfSharp.Drawing.XImage]::FromFile( $tempFile.FullName ) ) `
                , 0, 0 `
            );
            $gfx.Dispose();
            $page.Close();
            ## $tempFile.Delete();
        };

        $pdf.Save( $destFile.FullName ); 
        $pdf.Dispose();
        $sourceStream.Close();

        if ( $PassThru ) { $destFile };
    }
};  

Export-ModuleMember `
    Copy-Tiff2PDF `
;

Да, решение не выглядит красивым. Приходится каждую страницу в .tiff файле сохранять предварительно в файл, а потом уже создавать объекты библиотеки из файла. Спишем такой подход на особенности библиотеки.

Приведённый выше код прекрасно преобразует исходные многостраничные .tiff файлы в .pdf файлы, при этом сохраняя формат пикселей, цветность, размер страниц. Другими словами, указанным способом Вы можете подготовить .pdf файл, в котором будут страницы разного размера, некоторые из которых – чёрно-белые tiff, некоторые могут быть цветными. Другими словами, по своим возможностям такое решение превосходит виртуальне принтеры типа doPDF и прочие.

Касательно самой библиотеки PDFSharp. Да, замечания к ней есть. Но все исходники с ней поставляются, и, в принципе, её можно и переписать, если испытываете такую необходимость. Для задач типа моей (конвертация картинок в pdf) желательная реализация encoder’а и decoder’а в терминологии WPF для .pdf формата, и код был бы куда более прозрачным, да и менее прожорливыми при обработки многостраничных документов. Документ разрабатывался для динамической манипуляцией объектами в документе, и его задачи несколько иные, чем мне требуются. Так же не очень поддерживаю автора в создании избыточного (на мой взгляд) количества классов, во многом дублирующих классы GDI+, но при этом с потерей функциональности местами.

Но так или иначе, данная библиотека в достаточной степени логична, хорошо документирована, удобна в использовании. Автору – наши благодарности.

P.S. Теперь мы знаем, как легко и просто сделать tiff-to-pdf своими силами. В том числе – и для веб-сервиса. В обратную сторону, кстати, реализация будет не сложнее, но мне пока не надо PowerShell: пакетная генерация .pdf файлов (или tiff to pdf своими силами)

Буду рад, если Вы предложите альтернативные решения tiff-to-pdf, pdf-to-tiff для многостраничных документов со страницами разных форматов. Кстати, при преобразовании черно-белого сжатого .tiff файла в .pdf размер файла вырос всего на 1% – с 570 Кб до 576 Кб. При этом разрешение картинок прежнее. Могут ли так виртуальные принтеры? PowerShell: пакетная генерация .pdf файлов (или tiff to pdf своими силами)

Отзывы » (3)

  1. Наткнулся на ещё одно достаточно удобное решение, но не на powershell — http://www.dreamsyssoft.com/tiff-to-pdf.

  2. Возникла ещё одна идея — обеспечить возможность преобразования .tiff в .pdf описанным выше сценарием через контекстное меню проводника. Сделаем это!

  3. Обратное преобразование (.pdf-to-tiff) далеко не столь тривиально, как показалось на первый взгляд. По сути, необходимо именно рендерить страницу в bitmap, который потом сохранять как угодно. Пример рендеринга страницы нашёл, буду пробовать.

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка