Make PDF reports in Perl?

2019-04-19 09:53发布

All the PDF libraries for Perl seem a bit barbaric -- stuck in the 1980's. You have to specify PostScript points to do layout. Java has JasperReports, Ruby has Prawn, and Python has ReportLab. Is there a non-extinct library/module that will let me make a nice looking PDF in less than a week of coding? (I'm a little frustrated by PDF::API2, PDF::Table, etc.) I don't want to generate HTML and convert it. Perl is ideal for reporting, but the main report file format is not available in a usable way. What libraries do people use?

I need:

  • tables
  • charts (Images)
  • color
  • formatting (ideally automatic, not pixel by pixel)
  • headers / footers

I'm slightly open to wrapping external (non-Perl) open source tools, if absolutely needed. But not really interested in a major Java server approach. For the bounty, I want a pure Perl approach, since I want to run this on a server that I can't add more than modules to. If you have a public example that works well, please point me to it.

3条回答
聊天终结者
2楼-- · 2019-04-19 10:16

After much thought and experimentation, I ended up writing a lot of code to wrap PDF::API2. Unfortunately this was an internal project within a company so not going to be released open source, but frankly I'd recommend using a different language (Python / Ruby), perhaps passing the data through with JSON or something. My end result is efficient, but it required a lot of coding. There is a refactoring of PDF::API2 underway on CPAN but it seems to be stalled.

查看更多
狗以群分
3楼-- · 2019-04-19 10:30

Using Perl, generate LaTeX, perhaps using Template::Toolkit, then call the compiler, either TeXLive or MikTeX or whatever distribution you need for your OS. There is an extension called Template::LaTeX, though you probably don't need it, which manages the build process.

LaTeX has support for all the things you need. Tables get a little interesting but there are some modern table packages which ease things (I think that its called ltxtable). For charts (do you mean diagrams) there is a sub language called TikZ which is spectacularly powerful.

This really is a very easy workflow, especially if you want the results to be similar every time (i.e. can use a template). In fact it really is not unlike creating HTML from a template and serving it to a browser.

Another benefit of this is that the template (and prepared source) will be portable should you need to build a report in another language.

查看更多
The star\"
4楼-- · 2019-04-19 10:31

If LaTeX is too big, perhaps one could use Inline::Python to wrap ReportLab, that everyone seems to like so much (I haven't used it and am not too proficient at Python).

Edit 3: Here is Edit 2, except split into a modular style, if people like it (and if it is an kind of robust) perhaps I can publish to CPAN. For now place the .pm file in a file structure like Inline/Python/ReportLab.pm somewhere in your @INC (the script's own base directory is usually in @INC).

# Inline/Python/ReportLab.pm
package Inline::Python::ReportLab;

use strict;
use warnings;

use Carp;

use Inline::Python qw/py_eval/;
our @ISA = 'Inline::Python::Object';

sub import {
  py_eval('from reportlab.pdfgen.canvas import Canvas');
}

sub new {
  my $class = shift;
  my $filename = shift || croak "Must specify file name to contructor";
  return bless(Inline::Python::Object->new('__main__', 'Canvas', $filename), $class);
}

1;

Then a script could be something like:

#!/usr/bin/env perl

use strict;
use warnings;

use Inline::Python::ReportLab;

my $c = Inline::Python::ReportLab->new('hello.pdf');
$c->drawString(100,100,"Hello World");
$c->showPage();
$c->save();

Edit 2: While Edit 1 is still of interest, it seems (tell me if I am incorrect!) that I have figured out how to create an instance of 'Canvas' and expose its methods directly:

#!/usr/bin/env perl

use strict;
use warnings;

use Inline::Python qw/py_eval/;

py_eval('from reportlab.pdfgen.canvas import Canvas');

my $c = Inline::Python::Object->new('__main__', 'Canvas', 'hello.pdf');
$c->drawString(100,100,"Hello World");
$c->showPage();
$c->save();

Edit 2/3: This portion is left as an example of a more manual interface. I think Edits 2/3 give a better interface which leaves the heavy lifting to the original Python class without (too much) wrapping.

Edit 1: I have now exposed some of the functionality by manually hacking in the methods. This means that for every method one wants to use, a wrapper method must be added. While this is already a feasible solution, I wonder if there isn't some easier way to expose the entire python 'canvas' class, but for now this is where I am:

#!/usr/bin/env perl

use strict;
use warnings;

use Inline Python => <<END_PYTHON;
from reportlab.pdfgen import canvas

class Canvas:
  def __init__(self,filename):
    self.canvas = canvas.Canvas(filename)
  def drawString(self,x,y,text):
    self.canvas.drawString(x,y,text)
  def save(self):
    self.canvas.showPage()
    self.canvas.save()

END_PYTHON

my $c = Canvas->new('hello.pdf');
$c->drawString(100,100,"Hello World");
$c->save();
查看更多
登录 后发表回答