File Coverage

lib/ChordPro/lib/SVGPDF.pm
Criterion Covered Total %
statement 71 354 20.0
branch 0 136 0.0
condition 0 55 0.0
subroutine 24 51 47.0
pod 7 26 26.9
total 102 622 16.4


line stmt bran cond sub pod time code
1             #! perl
2              
3 1     1   32718 use v5.26;
  1         4  
4 1     1   7 use Object::Pad;
  1         6  
  1         22  
5 1     1   152 use Carp;
  1         3  
  1         53  
6 1     1   6 use utf8;
  1         2  
  1         15  
7              
8             class SVGPDF;
9              
10             our $VERSION = '0.070';
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 CONSTRUCTOR
50              
51             In its most simple form, a new SVGPDF object can be created with a
52             single argument, the PDF document.
53              
54             $svg = SVGPDF->new($pdf);
55              
56             There are a few additional arguments, these must be specified as
57             key/value pairs.
58              
59             =over 8
60              
61             =item C
62              
63             A reference to a callback routine to handle fonts.
64             See L.
65              
66             It may also be an array of routines which will be called in
67             sequence until one of them succeeds (returns a 'true' result).
68              
69             =item C
70              
71             The font size to be used for dimensions in 'ex' and 'em' units.
72              
73             Note that CSS defines 'em' to be the font size, and 'ex' half of the
74             font size.
75              
76             =item C
77              
78             An array reference containing the maximum width and height of the
79             resultant image.
80              
81             There is no widely accepted default for this, so we use C<[595,842]>
82             which corresponds to an ISO A4 page.
83              
84             =item C
85              
86             If not zero, a grid will be added to the image. This is mostly for
87             developing and debugging.
88              
89             The value determines the grid spacing.
90              
91             =item C
92              
93             Verbosity of informational messages. Set to zero to silence all but
94             fatal errors.
95              
96             =item C
97              
98             Internal use only.
99              
100             =back
101              
102             For convenience, the mandatory PDF argument can also be specified with
103             a key/value pair:
104              
105             $svg = SVGPDF->new( pdf => $pdf, grid => 1, fc => \&fonthandler );
106              
107             =cut
108              
109              
110 0 0   0 0   field $pdf :accessor :param;
  0            
111             field $atts :accessor :param = undef;
112              
113             # Callback for font and text handling.
114 0 0   0 0   field $fc :accessor :param = undef;
  0            
115 0 0   0 1   field $tc :accessor :param = undef;
  0            
116              
117             # If an SVG file contains more than a single SVG, the CSS applies to all.
118 0 0   0 0   field $css :accessor;
  0 0   0 0    
  0            
  0            
119              
120             # Font manager.
121 0 0   0 0   field $fontmanager :accessor;
  0            
122              
123 0 0   0 0   field $xoforms :accessor;
  0            
124 0 0   0 0   field $defs :accessor;
  0            
125              
126             # Defaults for rendering.
127 0 0   0 1   field $pagesize :accessor;
  0            
128 0 0   0 1   field $fontsize :accessor;
  0            
129 0     0 0   field $pxpi :mutator = 96; # pixels per inch
130 0     0 0   field $ptpi :accessor = 72; # points per inch
  0            
131              
132             # For debugging/development.
133 0 0   0 1   field $verbose :accessor;
  0 0          
  0            
134 0 0   0 1   field $debug :accessor;
  0            
135 0 0   0 1   field $grid :accessor;
  0            
136 0 0   0 0   field $prog :accessor;
  0            
137 0 0   0 0   field $debug_styles :accessor;
  0            
138 0 0   0 0   field $trace :accessor;
  0            
139 0 0   0 0   field $wstokens :accessor;
  0            
140              
141             our $indent = "";
142              
143 1     1   2075 use SVGPDF::Parser;
  1         3  
  1         49  
144 1     1   377 use SVGPDF::Element;
  1         3  
  1         74  
145 1     1   377 use SVGPDF::CSS;
  1         110  
  1         45  
146 1     1   372 use SVGPDF::FontManager;
  1         3  
  1         58  
147 1     1   425 use SVGPDF::PAST;
  1         4  
  1         46  
148              
149             # The SVG elements.
150 1     1   362 use SVGPDF::Circle;
  1         3  
  1         44  
151 1     1   356 use SVGPDF::Defs;
  1         3  
  1         42  
152 1     1   356 use SVGPDF::Ellipse;
  1         3  
  1         42  
153 1     1   355 use SVGPDF::G;
  1         3  
  1         43  
154 1     1   362 use SVGPDF::Image;
  1         2  
  1         44  
155 1     1   360 use SVGPDF::Line;
  1         2  
  1         43  
156 1     1   376 use SVGPDF::Path;
  1         3  
  1         58  
157 1     1   421 use SVGPDF::Polygon;
  1         3  
  1         46  
158 1     1   8 use SVGPDF::Polyline;
  1         2  
  1         30  
159 1     1   358 use SVGPDF::Rect;
  1         4  
  1         49  
160 1     1   372 use SVGPDF::Style;
  1         2  
  1         42  
161 1     1   365 use SVGPDF::Svg;
  1         3  
  1         52  
162 1     1   402 use SVGPDF::Text;
  1         4  
  1         57  
163 1     1   363 use SVGPDF::Tspan;
  1         2  
  1         48  
164 1     1   366 use SVGPDF::Use;
  1         3  
  1         6685  
