Формирование pdf-файлов в Ruby-среде

Если почитать пару-тройку веток на ror2ru про генерацию pdf-а на руби, то можно всерьез расстроиться, решив что для бедного девелопера/заказчика с головой обычных размеров вариантов вовсе нет, ибо удобные библиотеки - платные: PDFlib, PrinceXML, а бесплатные и работающие - это только Tex.

Как выяснилось, все не так плохо.

В общем, в очередном проекте потребовалось приделать генерацию пары отчетов в pdf-формате.
Чего нам хотелось от библиотеки:

  • возможность вставить картинку
  • вывести текст в несколько столбцов произвольной ширины
  • в идеале хотелось бы просто загнать готовый HTML, а на выходе получить PDF
  • бонус в виде поддержки UTF-8 нам бы тоже не помешал

Первое, на что упал взгляд - это PDF::Writer.
Подкупила многостраничная документация (понятно сразу, что возможностей - богато) в pdf-формате (по любому автор верстал ее с помощью своей же библиотеки) плюс наличие каких-никаких примеров для старта. Опять же Ryan Bates его расхвалил в одном из скринкастов.

Что в нем хорошего - из коробки есть обработка тегов b, u, i и возможность написать свои теги и обработчики к ним.
Что плохого - вывод текста в две колонки оказался настолько нетривиальной задачей и переносы строк работали так странно, что на этом пришлось свое знакомство с этой библиотекой закончить.

Едем дальше:

PDF::HTMLDoc у меня просто не завелся

Pdfwriter - совсем уж прост:

  • No image support (you can draw your own tables etc. though with lines)
  • Only font supported is Times New Roman (in bold and non-bold incarnations)

C JasperReports связываться совершенно не хотелось, потому что Java

PDFlib понравился, но не подошел потому-что он free только для personal use. Если кто будет с ним разбираться, то вот вам пачка ссылок, что я наковырял.

Ну вот в общем долго-ли коротко-ли, но набрел я на ruby-порт пхпшной библиотеки FPDF - rfpdf.
Сразу говорю, эту ссылку через гуголь вы фиг найдете, большинство сайтов по запросам “ruby fpdf” и “rfpdf” ведут куда угодно, но не туда куда надо. Как сам нашел, уже и сам не знаю.

В общем, она оказалась довольно-таки пригодной к использованию и все мои потребности вполне покрыла.
Документацией можно пользоваться прямо ПХП-шной, ибо портировано один в один.

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

Единственное, пара рекомендаций по code style и удобству применения.
Очень удобно для каждого шаблона сделать отдельную модель с типичными для этого шаблона методами:

user_report.rb (по клику полный листинг)


class UserRepor < FPDF

  def initialize(titlename, orientation='P', unit='mm', format='Letter')
    super(orientation, unit, format)
    @titlename = titlename

    @Y = 55
    @X = 30

    AddPage()
    SetMargins(@X,@Y)
  end

  def Header()
    # Page header
    if PageNo()>1
      SetXY(13,9)
      SetFont(’Helvetica’, ”, 8)
      SetTextColor(0)
      Cell(0, 10, “#{@titlename} (continued)”, 0, 0, ‘L’)

      SetY(20)
    end
  end

  def Footer()
    # Page footer
    if PageNo()>1
      SetY(-19)
      SetFont(’Helvetica’, ”, 8)
      SetTextColor(0)
      Cell(0, 10, ‘Page ‘ + PageNo().to_s, 0, 0, ‘R’)
    end
  end

  def SetPos
    SetX(@X)
    SetY(@Y)
  end

  def GetPos
     @X = GetX()
     @Y = GetY()
  end

   def li(text, width=140)
     SetFont(@font, ”, 9)
     SetFillColor(0, 81, 125)

     SetPos()
     Rect(@X+10, @Y+0.7, 2, 2, ‘F’)
     SetX(@X+15)
     MultiCell(width,4,text,0,’L',”)

     Ln(1.9)
     GetPos()
   end

   def h1(text,width=160)
     Ln(3)
     SetX(@X)
     SetFont(@font, ‘B’, 10)
     MultiCell(width, 4, text)
     Ln(3)
     GetPos()
   end

   def col1
     @X=20
     @Y=56
     SetPos()
     yield
   end

   def col2
     @X=75
     @Y=56
     SetPos()
     yield
   end

   #etc …

end

Это раз, ага. Второе - хоть всю рутину мы и прячем в методы нашего класса, код генерации PDF-a в контроллере - это не кошерно. У нас MVC все таки или как?

Складываем все в вид: report.rfpdf


<%
pdf = UserReport.new(@user.full_name)

pdf.col1 do
  pdf.h1 'Заголовок'
  @user.some_list.each do |item|
    pdf.li item
  end
end

pdf.col2 do
  #a lot of content generation
end

%>

<%= pdf.output %>

Ну и в контроллер дописываем:


send_data(render_to_string(:action => 'report', :layout => false),
      :filename => "report.rfpdf", :type => "application/pdf")

Вуаля.

Конечно, rfpdf - не та библиотека, у которой будет приятно поковыряться во внутренностях, ибо внутри полное торжество ПХП-стайл-программинга, но задачи оно свои решает, решает их хорошо и не требует нечеловеческих усилий для понимания того как с ним работать.

Но это еще не все. В сабвершне у проекта обнаружился порт TCPDF, который, внимание!, нормально работает с UTF-8 и умеет из коробки весьма и весьма приличную конвертацию HTML-я в PDF.

Скептикам смотреть сюда и сюда.

А вы говорите Tex..

Из замеченных минусов TCPDF - в нем на данный момент не работает вставка JPEG-картинок (с PNG работает без проблем), и больший по сравнению с RFPDF размер выходного файла.

UPD: Вот здесь мини-рецепт про то, как приклеить к отчету подложку в виде другого pdf-файла.

Tags: , , , , , , , , ,


Дополнительная информация