File Coverage

lib/PDF/WebKit.pm
Criterion Covered Total %
statement 81 136 59.5
branch 12 38 31.5
condition 4 17 23.5
subroutine 20 24 83.3
pod 3 6 50.0
total 120 221 54.3


line stmt bran cond sub pod time code
1             package PDF::WebKit;
2 3     3   202776 use 5.008008;
  3         12  
  3         80  
3 3     3   10 use strict;
  3         3  
  3         58  
4 3     3   9 use warnings;
  3         6  
  3         52  
5 3     3   10 use Carp ();
  3         4  
  3         22  
6 3     3   1131 use IO::File ();
  3         1903  
  3         46  
7 3     3   2080 use IPC::Run ();
  3         69135  
  3         72  
8              
9 3     3   869 use PDF::WebKit::Configuration;
  3         17  
  3         23  
10 3     3   860 use PDF::WebKit::Source;
  3         5  
  3         16  
11              
12             our $VERSION = '1.0';
13              
14 3     3   78 use Moo;
  3         3  
  3         8  
15 3     3   486 use namespace::clean;
  3         3  
  3         9  
16              
17             has source => ( is => 'rw' );
18             has stylesheets => ( is => 'rw' );
19             has options => ( is => 'ro', writer => '_set_options' );
20              
21             around 'BUILDARGS' => sub {
22             my $orig = shift;
23             my $class = shift;
24              
25             if (@_ % 2 == 0) {
26             Carp::croak "Usage: ${class}->new(\$url_file_or_html,%options)";
27             }
28              
29             my $url_file_or_html = shift;
30             my $options = { @_ };
31             return $class->$orig({ source => $url_file_or_html, options => $options });
32             };
33              
34             sub BUILD {
35 17     17 0 482 my ($self,$args) = @_;
36              
37 17         230 $self->source( PDF::WebKit::Source->new($args->{source}) );
38 17         448 $self->stylesheets( [] );
39 17         25 $self->_set_options({
40 17         36 $self->_normalize_options(%{ $self->configuration->default_options }),
41 17         16 $self->_normalize_options(%{ $args->{options} }),
42             $self->_normalize_options($self->_find_options_in_meta),
43             });
44              
45 17 50       35 if (not -e $self->configuration->wkhtmltopdf) {
46 0         0 my $msg = "No wkhtmltopdf executable found\n";
47 0         0 $msg .= ">> Please install wkhtmltopdf - https://github.com/jdpace/PDFKit/wiki/Installing-WKHTMLTOPDF";
48 0         0 die $msg;
49             }
50             }
51              
52             sub configuration {
53 45     45 0 100 PDF::WebKit::Configuration->configuration
54             }
55              
56             sub configure {
57 2     2 0 6128 my $class = shift;
58 2         8 $class->configuration->configure(@_);
59             }
60              
61             sub command {
62 9     9 1 324 my $self = shift;
63 9         9 my $path = shift;
64 9         14 my @args = ( $self->_executable, $self->_prepare_options, '--quiet' );
65            
66 9 100       41 if ($self->source->is_html) {
67 7         12 push @args, '-'; # Get HTML from stdin
68             }
69             else {
70 2         7 push @args, $self->source->content;
71             }
72              
73 9   100     32 push @args, $path || '-'; # write to file or stdout
74              
75 9         10 return grep { defined($_) } @args;
  156         156  
76             }
77              
78             sub _executable {
79 9     9   10 my $self = shift;
80 9         11 my $default = $self->configuration->wkhtmltopdf;
81 9 50       64 return $default if $default !~ /^\//; # it's not a path, so nothing we can do
82 0 0       0 if (-e $default) {
83 0         0 return $default;
84             }
85             else {
86 0         0 return (split(/\//, $default))[-1];
87             }
88             }
89              
90             sub to_pdf {
91 0     0 1 0 my $self = shift;
92 0         0 my $path = shift;
93              
94 0         0 $self->_append_stylesheets;
95 0         0 my @args = $self->command($path);
96              
97 0 0       0 my $input = $self->source->is_html ? $self->source->content : undef;
98 0         0 my $output;
99              
100 0         0 IPC::Run::run( \@args, "<", \$input, ">", \$output );
101              
102 0 0       0 if ($path) {
103 0         0 $output = do { local (@ARGV,$/) = ($path); <> };
  0         0  
  0         0  
104             }
105              
106 0 0 0     0 if (not (defined($output) && length($output))) {
107 0         0 Carp::croak "command failed: $args[0]";
108             }
109 0         0 return $output;
110             }
111              
112             sub to_file {
113 0     0 1 0 my $self = shift;
114 0         0 my $path = shift;
115 0         0 $self->to_pdf($path);
116 0   0     0 my $FH = IO::File->new($path,"<")
117             || Carp::croak "can't open '$path': $!";
118 0         0 $FH->binmode();
119 0         0 return $FH;
120             }
121              
122             sub _find_options_in_meta {
123 17     17   15 my ($self) = @_;
124 17 100       42 return () if $self->source->is_url;
125             # if we can't parse for whatever reason, keep calm and carry on.
126 15         18 my @result = eval { $self->_pdf_webkit_meta_tags };
  15         22  
127 15 50       63 return $@ ? () : @result;
128             }
129              
130             sub _pdf_webkit_meta_tags {
131 15     15   15 my ($self) = @_;
132 15 50       10 return () unless eval { require XML::LibXML };
  15         2385  
133 0         0 my $source = $self->source;
134              
135 0         0 my $prefix = $self->configuration->meta_tag_prefix;
136              
137             # these options do not work at the constructor level in XML::LibXML 1.70, so pass
138             # them through to the parser.
139 0         0 my %options = (
140             recover => 2,
141             suppress_errors => 1,
142             suppress_warnings => 1,
143             no_network => 1,
144             );
145              
146 0         0 my $parser = XML::LibXML->new();
147 0 0       0 my $doc = $source->is_html ? $parser->parse_html_string($source->content,\%options)
    0          
148             : $source->is_file ? $parser->parse_html_file($source->string,\%options)
149             : return ();
150              
151 0         0 my %meta;
152 0         0 for my $node ($doc->findnodes('html/head/meta')) {
153 0         0 my $name = $node->getAttribute('name');
154 0 0 0     0 next unless ($name && ($name =~ s{^\Q$prefix}{}s));
155 0         0 $meta{$name} = $node->getAttribute('content');
156             }
157              
158 0         0 return %meta;
159             }
160              
161             sub _style_tag_for {
162 0     0   0 my ($self,$stylesheet) = @_;
163 0         0 my $styles = do { local (@ARGV,$/) = ($stylesheet); <> };
  0         0  
  0         0  
164 0         0 return "";
165             }
166              
167             sub _append_stylesheets {
168 0     0   0 my $self = shift;
169 0 0 0     0 if (@{ $self->stylesheets } && !$self->source->is_html) {
  0         0  
170 0         0 Carp::croak "stylesheets may only be added to an HTML source";
171             }
172 0 0       0 return unless $self->source->is_html;
173              
174 0         0 my $styles = join "", map { $self->_style_tag_for($_) } @{$self->stylesheets};
  0         0  
  0         0  
175 0 0       0 return unless length($styles) > 0;
176              
177             # can't modify in-place, because the source might be a reference to a
178             # read-only constant string literal
179 0         0 my $html = $self->source->content;
180 0 0       0 if (not ($html =~ s{(?=)}{$styles})) {
181 0         0 $html = $styles . $html;
182             }
183 0         0 $self->source->string(\$html);
184             }
185              
186             sub _prepare_options {
187 9     9   9 my ($self) = @_;
188 9         16 my $options = $self->options;
189 9         8 my @args;
190 9         26 while (my ($name,$val) = each %$options) {
191 66 100 66     179 next unless defined($val) && length($val);
192 64 100       68 if (lc($val) eq 'yes') {
193 8         17 push @args, $name;
194             }
195             else {
196 56         107 push @args, $name, $val;
197             }
198             }
199 9         38 return @args;
200             }
201              
202             sub _normalize_options {
203 51     51   47 my $self = shift;
204 51         79 my %orig_options = @_;
205 51         40 my %normalized_options;
206 51         98 while (my ($key,$val) = each %orig_options) {
207 126         135 my $normalized_key = $self->_normalize_arg($key);
208 126         385 $normalized_options{$normalized_key} = $val;
209             }
210 51         166 return %normalized_options;
211             }
212              
213             sub _normalize_arg {
214 126     126   100 my ($self,$arg) = @_;
215 126         719 $arg =~ lc($arg);
216 126         298 $arg =~ s{[^a-z0-9]}{-}g;
217 126         198 $arg =~ s{^-*}{--};
218 126         141 return $arg;
219             }
220              
221             1;
222              
223             =head1 NAME
224              
225             PDF::WebKit - Use WebKit to Generate PDFs from HTML (via wkhtmltopdf)
226              
227             =head1 SYNOPSIS
228              
229             use PDF::WebKit;
230              
231             # PDF::WebKit->new takes the HTML and any options for wkhtmltopdf
232             # run `wkhtmltopdf --extended-help` for a full list of options
233             my $kit = PDF::WebKit->new(\$html, page_size => 'Letter');
234             push @{ $kit->stylesheets }, "/path/to/css/file";
235              
236             # Get an inline PDF
237             my $pdf = $kit->to_pdf;
238              
239             # save the PDF to a file
240             my $file = $kit->to_file('/path/to/save/pdf');
241              
242             # PDF::WebKit can optionally accept a URL or a File
243             # Stylesheets cannot be added when source is provided as a URL or File.
244             my $kit = PDF::WebKit->new('http://google.com');
245             my $kit = PDF::WebKit->new('/path/to/html');
246              
247             # Add any kind of option through meta tags
248             my $kit = PDF::WebKit->new(\'
249              
250             =head1 DESCRIPTION
251              
252             PDF::WebKit uses L to
253             convert HTML documents into PDFs. It is a port of the elegant
254             L Ruby library.
255              
256             wkhtmltopdf generates beautiful PDFs by leveraging the rendering power
257             of Qt's WebKit browser engine (used by both Apple Safari and Google
258             Chrome browsers).
259              
260             =head2 Configuration
261              
262             Configuration of PDF::WebKit is configured globally by calling the
263             Cconfigure> class method:
264              
265             PDF::WebKit->configure(sub {
266             # default `which wkhtmltopdf`
267             $_->wkhtmltopdf('/path/to/wkhtmltopdf');
268              
269             # default 'pdf-webkit-'
270             $_->meta_tag_prefix('my-prefix-');
271              
272             $_->default_options->{'--orientation'} = 'Portrait';
273             });
274              
275             See the L method for the standard default options.
276              
277             =head2 Constructor
278              
279             =over 4
280              
281             =item new($SOURCE_URL,%OPTIONS)
282              
283             =item new($SOURCE_FILENAME,%OPTIONS)
284              
285             =item new(\$SOURCE_HTML,%OPTIONS)
286              
287             Creates and returns a new instance. If the first parameter looks like a
288             URL, it is treated as a URL and handed off to wkhtmltopdf verbatim. If
289             it is is a reference to a scalar, it is an HTML document body.
290             Otherwise, the parameter is interpreted as a filename.
291              
292             The %OPTIONS hash is a list of name/value pairs for command-line
293             options to wkhtmltopdf. These options can augment or override the
294             default options. For options with no associated value, pass "YES" (case
295             insensitive) as the value, e.g. C "YES">.
296              
297             The default options are:
298              
299             --page-size Letter
300             --margin-top 0.75in
301             --margin_right 0.75in
302             --margin_bottom 0.75in
303             --margin_left 0.75in
304             --encoding UTF-8
305              
306             =back
307              
308             =head2 Methods
309              
310             =over 4
311              
312             =item command
313              
314             Returns the list of command-line arguments that would be used to execute
315             wkhtmltopdf.
316              
317             =item to_pdf
318              
319             Processes the source material and returns a PDF as a string.
320              
321             =item to_file($PATH)
322              
323             Processes the source material and creates a PDF at C<$PATH>. Returns a
324             filehandle opened on C<$PATH>.
325              
326             =back
327              
328             =head1 SEE ALSO
329              
330             L,
331             L,
332             L
333             (a lower-level wrapper for wkhtmltopdf).
334              
335             =head1 AUTHOR
336              
337             Philip Garrett
338              
339             =head1 CONTRIBUTING
340              
341             If you'd like to contribute, just fork my repository on Github, commit
342             your changes and send me a pull request.
343              
344             http://github.com/kingpong/perl-PDF-WebKit
345              
346             =head1 ACKNOWLEDGMENTS
347              
348             This code is nearly a line-by-line port of Jared Pace's PDFKit.
349             https://github.com/jdpace/PDFKit
350              
351             =head1 COPYRIGHT & LICENSE
352              
353             Copyright (c) 2011 by Informatics Corporation of America.
354              
355             This library is free software; you can redistribute it and/or modify
356             it under the same terms as Perl itself, either Perl version 5.8.8 or,
357             at your option, any later version of Perl 5 you may have available.
358              
359             =cut