File Coverage

blib/lib/SVGPDF.pm
Criterion Covered Total %
statement 260 409 63.5
branch 66 158 41.7
condition 22 69 31.8
subroutine 43 55 78.1
pod 8 29 27.5
total 399 720 55.4


line stmt bran cond sub pod time code
1             #! perl
2              
3 2     2   392299 use v5.26;
  2         5  
4 2     2   579 use Object::Pad;
  2         7449  
  2         8  
5 2     2   198 use Carp;
  2         2  
  2         86  
6 2     2   7 use utf8;
  2         2  
  2         10  
7              
8             class SVGPDF;
9              
10             our $VERSION = '1.000';
11              
12             =head1 NAME
13              
14             SVGPDF - Create PDF XObject from SVG data
15              
16             =head1 SYNOPSIS
17              
18             my $pdf = PDF::API2->new;
19             my $svg = SVGPDF->new($pdf);
20             my $xof = $svg->process("demo.svg");
21              
22             # If all goes well, $xof is an array of hashes, each representing an
23             # XObject corresponding to the elements in the file.
24             # Get a page and graphics context.
25             my $page = $pdf->page;
26             $page->bbox( 0, 0, 595, 842 );
27             my $gfx = $pdf->gfx;
28              
29             # Place the objects.
30             my $y = 832;
31             foreach my $xo ( @$xof ) {
32             my @bb = @{$xo->{vbox}};
33             my $h = $bb[3];
34             $gfx->object( $xo->{xo}, 10, $y-$h, 1 );
35             $y -= $h;
36             }
37              
38             $pdf->save("demo.pdf");
39              
40             =head1 DESCRIPTION
41              
42             This module processes SVG data and produces one or more PDF XObjects
43             to be placed in a PDF document. This module is intended to be used
44             with L, L and compatible PDF packages.
45              
46             The main method is process(). It takes the SVG from an input source, see
47             L.
48              
49             =head1 COORDINATES & UNITS
50              
51             SVG coordinates run from top-left to bottom-right.
52              
53             Dimensions without units are B, at 96 pixels / inch. E.g.,
54             C means 96px (pixels) and is equal to 72pt (points) or 1in (inch).
55              
56             For font sizes, CSS defines C to be equal to the font size, and
57             C is half of the font size.
58              
59             =head1 CONSTRUCTORS
60              
61             =head2 SVGPDF->new($pdf)
62              
63             In its most simple form, a new SVGPDF object can be created with a
64             single argument, the PDF document.
65              
66             There are a few optional arguments, these can be specified as
67             key/value pairs.
68              
69             =over 8
70              
71             =item C
72              
73             A reference to a callback routine to handle fonts.
74             See L.
75              
76             It may also be an array of routines which will be called in
77             sequence until one of them succeeds (returns a 'true' result).
78              
79             =item C
80              
81             The font size to be used for dimensions in 'ex' and 'em' units.
82              
83             Note that CSS defines 'em' to be the font size, and 'ex' half of the
84             font size.
85              
86             =item C
87              
88             An array reference containing the maximum width and height of the
89             resultant image.
90              
91             There is no widely accepted default for this, so we use C<[595,842]>
92             which corresponds to an ISO A4 page.
93              
94             =item C
95              
96             If not zero, a grid will be added to the image. This is mostly for
97             developing and debugging.
98              
99             The value determines the grid spacing.
100              
101             =item C
102              
103             Verbosity of informational messages. Set to zero to silence all but
104             fatal errors.
105              
106             =item C
107              
108             Internal use only.
109              
110             =back
111              
112             For convenience, the mandatory PDF argument can also be specified with
113             a key/value pair:
114              
115             $svg = SVGPDF->new( pdf => $pdf, grid => 1, fc => \&fonthandler );
116              
117             =cut
118              
119              
120 26 50   26 1 48 field $pdf :accessor :param;
  26         129  
121             field $atts :accessor :param = undef;
122              
123             # Callback for font and text handling.
124 0 0   0 0 0 field $fc :accessor :param = undef;
  0         0  
125 35 50   35 1 45 field $tc :accessor :param = undef;
  35         79  
126              
127             # If an SVG file contains more than a single SVG, the CSS applies to all.
128 329 50   329 0 389 field $css :accessor;
  329 50   28 0 624  
  28         38  
  28         82  
129              
130             # Font manager.
131 45 50   45 0 77 field $fontmanager :accessor;
  45         185  
132              
133 393 50   393 0 485 field $xoforms :accessor;
  393         1469  
134 13 50   13 0 21 field $defs :accessor;
  13         62  
135              
136             # Defaults for rendering.
137 0 0   0 1 0 field $pagesize :accessor;
  0         0  
138 0 0   0 1 0 field $fontsize :accessor;
  0         0  
139 811     811 0 947 field $pxpi :mutator = 96; # pixels per inch
140 811     803 0 1284 field $ptpi :accessor = 72; # points per inch
  803         896  
