File Coverage

lib/ChordPro/lib/SVGPDF.pm
Criterion Covered Total %
statement 74 409 18.0
branch 0 158 0.0
condition 0 69 0.0
subroutine 25 55 45.4
pod 8 29 27.5
total 107 720 14.8


line stmt bran cond sub pod time code
1             #! perl
2              
3 10     10   56888 use v5.26;
  10         47  
4 10     10   70 use Object::Pad;
  10         24  
  10         97  
5 10     10   1901 use Carp;
  10         23  
  10         889  
6 10     10   72 use utf8;
  10         22  
  10         97  
7              
8             class SVGPDF;
9              
10             our $VERSION = '0.091';
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 0 0   0 1   field $pdf :accessor :param;
  0            
121             field $atts :accessor :param = undef;
122              
123             # Callback for font and text handling.
124 0 0   0 0   field $fc :accessor :param = undef;
  0            
125 0 0   0 1   field $tc :accessor :param = undef;
  0            
126              
127             # If an SVG file contains more than a single SVG, the CSS applies to all.
128 0 0   0 0   field $css :accessor;
  0 0   0 0    
  0            
  0            
129              
130             # Font manager.
131 0 0   0 0   field $fontmanager :accessor;
  0            
132              
133 0 0   0 0   field $xoforms :accessor;
  0            
134 0 0   0 0   field $defs :accessor;
  0            
135              
136             # Defaults for rendering.
137 0 0   0 1   field $pagesize :accessor;
  0            
138 0 0   0 1   field $fontsize :accessor;
  0            
139 0     0 0   field $pxpi :mutator = 96; # pixels per inch
140 0     0 0   field $ptpi :accessor = 72; # points per inch
  0            
141              
142             # For debugging/development.
143 0 0   0 1   field $verbose :accessor;
  0 0          
  0            
144 0 0   0 1   field $debug :accessor;
  0            
145 0 0   0 1   field $grid :accessor;
  0            
146 0 0   0 0   field $prog :accessor;
  0            
147 0 0   0 0   field $debug_styles :accessor;
  0            
148 0 0   0 0   field $trace :accessor;
  0            
149 0 0   0 0   field $wstokens :accessor;
  0            
150              
151             our $indent = "";
152              
153 10     10   23142 use SVGPDF::Parser;
  10         41  
  10         1559  
154 10     10   6025 use SVGPDF::Element;
  10         45  
  10         1970  
155 10     10   13907 use SVGPDF::CSS;
  10         46  
  10         1469  
156 10     10   6202 use SVGPDF::FontManager;
  10         42  
  10         1243  
157 10     10   5701 use SVGPDF::PAST;
  10         46  
  10         1213  
158 10     10   19631 use SVGPDF::Colour;
  10         49  
  10         709  
159              
160             # The SVG elements.
161 10     10   6747 use SVGPDF::Circle;
  10         36  
  10         637  
162 10     10   5314 use SVGPDF::Defs;
  10         34  
  10         656  
163 10     10   4978 use SVGPDF::Ellipse;
  10         40  
  10         630  
164 10     10   4987 use SVGPDF::G;
  10         38  
  10         639  
165 10     10   4831 use SVGPDF::Image;
  10         37  
  10         729  
166 10     10   4990 use SVGPDF::Line;
  10         33  
  10         565  
167 10     10   5061 use SVGPDF::Path;
  10         44  
  10         1158  
168 10     10   6357 use SVGPDF::Polygon;
  10         35  
  10         603  
169 10     10   84 use SVGPDF::Polyline;
  10         22  
  10         332  
170 10     10   4907 use SVGPDF::Rect;
  10         38  
  10         731  
171 10     10   4934 use SVGPDF::Style;
  10         67  
  10         564  
172 10     10   4986 use SVGPDF::Svg;
  10         36  
  10         797  
173 10     10   5443 use SVGPDF::Text;
  10         63  
  10         1069  
174 10     10   5305 use SVGPDF::Tspan;
  10         41  
  10         771  
