Когда нужно сделать PDF документ

    • ASP.NET
    • .NET
    • C#
    • PDF
    • Reports
    • XML
    • XSLT
  • modified:
  • reading: 4 minutes

Если перед Вами стоит задача - создавать простенькие (или не совсем) pdf документы в вашем приложении - это могут быть и отчеты и рецепты, ну либо вы захотите печатать так информацию о ваших объектах, то для решения этой задачи можно воспользоваться, к примеру, установленным OpenOffice и его возможностями (это тяжеловестно), а можно библиотекой iTextSharp (Free C#-PDF library), вот про это я и хочу поведать небольшой пример, при помощи которого я создам такой вот документ:

Способов создания pdf документов при помощи данной библиотеки множество. Мне больше всего понравился способ описания при помощи xml документа. Создавать такой xml я решил  xslt преобразованием, а данные подсовывать xml-документом.

Наши данные будут выглядить вот так:

<?xml version="1.0" encoding="utf-8" ?>
<Profile>
 <FirstName>Иван</FirstName>
 <SecondName>Иванович</SecondName>
 <LastName>Иванов</LastName>
 <Subtexts>
  <Text>Какой то непонятный текст. Какой то непонятный текст. Какой то непонятный текст. Какой то непонятный текст.
Какой то непонятный текст. Какой то непонятный текст. Какой то непонятный текст. Какой то непонятный текст.</Text> </Subtexts>
 <Subtexts>
  <Text>Другой непонятный текст. Другой непонятный текст. Другой непонятный текст. Другой непонятный текст. Другой непонятный текст. 
Другой непонятный текст. Другой непонятный текст. Другой непонятный текст. Другой непонятный текст.</Text> </Subtexts>
</Profile>

Далее нам необходимо создавать xml для pdf (скажу что ссылка на iText.dtd схему в документе tutorial битая, верную ссылку я привожу внизу), как я и сказал выше для этого мы напишем xslt преобразование:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
 <xsl:param name="picturePath"/>
 <xsl:output method="xml" indent="yes"/>
 
 <xsl:template match="/Profile">
  <itext>
   <paragraph align="Center">
    <phrase fontstyle="bold" size="16.0" >Информация о пользователе</phrase>
   </paragraph>
 
   <image >
    <xsl:attribute name="url">
     <xsl:value-of select="$picturePath"/>
    </xsl:attribute>
   </image>
    
   <table width="100%" columns="3" cellpadding="1" cellspacing="1" borderwidth="0.5" red="0" 
       green="0" blue="0" left="true" right="true" top="true" bottom="true" widths="33;33;33">
    <row >
     <cell borderwidth="0.5" red="0" green="0" blue="0" left="false" 
        right="true" top="false" bottom="true" header="true" horizontalalign="Center">
      <phrase size="10.0" >Фамилия</phrase>
      </cell>
     <cell borderwidth="0.5" red="0" green="0" blue="0" left="false" 
        right="true" top="false" bottom="true" header="true" horizontalalign="Center">
      <phrase size="10.0" >Имя</phrase>
     </cell>
     <cell borderwidth="0.5" red="0" green="0" blue="0" left="false" 
        right="false" top="false" bottom="true" header="true" horizontalalign="Center">
      <phrase size="10.0" >Отчество</phrase>
     </cell>
    </row>
    <row >
     <cell borderwidth="0.5" red="0" green="0" blue="0" left="false" 
        right="true" top="false" bottom="false" header="false" horizontalalign="Left">
      <phrase size="10.0" >
       <xsl:value-of select="FirstName"/>
      </phrase>
     </cell>
     <cell borderwidth="0.5" red="0" green="0" blue="0" left="false" 
        right="true" top="false" bottom="false" header="false" horizontalalign="Left">
      <phrase size="10.0" >
       <xsl:value-of select="SecondName"/>
      </phrase>
     </cell>
     <cell borderwidth="0.5" red="0" green="0" blue="0" left="false" 
        right="false" top="false" bottom="false" header="false" horizontalalign="Left">
      <phrase size="10.0" >
       <xsl:value-of select="LastName"/>
      </phrase>
     </cell>
    </row>
   </table>
 
   <xsl:apply-templates select="Subtexts"/>
       
  </itext>
 </xsl:template>
 
 <xsl:template match="Subtexts">
  <paragraph align="Justify">
   <phrase size="14.0" >
    <xsl:value-of select="Text"/>
   </phrase>
  </paragraph>
  <newline/>
 </xsl:template>
 
</xsl:stylesheet>

По мне так схему описания они выбрали не самую удачную, уж слишком получается громозкий код для небольшого pdf документа. Так, очень не привычно, описывать цвета red green и blue отдельно друг от друга, уж можно было бы их сделать в HEX записи. Как видно в xslt схеме - в документе есть параметр, который указывает на путь картинки, которая вставится в наш pdf документ, так же там описывается при помощи схемы таблица, состоящая из 3х столбцов и пару параграфов.

Следующий шаг: описываем весь код, который будет возвращать нам массив байт нашего pdf документа, комментариями я попытался описать всю последовательность действий:

public byte[] GetPdfRaw()
{
  XmlDocument doc = new XmlDocument();
  //Загружаем данные их xml файла
  doc.Load(MapPath(@"~\Resources\UserProfile.xml"));
  XslCompiledTransform xslTransform = new XslCompiledTransform();
  //Загружаем схему XSLT преобразований
  xslTransform.Load(MapPath(@"~\Resources\ReportProcessor.xslt"));
  XsltArgumentList list = new XsltArgumentList();
  //Записываем в параметры схемы путь до картинки
  list.AddParam("picturePath", string.Empty, MapPath(@"~\Resources\Toco.jpg"));
  //Создаем поток в памяти, куда будет писаться наша xml схема для pdf документа
  using (MemoryStream stream = new MemoryStream())
  {
    //Создаем xml схему нашего pdf документа
    xslTransform.Transform(doc, list, stream);
 
    //Основной Font для pdf документа
    BaseFont baseFont = BaseFont.CreateFont(Environment.ExpandEnvironmentVariables(@"%systemroot%\fonts\Tahoma.TTF"),
              "CP1251", BaseFont.EMBEDDED);
 
    //Создаем PDF документ
    Document document = new Document();
    using (MemoryStream pdfStream = new MemoryStream())
    {
      PdfWriter.GetInstance(document, pdfStream);
      XmlDocument d = new XmlDocument();
      string str = Encoding.UTF8.GetString(stream.ToArray()).Substring(1);
      d.LoadXml(str);
      //Определяем преобразователь из xml в pdf
      ITextHandler h = new ITextHandler(document) {DefaultFont = baseFont};
      h.Parse(d);
      //Возвращаем полученный pdf файл в формате byte[]
      return pdfStream.ToArray();
    }
  }
}

Хочу обратить внимание на определение baseFont и установки его в качестве деволтного шрифта для ITextHandler. Если данную процедуру не выполнить, тогда кирилика отображаться не будет. Данный подход позволяет отображать кириллику, но не дает нам возможности использовать несколько шрифтов в одному документе. В их коде (благо есть исходный код) они всегда берут codepage 1252 (прямо жестко зашито в код), потому, если вам необходимо будет использовать несколько шрифтов в одном документе, тогда исправляйте код и собирайте сами библиотеку, либо не используйте xml в качестве исходника данных (но не факт что проблема только в ITextHandler).

Еще очень сильно интересует, почему они классы иногда называют с буквы I, в c# так называют интерфейсы. Конечно, я понимаю, что библиотека портирована с java, но все же - меня это ввело сначала в ступор...

Последнее это вывод pdf документа в Responce страницы (если у вас ASP.NET приложение как у меня)

protected override void OnLoad(EventArgs e)
{
  //Записываем в Response pdf файл, указываем его MIME тип и имя файла
  HttpContext.Current.Response.Clear();
  HttpContext.Current.Response.ContentType = "application/pdf";
  HttpContext.Current.Response.AddHeader("Content-Disposition", string.Format("attachment;filename=\"{0}\"", "report.pdf"));
  byte[] pdfRaw = GetPdfRaw();
  HttpContext.Current.Response.OutputStream.Write(pdfRaw, 0, pdfRaw.Length);
}

Скачать пример: ASPNETPdfSample.zip (для работы примера необходимо так же скачать библиотеку itextsharp, пример использует версию 4.1.2.0)

Ссылки по теме:

See Also