141              
142             # For debugging/development.
143 803 50   3 1 1487 field $verbose :accessor;
  3 50       4  
  3         47  
144 2 50   2 1 2 field $debug :accessor;
  2         9  
145 0 0   0 1 0 field $grid :accessor;
  0         0  
146 0 0   0 0 0 field $prog :accessor;
  0         0  
147 0 0   0 0 0 field $debug_styles :accessor;
  0         0  
148 0 0   0 0 0 field $trace :accessor;
  0         0  
149 0 0   0 0 0 field $wstokens :accessor;
  0         0  
150              
151             our $indent = "";
152              
153 2     2   2707 use SVGPDF::Parser;
  2         3  
  2         130  
154 2     2   316 use SVGPDF::Element;
  2         3  
  2         174  
155 2     2   287 use SVGPDF::CSS;
  2         3  
  2         130  
156 2     2   729 use SVGPDF::FontManager;
  2         4  
  2         159  
157 2     2   760 use SVGPDF::PAST;
  2         4  
  2         139  
158 2     2   302 use SVGPDF::Colour;
  2         5  
  2         58  
159              
160             # The SVG elements.
161 2     2   349 use SVGPDF::Circle;
  2         4  
  2         68  
162 2     2   375 use SVGPDF::Defs;
  2         3  
  2         58  
163 2     2   270 use SVGPDF::Ellipse;
  2         4  
  2         50  
164 2     2   272 use SVGPDF::G;
  2         4  
  2         50  
165 2     2   270 use SVGPDF::Image;
  2         4  
  2         76  
166 2     2   344 use SVGPDF::Line;
  2         3  
  2         52  
167 2     2   290 use SVGPDF::Path;
  2         5  
  2         118  
168 2     2   424 use SVGPDF::Polygon;
  2         3  
  2         49  
169 2     2   8 use SVGPDF::Polyline;
  2         3  
  2         29  
170 2     2   332 use SVGPDF::Rect;
  2         3  
  2         89  
171 2     2   304 use SVGPDF::Style;
  2         3  
  2         60  
172 2     2   311 use SVGPDF::Svg;
  2         4  
  2         90  
173 2     2   324 use SVGPDF::Text;
  2         3  
  2         149  
174 2     2   284 use SVGPDF::Tspan;
  2         3  
  2         71  
175 2     2   276 use SVGPDF::Use;
  2         3  
  2         14467  
