«Клавиша Print Screen отлично справляется с поставленной задачей. Что может быть проще, чем сохранить документ как изображение?» — спросите вы. Долгое время я работал над задачами сохранения отчётов и форм в формате PDF. Но даже с простыми многостраничными таблицами цифр не все PDF генераторы справлялись одинаково успешно.
Не так давно, мне попался проект, заказчик которого хотел свои сохранённый в PDF маркетинговые шедевры конвертировать в один из графических форматов, например PNG. После долгих уговоров и приведения контраргументов, бюджет проекта позволял купить недорогой .NET компонент.
Оставалось выбрать самый подходящий под требования заказчика и, по-возможности, с хорошей англоговорящей, англо пишущей службой поддержки (не с полуострова Индостан):
Требования к PDF конвертерам
Основное внимание уделялось таким особенностям конвертеров как:
- Простотой API, с том числе, для настройки шрифтов (помимо встроенных шрифтов, должна быть возможность добавить недостающий системный шрифт или ссылку на него)
- Возможность экспорта в TIFF, PNG, JPEG, BMP форматы
- Поддержка прозрачных картинок внутри PDF документа
- Поддержка цветовых масок
- Поддержка азиатских шрифтов
- Аннотации должны конвертироваться вместе с документом (возможность отключить эту опцию)
- Различные режимы смешивания цветов (blending modes)
- Различные шаблоны заливки (tiling patterns)
- Различные цветовые пространства RGB, CMYK, Gray, DeviceN
- Прозрачные группы для документов, созданных при помощи Adobe Illustrator
После нескольких запросов, поисковик выдал группу подходящих .NET компонентов:
ABCpdf | 6.1.1.5 |
Adobe Acrobat (Interop.Acrobat) | Adobe Acrobat 10.0 Type Library |
Apitron.PDF.Rasterizer | 3.0.1.0 |
O2S.Components.PDFRender4NET | 4.5.1.0 |
PDFLibNET | |
PDFSharp | 1.31.1789.0 |
SautinSoft.PdfFocus | 2.2.2.2 |
TallCоmponents.PDF.Rasterizer | 3.0.91.0 |
Начало испытаний
Для тестовых целей был выбран одностраничный PDF файл 3BigPreview.pdf (взят с официального сайта компании Adobe). Он включает в себя большое количество графических элементов, демонстрируя возможности визуализации компонентой графических объектов PDF и их свойств.
ABCPDF
Запустить пример для данной библиотеки на 64-разрядной машине удалось не сразу, лишь поменяв платформу с AnyCPU на x86 был получен результат. Проблема возникла с выставлением правильного разрешения картинки. Картинку правильного размера 612 x 792 пикселей удалось получить только явно выставив разрешение результирующей картинки в 72 точки на дюйм, что странно, так как другие компоненты выставляют 96dpi (для Win7). Правильное отображение иероглифов Kinsoku Shori порадовало. Некоторые буквы выглядят более яркими чем остальные, что говорит о не совсем честном использовании сглаживания (antialiasing). Результат хороший для тех кому не важно, что используется не 100% managed код, а мы идём дальше.
Кусок кода для ABCpdf:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
FileStream fs = new FileStream(pdfInputPath, FileMode.Open);
Doc document = new Doc();
document.Read(fs);
document.Rendering.DotsPerInch = 72;
document.Rendering.DrawAnnotations = true;
document.Rendering.AntiAliasImages = true;
document.Rect.String = document.CropBox.String;
document.Rendering.Save(Path.ChangeExtension(Path.Combine(imageOutputPath ,imageName), imageFormat.ToString()));
}
Результат:
Adobe Acrobat 10.0 Type Library
Нетрудно понять, что нативная библиотека Adobe не может быть не в фаворитах. Но вызовы com объектов, это не совсем то, чего нам хотелось получить, тем более, что для этого нужна установленная Pro версия продукта.
Кусок кода для Adobe:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
CAcroPDDoc pdfDoc = (CAcroPDDoc) Interaction.CreateObject("AcroExch.PDDoc", "");
pdfDoc.Open(pdfInputPath);
CAcroPDPage pdfPage = (CAcroPDPage) pdfDoc.AcquirePage(0);
CAcroPoint pdfPoint = (CAcroPoint) pdfPage.GetSize();
CAcroRect pdfRect = (CAcroRect) Interaction.CreateObject("AcroExch.Rect", "");
pdfRect.Left = pdfRect.Top = 0;
pdfRect.right = pdfPoint.x;
pdfRect.bottom = pdfPoint.y;
pdfPage.CopyToClipboard(pdfRect, 0, 0, 100);
IDataObject clipboardData = Clipboard.GetDataObject();
if (clipboardData.GetDataPresent(DataFormats.Bitmap))
{
using(Bitmap pdfBitmap = (Bitmap) clipboardData.GetData(DataFormats.Bitmap))
{
pdfBitmap.Save(Path.ChangeExtension(Path.Combine(imageOutputPath, imageName), imageFormat.ToString()), imageFormat);
}
}
pdfDoc.Close();
Marshal.ReleaseComObject(pdfPage);
Marshal.ReleaseComObject(pdfRect);
Marshal.ReleaseComObject(pdfDoc);
Marshal.ReleaseComObject(pdfPoint);
}
Результат:
Apitron.PDF.Rasterizer for .NET
Компонент хорошо справился с тестовым испытанием. Удобный API, есть возможность настраивать шрифты и отключаемое рисование аннотаций. Изображение выглядит чётким. Все элементы оригинального PDF документа нарисованы. Заметил, что при конвертировании документа все восемь ядер рабочей машины были задействованы, скорее всего это будет удобно для тех, кто хочет увеличить производительность приложения увеличением оперативной памяти и количества процессоров рабочей системы.
Кусок кода для Apitron:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
FileStream fs = new FileStream(pdfInputPath, FileMode.Open);
Document doc = new Apitron.PDF.Rasterizer.Document(fs);
RenderingSettings option = new RenderingSettings();
option.DrawAnotations = true;
doc.Pages[0].Render((int) doc.Pages[0].Width, (int) doc.Pages[0].Height, option).Save(Path.ChangeExtension(Path.Combine(imageOutputPath, imageName), imageFormat.ToString()), imageFormat);
}
Результат:
O2S.Components.PDFRender4NET
Румынский компонент удовлетворительно справился с тестовым испытанием. Не все элементы документа сохраняются правильно. Как видно из результирующего файла все элементы спецификации поддерживаются. Есть явные проблемы с рисованием текста.
Кусок кода для O2S:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
PDFFile pdfFile = O2S.Components.PDFRender4NET.PDFFile.Open(pdfInputPath);
using (Bitmap pageImage = pdfFile.GetPageImage(0, 300))
{
pageImage.Save(Path.ChangeExtension(Path.Combine(imageOutputPath, imageName), imageFormat.ToString()), imageFormat);
}
}
Результат:
xPDF Wrapper Library (PDFLibNET)
Генерация тестового примера не удалась. Изображение выглядит размытым, текст не читается. При конвертировании тестового файла получили ошибки.
Кусок кода для PDFLibNET:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
PDFWrapper pdfWrapper = new PDFWrapper();
pdfWrapper.LoadPDF(pdfInputPath);
pdfWrapper.ExportJpg(Path.ChangeExtension(Path.Combine(imageOutputPath, imageName), imageFormat.ToString()), 10);
pdfWrapper.Dispose();
}
Результат:
PDFSharp (GhostScript и другие обёртки)
PDFSharp, как и другие обёртки известного инструмента GhostScript (например gouda, GhostscriptSharp) работает специфично и не всегда предсказуемо. Потратив несколько часов времени получилось сделать только извлечение картинок, целиком документ сохранить в виде картинки не получилось. Отмечу как хорошую идею для новой статьи.
Можно здесь же отметить, всеми любимый, iTextSharp. Удобный инструмент, но не для нашей задачи.
PdfFocus от SautinSoft
В мой обзор попался также отечественный компонент. Но, к сожалению, с испытаниями он не справился.
На тестовом файле он выдал NRE ошибку. Зато, на других файлах он неплохо показал себя.
(Автору: «Максим, я уверен, что у Вас отличный софт и эта мелочь будет быстро исправлена.»)
Кусок кода для SautinSoft:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
PdfFocus pdfFocus = new PdfFocus();
pdfFocus.OpenPdf(pdfInputPath);
pdfFocus.ImageOptions.Dpi = 96;
pdfFocus.ImageOptions.ImageFormat = imageFormat;
using (Image bitmap = pdfFocus.ToDrawingImage(1))
{
bitmap.Save(Path.ChangeExtension(Path.Combine(imageOutputPath, imageName), imageFormat.ToString()), imageFormat);
}
pdfFocus.ClosePdf();
}
TallCompоnents.PDF.Rаsterizer
Нидерландский компонент хорошо справился с тестовым испытанием. Сложности специфичного API были компенсированы базовыми знаниями Graphics. Видны небольшие проблемы с рисованием текста.
Кусок кода для TаllCоmponents:
public static void ConvertPDFToImage(string pdfInputPath, string imageOutputPath, string imageName, ImageFormat imageFormat)
{
FileStream fs = new FileStream(pdfInputPath, FileMode.Open);
Document document = new Document(fs);
Page page = document.Pages[0];
RenderSettings renderSettings = new RenderSettings();
renderSettings.GdiSettings.WorkAroundImageTransparencyPrintSize = true;
using (Bitmap bitmap = new Bitmap((int) page.Width, (int) page.Height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.Clear(Color.White);
page.Draw(graphics, renderSettings);
}
bitmap.Save(Path.ChangeExtension(Path.Combine(imageOutputPath, imageName), imageFormat.ToString()), imageFormat);
}
}
Результат:
Итог
Сохранение PDF документов в изображение BMP, JPEG, TIFF задача нетривиальная, как это может показаться на первый взгляд. Можно найти массу программных утилит, библиотек и коммерческих сервисов и их условно бесплатных аналогов, но в маленьком стартапе не обойтись без надежных компонентов сторонних разработчиков. Проанализировав результаты, перечитав документацию, примеры кода и цены на сайтах, я выбрал компонент для своего проекта. По производительности, все библиотеки находятся на одном уровне, возможно, из-за специфики считывания PDF документа в один поток. При выборе я не учёл возможность использования продуктов на мобильных устройствах, так как не все компоненты из-за ограничений GDI+ смогут работать корректно под платформу Android или совместимы с Mono.Xamarin.
Автор: Apitron