165              
166             ################ General methods ################
167              
168              
169             =head1 METHODS
170              
171             =cut
172              
173             # $pdf [ , fc => $callback ] [, atts => { ... } ] [, foo => bar ]
174             # pdf => $pdf [ , fc => $callback ] [, atts => { ... } ] [, foo => bar ]
175              
176 0     0 0   sub BUILDARGS ( @args ) {
  0            
  0            
177 0           my $cls = shift(@args);
178              
179             # Assume first is pdf if uneven.
180 0 0         unshift( @args, "pdf" ) if @args % 2;
181              
182 0           my %args = @args;
183 0           @args = ();
184 0           push( @args, $_, delete $args{$_} ) for qw( pdf fc tc );
185              
186             # Flatten everything else into %atts.
187 0   0       my %x = %{ delete($args{atts}) // {} };
  0            
188 0           $x{$_} = $args{$_} for keys(%args);
189              
190             # And store as ref.
191 0           push( @args, "atts", \%x );
192              
193             # Return new argument list.
194 0           @args;
195             }
196              
197             BUILD {
198             $verbose = $atts->{verbose} // 1;
199             $debug = $atts->{debug} || 0;
200             $grid = $atts->{grid} || 0;
201             $prog = $atts->{prog} || 0;
202             $debug_styles = $atts->{debug_styles} || $debug > 1;
203             $trace = $atts->{trace} || 0;
204             $pagesize = $atts->{pagesize} || [ 595, 842 ];
205             $fontsize = $atts->{fontsize} || 12;
206             $wstokens = $atts->{wstokens} || 0;
207             $indent = "";
208             $xoforms = [];
209             $defs = {};
210             $fontmanager = SVGPDF::FontManager->new( svg => $self );
211             $self;
212             }
213              
214             =head2 process
215              
216             $xof = $svg->process( $data, %options )
217              
218             This methods gets SVG data from C<$data> and returns an array reference
219             with rendered images. See L for details.
220              
221             The input is read using File::LoadLines. See L for details.
222              
223             Recognized attributes in C<%options> are:
224              
225             =over 4
226              
227             =item fontsize
228              
229             The font size to be used for dimensions in 'ex' and 'em' units.
230              
231             This value overrides the value set in the constructor.
232              
233             =back
234              
235             =cut
236              
237 0     0 1   method process ( $data, %options ) {
  0            
  0            
  0            
  0            
238              
239 0 0         if ( $options{reset} ) { # for testing, mostly
240 0           $xoforms = [];
241             }
242              
243 0           my $save_fontsize = $fontsize;
244 0 0         $fontsize = $options{fontsize} if $options{fontsize};
245             # TODO: Page size
246              
247             # Load the SVG data.
248 0           my $svg = SVGPDF::Parser->new;
249             my $tree = $svg->parse_file
250             ( $data,
251 0   0       whitespace_tokens => $wstokens||$options{whitespace_tokens} );
252 0 0         return unless $tree;
253              
254             # CSS persists over svgs, but not over files.
255 0           $css = SVGPDF::CSS->new;
256              
257             # Search for svg elements and process them.
258 0           $self->search($tree);
259              
260             # Restore.
261 0           $fontsize = $save_fontsize;
262              
263             # Return (hopefully a stack of XObjects).
264 0           return $xoforms;
265             }
266              
267 0     0     method _dbg ( @args ) {
  0            
  0            
  0            
268 0 0         return unless $debug;
269 0           my $msg;
270 0 0         if ( $args[0] =~ /\%/ ) {
271 0           $msg = sprintf( $args[0], @args[1..$#args] );
272             }
273             else {
274 0           $msg = join( "", @args );
275             }
276 0 0         if ( $msg =~ /^\+\s*(.*)/ ) {
    0          
    0          
277 0           $indent = $indent . " ";
278 0 0         warn( $indent, $1, "\n") if $1;
279             }
280             elsif ( $msg =~ /^\-\s*(.*)/ ) {
281 0 0         warn( $indent, $1, "\n") if $1;
282 0 0         confess("oeps") if length($indent) < 2;
283 0           $indent = substr( $indent, 2 );
284             }
285             elsif ( $msg =~ /^\^\s*(.*)/ ) {
286 0           $indent = "";
287 0 0         warn( $indent, $1, "\n") if $1;
288             }
289             else {
290 0 0         warn( $indent, $msg, "\n") if $msg;
291             }
292             }
293              
294 0     0 0   method search ( $content ) {
  0            
  0            
  0            
295              
296             # In general, we'll have an XHTML tree with one or more
297             # elements.
298              
299 0           for ( @$content ) {
300 0 0         next if $_->{type} eq 't';
301 0           my $name = $_->{name};
302 0 0         if ( $name eq "svg" ) {
303 0           $indent = "";
304 0           $self->handle_svg($_);
305             # Adds results to $self->{xoforms}.
306             }
307             else {
308             # Skip recursively.
309 0 0         $self->_dbg( "$name (ignored)" ) unless $name eq "<>"; # top
310 0           $self->search($_->{content});
311             }
312             }
313             }
314              
315 0     0 0   method handle_svg ( $e ) {
  0            
  0            
  0            
316              
317 0           $self->_dbg( "^ ==== start ", $e->{name}, " ====" );
318              
319 0           my $xo;
320 0 0         if ( $prog ) {
321 0           $xo = SVGPDF::PAST->new( pdf => $pdf );
322             }
323             else {
324 0           $xo = $pdf->xo_form;
325             }
326 0           push( @$xoforms, { xo => $xo } );
327              
328 0           $self->_dbg("XObject #", scalar(@$xoforms) );
329             my $svg = SVGPDF::Element->new
330             ( name => $e->{name},
331 0           atts => { map { lc($_) => $e->{attrib}->{$_} } keys %{$e->{attrib}} },
  0            
332             content => $e->{content},
333 0           root => $self,
334             );
335              
336             # If there are