176              
177             ################ General methods ################
178              
179              
180             =head1 METHODS
181              
182             =cut
183              
184             # $pdf [ , fc => $callback ] [, atts => { ... } ] [, foo => bar ]
185             # pdf => $pdf [ , fc => $callback ] [, atts => { ... } ] [, foo => bar ]
186              
187 1     1 0 6694 sub BUILDARGS ( @args ) {
  1         3  
  1         1  
188 1         2 my $cls = shift(@args);
189              
190             # Assume first is pdf if uneven.
191 1 50       4 unshift( @args, "pdf" ) if @args % 2;
192              
193 1         3 my %args = @args;
194 1         2 @args = ();
195 1         4 push( @args, $_, delete $args{$_} ) for qw( pdf fc tc );
196              
197             # Flatten everything else into %atts.
198 1   50     2 my %x = %{ delete($args{atts}) // {} };
  1         5  
199 1         13 $x{$_} = $args{$_} for keys(%args);
200              
201             # And store as ref.
202 1         2 push( @args, "atts", \%x );
203              
204             # Return new argument list.
205 1         12 @args;
206             }
207              
208             BUILD {
209             $debug = $atts->{debug} || 0;
210             $verbose = $atts->{verbose} // $debug;
211             $grid = $atts->{grid} || 0;
212             $prog = $atts->{prog} || 0;
213             $debug_styles = $atts->{debug_styles} || $debug > 1;
214             $trace = $atts->{trace} || 0;
215             $pagesize = $atts->{pagesize} || [ 595, 842 ];
216             $fontsize = $atts->{fontsize} || 12;
217             $wstokens = $atts->{wstokens} || 0;
218             $indent = "";
219             $xoforms = [];
220             $defs = {};
221             $fontmanager = SVGPDF::FontManager->new( svg => $self );
222             $self;
223             }
224              
225             =head2 process
226              
227             $xof = $svg->process( $data, %options )
228              
229             This methods gets SVG data from C<$data> and returns an array reference
230             with rendered images. See L for details.
231              
232             The input is read using File::LoadLines. See L for details.
233              
234             Recognized attributes in C<%options> are:
235              
236             =over 4
237              
238             =item fontsize
239              
240             The font size to be used for dimensions in 'ex' and 'em' units.
241              
242             This value overrides the value set in the constructor.
243              
244             =item combine
245              
246             An SVG can produce multiple XObjects, but sometimes these should be
247             kept as a single image.
248              
249             There are two ways to combine the image objects. This can be selected
250             by setting $opts{combine} to either C<"stacked"> or C<"bbox">.
251              
252             Type C<"stacked"> (default) stacks the images on top of each other,
253             left sides aligned. The bounding box of each object is only used to
254             obtain the width and height.
255              
256             Type C<"bbox"> stacks the images using the bounding box details. The
257             origins of the images are vertically aligned and images may protrude
258             other images when the image extends below the origin.
259              
260             =item sep
261              
262             When combining images, add additional vertical space between the
263             individual images.
264              
265             =back
266              
267             =cut
268              
269 33     33 1 55813 method process ( $data, %options ) {
  33         137  
  33         51  
  33         70  
  33         35  
270              
271 33 50       159 if ( $options{reset} ) { # for testing, mostly
272 33         63 $xoforms = [];
273             }
274              
275 33         181 my $save_fontsize = $fontsize;
276 33 50       91 $fontsize = $options{fontsize} if $options{fontsize};
277             # TODO: Page size
278              
279             # Load the SVG data.
280 33         358 my $svg = SVGPDF::Parser->new;
281             my $tree = $svg->parse_file
282             ( $data,
283 33   33     98 whitespace_tokens => $wstokens||$options{whitespace_tokens} );
284 33 50       68 return unless $tree;
285              
286             # CSS persists over svgs, but not over files.
287 33         402 $css = SVGPDF::CSS->new;
288              
289             # Search for svg elements and process them.
290 33         116 $self->search($tree);
291              
292             # Restore.
293 33         57 $fontsize = $save_fontsize;
294              
295 33   50     163 my $combine = $options{combine} // "none";
296 33 50 33     87 if ( $combine ne "none" && @$xoforms > 1 ) {
297 0   0     0 my $sep = $options{sep} || 0;
298 0         0 $xoforms = $self->combine_svg( $xoforms,
299             type => $combine, sep => $sep );
300             }
301              
302             # Return (hopefully a stack of XObjects).
303 33         522 return $xoforms;
304             }
305              
306 2008     2008   2159 method _dbg ( @args ) {
  2008         3275  
  2008         2642  
  2008         1860  
307 2008 50       3111 return unless $debug;
308 2008         1857 my $msg;
309 2008 100       3857 if ( $args[0] =~ /\%/ ) {
310 579         3905 $msg = sprintf( $args[0], @args[1..$#args] );
311             }
312             else {
313 1429         2687 $msg = join( "", @args );
314             }
315 2008 100       6667 if ( $msg =~ /^\+\s*(.*)/ ) {
    100          
    100          
316 560         770 $indent = $indent . " ";
317 560 50       10955 warn( $indent, $1, "\n") if $1;
318             }
319             elsif ( $msg =~ /^\-\s*(.*)/ ) {
320 560 100       7681 warn( $indent, $1, "\n") if $1;
321 560 50       1140 confess("oeps") if length($indent) < 2;
322 560         1838 $indent = substr( $indent, 2 );
323             }
324             elsif ( $msg =~ /^\^\s*(.*)/ ) {
325 33         44 $indent = "";
326 33 50       1584 warn( $indent, $1, "\n") if $1;
327             }
328             else {
329 855 50       22526 warn( $indent, $msg, "\n") if $msg;
330             }
331             }
332              
333 33     33 0 41 method search ( $content ) {
  33         79  
  33         35  
  33         32  
334              
335             # In general, we'll have an XHTML tree with one or more
336             # elements.
337              
338 33         48 for ( @$content ) {
339 33 50       75 next if $_->{type} eq 't';
340 33         40 my $name = $_->{name};
341 33 50       56 if ( $name eq "svg" ) {
342 33         48 $indent = "";
343 33         76 $self->handle_svg($_);
344             # Adds results to $self->{xoforms}.
345             }
346             else {
347             # Skip recursively.
348 0 0       0 $self->_dbg( "$name (ignored)" ) unless $name eq "<>"; # top
349 0         0 $self->search($_->{content});
350             }
351             }
352             }
353              
354 33     33 0 32 method handle_svg ( $e ) {
  33         56  
  33         31  
  33         28  
355              
356 33         91 $self->_dbg( "^ ==== start ", $e->{name}, " ====" );
357              
358 33         78 my $xo;
359 33 50       58 if ( $prog ) {
360 0         0 $xo = SVGPDF::PAST->new( pdf => $pdf );
361             }
362             else {
363 33         156 $xo = $pdf->xo_form;
364             }
365 33         10145 push( @$xoforms, { xo => $xo } );
366              
367 33         101 $self->_dbg("XObject #", scalar(@$xoforms) );
368             my $svg = SVGPDF::Element->new
369             ( name => $e->{name},
370 125         793 atts => { map { lc($_) => $e->{attrib}->{$_} } keys %{$e->{attrib}} },
  33         138  
371             content => $e->{content},
372 33         115 root => $self,
373             );
374              
375             # If there are