PowerShell: пакетная генерация .pdf файлов (или tiff-to-pdf своими силами)
Продолжая тему пакетной обработки изображений (для электронного архива документации), пришёл к необходимости для некоторых подрядчиков генерировать .pdf (ну – для тех, кто в .tiff ничего, кроме первой страницы увидеть не может). Использовать для этого всевозможные принтеры не очень хочется по ряду причин (основная – разворачивать подобное решение по сети не особо удобно, хотя GPP в этом могли бы помочь). Поэтому искал и нашёл решение на powershell + .net.
Пересмотрел массу библиотек под dotNet:
- PDF Clown
- iTextSharp
- novaPDF SDK
- Ghostscript (не библиотека для .net, но использовать можно и через .dll, и как внешнее приложение из сценария powershell)
- пример создания .pdf средствами .net и nFOP
Но остановился только на 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 своими силами. В том числе – и для веб-сервиса. В обратную сторону, кстати, реализация будет не сложнее, но мне пока не надо
Буду рад, если Вы предложите альтернативные решения tiff-to-pdf, pdf-to-tiff для многостраничных документов со страницами разных форматов. Кстати, при преобразовании черно-белого сжатого .tiff файла в .pdf размер файла вырос всего на 1% – с 570 Кб до 576 Кб. При этом разрешение картинок прежнее. Могут ли так виртуальные принтеры?
Отзывы » (3)
RSS комментарии
Обратная ссылка
Наткнулся на ещё одно достаточно удобное решение, но не на powershell — http://www.dreamsyssoft.com/tiff-to-pdf.
Возникла ещё одна идея — обеспечить возможность преобразования .tiff в .pdf описанным выше сценарием через контекстное меню проводника. Сделаем это!
Обратное преобразование (.pdf-to-tiff) далеко не столь тривиально, как показалось на первый взгляд. По сути, необходимо именно рендерить страницу в bitmap, который потом сохранять как угодно. Пример рендеринга страницы нашёл, буду пробовать.