File Coverage

blib/lib/PDF/Builder/Resource/XObject/Image/SVG.pm
Criterion Covered Total %
statement 12 77 15.5
branch 0 40 0.0
condition 0 84 0.0
subroutine 4 6 66.6
pod 1 2 50.0
total 17 209 8.1


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::XObject::Image::SVG;
2              
3 1     1   1490 use base 'PDF::Builder::Resource::XObject::Image';
  1         2  
  1         125  
4              
5 1     1   18 use strict;
  1         3  
  1         20  
6 1     1   3 use warnings;
  1         2  
  1         74  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
10              
11 1     1   6 use Carp;
  1         1  
  1         918  
12              
13             =head1 NAME
14              
15             PDF::Builder::Resource::XObject::Image::SVG - Support routines for SVG (Scalable Vector Graphics) image library
16              
17             Inherits from L<PDF::Builder::Resource::XObject::Image>
18              
19             Note that, unlike the output of other image formats, C<image_svg()> returns
20             an I<array> of anonymous hashes, one for each E<lt>svgE<gt> tag within the
21             SVG input. See L</What is returned> below for details.
22              
23             =head2 METHODS
24              
25             =head2 new
26              
27             $res = PDF::Builder::Resource::XObject::Image::SVG->new($pdf, $file, %opts)
28              
29             C<$file> gives the SVG input (see the SVGPDF library for details and
30             limitations). It may be a filename, a string reference (with SVG content),
31             or a filehandle.
32              
33             =over
34              
35             Options:
36              
37             =over
38              
39             =item subimage => n
40              
41             If multiple C<svg> entries are in an SVG file, and they are not combined by
42             the SVGPDF C<combine> option into one image, this permits selection of which
43             image to display.
44             Any I<n> may be given, from 0 up to the number of C<svg> images minus 1. The
45             n-th element will be retained, and all others discarded. The default
46             is to return the entire array, and not remove any elements, permitting display
47             of mulitple images in any desired order.
48             See the discussion on combining multiple images
49             L</Dealing with multiple image objects, and combining them>.
50             The default behavior of the display routine
51             (C<object>) is to display only the first element (there must be at least one).
52              
53             =item MScore => flag
54              
55             If set to true (a non-zero value), a font callback for the 14 Microsoft
56             Windows "core" extensions will be added to any other font callbacks given by
57             the user. These include "Georgia" serif, "Verdana" sans-serif, and "Trebuchet"
58             sans-serif fonts, and "Wingdings" and "Webdings" symbology fonts. Non-Windows
59             systems usually don't include these "core" fonts, so it may be unsafe to use
60             them.
61              
62             This option is enabled for all operating systems, not just MS Windows, so you
63             can create PDFs that make use of Windows "core" fonts (extension). This does
64             not guarantee that such fonts I<will> be available on the machine used to read
65             the resulting PDF!
66              
67             =item compress => flag
68              
69             If set to true (a non-zero value; default is 1), the resulting XObject stream
70             will be compressed. This is what you would normally do. On the other hand,
71             setting it to false (0) leaves the stream uncompressed. You may wish to do this
72             if you want to examine the stream in the finished PDF, such as is done for the
73             t-test.
74              
75             =back
76              
77             SVGPDF Options:
78              
79             These are options which, if given, are passed on to the SVGPDF library. Some of
80             them are fixed by C<image_svg> and can not be changed, while others are
81             defaulted by C<image_svg> but I<can> be overridden by the user.
82              
83             You should consult the SVGPDF library documentation for more details on such
84             options.
85              
86             =over
87              
88             =item pdf => PDF object
89              
90             This is automatically set by the SVG routine, and can B<not> be overridden by
91             the user. It is passed to C<SVGPDF-E<gt>new()>.
92              
93             =item fontsize => n
94              
95             This is the font size (in points) for SVGPDF to use to scale text and figure
96             the I<em> and I<ex> sizes. The default is 12 points. It is passed on to
97             both the C<SVGPDF-E<gt>new()> and C<$svg-E<gt>process()> SVGPDF methods.
98              
99             =item pagesize => [ width, height ]
100              
101             This is the maximum dimensions of the resulting object, in case there are no
102             dimensions given, or they are too large to display. The default is 595 pt x
103             842 pt (A4 page size), internal to SVGPDF. It is passed to C<SVGPDF-E<gt>new()>.
104              
105             =item grid => n
106              
107             The default is 0. A value greater than 0 indicates the spacing (in points) of
108             a grid for development/debugging purposes. It is passed to C<SVGPDF-E<gt>new()>.
109              
110             =item verbose => n
111              
112             It defaults to 0 (fatal messages only), but the user may set it to a higher
113             value for outputting informational messages. It is passed to
114             C<SVGPDF-E<gt>new()>.
115              
116             =item fc => \&fonthandler_callback
117              
118             This is a list of one or more callbacks for the font handler. If the C<MScore>
119             flag is true (see above), another callback will be added to the list to handle
120             MS Windows "core" font faces. It is passed to C<SVGPDF-E<gt>new()>.
121              
122             Note that some supporting packages, such as GnuPlot, make use of Windows fonts
123             such as B<Arial>, which may or may not be installed on non-Windows platforms,
124             and even for Windows, would need to be added to FontManager. You should check
125             the resulting SVG files produced by Third-Party packages to see what fonts
126             they are expecting. MathJax creates text characters as filled outlines in SVG,
127             and does not appear to use any standard fonts.
128              
129             =item combine => 'method'
130              
131             If there are multiple XObjects defined by an SVG (due to multiple C<svg>
132             entries), they may be combined into a single XObject. The default is B<none>,
133             which does I<not> combine XObjects. Currently, the only other supported method
134             is B<stacked>, which vertically stacks images, with C<sep> spacing between
135             them. At this writing, the B<bbox> combine method is I<not> supported by
136             SVGPDF, but may be in the future. It is passed to C<$svg-E<gt>process()>.
137              
138             =item sep => n
139              
140             Vertical space (in points) to add between individual images when C<combine> is
141             not 'none'. The default is 0 (no space between images).
142             It is passed to C<$svg-E<gt>process()>.
143              
144             =back
145              
146             =back
147              
148             =head3 What is returned
149              
150             Returns an image in the SVG. Unlike other image formats, it is I<not> actually
151             some form of image object, but an array (of at least one element) containing
152             XObjects of the SVG converted into PDF graphics and text commands. If an SVG
153             includes a pixel-based image, that image will be scaled up and down in the
154             normal image way, while PDF graphics and text are always fully scalable, both
155             when setting an image size I<and> when zooming in and out in a PDF Reader.
156              
157             A returned "object" is always an array of hashes (including the XObject as one
158             of the elements), one per C<svg> tag. Note that C<svg>s must be peers
159             (top-level), and may B<not> be nested one within another! In most applications,
160             an SVG file will have one C<svg> tag and thus a single element in the array.
161             However, some SVGs will produce multiple array elements from multiple C<svg>
162             tags.
163              
164             =head3 Dealing with multiple image objects, and combining them
165              
166             If you don't set C<subimage>, the full array will be returned. If
167             you set C<subimage =E<gt> n>, where I<n> is a valid element number (0...), all
168             elements I<except> the n-th will be discarded, leaving a single element array.
169             When it comes time for C<object()> to display this XObject array, the first
170             (0th) element will be displayed, and any other elements will be ignored. Thus,
171             the default behavior is effectively C<subimage =E<gt> 0>. You may call either
172             C<object> or C<image>, as C<image> will simply pass everything on to C<object>.
173              
174             Remember that I<not> setting C<subimage> will cause the entire array to be
175             returned. You are free to rearrange and/or subset this array, if you wish.
176             If you want to display (in the PDF) multiple images, you can select one or more
177             of the array elements to be processed (see the examples). If you want to stack
178             all of them vertically, perhaps with some space between them, consider using
179             the C<combine =E<gt> 'stacked'> option, but be aware that the total height of
180             the single resulting image may be too large for your page! You may need to
181             output them separately, as many as will fit on a page.
182              
183             This leaves the possibility of I<overlaying> multiple images to overlap in one
184             large combined image. You have the various width and height (and bounding box
185             coordinates), so it I<is> possible to align images to have the same origin.
186             SVGPDF I<may> get C<combine =E<gt> 'bbox'> at some point in the future, to
187             automate this, but for the time being you need to do it yourself. Keep an eye
188             out for different C<svg>s scaled at different sizes; they may need rescaling
189             to overlay properly.
190              
191             =cut
192              
193             # -------------------------------------------------------------------
194             # produce an array of XObject hashes describing one or more <svg> tags in
195             # the input, by calling SVGPDF new() and process(). if 'subimage' is given,
196             # discard all other array elements.
197             sub new {
198 0     0 1   my ($class, $pdf, $file, %opts) = @_;
199              
200 0   0       my $verbose = $opts{'verbose'} || 0;
201 0   0       my $fontsz = $opts{'fontsize'} || 12;
202 0           my $callbacks = $opts{'fc'}; # either routine or array ref of routines
203              
204 0           my $subimage = $opts{'subimage'};
205 0   0       my $MScore = $opts{'MScore'} || 0;
206 0 0         if ($MScore) { # TBD
207             }
208              
209             # delete modified options
210 0           delete $opts{'verbose'};
211 0           delete $opts{'fontsize'};
212              
213             # TBD for 'display' SVG's with tags, remove tag (nested <svg>) and
214             # deal with separately. must remove before call new(). might extract
215             # back in Builder.pm (image_svg) and could let high-level code in
216             # examples/SVG.pl handle its placement? or possibly as separate array
217             # element (if includes sufficient positioning information).
218              
219 0           my $svg;
220 0 0         if ($MScore) {
221 0 0         if (defined $callbacks) {
222             # existing callback(s) to add MScore processing to
223 0           delete $opts{'fc'};
224 0           $callbacks = [ ($callbacks), &MScoreCB ];
225 0           $svg = SVGPDF->new( 'pdf'=>$pdf, 'verbose'=>$verbose,
226             'fc'=>$callbacks,
227             'fontsize'=>$fontsz, %opts );
228             } else {
229             # no existing callback(s) in %opts to add MScore processing to
230 0           $callbacks = \&MScoreCB;
231 0           $svg = SVGPDF->new( 'pdf'=>$pdf, 'verbose'=>$verbose,
232             'fc'=>$callbacks,
233             'fontsize'=>$fontsz, %opts );
234             }
235             } else {
236             # not adding MScore, any existing callbacks still in %opts
237 0           $svg = SVGPDF->new( 'pdf'=>$pdf, 'verbose'=>$verbose,
238             'fontsize'=>$fontsz, %opts );
239             }
240              
241 0           my $xof = $svg->process($file, 'fontsize'=>$fontsz, %opts);
242             # $xof is anonymous array ref with one element per <svg> (should be
243             # at minimum, one), and each element is a hash of width, vwidth,
244             # height, vheight, vbox, bbox(), and the xobject itself, xo
245              
246             # if subimage given, return only that subimage (0th if invalid number)
247 0 0         if (defined $subimage) {
248 0           my @array = @$xof;
249 0 0 0       if ($subimage < 0 || $subimage > $#array ) {
250 0           carp "Invalid subimage number ignored, using 0";
251 0           $subimage = 0;
252             }
253 0           $xof = [ $array[$subimage] ];
254             }
255              
256 0           return $xof;
257             }
258              
259             # -------------------------------------------------------------------
260             # sample font callback to implement MS Windows core font extensions.
261             # enable for all OSs, not just $^O eq 'MSWin32'.
262             sub MScoreCB {
263 0     0 0   my ($self, %args) = @_;
264 0           my $pdf = $args{'pdf'};
265              
266 0           my $family = $args{'style'}->{'font-family'};
267 0           my $style = $args{'style'}->{'font-style'};
268 0           my $weight = $args{'style'}->{'font-weight'};
269 0           my $fontsz = $args{'style'}->{'font-size'}; # ignored
270              
271 0           my $font;
272 0           my $Wfam = '';
273             # check $family requested and style and weight, return a font object
274             # via FontManager
275 0 0         if ($family =~ m/^Verdana/i) { $Wfam = 'Verdana'; }
  0            
276 0 0         if ($family =~ m/^Georgia/i) { $Wfam = 'Georgia'; }
  0            
277 0 0         if ($family =~ m/^Trebuchet/i) { $Wfam = 'Trebuchet'; }
  0            
278 0 0         if ($family =~ m/^Wingdings/i) { $Wfam = 'Wingdings'; }
  0            
279 0 0         if ($family =~ m/^Webdings/i) { $Wfam = 'Webdings'; }
  0            
280 0 0         if ($Wfam ne '') {
281             # acceptable Windows "core" font names
282            
283             # if includes Bold and/or Italic, override $weight and $style
284 0 0         if ($family =~ m/Bold/i) { $weight = 'bold'; }
  0            
285 0 0         if ($family =~ m/Italic/i) { $style = 'italic'; }
  0            
286 0 0         if ($family =~ m/Oblique/i) { $style = 'italic'; }
  0            
287             # Wingdings and Webdings NOT bold or italic
288 0 0 0       if ($Wfam eq 'Wingdings' || $Wfam eq 'Webdings') {
289 0           $style = 'normal';
290 0           $weight = 'normal';
291             }
292 0           $style = lc($style);
293 0           $weight = lc($weight);
294             # core only supports two styles: normal and italic
295             # TBD 'oblique' with optional number of degrees is apparently valid
296             # CSS, so need to be more general in FontManager
297 0 0 0       if ($style eq 'oblique' || $style eq 'slant' || $style eq 'slanted') {
      0        
298 0           $style = 'italic';
299             } else {
300 0           $style = 'normal';
301             }
302             # core only supports two weights: normal and bold
303             # following lists standard CSS names and numeric weights
304             # TBD other weight names and numbers HAVE been seen in the wild, also
305             # 'bolder' and 'lighter', so need to be more general in FontManager
306 0 0 0       if ($weight eq 'normal' || $weight eq 'regular' || $weight eq '400' ||
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
307             $weight eq 'thin' || $weight eq 'hairline' || $weight eq '100' ||
308             $weight =~ m/^extra\s+light$/ || $weight =~ m/^ultra\s+light$/ ||
309             $weight eq '200' ||
310             $weight eq 'light' || $weight eq '300' ||
311             $weight eq 'medium' || $weight eq '500') {
312 0           $weight = 'normal';
313             } elsif ($weight eq 'bold' || $weight eq '700' ||
314             $weight =~ m/^semi\s+bold$/ || $weight =~ m/^demi\s+bold$/ ||
315             $weight eq '600' ||
316             $weight =~ m/^extra\s+bold$/ || $weight =~ m/^ultra\s+bold$/ ||
317             $weight eq '800' ||
318             $weight eq 'black' || $weight eq 'heavy' || $weight eq '900') {
319 0           $weight = 'bold';
320             } else {
321             # who knows... not a standard designation
322 0           $weight = 'bold';
323             }
324              
325 0 0         $font = $pdf->get_font('face' => $family,
    0          
326             'italic' => ($style eq 'italic')? 1:0,
327             'bold' => ($weight eq 'bold')? 1:0);
328             }
329            
330 0           return $font;
331             } # end of MScoreCB()
332              
333             1;