175 10     10   5039 use SVGPDF::Use;
  10         39  
  10         124578  
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 0     0 0   sub BUILDARGS ( @args ) {
  0            
  0            
188 0           my $cls = shift(@args);
189              
190             # Assume first is pdf if uneven.
191 0 0         unshift( @args, "pdf" ) if @args % 2;
192              
193 0           my %args = @args;
194 0           @args = ();
195 0           push( @args, $_, delete $args{$_} ) for qw( pdf fc tc );
196              
197             # Flatten everything else into %atts.
198 0   0       my %x = %{ delete($args{atts}) // {} };
  0            
199 0           $x{$_} = $args{$_} for keys(%args);
200              
201             # And store as ref.
202 0           push( @args, "atts", \%x );
203              
204             # Return new argument list.
205 0           @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 0     0 1   method process ( $data, %options ) {
  0            
  0            
  0            
  0            
270              
271 0 0         if ( $options{reset} ) { # for testing, mostly
272 0           $xoforms = [];
273             }
274              
275 0           my $save_fontsize = $fontsize;
276 0 0         $fontsize = $options{fontsize} if $options{fontsize};
277             # TODO: Page size
278              
279             # Load the SVG data.
280 0           my $svg = SVGPDF::Parser->new;
281             my $tree = $svg->parse_file
282             ( $data,
283 0   0       whitespace_tokens => $wstokens||$options{whitespace_tokens} );
284 0 0         return unless $tree;
285              
286             # CSS persists over svgs, but not over files.
287 0           $css = SVGPDF::CSS->new;
288              
289             # Search for svg elements and process them.
290 0           $self->search($tree);
291              
292             # Restore.
293 0           $fontsize = $save_fontsize;
294              
295 0   0       my $combine = $options{combine} // "none";
296 0 0 0       if ( $combine ne "none" && @$xoforms > 1 ) {
297 0   0       my $sep = $options{sep} || 0;
298 0           $xoforms = $self->combine_svg( $xoforms,
299             type => $combine, sep => $sep );
300             }
301              
302             # Return (hopefully a stack of XObjects).
303 0           return $xoforms;
304             }
305              
306 0     0     method _dbg ( @args ) {
  0            
  0            
  0            
307 0 0         return unless $debug;
308 0           my $msg;
309 0 0         if ( $args[0] =~ /\%/ ) {
310 0           $msg = sprintf( $args[0], @args[1..$#args] );
311             }
312             else {
313 0           $msg = join( "", @args );
314             }
315 0 0         if ( $msg =~ /^\+\s*(.*)/ ) {
    0          
    0          
316 0           $indent = $indent . " ";
317 0 0         warn( $indent, $1, "\n") if $1;
318             }
319             elsif ( $msg =~ /^\-\s*(.*)/ ) {
320 0 0         warn( $indent, $1, "\n") if $1;
321 0 0         confess("oeps") if length($indent) < 2;
322 0           $indent = substr( $indent, 2 );
323             }
324             elsif ( $msg =~ /^\^\s*(.*)/ ) {
325 0           $indent = "";
326 0 0         warn( $indent, $1, "\n") if $1;
327             }
328             else {
329 0 0         warn( $indent, $msg, "\n") if $msg;
330             }
331             }
332              
333 0     0 0   method search ( $content ) {
  0            
  0            
  0            
334              
335             # In general, we'll have an XHTML tree with one or more
336             # elements.
337              
338 0           for ( @$content ) {
339 0 0         next if $_->{type} eq 't';
340 0           my $name = $_->{name};
341 0 0         if ( $name eq "svg" ) {
342 0           $indent = "";
343 0           $self->handle_svg($_);
344             # Adds results to $self->{xoforms}.
345             }
346             else {
347             # Skip recursively.
348 0 0         $self->_dbg( "$name (ignored)" ) unless $name eq "<>"; # top
349 0           $self->search($_->{content});
350             }
351             }
352             }
353              
354 0     0 0   method handle_svg ( $e ) {
  0            
  0            
  0            
355              
356 0           $self->_dbg( "^ ==== start ", $e->{name}, " ====" );
357              
358 0           my $xo;
359 0 0         if ( $prog ) {
360 0           $xo = SVGPDF::PAST->new( pdf => $pdf );
361             }
362             else {
363 0           $xo = $pdf->xo_form;
364             }
365 0           push( @$xoforms, { xo => $xo } );
366              
367 0           $self->_dbg("XObject #", scalar(@$xoforms) );
368             my $svg = SVGPDF::Element->new
369             ( name => $e->{name},
370 0           atts => { map { lc($_) => $e->{attrib}->{$_} } keys %{$e->{attrib}} },
  0            
371             content => $e->{content},
372 0           root => $self,
373             );
374              
375             # If there are