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   399781 use v5.26;
  2         6  
4 2     2   600 use Object::Pad;
  2         7567  
  2         23  
5 2     2   211 use Carp;
  2         4  
  2         88  
6 2     2   8 use utf8;
  2         3  
  2         13  
7              
8             class SVGPDF;
9              
10             our $VERSION = '0.092.2';
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 49 field $pdf :accessor :param;
  26         155  
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 79 field $tc :accessor :param = undef;
  35         81  
126              
127             # If an SVG file contains more than a single SVG, the CSS applies to all.
128 329 50   329 0 416 field $css :accessor;
  329 50   28 0 612  
  28         40  
  28         71  
129              
130             # Font manager.
131 45 50   45 0 84 field $fontmanager :accessor;
  45         207  
132              
133 393 50   393 0 523 field $xoforms :accessor;
  393         1543  
134 13 50   13 0 17 field $defs :accessor;
  13         54  
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 924 field $pxpi :mutator = 96; # pixels per inch
140 811     803 0 1299 field $ptpi :accessor = 72; # points per inch
  803         918  
141              
142             # For debugging/development.
143 803 50   3 1 1575 field $verbose :accessor;
  3 50       4  
  3         44  
144 2 50   2 1 14 field $debug :accessor;
  2         8  
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   3899 use SVGPDF::Parser;
  2         4  
  2         99  
154 2     2   372 use SVGPDF::Element;
  2         4  
  2         178  
155 2     2   344 use SVGPDF::CSS;
  2         3  
  2         150  
156 2     2   766 use SVGPDF::FontManager;
  2         4  
  2         156  
157 2     2   757 use SVGPDF::PAST;
  2         5  
  2         137  
158 2     2   357 use SVGPDF::Colour;
  2         4  
  2         67  
159              
160             # The SVG elements.
161 2     2   305 use SVGPDF::Circle;
  2         3  
  2         71  
162 2     2   278 use SVGPDF::Defs;
  2         4  
  2         79  
163 2     2   305 use SVGPDF::Ellipse;
  2         4  
  2         53  
164 2     2   278 use SVGPDF::G;
  2         3  
  2         52  
165 2     2   296 use SVGPDF::Image;
  2         3  
  2         64  
166 2     2   336 use SVGPDF::Line;
  2         5  
  2         69  
167 2     2   322 use SVGPDF::Path;
  2         6  
  2         154  
168 2     2   473 use SVGPDF::Polygon;
  2         3  
  2         50  
169 2     2   9 use SVGPDF::Polyline;
  2         3  
  2         30  
170 2     2   305 use SVGPDF::Rect;
  2         4  
  2         115  
171 2     2   412 use SVGPDF::Style;
  2         5  
  2         78  
172 2     2   477 use SVGPDF::Svg;
  2         6  
  2         151  
173 2     2   413 use SVGPDF::Text;
  2         6  
  2         231  
174 2     2   565 use SVGPDF::Tspan;
  2         5  
  2         83  
175 2     2   289 use SVGPDF::Use;
  2         4  
  2         16271  
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 8989 sub BUILDARGS ( @args ) {
  1         3  
  1         2  
188 1         2 my $cls = shift(@args);
189              
190             # Assume first is pdf if uneven.
191 1 50       6 unshift( @args, "pdf" ) if @args % 2;
192              
193 1         6 my %args = @args;
194 1         5 @args = ();
195 1         7 push( @args, $_, delete $args{$_} ) for qw( pdf fc tc );
196              
197             # Flatten everything else into %atts.
198 1   50     3 my %x = %{ delete($args{atts}) // {} };
  1         9  
199 1         5 $x{$_} = $args{$_} for keys(%args);
200              
201             # And store as ref.
202 1         4 push( @args, "atts", \%x );
203              
204             # Return new argument list.
205 1         19 @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 66789 method process ( $data, %options ) {
  33         639  
  33         139  
  33         80  
  33         50  
270              
271 33 50       147 if ( $options{reset} ) { # for testing, mostly
272 33         63 $xoforms = [];
273             }
274              
275 33         219 my $save_fontsize = $fontsize;
276 33 50       94 $fontsize = $options{fontsize} if $options{fontsize};
277             # TODO: Page size
278              
279             # Load the SVG data.
280 33         445 my $svg = SVGPDF::Parser->new;
281             my $tree = $svg->parse_file
282             ( $data,
283 33   33     156 whitespace_tokens => $wstokens||$options{whitespace_tokens} );
284 33 50       85 return unless $tree;
285              
286             # CSS persists over svgs, but not over files.
287 33         428 $css = SVGPDF::CSS->new;
288              
289             # Search for svg elements and process them.
290 33         138 $self->search($tree);
291              
292             # Restore.
293 33         59 $fontsize = $save_fontsize;
294              
295 33   50     155 my $combine = $options{combine} // "none";
296 33 50 33     105 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         593 return $xoforms;
304             }
305              
306 2008     2008   2234 method _dbg ( @args ) {
  2008         2736  
  2008         2733  
  2008         1679  
307 2008 50       2674 return unless $debug;
308 2008         1830 my $msg;
309 2008 100       3783 if ( $args[0] =~ /\%/ ) {
310 579         3879 $msg = sprintf( $args[0], @args[1..$#args] );
311             }
312             else {
313 1429         2929 $msg = join( "", @args );
314             }
315 2008 100       6378 if ( $msg =~ /^\+\s*(.*)/ ) {
    100          
    100          
316 560         873 $indent = $indent . " ";
317 560 50       10410 warn( $indent, $1, "\n") if $1;
318             }
319             elsif ( $msg =~ /^\-\s*(.*)/ ) {
320 560 100       7194 warn( $indent, $1, "\n") if $1;
321 560 50       1219 confess("oeps") if length($indent) < 2;
322 560         1748 $indent = substr( $indent, 2 );
323             }
324             elsif ( $msg =~ /^\^\s*(.*)/ ) {
325 33         46 $indent = "";
326 33 50       1545 warn( $indent, $1, "\n") if $1;
327             }
328             else {
329 855 50       21698 warn( $indent, $msg, "\n") if $msg;
330             }
331             }
332              
333 33     33 0 49 method search ( $content ) {
  33         69  
  33         60  
  33         35  
334              
335             # In general, we'll have an XHTML tree with one or more
336             # elements.
337              
338 33         59 for ( @$content ) {
339 33 50       109 next if $_->{type} eq 't';
340 33         63 my $name = $_->{name};
341 33 50       66 if ( $name eq "svg" ) {
342 33         53 $indent = "";
343 33         89 $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 44 method handle_svg ( $e ) {
  33         66  
  33         35  
  33         29  
355              
356 33         105 $self->_dbg( "^ ==== start ", $e->{name}, " ====" );
357              
358 33         61 my $xo;
359 33 50       65 if ( $prog ) {
360 0         0 $xo = SVGPDF::PAST->new( pdf => $pdf );
361             }
362             else {
363 33         203 $xo = $pdf->xo_form;
364             }
365 33         11652 push( @$xoforms, { xo => $xo } );
366              
367 33         110 $self->_dbg("XObject #", scalar(@$xoforms) );
368             my $svg = SVGPDF::Element->new
369             ( name => $e->{name},
370 125         888 atts => { map { lc($_) => $e->{attrib}->{$_} } keys %{$e->{attrib}} },
  33         141  
371             content => $e->{content},
372 33         106 root => $self,
373             );
374              
375             # If there are