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   583285 use v5.26;
  2         9  
4 2     2   761 use Object::Pad;
  2         11889  
  2         33  
5 2     2   248 use Carp;
  2         3  
  2         110  
6 2     2   9 use utf8;
  2         5  
  2         16  
7              
8             class SVGPDF;
9              
10             our $VERSION = '0.091.1';
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 76 field $pdf :accessor :param;
  26         237  
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 31 50   31 1 106 field $tc :accessor :param = undef;
  31         107  
126              
127             # If an SVG file contains more than a single SVG, the CSS applies to all.
128 279 50   279 0 523 field $css :accessor;
  279 50   24 0 866  
  24         44  
  24         75  
129              
130             # Font manager.
131 33 50   33 0 82 field $fontmanager :accessor;
  33         176  
132              
133 328 50   328 0 588 field $xoforms :accessor;
  328         1930  
134 13 50   13 0 26 field $defs :accessor;
  13         105  
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 711     711 0 1384 field $pxpi :mutator = 96; # pixels per inch
140 711     703 0 1845 field $ptpi :accessor = 72; # points per inch
  703         1394  
141              
142             # For debugging/development.
143 703 50   3 1 2123 field $verbose :accessor;
  3 50       8  
  3         62  
144 2 50   2 1 34 field $debug :accessor;
  2         16  
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   4200 use SVGPDF::Parser;
  2         6  
  2         175  
154 2     2   541 use SVGPDF::Element;
  2         6  
  2         293  
155 2     2   581 use SVGPDF::CSS;
  2         5  
  2         211  
156 2     2   1039 use SVGPDF::FontManager;
  2         8  
  2         309  
157 2     2   1054 use SVGPDF::PAST;
  2         7  
  2         252  
158 2     2   545 use SVGPDF::Colour;
  2         7  
  2         95  
159              
160             # The SVG elements.
161 2     2   558 use SVGPDF::Circle;
  2         6  
  2         86  
162 2     2   526 use SVGPDF::Defs;
  2         6  
  2         90  
163 2     2   460 use SVGPDF::Ellipse;
  2         5  
  2         108  
164 2     2   469 use SVGPDF::G;
  2         4  
  2         78  
165 2     2   468 use SVGPDF::Image;
  2         6  
  2         116  
166 2     2   750 use SVGPDF::Line;
  2         5  
  2         85  
167 2     2   496 use SVGPDF::Path;
  2         6  
  2         252  
168 2     2   619 use SVGPDF::Polygon;
  2         5  
  2         94  
169 2     2   26 use SVGPDF::Polyline;
  2         3  
  2         52  
170 2     2   416 use SVGPDF::Rect;
  2         6  
  2         136  
171 2     2   616 use SVGPDF::Style;
  2         6  
  2         72  
172 2     2   421 use SVGPDF::Svg;
  2         5  
  2         193  
173 2     2   636 use SVGPDF::Text;
  2         5  
  2         206  
174 2     2   451 use SVGPDF::Tspan;
  2         4  
  2         137  
175 2     2   483 use SVGPDF::Use;
  2         6  
  2         22669  
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 11275 sub BUILDARGS ( @args ) {
  1         6  
  1         2  
188 1         3 my $cls = shift(@args);
189              
190             # Assume first is pdf if uneven.
191 1 50       7 unshift( @args, "pdf" ) if @args % 2;
192              
193 1         5 my %args = @args;
194 1         3 @args = ();
195 1         8 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         7  
199 1         4 $x{$_} = $args{$_} for keys(%args);
200              
201             # And store as ref.
202 1         3 push( @args, "atts", \%x );
203              
204             # Return new argument list.
205 1         17 @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 32     32 1 108347 method process ( $data, %options ) {
  32         197  
  32         82  
  32         111  
  32         75  
270              
271 32 50       167 if ( $options{reset} ) { # for testing, mostly
272 32         89 $xoforms = [];
273             }
274              
275 32         434 my $save_fontsize = $fontsize;
276 32 50       248 $fontsize = $options{fontsize} if $options{fontsize};
277             # TODO: Page size
278              
279             # Load the SVG data.
280 32         615 my $svg = SVGPDF::Parser->new;
281             my $tree = $svg->parse_file
282             ( $data,
283 32   33     206 whitespace_tokens => $wstokens||$options{whitespace_tokens} );
284 32 50       131 return unless $tree;
285              
286             # CSS persists over svgs, but not over files.
287 32         578 $css = SVGPDF::CSS->new;
288              
289             # Search for svg elements and process them.
290 32         213 $self->search($tree);
291              
292             # Restore.
293 32         121 $fontsize = $save_fontsize;
294              
295 32   50     224 my $combine = $options{combine} // "none";
296 32 50 33     141 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 32         930 return $xoforms;
304             }
305              
306 1705     1705   3110 method _dbg ( @args ) {
  1705         3883  
  1705         3488  
  1705         2195  
307 1705 50       3590 return unless $debug;
308 1705         2317 my $msg;
309 1705 100       5171 if ( $args[0] =~ /\%/ ) {
310 488         5111 $msg = sprintf( $args[0], @args[1..$#args] );
311             }
312             else {
313 1217         3346 $msg = join( "", @args );
314             }
315 1705 100       8149 if ( $msg =~ /^\+\s*(.*)/ ) {
    100          
    100          
316 460         836 $indent = $indent . " ";
317 460 50       13267 warn( $indent, $1, "\n") if $1;
318             }
319             elsif ( $msg =~ /^\-\s*(.*)/ ) {
320 460 100       9577 warn( $indent, $1, "\n") if $1;
321 460 50       1424 confess("oeps") if length($indent) < 2;
322 460         2156 $indent = substr( $indent, 2 );
323             }
324             elsif ( $msg =~ /^\^\s*(.*)/ ) {
325 32         65 $indent = "";
326 32 50       1952 warn( $indent, $1, "\n") if $1;
327             }
328             else {
329 753 50       28440 warn( $indent, $msg, "\n") if $msg;
330             }
331             }
332              
333 32     32 0 58 method search ( $content ) {
  32         102  
  32         57  
  32         55  
334              
335             # In general, we'll have an XHTML tree with one or more
336             # elements.
337              
338 32         105 for ( @$content ) {
339 32 50       123 next if $_->{type} eq 't';
340 32         98 my $name = $_->{name};
341 32 50       171 if ( $name eq "svg" ) {
342 32         75 $indent = "";
343 32         162 $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 32     32 0 56 method handle_svg ( $e ) {
  32         130  
  32         60  
  32         44  
355              
356 32         161 $self->_dbg( "^ ==== start ", $e->{name}, " ====" );
357              
358 32         105 my $xo;
359 32 50       90 if ( $prog ) {
360 0         0 $xo = SVGPDF::PAST->new( pdf => $pdf );
361             }
362             else {
363 32         296 $xo = $pdf->xo_form;
364             }
365 32         18236 push( @$xoforms, { xo => $xo } );
366              
367 32         182 $self->_dbg("XObject #", scalar(@$xoforms) );
368             my $svg = SVGPDF::Element->new
369             ( name => $e->{name},
370 120         1337 atts => { map { lc($_) => $e->{attrib}->{$_} } keys %{$e->{attrib}} },
  32         284  
371             content => $e->{content},
372 32         146 root => $self,
373             );
374              
375             # If there are