File Coverage

blib/lib/XML/Handler/AxPoint.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             # $Id: AxPoint.pm,v 1.49 2005/10/19 16:43:48 matt Exp $
2              
3             package XML::Handler::AxPoint;
4 2     2   37595 use strict;
  2         8  
  2         119  
5              
6 2     2   2433 use XML::SAX::Writer;
  2         119660  
  2         66  
7 2     2   1879 use Text::Iconv;
  2         7306  
  2         125  
8 2     2   15 use File::Spec;
  2         4  
  2         46  
9 2     2   11 use File::Basename;
  2         5  
  2         178  
10 2     2   2230 use Data::Dumper;
  2         15038  
  2         150  
11 2     2   11710 use PDFLib 0.13;
  0            
  0            
12             use POSIX qw(ceil acos);
13             use Time::Piece;
14             use Carp qw(carp verbose);
15              
16             use vars qw($VERSION);
17             $VERSION = '1.5';
18              
19             sub new {
20             my $class = shift;
21             my $opt = (@_ == 1) ? { %{shift()} } : {@_};
22              
23             $opt->{Output} ||= *{STDOUT}{IO};
24             return bless $opt, $class;
25             }
26              
27             sub set_document_locator {
28             my ($self, $locator) = @_;
29             $self->{locator} = $locator;
30             }
31              
32             sub start_document {
33             my ($self, $doc) = @_;
34              
35             # setup consumer
36             my $ref = ref $self->{Output};
37             if ($ref eq 'SCALAR') {
38             $self->{Consumer} = XML::SAX::Writer::StringConsumer->new($self->{Output});
39             }
40             elsif ($ref eq 'ARRAY') {
41             $self->{Consumer} = XML::SAX::Writer::ArrayConsumer->new($self->{Output});
42             }
43             elsif ($ref eq 'GLOB' or UNIVERSAL::isa($self->{Output}, 'IO::Handle')) {
44             $self->{Consumer} = XML::SAX::Writer::HandleConsumer->new($self->{Output});
45             }
46             elsif (not $ref) {
47             local *FH;
48              
49             open FH, '> ' . $self->{Output} or
50             XML::SAX::Writer::Exception->throw( Message => "Error opening '" .
51             $self->{Output} . "': $!" );
52             binmode FH;
53             $self->{Consumer} = XML::SAX::Writer::HandleConsumer->new(*FH);
54             }
55             elsif (UNIVERSAL::can($self->{Output}, 'output')) {
56             $self->{Consumer} = $self->{Output};
57             }
58             else {
59             XML::SAX::Writer::Exception->throw({ Message => 'Unknown option for Output' });
60             }
61              
62             $self->{Encoder} = XML::SAX::Writer::NullConverter->new();
63              
64             $self->{text_encoder} = Text::Iconv->new('utf-8', 'ISO-8859-1');
65              
66             # create PDF and set defaults
67             $self->{pdf} = PDFLib->new();
68             $self->{pdf}->papersize("slides");
69             $self->{pdf}->set_border_style("solid", 0);
70              
71             $self->{headline_font} = "Helvetica";
72             $self->{headline_size} = 18.0;
73              
74             $self->{title_font} = "Helvetica-Bold";
75             $self->{title_size} = 24.0;
76              
77             $self->{subtitle_font} = "Helvetica-Bold";
78             $self->{subtitle_size} = 20.0;
79              
80             $self->{normal_font} = "Helvetica";
81              
82             $self->{todo} = [];
83             $self->{bookmarks} = [];
84              
85             $self->{default_transition} = [];
86              
87             $self->{gathered_text} = '';
88             $self->{bullets} = [ ' ', 'l', 'u', 'p', 'n', 'm', 'F' ];
89             $self->{numbers} = [ ' ', '$1.', '$a)', '$i.', '$A)', '$I' ];
90             $self->{captions} = [];
91             $self->{list_index} = [];
92             $self->{values} = { 'current-slide' => 0 };
93             $self->{slide_index} = [ 0 ];
94             $self->{boxtransition} = [];
95             $self->{fill} = 1;
96             $self->{stroke} = 0;
97             $self->{coords} = 'svg';
98             }
99              
100             sub run_todo {
101             my $self = shift;
102             while (my $todo = shift(@{$self->{todo}})) {
103             $todo->();
104             }
105             }
106              
107             sub push_todo {
108             my $self = shift;
109              
110             push @{$self->{todo}}, shift;
111             }
112              
113             sub push_bookmark {
114             my $self = shift;
115             # warn("push_bookmark($_[0]) from ", caller, "\n");
116             push @{$self->{bookmarks}}, shift;
117             }
118              
119             sub top_bookmark {
120             my $self = shift;
121             return $self->{bookmarks}[-1];
122             }
123              
124             sub pop_bookmark {
125             my $self = shift;
126             # warn("pop_bookmark() from ", caller, "\n");
127             pop @{$self->{bookmarks}};
128             }
129              
130             sub end_document {
131             my ($self) = @_;
132              
133             $self->{pdf}->finish;
134              
135             $self->{Consumer}->output( $self->{pdf}->get_buffer );
136             $self->{Consumer}->finalize;
137             }
138              
139             sub new_page {
140             my $self = shift;
141             my ($trans,$type) = @_;
142             $type ||= 'normal';
143              
144             $self->{pdf}->start_page;
145             $self->{values}->{'current-slide'}++ unless $self->{transitional};
146              
147             my $transition = $trans || $self->get_transition || 'replace';
148             $transition = 'replace' if $transition eq 'none';
149             $transition = 'replace' if $self->{PrintMode};
150              
151             $self->{pdf}->set_parameter(transition => lc($transition));
152              
153             if ($type ne 'empty') {
154             if (my $bg = $self->{bg}) {
155             my @scale = split(/\*/,$bg->{scale});
156             my $imgw = $self->get_scale($scale[0],0,$self->{pdf}->get_value("imagewidth", $bg->{image}->img),$self->{pdf}->get_value("resx", $bg->{image}->img));
157             my $imgh = $self->get_scale($scale[1]||$scale[0],1,$self->{pdf}->get_value("imageheight", $bg->{image}->img),$self->{pdf}->get_value("resy", $bg->{image}->img));
158             $self->{pdf}->add_image(img => $bg->{image}, x => 0, y => 0, w => $imgw, h => $imgh);
159             }
160              
161             if (my $logo = $self->{logo}) {
162             my @scale = split(/\*/,$logo->{scale});
163             my $imgw = $self->get_scale($scale[0],0,$self->{pdf}->get_value("imagewidth", $logo->{image}->img),$self->{pdf}->get_value("resx", $logo->{image}->img));
164             my $imgh = $self->get_scale($scale[1]||$scale[0],1,$self->{pdf}->get_value("imageheight", $logo->{image}->img),$self->{pdf}->get_value("resy", $logo->{image}->img));
165             $self->{pdf}->add_image(img => $logo->{image}, x => 612 - $imgw - $logo->{x}, y => $logo->{y}, w => $imgw, h => $imgh);
166             }
167             }
168              
169             $self->{pagetype} = $type || 'normal';
170              
171             $self->process_css_styles("font-family:".$self->{headline_font}.";font-size:".$self->{headline_size}.";stroke:none;fill:black;font-weight:normal;font-style:normal;");
172             pop @{$self->{font_stack}};
173              
174             $self->{xindent} = [];
175              
176             $self->{pdf}->set_text_pos(80, 300);
177             }
178              
179             sub get_node_transition {
180             my $self = shift;
181             my ($node) = @_;
182              
183             if (exists($node->{Attributes}{"{}transition"})) {
184             return $node->{Attributes}{"{}transition"}{Value};
185             }
186             return;
187             }
188              
189             sub get_scale {
190             my ($self, $spec, $vertical, $rel, $res) = @_;
191             $res = 72 if ($res <= 0); # substitute sensible fallback
192              
193             my ($num, $unit) = ($spec =~ m/^\s*([0-9]*(?:\.[0-9]+)?)\s*(em|ex|pt|px|line|page|)\s*$/);
194             die "unknown scale specifier: $spec" if !defined $unit;
195              
196             my $pdf = $self->{bb} || $self->{pdf}; # don't use 'line' outside of a slide, will return "0".
197             if ($unit eq 'em') {
198             if ($vertical) {
199             return $num*$pdf->get_value('capheight')*$pdf->get_value('fontsize');
200             } else {
201             return $num*$pdf->string_width(text => 'M');
202             }
203             } elsif ($unit eq 'ex') {
204             if ($vertical) {
205             # FIXME: (probably unfixable) this uses an estimation, not the real value
206             return $num*$pdf->get_value('ascender')*2/3*$pdf->get_value('fontsize');
207             } else {
208             return $num*$pdf->string_width(text => 'x');
209             }
210             } elsif ($unit eq 'pt') {
211             return $num;
212             } elsif ($unit eq 'px') {
213             return $num*72/$res;
214             } elsif ($unit eq 'line') {
215             if ($vertical) {
216             return $num*$pdf->get_value('leading');
217             } else {
218             return $num*($pdf->{w} || $self->{extents}[0]{w});
219             }
220             } elsif ($unit eq 'page') {
221             if ($vertical) {
222             return $num*$pdf->get_value('pageheight');
223             } else {
224             return $num*$pdf->get_value('pagewidth');
225             }
226             } else {
227             return $num*$rel*72/$res;
228             }
229             die "unknown unit: $unit";
230             }
231              
232             sub get_transition {
233             my $self = shift;
234              
235             my $node = $self->{SlideCurrent} || $self->{Current};
236              
237             my $transition;
238             while ($node && !($transition = $self->get_node_transition($node))) {
239             $node = $node->{Parent};
240             }
241             return $transition;
242             }
243              
244             sub playback_cache {
245             my $self = shift;
246             $self->{cache_trash} = [];
247              
248             while (@{$self->{cache}}) {
249             my $thing = shift @{$self->{cache}};
250             my ($method, $node) = @$thing;
251             $self->$method($node);
252             push @{$self->{cache_trash}}, $thing;
253             }
254              
255             delete $self->{cache_trash};
256             }
257              
258             sub start_element {
259             my ($self, $el) = @_;
260              
261             my $parent = $el->{Parent} = $self->{Current};
262             $self->{Current} = $el;
263              
264             if ($self->{cache_until}) {
265             push @{$self->{cache}}, ["slide_start_element", $el];
266             }
267              
268             my $name = $el->{LocalName};
269              
270             # warn("start_ $name\n");
271              
272             if ($name eq 'slideshow') {
273             $self->push_todo(sub { $self->new_page(undef,$el->{Attributes}{"{}type"}{Value}) });
274             if (exists($el->{Attributes}{"{}default-transition"})) {
275             unshift @{$self->{default_transition}}, $el->{Attributes}{"{}default-transition"}{Value};
276             }
277             if (exists($el->{Attributes}{"{}coordinates"})) {
278             $self->{coords} = $el->{Attributes}{"{}coordinates"}{Value};
279             if ($self->{coords} !~ /^(svg|old)$/) {
280             Carp::croak("Unknown coordinate system: $self->{coords}");
281             }
282             }
283             }
284             elsif ($name eq 'title') {
285             $self->gathered_text; # reset
286             }
287             elsif ($name eq 'metadata') {
288             }
289             elsif ($name eq 'total-slides') {
290             $self->gathered_text; # reset
291             }
292             elsif ($name eq 'speaker') {
293             $self->gathered_text; # reset
294             }
295             elsif ($name eq 'email') {
296             $self->gathered_text; # reset
297             }
298             elsif ($name eq 'organisation') {
299             $self->gathered_text; # reset
300             }
301             elsif ($name eq 'link') {
302             $self->gathered_text; # reset
303             }
304             elsif ($name eq 'logo') {
305             if (exists($el->{Attributes}{"{}scale"})) {
306             $self->{logo}{scale} = $el->{Attributes}{"{}scale"}{Value};
307             }
308             if (exists($el->{Attributes}{"{}x"})) {
309             $self->{logo}{x} = $el->{Attributes}{"{}x"}{Value};
310             }
311             if (exists($el->{Attributes}{"{}y"})) {
312             $self->{logo}{y} = $el->{Attributes}{"{}y"}{Value};
313             }
314             $self->{logo}{x} ||= 0;
315             $self->{logo}{y} ||= 0;
316             $self->{logo}{scale} ||= 1.0;
317             $self->gathered_text; # reset
318             }
319             elsif ($name eq 'background') {
320             if (exists($el->{Attributes}{"{}scale"})) {
321             $self->{bg}{scale} = $el->{Attributes}{"{}scale"}{Value};
322             }
323             $self->{bg}{scale} ||= 1.0;
324             $self->gathered_text; # reset
325             }
326             elsif ($name eq 'bullet' or $name eq 'numbers') {
327             $self->gathered_text; # reset
328             }
329             elsif ($name eq 'slideset') {
330             $self->run_todo;
331             $self->{slide_index}[0]++;
332             unshift @{$self->{slide_index}}, 0;
333             if (exists($el->{Attributes}{"{}default-transition"})) {
334             unshift @{$self->{default_transition}}, $el->{Attributes}{"{}default-transition"}{Value};
335             }
336             $self->new_page(undef,$el->{Attributes}{"{}type"}{Value}) unless ($el->{Attributes}{"{}type"}{Value}||'normal') eq 'empty';
337             }
338             elsif ($name eq 'subtitle') {
339             }
340             elsif ($name eq 'slide') {
341             $self->run_todo; # might need to create slideset here.
342             $self->{pdf}->end_page;
343              
344             $self->{slide_index}[0]++;
345              
346             if (exists($el->{Attributes}{"{}default-transition"})) {
347             unshift @{$self->{default_transition}}, $el->{Attributes}{"{}default-transition"}{Value};
348             }
349             $self->{images} = [];
350             # cache these events now...
351             $self->{cache_until} = $el->{Name};
352             $self->{cache} = [["slide_start_element", $el]];
353             }
354             elsif ($name eq 'image') {
355             $self->gathered_text;
356             if (exists($el->{Attributes}{"{http://www.w3.org/1999/xlink}href"})) {
357             # uses xlink, not characters
358             $self->characters({ Data => $el->{Attributes}{"{http://www.w3.org/1999/xlink}href"}{Value}});
359             } elsif (exists($el->{Attributes}{"{}href"})) {
360             # workaround for XML::LibXML::SAX problem
361             $self->characters({ Data => $el->{Attributes}{"{}href"}{Value}});
362             }
363             }
364             elsif ($name =~ /^(source[_-]code|box|table|list|point|plain|value|i|b|u|colou?r|row|col|rect|circle|ellipse|polyline|line|path|text|g|span|variable)$/) {
365             # passthrough to allow these types
366             }
367             else {
368             warn("Unknown tag: $name");
369             }
370             }
371              
372             sub end_element {
373             my ($self, $el) = @_;
374              
375             if ($self->{cache_until}) {
376             push @{$self->{cache}}, ["slide_end_element", $el];
377             if ($el->{Name} eq $self->{cache_until}) {
378             delete $self->{cache_until};
379             $self->playback_cache;
380             }
381             }
382              
383             $el = $self->{Current};
384             my $parent = $self->{Current} = $el->{Parent};
385              
386             my $name = $el->{LocalName};
387             # warn("end_ $name\n");
388             if ($name eq 'slideshow') {
389             $self->run_todo;
390             if (exists($el->{Attributes}{"{}default-transition"})) {
391             shift @{$self->{default_transition}};
392             }
393             $self->pop_bookmark;
394             }
395             elsif ($name eq 'title') {
396             if ($parent->{LocalName} eq 'slideshow') {
397             my $title = $self->gathered_text;
398             $self->{values}->{'slideshow-title'} = $title;
399             $self->push_todo(sub {
400             $self->{pdf}->set_font(face => $self->{title_font}, size => $self->{title_size});
401              
402             $self->push_bookmark( $self->{pdf}->add_bookmark(text => "Title", open => 1) );
403              
404             if ($self->{pagetype} ne 'empty') {
405             $self->{pdf}->print_boxed(
406             $title,
407             x => 20, y => 50, w => 570, h => 300, mode => "center");
408              
409             $self->{pdf}->print_line("") for (1..4);
410              
411             my ($x, $y) = $self->{pdf}->get_text_pos();
412              
413             $self->{pdf}->set_font(face => $self->{subtitle_font}, size => $self->{subtitle_size});
414              
415             # speaker
416             if ($self->{metadata}{speaker}) {
417             $self->{pdf}->add_link(link => "mailto:" . $self->{metadata}{email},
418             x => 20, y => $y - 10, w => 570, h => 24)
419             if defined $self->{metadata}{email};
420             $self->{pdf}->print_boxed(
421             $self->{metadata}{speaker},
422             x => 20, y => 40, w => 570, h => $y - 24, mode => "center");
423             }
424              
425             $self->{pdf}->print_line("");
426             (undef, $y) = $self->{pdf}->get_text_pos();
427              
428             # organisation
429             if ($self->{metadata}{organisation}) {
430             $self->{pdf}->add_link(
431             link => $self->{metadata}{link},
432             x => 20, y => $y - 10, w => 570, h => 24);
433             $self->{pdf}->print_boxed(
434             $self->{metadata}{organisation},
435             x => 20, y => 40, w => 570, h => $y - 24, mode => "center");
436             }
437             }
438             });
439             }
440             elsif ($parent->{LocalName} eq 'slideset') {
441             my $title = $self->gathered_text;
442             $self->push_bookmark(
443             $self->{pdf}->add_bookmark(
444             text => $title,
445             level => 2,
446             parent_of => $self->top_bookmark,
447             open => 1,
448             )
449             );
450              
451             $self->{pdf}->set_font(face => $self->{title_font}, size => $self->{title_size});
452             if ($self->{pagetype} ne 'empty') {
453             $self->{pdf}->print_boxed(
454             $title,
455             x => 20, y => 50, w => 570, h => 200, mode => "center");
456              
457             my ($x, $y) = $self->{pdf}->get_text_pos();
458             $self->{pdf}->add_link(
459             link => $el->{Attributes}{"{}href"}{Value},
460             x => 20, y => $y - 5, w => 570, h => 24) if exists($el->{Attributes}{"{}href"});
461             }
462             }
463             }
464             elsif ($name eq 'metadata') {
465             $self->run_todo;
466             }
467             elsif ($name eq 'total-slides') {
468             $self->{metadata}{'total-slides'} = $self->gathered_text;
469             }
470             elsif ($name eq 'speaker') {
471             $self->{metadata}{speaker} = $self->gathered_text;
472             }
473             elsif ($name eq 'email') {
474             $self->{metadata}{email} = $self->gathered_text;
475             }
476             elsif ($name eq 'organisation') {
477             $self->{metadata}{organisation} = $self->gathered_text;
478             }
479             elsif ($name eq 'link') {
480             $self->{metadata}{link} = $self->gathered_text;
481             }
482             elsif ($name eq 'logo') {
483             my $logo_file =
484             File::Spec->rel2abs(
485             $self->gathered_text,
486             File::Basename::dirname($self->{locator}{SystemId} || '')
487             );
488             my $type = get_filetype($logo_file);
489             my $logo = $self->{pdf}->load_image(
490             filename => $logo_file,
491             filetype => $type,
492             ) || die "Couldn't load $logo_file";
493             if (!$logo) {
494             $self->{pdf}->finish;
495             die "Cannot load image $logo_file!";
496             }
497             $self->{logo}{image} = $logo;
498             }
499             elsif ($name eq 'background') {
500             my $bg_file =
501             File::Spec->rel2abs(
502             $self->gathered_text,
503             File::Basename::dirname($self->{locator}{SystemId} || '')
504             );
505             my $type = get_filetype($bg_file);
506             my $bg = $self->{pdf}->load_image(
507             filename => $bg_file,
508             filetype => $type,
509             ) || die "Couldn't load $bg_file";
510             if (!$bg) {
511             $self->{pdf}->finish;
512             die "Cannot load image $bg_file!";
513             }
514             $self->{bg}{image} = $bg;
515             }
516             elsif ($name eq 'bullet') {
517             die "need 'level' attribute for bullet tag" if (!exists($el->{Attributes}{"{}level"}));
518             die "'level' attribute of bullet tag must be an integer > 1" if (int($el->{Attributes}{"{}level"}) < 1);
519             my $bullet = $self->gathered_text;
520             die "bullet text must be a single character" if length($bullet) != 1;
521             $self->{bullets}[int($el->{Attributes}{"{}level"}{Value})] = $bullet;
522             }
523             elsif ($name eq 'numbers') {
524             die "need 'level' attribute for numbers tag" if (!exists($el->{Attributes}{"{}level"}));
525             die "'level' attribute of numbers tag must be an integer > 1" if (int($el->{Attributes}{"{}level"}) < 1);
526             my $num = $self->gathered_text;
527             $self->{numbers}[int($el->{Attributes}{"{}level"}{Value})] = $num;
528             }
529             elsif ($name eq 'slideset') {
530             $self->pop_bookmark;
531             shift @{$self->{slide_index}};
532             if (exists($el->{Attributes}{"{}default-transition"})) {
533             shift @{$self->{default_transition}};
534             }
535             }
536             elsif ($name eq 'subtitle') {
537             if ($parent->{LocalName} eq 'slideset') {
538             $self->{pdf}->set_font(face => $self->{subtitle_font}, size => $self->{subtitle_size});
539             if ($self->{pagetype} ne 'empty') {
540             $self->{pdf}->print_boxed(
541             $self->gathered_text,
542             x => 20, y => 20, w => 570, h => 200, mode => "center");
543             if (exists($el->{Attributes}{"{}href"})) {
544             my ($x, $y) = $self->{pdf}->get_text_pos();
545             $self->{pdf}->add_link(
546             link => $el->{Attributes}{"{}href"}{Value},
547             x => 20, y => $y - 5, w => 570, h => 18);
548             }
549             }
550             }
551             }
552             elsif ($name eq 'slide') {
553             $self->run_todo;
554             if (exists($el->{Attributes}{"{}default-transition"})) {
555             shift @{$self->{default_transition}};
556             }
557             }
558             elsif ($name eq 'image') {
559             my $image =
560             File::Spec->rel2abs(
561             $self->gathered_text,
562             File::Basename::dirname($self->{locator}{SystemId} || '')
563             );
564             my $image_ref = $self->{pdf}->load_image(
565             filename => $image,
566             filetype => get_filetype($image),
567             ) || die "Couldn't load $image";
568             my $scale = $el->{Attributes}{"{}scale"}{Value} || 1.0;
569             my $href = $el->{Attributes}{"{}href"}{Value};
570             my $x = $el->{Attributes}{"{}x"}{Value};
571             my $y = $el->{Attributes}{"{}y"}{Value};
572             my $width = $el->{Attributes}{"{}width"}{Value};
573             my $height = $el->{Attributes}{"{}height"}{Value};
574              
575             push @{$self->{images}},
576             {
577             scale => $scale,
578             image_ref => $image_ref,
579             href => $href,
580             x => $x,
581             y => $y,
582             width => $width,
583             height => $height,
584             };
585             }
586              
587             $self->{Current} = $parent;
588             }
589              
590             sub characters {
591             my ($self, $chars) = @_;
592              
593             if ($self->{cache_until}) {
594             push @{$self->{cache}}, ["slide_characters", $chars];
595             }
596              
597             $self->{gathered_text} .= $self->{text_encoder}->convert($chars->{Data});
598             }
599              
600             sub invalid_parent {
601             my $self = shift;
602             warn("Invalid tag nesting: <$self->{Current}{Parent}{LocalName}> <$self->{Current}{LocalName}>");
603             }
604              
605             sub gathered_text {
606             my $self = shift;
607             return substr($self->{gathered_text}, 0, length($self->{gathered_text}), '');
608             }
609              
610             sub image {
611             my ($self, $scale, $file_handle, $href) = @_;
612             my $pdf = $self->{pdf};
613              
614             $pdf->print_line("");
615              
616             my ($x, $y) = $pdf->get_text_pos;
617              
618             my @scale = split(/\*/,$scale);
619             my $imgw = $self->get_scale($scale[0],0,$pdf->get_value("imagewidth", $file_handle->img),$pdf->get_value("resx", $file_handle->img));
620             my $imgh = $self->get_scale($scale[1]||$scale[0],1,$pdf->get_value("imageheight", $file_handle->img),$pdf->get_value("resy", $file_handle->img));
621              
622             my $xpos = (($self->{extents}[0]{x} + ($self->{extents}[0]{w} / 2))
623             - ($imgw / 2));
624             my $ypos = ($y - $imgh);
625              
626             # warn("image: ($xpos,$ypos) $imgw x $imgh");
627              
628             $pdf->add_image(img => $file_handle,
629             x => $xpos,
630             y => $ypos,
631             w => $imgw,
632             h => $imgh);
633             $pdf->add_link(link => $href, x => $xpos, y => $ypos, w => $imgw, h => $imgh) if $href;
634              
635             $pdf->set_text_pos($x, $ypos);
636             }
637              
638             sub bullet {
639             my ($self, $el) = @_;
640              
641             my $pdf = $self->{pdf};
642              
643             my ($char, $size);
644              
645             my $level = $el->{Attributes}{"{}level"}{Value} || @{$self->{list_index}} || 1;
646             my ($x, $y) = $pdf->get_text_pos;
647              
648             if (@{$self->{xindent}} && $level <= $self->{xindent}[0]{level}) {
649             my $last;
650             while ($last = shift @{$self->{xindent}}) {
651             if ($last->{level} == $level) {
652             $self->{pdf}->set_text_pos($last->{x}, $y);
653             $x = $last->{x};
654             last;
655             }
656             }
657             }
658              
659             if ($level == 1) {
660             my $indent = 80 * ($self->{extents}[0]{w} / $self->{extents}[-1]{w});
661             $self->{pdf}->set_text_pos($self->{extents}[0]{x} + $indent, $y);
662             }
663              
664             $char = $self->{bullets}->[$level];
665             $size = 20-(2*$level);
666              
667             if ($level == 1) {
668             my ($x, $y) = $pdf->get_text_pos;
669             $y += 9;
670             $pdf->set_text_pos($x, $y);
671             $pdf->print_line("");
672             }
673              
674             ($x, $y) = $pdf->get_text_pos;
675              
676             if (!@{$self->{xindent}} || $level > $self->{xindent}[0]{level}) {
677             unshift @{$self->{xindent}}, {level => $level, x => $x};
678             }
679              
680             my $bw;
681             if (!$el->{Attributes}{"{}level"}{Value} && @{$self->{list_index}} && $self->{list_index}->[-1] > 0) {
682             my $index = $self->{list_index}->[-1];
683             $self->{list_index}->[-1]++;
684             $char = $self->{numbers}->[$level];
685             $char =~ s/(([^\$]|^)(\$\$)*)\$1/$1$index/g;
686             my $alpha = ('','a'..'z')[$index];
687             $char =~ s/(([^\$]|^)(\$\$)*)\$a/$1$alpha/g;
688             $alpha = uc($alpha);
689             $char =~ s/(([^\$]|^)(\$\$)*)\$A/$1$alpha/g;
690             $alpha = '';
691             $alpha .= 'x', $index -= 10 while ($index > 10);
692             $alpha .= ('','i','ii','iii','iv','v','vi','vii','viii','ix','x')[$index];
693             $char =~ s/(([^\$]|^)(\$\$)*)\$i/$1$alpha/g;
694             $alpha = uc($alpha);
695             $char =~ s/(([^\$]|^)(\$\$)*)\$I/$1$alpha/g;
696             $char =~ s/\$\$/\$/g;
697             $pdf->set_font(face => $self->{normal_font}, size => $size);
698             $bw = $pdf->string_width(text => $char." ");
699             $pdf->print($char);
700             } else {
701             $pdf->set_font(face => "ZapfDingbats", size => $size - 4, encoding => "builtin");
702             $bw = $pdf->string_width(text => $char);
703             $pdf->print($char);
704             $pdf->set_font(face => $self->{normal_font}, size => $size);
705             }
706             if ($pdf->string_width(text => " ") < $bw) {
707             $pdf->print(" ");
708             } else {
709             $pdf->set_text_pos($x, $y);
710             $pdf->print(" ");
711             }
712              
713             return ($pdf->get_text_pos, $size);
714             }
715              
716             sub get_filetype {
717             my $filename = shift;
718              
719             my ($suffix) = $filename =~ /([^\.]+?)$/;
720             $suffix = lc($suffix);
721             if ($suffix eq 'jpg') {
722             return 'jpeg';
723             }
724             return $suffix;
725             }
726              
727             my %colours = (
728             black => "000000",
729             green => "008000",
730             silver => "C0C0C0",
731             lime => "00FF00",
732             gray => "808080",
733             olive => "808000",
734             white => "FFFFFF",
735             yellow => "FFFF00",
736             maroon => "800000",
737             navy => "000080",
738             red => "FF0000",
739             blue => "0000FF",
740             purple => "800080",
741             teal => "008080",
742             fuchsia => "FF00FF",
743             aqua => "00FFFF",
744             );
745              
746             sub get_colour {
747             my $colour = shift;
748             if ($colour !~ s/^#//) {
749             $colour = $colours{$colour} || die "Unknown colour: $colour";
750             }
751             if ($colour !~ /^[0-9a-fA-F]{6}$/) {
752             die "Invalid colour format: #$colour";
753             }
754             my ($r, $g, $b) = map { hex()/255 } ($colour =~ /(..)/g);
755             return [$r, $g, $b];
756             }
757              
758             my $current_fill_colour = [0,0,0];
759             my $current_stroke_colour = [0,0,0];
760             my $current_rendering = 0;
761             my $current_line_cap = 0;
762             my $current_line_join = 0;
763             my $current_line_width = 1;
764             my $current_miter_limit = 10;
765             my $current_fillrule = "winding";
766              
767             sub push_font {
768             my ($self) = @_;
769             my $pdf = $self->{bb} || $self->{pdf};
770             my $elt = $self->{SlideCurrent} || $self->{Current};
771             push @{$self->{font_stack}}, [
772             $pdf->get_parameter("fontname"),
773             $pdf->get_value("fontsize"),
774             $current_fill_colour,
775             $current_stroke_colour,
776             $pdf->{underline},
777             $elt->{LocalName},
778             $self->{fill},
779             $self->{stroke},
780             $current_line_cap,
781             $current_line_join,
782             $current_line_width,
783             $current_miter_limit,
784             $current_fillrule,
785             $self->{transitional},
786             ];
787             }
788              
789             sub pop_font {
790             my ($self) = @_;
791             my ($font, $size, $fill_colour, $stroke_colour, $underline, $name, $fill, $stroke, $lc, $lj, $lw, $ml, $fr, $trans) = @{pop @{$self->{font_stack}}};
792             my $pdf = $self->{bb} || $self->{pdf};
793             my $elt = $self->{SlideCurrent} || $self->{Current};
794             $pdf->set_font(face => $font, size => $size);
795             $current_fill_colour = $fill_colour;
796             $pdf->set_colour(rgb => $current_fill_colour, type => "fill");
797             $current_stroke_colour = $stroke_colour;
798             $pdf->set_colour(rgb => $current_stroke_colour, type => "stroke");
799             $pdf->set_decoration($underline?"underline":"none");
800             $current_line_cap = $lc;
801             $pdf->set_line_cap($lc);
802             $current_line_join = $lj;
803             $pdf->set_line_join($lj);
804             $current_line_width = $lw;
805             $pdf->set_line_width($lw);
806             $current_miter_limit = $ml;
807             $pdf->set_miter_limit($ml);
808             $pdf->set_parameter("fillrule",$fr);
809             $self->{fill} = $fill;
810             $self->{stroke} = $stroke;
811             if ($self->{fill} && $self->{stroke}) {
812             $pdf->set_value(textrendering => 2);
813             }
814             elsif ($self->{fill}) {
815             $pdf->set_value(textrendering => 0);
816             }
817             elsif ($self->{stroke}) {
818             $pdf->set_value(textrendering => 1);
819             }
820             else {
821             $pdf->set_value(textrendering => 3); # invisible
822             }
823             }
824              
825             sub process_css_styles {
826             my ($self, $style, $text_mode) = @_;
827              
828             if ($text_mode) {
829             $self->{stroke} = 0;
830             $self->{fill} = 1;
831             }
832             else {
833             $self->{stroke} = 1;
834             $self->{fill} = 0;
835             }
836              
837             $self->push_font();
838             return unless $style;
839              
840             my $pdf = $self->{bb} || $self->{pdf};
841              
842             my $new_font = $pdf->get_parameter("fontname");
843             my $bold = 0;
844             my $italic = 0;
845             my $underline = 0;
846             my $size = $pdf->get_value('fontsize');
847             if ($new_font =~ s/-(.*)$//) {
848             my $removed = $1;
849             if ($removed =~ /Bold/i) {
850             $bold = 1;
851             }
852             if ($removed =~ /(Oblique|Italic)/i) {
853             $italic = 1;
854             }
855             }
856             foreach my $part (split(/;\s*/s, $style)) {
857             my ($key, $value) = split(/\s*:\s*/, $part, 2);
858             # Keys we need to implement:
859             # color, fill, font, font-style, font-weight, font-size,
860             # font-family, stroke, stroke-linecap, stroke-linejoin, stroke-width,
861              
862             # warn("got $key = $value\n");
863             if ($key eq 'font') {
864             # [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ]
865             if ($value =~ /^((\S+)\s+)?((\S+)\s+)(\S+)$/) {
866             my ($attribs, $ptsize, $name) = ($2, $4, $5);
867             $attribs ||= 'inherit';
868             if ($attribs eq 'normal') {
869             $bold = 0; $italic = 0;
870             }
871             elsif ($attribs eq 'inherit') {
872             # Do nothing
873             }
874             elsif ($attribs eq 'bold' || $attribs eq 'bolder') {
875             $bold = 1;
876             }
877             elsif ($attribs eq 'italic' || $attribs eq 'oblique') {
878             $italic = 1;
879             }
880              
881             if ($ptsize !~ s/pt$//) {
882             die "Cannot support fonts in anything but point sizes yet: $value";
883             }
884             $size = $ptsize;
885              
886             $name =~ s/sans-serif/Helvetica/;
887             $name =~ s/serif/Times/;
888             $name =~ s/monospace/Courier/;
889             $new_font = $name;
890             }
891             else {
892             die "Failed to parse CSS font attribute: $value";
893             }
894             }
895             elsif ($key eq 'font-family') {
896             $value =~ s/sans-serif/Helvetica/;
897             $value =~ s/serif/Times/;
898             $value =~ s/monospace/Courier/;
899             $new_font = $value;
900             }
901             elsif ($key eq 'font-style') {
902             if ($value eq 'normal') {
903             $italic = 0;
904             }
905             elsif ($value eq 'italic') {
906             $italic = 1;
907             }
908             }
909             elsif ($key eq 'font-weight') {
910             if ($value eq 'normal') {
911             $bold = 0;
912             }
913             elsif ($value eq 'bold') {
914             $bold = 1;
915             }
916             }
917             elsif ($key eq 'text-decoration') {
918             if ($value eq 'none') {
919             $underline = 0;
920             }
921             elsif ($value eq 'underline') {
922             $underline = 1;
923             }
924             }
925             elsif ($key eq 'font-size') {
926             if ($value !~ s/pt$// && $value =~ m/[a-z]/) {
927             die "Can't do anything but font-size in pt yet";
928             }
929             $size = $value;
930             }
931             elsif ($key eq 'color') {
932             # set both the stroke and fill color
933             $current_fill_colour = $current_stroke_colour = get_colour($value);
934             $pdf->set_colour(rgb => $current_fill_colour, type => "both");
935             }
936             elsif ($key eq 'fill') {
937             if ($value eq 'none') {
938             $self->{fill} = 0;
939             }
940             else {
941             # it's a color
942             $self->{fill} = 1;
943             $current_fill_colour = get_colour($value);
944             $pdf->set_colour(rgb => $current_fill_colour, type => "fill");
945             }
946             }
947             elsif ($key eq 'fill-rule') {
948             $value = 'winding' if $value eq 'nonzero';
949             $pdf->set_parameter(fillrule => $value);
950             $current_fillrule = $value;
951             }
952             elsif ($key eq 'stroke') {
953             if ($value eq 'none') {
954             $self->{stroke} = 0;
955             }
956             else {
957             # it's a color
958             $self->{stroke} = 1;
959             $current_stroke_colour = get_colour($value);
960             $pdf->set_colour(rgb => $current_stroke_colour, type => "stroke");
961             }
962             }
963             elsif ($key eq 'stroke-linecap') {
964             $pdf->set_line_cap("${value}_end"); # PDFLib takes care of butt|round|square
965             $current_line_cap = $value;
966             }
967             elsif ($key eq 'stroke-linejoin') {
968             $pdf->set_line_join($value); # PDFLib takes care of miter|round|bevel
969             $current_line_join = $value;
970             }
971             elsif ($key eq 'stroke-width') {
972             $pdf->set_line_width($value);
973             $current_line_width = $value;
974             }
975             elsif ($key eq 'stroke-miterlimit') {
976             $pdf->set_miter_limit($value);
977             $current_miter_limit = $value;
978             }
979             }
980              
981             return unless $text_mode;
982              
983             $pdf->set_decoration($underline?"underline":"none");
984              
985             my $ok = 0;
986             # warn(sprintf("set_font(%s => %s, %s => %s, %s => %s, %s => %s)\n",
987             # face => $new_font,
988             # italic => $italic,
989             # bold => $bold,
990             # size => $size,
991             # )
992             # );
993             foreach my $face (split(/\s*/, $new_font)) {
994             eval {
995             $pdf->set_font(
996             face => $new_font,
997             italic => $italic,
998             bold => $bold,
999             size => $size,
1000             );
1001             };
1002             if (!$@) {
1003             $ok = 1;
1004             last;
1005             }
1006             }
1007             if (!$ok) {
1008             die "Unable to find font: $new_font : $@";
1009             }
1010              
1011             if ($self->{fill} && $self->{stroke}) {
1012             $pdf->set_value(textrendering => 2);
1013             }
1014             elsif ($self->{fill}) {
1015             $pdf->set_value(textrendering => 0);
1016             }
1017             elsif ($self->{stroke}) {
1018             $pdf->set_value(textrendering => 1);
1019             }
1020             else {
1021             $pdf->set_value(textrendering => 3); # invisible
1022             }
1023             }
1024              
1025             sub slide_start_element {
1026             my ($self, $el) = @_;
1027              
1028             $self->{SlideCurrent} = $el;
1029              
1030             my $name = $el->{LocalName};
1031              
1032             #warn("slide_start_ $name ".join(",",map { $_."=>".$el->{Attributes}{$_}->{Value} } keys %{$el->{Attributes}})."\n");
1033              
1034             # transitions...
1035             if ( (!$self->{PrintMode}) &&
1036             $name =~ /^(point|plain|image|source[_-]code|table|col|row|circle|ellipse|rect|text|line|path)$/) {
1037             if (exists($el->{Attributes}{"{}transition"})
1038             || @{$self->{default_transition}}) {
1039             # has a transition
1040             my $trans = $el->{Attributes}{"{}transition"};
1041             # default transition if unspecified (and not for table tags)
1042             if ( (!$trans) && ($name ne 'table') && ($name ne 'row') && ($name ne 'col') && ($name ne 'box') ) {
1043             $trans = { Value => $self->{default_transition}[0] };
1044             }
1045             if ($trans && ($trans->{Value} ne 'none') ) {
1046             my @cache = @{$self->{cache_trash}};
1047             local $self->{cache} = \@cache;
1048             local $self->{cache_trash};
1049             # warn("playback on $el\n");
1050             $self->{transitional} = 1;
1051             my $parent = $el->{Parent};
1052             while ($parent) {
1053             last if $parent->{LocalName} eq 'slide';
1054             $parent = $parent->{Parent};
1055             }
1056             die "No parent slide element" unless $parent;
1057             local $parent->{Attributes}{"{}transition"}{Value} = $trans->{Value};
1058             $self->playback_cache; # should get us back here.
1059             $self->run_todo;
1060             # make sure we don't transition this node again
1061             $el->{Attributes}{"{}transition"}{Value} = 'none';
1062             # warn("playback returns\n");
1063             $self->{transitional} = 0;
1064             pop @{$self->{font_stack}} while (@{$self->{font_stack}} && $self->{font_stack}[-1][-1]);
1065             }
1066             } else {
1067             $el->{Attributes}{"{}transition"}{Value} = 'none';
1068             }
1069             }
1070              
1071             if ($name =~ m/^(table|list|image|source[-_]code)$/ && $el->{Attributes}{'{}title'}) {
1072             $self->push_font();
1073             $self->{pdf}->set_font(face => $self->{normal_font}, italic => 1, size => 15);
1074             my ($x, $y) = $self->{pdf}->get_text_pos;
1075             my $indent = 80 * ($self->{extents}[0]{w} / $self->{extents}[-1]{w});
1076             $self->{pdf}->set_text_pos($self->{bb}?$self->{bb}->{x}:$self->{extents}[0]{x}+$indent, $y);
1077             $self->{pdf}->print($el->{Attributes}{'{}title'}{Value});
1078             $self->{pdf}->print_line("") unless $name eq 'image';
1079             $self->pop_font();
1080             }
1081              
1082             if ($name eq 'slide') {
1083             $self->new_page(undef,$el->{Attributes}{"{}type"}{Value});
1084             $self->{image_id} = 0;
1085             # if we do bullet/image transitions, make sure new pages don't use a transition
1086             $el->{Attributes}{"{}transition"}{Value} = "replace";
1087             $self->{extents} = [{ x => 0, w => 612 }];
1088             }
1089             elsif ($name eq 'title') {
1090             $self->gathered_text; # reset
1091             $self->{chars_ok} = 1;
1092              
1093             if ($self->{pagetype} ne 'empty') {
1094             my $bb = $self->{pdf}->new_bounding_box(
1095             x => 5, y => 400, w => 602, h => 50,
1096             align => "centre",
1097             );
1098             $self->{bb} = $bb;
1099             $bb->set_font(
1100             face => $self->{title_font},
1101             size => $self->{title_size},
1102             );
1103             }
1104             }
1105             elsif ($name eq 'table') {
1106             # push extents.
1107             $self->{extents} = [{ %{$self->{extents}[0]} }, @{$self->{extents}}];
1108             $self->{col_widths} = [];
1109             my ($x, $y) = $self->{pdf}->get_text_pos;
1110             $self->{pdf}->set_text_pos($self->{extents}[1]{x}, $y);
1111             $self->{max_height} = $y;
1112             $self->{row_number} = 0;
1113             }
1114             elsif ($name eq 'box') {
1115             if (!$self->{transitional}) {
1116             if (exists($el->{Attributes}{"{}default-transition"})) {
1117             unshift @{$self->{default_transition}}, $el->{Attributes}{"{}default-transition"}{Value};
1118             unshift @{$self->{boxtransition}}, 1;
1119             delete $el->{Attributes}{"{}default-transition"};
1120             } else {
1121             unshift @{$self->{boxtransition}}, 0;
1122             }
1123             }
1124             # push extents.
1125             $self->{extents} = [{ %{$self->{extents}[0]} }, @{$self->{extents}}];
1126             $self->{extents}[0]{x} = $el->{Attributes}{'{}x'}{Value};
1127             $self->{extents}[0]{w} = $el->{Attributes}{'{}width'}{Value};
1128             $self->{extents}[0]{y} = $el->{Attributes}{'{}y'}{Value};
1129             $self->{extents}[0]{h} = $el->{Attributes}{'{}height'}{Value};
1130             $self->{boxlast} = [ $self->{pdf}->get_text_pos() ];
1131             $self->{pdf}->set_text_pos($self->{extents}[0]{x}, $self->{extents}[0]{y});
1132             }
1133             elsif ($name eq 'row') {
1134             $self->{col_number} = 0;
1135             $self->{row_start} = [];
1136             @{$self->{row_start}} = $self->{pdf}->get_text_pos;
1137             }
1138             elsif ($name eq 'col') {
1139             my $width;
1140             my $prev_x = $self->{extents}[1]{x};
1141             if ($self->{row_number} > 0) {
1142             $width = $self->{col_widths}[$self->{col_number}];
1143             }
1144             else {
1145             $width = $el->{Attributes}{"{}width"}{Value};
1146             $width =~ s/%$// || die "Column widths must be in percentages";
1147             # warn("calculating ${width}% of $self->{extents}[1]{w}\n");
1148             $width = $self->{extents}[1]{w} * ($width/100);
1149             $self->{col_widths}[$self->{col_number}] = $width;
1150             }
1151             if ($self->{col_number} > 0) {
1152             my $up_to = $self->{col_number} - 1;
1153             foreach my $col (0 .. $up_to) {
1154             $prev_x += $self->{col_widths}[$col];
1155             }
1156             }
1157             # warn("col setting extents to x => $prev_x, w => $width\n");
1158             $self->{extents}[0]{x} = $prev_x;
1159             $self->{extents}[0]{w} = $width;
1160             $self->{pdf}->set_text_pos(@{$self->{row_start}});
1161             }
1162             elsif ($name eq 'value') {
1163             my $type = $el->{Attributes}{'{}type'}{Value};
1164             my $pdf = $self->{bb} || $self->{pdf};
1165             if (exists $self->{values}->{$type}) {
1166             $pdf->print($self->{values}->{$type});
1167             } elsif (exists $self->{metadata}->{$type}) {
1168             $pdf->print($self->{metadata}->{$type});
1169             } elsif ($type eq 'today') {
1170             $pdf->print(localtime->strftime($el->{Attributes}{'{}format'}{Value}||'%Y-%m-%d'));
1171             } elsif ($type eq 'logo') {
1172             if (my $logo = $self->{logo}) {
1173             my @scale = split(/\*/,$logo->{scale});
1174             my $imgw = $self->get_scale($scale[0],0,$self->{pdf}->get_value("imagewidth", $logo->{image}->img),$self->{pdf}->get_value("resx", $logo->{image}->img));
1175             my $imgh = $self->get_scale($scale[1]||$scale[0],1,$self->{pdf}->get_value("imageheight", $logo->{image}->img),$self->{pdf}->get_value("resy", $logo->{image}->img));
1176             my ($x, $y) = $pdf->get_text_pos();
1177             if ($self->{bb}) {
1178             $pdf->push_todo('add_image',img => $logo->{image}, x => $x+$pdf->{cur_width}, y => $y, w => $imgw, h => $imgh);
1179             $pdf->push_todo('set_text_pos',$x+$pdf->{cur_width}+$imgw,$y);
1180             } else {
1181             $pdf->add_image(img => $logo->{image}, x => $x, y => $y, w => $imgw, h => $imgh);
1182             }
1183             }
1184             } elsif ($type eq 'background') {
1185             if (my $bg = $self->{bg}) {
1186             my @scale = split(/\*/,$bg->{scale});
1187             my $imgw = $self->get_scale($scale[0],0,$self->{pdf}->get_value("imagewidth", $bg->{image}->img),$self->{pdf}->get_value("resx", $bg->{image}->img));
1188             my $imgh = $self->get_scale($scale[1]||$scale[0],1,$self->{pdf}->get_value("imageheight", $bg->{image}->img),$self->{pdf}->get_value("resy", $bg->{image}->img));
1189             my ($x, $y) = $pdf->get_text_pos();
1190             if ($self->{bb}) {
1191             $pdf->push_todo('add_image',img => $bg->{image}, x => $x+$pdf->{cur_width}, y => $y, w => $imgw, h => $imgh);
1192             $pdf->push_todo('set_text_pos',$x+$pdf->{cur_width}+$imgw,$y);
1193             } else {
1194             $pdf->add_image(img => $bg->{image}, x => $x, y => $y, w => $imgw, h => $imgh);
1195             }
1196             }
1197             } elsif ($type eq 'current-slideset') {
1198             $pdf->print(join(".",reverse @{$self->{slide_index}}).". ");
1199             }
1200             }
1201             elsif ($name eq 'i') {
1202             my $new = $self->{pdf}->get_parameter("fontname") || $self->{normal_font};
1203             my $bold = 0;
1204             if ($new =~ s/-(.*)$//) {
1205             my $removed = $1;
1206             if ($removed =~ /Bold/i) {
1207             $bold = 1;
1208             }
1209             }
1210             $self->push_font();
1211             $self->{bb}->set_font(face => $new, italic => 1, bold => $bold);
1212             }
1213             elsif ($name eq 'b') {
1214             my $new = $self->{pdf}->get_parameter("fontname") || $self->{normal_font};
1215             my $italic = 0;
1216             if ($new =~ s/-(.*)$//) {
1217             my $removed = $1;
1218             if ($removed =~ /(Oblique|Italic)/i) {
1219             $italic = 1;
1220             }
1221             }
1222             $self->push_font();
1223             $self->{bb}->set_font(face => $new, italic => $italic, bold => 1);
1224             }
1225             elsif ($name eq 'u') {
1226             $self->push_font();
1227             $self->{bb}->set_decoration("underline");
1228             }
1229             elsif ($name eq 'plain') {
1230             $self->{chars_ok} = 1;
1231             my ($x, $y) = $self->{pdf}->get_text_pos;
1232              
1233             my $indent = 80 * ($self->{extents}[0]{w} / $self->{extents}[-1]{w});
1234             $y += 9;
1235             $self->{pdf}->set_text_pos($self->{extents}[0]{x} + $indent, $y);
1236             $self->{pdf}->set_font(face => $self->{normal_font}, size => 18);
1237             $self->{pdf}->print_line("");
1238              
1239             ($x, $y) = $self->{pdf}->get_text_pos;
1240             my $align = $el->{Attributes}{"{}align"}{Value} || 'left';
1241             my $bb = $self->{pdf}->new_bounding_box(
1242             x => $x, y => $y, w => ($self->{extents}[0]{w} - ($x - $self->{extents}[0]{x})), h => $y, align => $align
1243             );
1244             $self->{bb} = $bb;
1245             }
1246             elsif ($name eq 'point') {
1247             $self->{chars_ok} = 1;
1248              
1249             my ($x, $y, $size) = $self->bullet($el);
1250              
1251             # warn(sprintf("creating new bb: %s => %d, %s => %d, %s => %d, %s => %d",
1252             # x => $x, y => $y, w => ($self->{extents}[0]{w} - ($x - $self->{extents}[0]{x})), h => (450 - $y)
1253             # ));
1254             my $bb = $self->{pdf}->new_bounding_box(
1255             x => $x, y => $y, w => ($self->{extents}[0]{w} - ($x - $self->{extents}[0]{x})), h => $y
1256             );
1257             $self->{bb} = $bb;
1258             }
1259             elsif ($name eq 'image') {
1260             my $image = $self->{images}[$self->{image_id}];
1261             my ($scale, $handle, $href) =
1262             ($image->{scale}, $image->{image_ref}, $image->{href});
1263             if (defined($image->{x}) && defined($image->{y})) {
1264             my $pdf = $self->{pdf};
1265             my @scale = split(/\*/,$scale);
1266             my $imgw = $self->get_scale($scale[0],0,$pdf->get_value("imagewidth", $handle->img),$pdf->get_value("resx", $handle->img));
1267             my $imgh = $self->get_scale($scale[1]||$scale[0],1,$pdf->get_value("imageheight", $handle->img),$pdf->get_value("resy", $handle->img));
1268             $pdf->add_image(img => $handle,
1269             x => $image->{x},
1270             y => $image->{y},
1271             w => $imgw,
1272             h => $imgh
1273             );
1274             }
1275             else {
1276             $self->image($scale, $handle, $href);
1277             }
1278             }
1279             elsif ($name eq 'source_code' || $name eq 'source-code') {
1280             my $size = $el->{Attributes}{"{}fontsize"}{Value} || 14;
1281             $self->{chars_ok} = 1;
1282              
1283             my ($x, $y) = $self->{pdf}->get_text_pos;
1284             my $indent = 80 * ($self->{extents}[0]{w} / $self->{extents}[-1]{w});
1285             $self->{pdf}->set_text_pos($self->{extents}[0]{x} + $indent, $y);
1286              
1287             $self->push_font();
1288             $self->{pdf}->set_font(face => "Courier", size => $size);
1289             ($x, $y) = $self->{pdf}->get_text_pos;
1290             my $bb = $self->{pdf}->new_bounding_box(
1291             x => $x, y => $y, w => ($self->{extents}[0]{w} - ($x - $self->{extents}[0]{x})), h => $y,
1292             wrap => 0,
1293             );
1294             # warn("new_bounding_box( h => $y ) => $bb\n");
1295             $self->{bb} = $bb;
1296             }
1297             elsif ($name eq 'color' || $name eq 'colour') {
1298             my $hex_colour;
1299             if (exists($el->{Attributes}{"{}name"})) {
1300             my $colour = lc($el->{Attributes}{"{}name"}{Value});
1301             $hex_colour = $colours{$colour}
1302             || die "No such colour: $colour";
1303             }
1304             else {
1305             $hex_colour = $el->{Attributes}{"{}rgb"}{Value};
1306             }
1307             if (!$hex_colour) {
1308             die "Missing colour attribute: name or rgb (found: " . join(', ', keys(%{$el->{Attributes}})) .")";
1309             }
1310             $hex_colour =~ s/^#//;
1311             if ($hex_colour !~ /^[0-9a-fA-F]{6}$/) {
1312             die "Invalid hex format: $hex_colour";
1313             }
1314              
1315             my ($r, $g, $b) = map { hex()/255 } ($hex_colour =~ /(..)/g);
1316              
1317             $self->push_font();
1318             $self->{bb}->set_color(rgb => [$r,$g,$b]);
1319             }
1320             elsif ($name eq 'span') {
1321             $self->process_css_styles($el->{Attributes}{"{}style"}{Value}, 1);
1322             }
1323             elsif ($name eq 'g') {
1324             $self->process_css_styles($el->{Attributes}{"{}style"}{Value}, 1);
1325             }
1326             elsif ($name eq 'rect') {
1327             my ($x, $y, $width, $height) = (
1328             $el->{Attributes}{"{}x"}{Value},
1329             $el->{Attributes}{"{}y"}{Value},
1330             $el->{Attributes}{"{}width"}{Value},
1331             $el->{Attributes}{"{}height"}{Value},
1332             );
1333             $self->{pdf}->save_graphics_state();
1334             $self->process_css_styles($el->{Attributes}{"{}style"}{Value});
1335             if ($self->{coords} eq 'svg') {
1336             $self->{pdf}->rect(x => $x, y => $self->{pdf}->get_value('pageheight')-$y-$height, w => $width, h => $height);
1337             }
1338             else {
1339             $self->{pdf}->rect(x => $x, y => $y, w => $width, h => $height);
1340             }
1341              
1342             if ($self->{fill} && $self->{stroke}) {
1343             $self->{pdf}->fill_stroke;
1344             }
1345             elsif ($self->{fill}) {
1346             $self->{pdf}->fill;
1347             }
1348             elsif ($self->{stroke}) {
1349             $self->{pdf}->stroke;
1350             }
1351             }
1352             elsif ($name eq 'circle') {
1353             my ($cx, $cy, $r) = (
1354             $el->{Attributes}{"{}cx"}{Value},
1355             $el->{Attributes}{"{}cy"}{Value},
1356             $el->{Attributes}{"{}r"}{Value},
1357             );
1358             $self->{pdf}->save_graphics_state();
1359             $self->process_css_styles($el->{Attributes}{"{}style"}{Value});
1360             if ($self->{coords} eq 'svg') {
1361             $self->{pdf}->circle(x => $cx, y => $self->{pdf}->get_value('pageheight')-$cy, r => $r);
1362             }
1363             else {
1364             $self->{pdf}->circle(x => $cx, y => $cy, r => $r);
1365             }
1366             if ($self->{fill} && $self->{stroke}) {
1367             $self->{pdf}->fill_stroke;
1368             }
1369             elsif ($self->{fill}) {
1370             $self->{pdf}->fill;
1371             }
1372             elsif ($self->{stroke}) {
1373             $self->{pdf}->stroke;
1374             }
1375             }
1376             elsif ($name eq 'ellipse') {
1377             my ($cx, $cy, $rx, $ry) = (
1378             $el->{Attributes}{"{}cx"}{Value},
1379             $el->{Attributes}{"{}cy"}{Value},
1380             $el->{Attributes}{"{}rx"}{Value},
1381             $el->{Attributes}{"{}ry"}{Value},
1382             );
1383             my $r = $rx;
1384             my $scale = $ry / $r;
1385             $cy /= $scale;
1386             # warn("ellipse at $cx, $cy, scale: $scale, r: $r\n");
1387             $self->{pdf}->save_graphics_state();
1388             $self->process_css_styles($el->{Attributes}{"{}style"}{Value});
1389             $self->{pdf}->coord_scale(1, $scale);
1390             if ($self->{coords} eq 'svg') {
1391             $self->{pdf}->circle(x => $cx, y => $self->{pdf}->get_value('pageheight')-$cy, r => $r);
1392             }
1393             else {
1394             $self->{pdf}->circle(x => $cx, y => $cy, r => $r);
1395             }
1396             if ($self->{fill} && $self->{stroke}) {
1397             $self->{pdf}->fill_stroke;
1398             }
1399             elsif ($self->{fill}) {
1400             $self->{pdf}->fill;
1401             }
1402             elsif ($self->{stroke}) {
1403             $self->{pdf}->stroke;
1404             }
1405             }
1406             elsif ($name eq 'line') {
1407             my ($x1, $y1, $x2, $y2) = (
1408             $el->{Attributes}{"{}x1"}{Value},
1409             $el->{Attributes}{"{}y1"}{Value},
1410             $el->{Attributes}{"{}x2"}{Value},
1411             $el->{Attributes}{"{}y2"}{Value},
1412             );
1413             $self->{pdf}->save_graphics_state();
1414             $self->process_css_styles($el->{Attributes}{"{}style"}{Value});
1415             if ($self->{coords} eq 'svg') {
1416             $self->{pdf}->move_to($x1, $self->{pdf}->get_value('pageheight')-$y1);
1417             $self->{pdf}->line_to($x2, $self->{pdf}->get_value('pageheight')-$y2);
1418             }
1419             else {
1420             $self->{pdf}->move_to($x1, $y1);
1421             $self->{pdf}->line_to($x2, $y2);
1422             }
1423             if ($self->{fill} && $self->{stroke}) {
1424             $self->{pdf}->fill_stroke;
1425             }
1426             elsif ($self->{fill}) {
1427             $self->{pdf}->fill;
1428             }
1429             elsif ($self->{stroke}) {
1430             $self->{pdf}->stroke;
1431             }
1432             }
1433             elsif ($name eq 'text') {
1434             my ($x, $y) = (
1435             $el->{Attributes}{"{}x"}{Value},
1436             $el->{Attributes}{"{}y"}{Value},
1437             );
1438             $self->{pdf}->save_graphics_state();
1439             $self->push_font();
1440             $self->{pdf}->set_font( face => $self->{normal_font}, size => 14.0 ) unless $el->{Parent}->{LocalName} eq 'g';
1441             $self->process_css_styles($el->{Attributes}{"{}style"}{Value}, 1);
1442             if ($self->{coords} eq 'svg') {
1443             $self->{pdf}->set_text_pos($x, $self->{pdf}->get_value('pageheight')-$y);
1444             }
1445             else {
1446             $self->{pdf}->set_text_pos($x, $y);
1447             }
1448             $self->{chars_ok} = 1;
1449             $self->gathered_text; # reset
1450             }
1451             elsif ($name eq 'list') {
1452             if ($el->{Attributes}{"{}ordered"}) {
1453             push @{$self->{list_index}}, 1;
1454             } else {
1455             push @{$self->{list_index}}, 0;
1456             }
1457             }
1458             elsif ($name eq 'path') {
1459             my ($data) = (
1460             $el->{Attributes}{"{}d"}{Value},
1461             );
1462             $self->{pdf}->save_graphics_state();
1463             $self->process_css_styles($el->{Attributes}{"{}style"}{Value});
1464             $self->process_path($data);
1465             }
1466             }
1467              
1468             use constant PI => atan2(1, 1) * 4.0;
1469              
1470             sub convert_from_svg
1471             {
1472             my ($x0, $y0, $rx, $ry, $phi, $large_arc, $sweep, $x, $y) = @_;
1473             my ($cx, $cy, $theta, $delta);
1474              
1475             # a plethora of temporary variables
1476             my (
1477             $dx2, $dy2, $phi_r, $x1, $y1,
1478             $rx_sq, $ry_sq,
1479             $x1_sq, $y1_sq,
1480             $sign, $sq, $coef,
1481             $cx1, $cy1, $sx2, $sy2,
1482             $p, $n,
1483             $ux, $uy, $vx, $vy
1484             );
1485              
1486             # Compute 1/2 distance between current and final point
1487             $dx2 = ($x0 - $x) / 2.0;
1488             $dy2 = ($y0 - $y) / 2.0;
1489              
1490             # Convert from degrees to radians
1491             $phi %= 360;
1492             $phi_r = $phi * PI / 180.0;
1493              
1494             # Compute (x1, y1)
1495             $x1 = cos($phi_r) * $dx2 + sin($phi_r) * $dy2;
1496             $y1 = -sin($phi_r) * $dx2 + cos($phi_r) * $dy2;
1497              
1498             # Make sure radii are large enough
1499             $rx = abs($rx); $ry = abs($ry);
1500             $rx_sq = $rx * $rx;
1501             $ry_sq = $ry * $ry;
1502             $x1_sq = $x1 * $x1;
1503             $y1_sq = $y1 * $y1;
1504              
1505             my $radius_check = ($x1_sq / $rx_sq) + ($y1_sq / $ry_sq);
1506             if ($radius_check > 1)
1507             {
1508             $rx *= sqrt($radius_check);
1509             $ry *= sqrt($radius_check);
1510             $rx_sq = $rx * $rx;
1511             $ry_sq = $ry * $ry;
1512             }
1513              
1514             # Step 2: Compute (cx1, cy1)
1515              
1516             $sign = ($large_arc == $sweep) ? -1 : 1;
1517             $sq = (($rx_sq * $ry_sq) - ($rx_sq * $y1_sq) - ($ry_sq * $x1_sq)) /
1518             (($rx_sq * $y1_sq) + ($ry_sq * $x1_sq));
1519             $sq = ($sq < 0) ? 0 : $sq;
1520             $coef = ($sign * sqrt($sq));
1521             $cx1 = round($coef * (($rx * $y1) / $ry));
1522             $cy1 = round($coef * -(($ry * $x1) / $rx));
1523              
1524             # Step 3: Compute (cx, cy) from (cx1, cy1)
1525              
1526             $sx2 = ($x0 + $x) / 2.0;
1527             $sy2 = ($y0 + $y) / 2.0;
1528              
1529             # Step 4: Compute angle start and angle extent
1530              
1531             #$ux = ($x0-$cx);
1532             #$uy = ($y0-$cy);
1533             #$vx = ($x-$cx);
1534             #$vy = ($y-$cy);
1535              
1536             #print STDERR " u: ($ux,$uy) | v: ($vx,$vy)\n";
1537              
1538             $ux = ($x1 - $cx1) / $rx;
1539             $uy = ($y1 - $cy1) / $ry;
1540             $vx = (-$x1 - $cx1) / $rx;
1541             $vy = (-$y1 - $cy1) / $ry;
1542              
1543             $n = sqrt( ($ux * $ux) + ($uy * $uy) );
1544             $p = $ux; # 1 * ux + 0 * uy
1545             $sign = ($uy > 0) ? -1 : 1;
1546              
1547             $theta = $sign * acos( $p / $n );
1548             $theta = $theta * 180 / PI;
1549              
1550             $n = sqrt(($ux * $ux + $uy * $uy) * ($vx * $vx + $vy * $vy));
1551             $p = $ux * $vx + $uy * $vy;
1552             $sign = (($ux * $vy - $uy * $vx) > 0) ? -1 : 1;
1553             $delta = $sign * acos( $p / $n );
1554             $delta = round($delta * 180 / PI);
1555             #print STDERR " delta: $delta\n";
1556              
1557             if ($large_arc == 0 && $delta >= 180) {
1558             $delta -= 360;
1559             } elsif ($large_arc == 0 && $delta < -180) {
1560             $delta += 360;
1561             } elsif ($large_arc == 1 && $delta <= 180 && $delta > 0) {
1562             $delta -= 360;
1563             } elsif ($large_arc == 1 && $delta > -180 && $delta <= 0) {
1564             $delta += 360;
1565             }
1566              
1567             #print STDERR " actually doing arc ($large_arc,$sweep): $cx1, $cy1 $rx,$ry, $theta, $delta, $phi\n";
1568              
1569             return bezier_arc_approximation($cx1, $cy1, $rx, $ry, $theta, $delta, $phi_r, $sx2, $sy2);
1570             }
1571              
1572             sub round {
1573             return int(($_[0])*100+.5)/100;
1574             }
1575              
1576             # Taken from http://www.faqts.com/knowledge_base/view.phtml/aid/4313
1577             sub bezier_arc_approximation {
1578             my ($cx, $cy, $rx, $ry, $start, $extent, $phi_r, $rcx, $rcy) = @_;
1579              
1580             # The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4) such that
1581             # the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and (x3, y3) as their
1582             # respective Bézier control points.
1583              
1584             my $nfrag = int(ceil(abs($extent)/90));
1585             my $fragAngle = $extent/$nfrag;
1586              
1587             my $halfAng = $fragAngle * PI / 360;
1588             my $kappa = 4 / 3 * (1-cos($halfAng))/sin($halfAng);
1589              
1590             my @ret;
1591              
1592             for my $i (0..($nfrag-1)) {
1593             my $theta0 = ($start + $i*$fragAngle) * PI / 180;
1594             my $theta1 = ($start + ($i+1)*$fragAngle) * PI / 180;
1595             push @ret, [
1596             rotate($rcx,$rcy,$phi_r, $cx + $rx * cos($theta0), $cy -$ry * sin($theta0)),
1597             rotate($rcx,$rcy,$phi_r, $cx + $rx * (cos($theta0) - $kappa * sin($theta0)), $cy -$ry * (sin($theta0) + $kappa * cos($theta0))),
1598             rotate($rcx,$rcy,$phi_r, $cx + $rx * (cos($theta1) + $kappa * sin($theta1)), $cy -$ry * (sin($theta1) - $kappa * cos($theta1))),
1599             rotate($rcx,$rcy,$phi_r, $cx + $rx * cos($theta1), $cy -$ry * sin($theta1)),
1600             ];
1601             }
1602              
1603             return @ret;
1604             }
1605              
1606             sub rotate {
1607             my ($rcx, $rcy, $phi_r, $x, $y) = @_;
1608             return (($rcx + (cos($phi_r) * $x - sin($phi_r) * $y)), ($rcy + (sin($phi_r) * $x + cos($phi_r) * $y)));
1609             }
1610              
1611             sub process_path {
1612             my $self = shift;
1613             my ($data) = @_;
1614             $data =~ s/^\s*//;
1615             my @parts = split(/([A-Za-z])/, $data);
1616             # warn("got: '", join("', '", @parts), "'\n");
1617             shift(@parts); # get rid of junk at start
1618             my $ytotal = $self->{pdf}->get_value('pageheight');
1619              
1620             my $relative = 0;
1621              
1622             my ($xoffset, $yoffset) = map { $self->{pdf}->get_value($_) } qw(currentx currenty);
1623             $yoffset = $ytotal-$yoffset;
1624              
1625             my ($last_reflect_x, $last_reflect_y, $need_to_close);
1626              
1627             while (@parts) {
1628             my $type = shift(@parts);
1629             my $rest = shift(@parts);
1630              
1631             if ($type eq lc($type)) {
1632             # warn("using relative coordinates\n");
1633             $relative++;
1634             }
1635              
1636             my @coords = grep { /^[\d\.\-]+$/ } split(/[^\d\.\-]+/, $rest||'');
1637             # warn("got coords: '", join("', '", @coords), "'\n");
1638              
1639             my ($x, $y);
1640              
1641             if (lc($type) eq 'm') { # moveto
1642             if (@coords % 2) {
1643             warn("moveto coords must be in pairs, skipping.\n");
1644             next;
1645             }
1646              
1647             $need_to_close = 1;
1648              
1649             ($x, $y) = splice(@coords, 0, 2);
1650             if ($relative) {
1651             $x += $xoffset;
1652             $y += $yoffset;
1653             }
1654             # warn("move_to($x, $y)\n");
1655             if ($self->{coords} eq 'svg') {
1656             $self->{pdf}->move_to($x, $ytotal-$y);
1657             }
1658             else {
1659             $self->{pdf}->move_to($x, $y);
1660             }
1661              
1662             if (@coords) {
1663             # more coords == lines
1664             unshift @parts, ($relative ? 'l' : 'L'), join(',', @coords);
1665             next;
1666             }
1667             $xoffset = $x; $yoffset = $y;
1668             }
1669             elsif (lc($type) eq 'z') { # closepath
1670             if ($self->{fill} && $self->{stroke}) {
1671             $self->{pdf}->close_path_fill_stroke;
1672             }
1673             elsif ($self->{fill}) {
1674             $self->{pdf}->close_path_fill;
1675             }
1676             elsif ($self->{stroke}) {
1677             $self->{pdf}->close_path_stroke;
1678             }
1679             }
1680             elsif (lc($type) eq 'l') { # lineto
1681             if (@coords % 2) {
1682             warn("moveto coords must be in pairs, skipping.\n");
1683             next;
1684             }
1685              
1686             $need_to_close = 1;
1687              
1688             while(@coords) {
1689             ($x, $y) = splice(@coords, 0, 2);
1690             # warn("line: $x, $y\n");
1691             if ($relative) {
1692             $x += $xoffset;
1693             $y += $yoffset;
1694             }
1695             # warn("line_to($x, $y)\n");
1696             if ($self->{coords} eq 'svg') {
1697             $self->{pdf}->line_to($x, $ytotal-$y);
1698             }
1699             else {
1700             $self->{pdf}->line_to($x, $y);
1701             }
1702             }
1703             $xoffset = $x; $yoffset = $y;
1704             }
1705             elsif (lc($type) eq 'h') { # horizontal lineto
1706             $need_to_close = 1;
1707              
1708             while (@coords) {
1709             $x = shift @coords;
1710             if ($relative) {
1711             $x += $xoffset;
1712             }
1713             if ($self->{coords} eq 'svg') {
1714             $self->{pdf}->line_to($x, $ytotal-$yoffset);
1715             }
1716             else {
1717             $self->{pdf}->line_to($x, $yoffset);
1718             }
1719             }
1720             $xoffset = $x;
1721             }
1722             elsif (lc($type) eq 'v') { # vertical lineto
1723             $need_to_close = 1;
1724              
1725             while (@coords) {
1726             $y = shift @coords;
1727             if ($relative) {
1728             $y += $yoffset;
1729             }
1730             if ($self->{coords} eq 'svg') {
1731             $self->{pdf}->line_to($xoffset, $ytotal-$y);
1732             }
1733             else {
1734             $self->{pdf}->line_to($xoffset, $y);
1735             }
1736             }
1737             $yoffset = $y;
1738             }
1739             elsif (lc($type) eq 'c') { # curveto
1740             if (@coords % 6) {
1741             warn("curveto coords must be in 6's, skipping.\n");
1742             next;
1743             }
1744            
1745             $need_to_close = 1;
1746              
1747             while (@coords) {
1748             my ($x1, $y1, $x2, $y2, $x3, $y3) = splice(@coords, 0, 6);
1749             if ($relative) {
1750             for ($x1, $x2, $x3) {
1751             $_ += $xoffset;
1752             }
1753             for ($y1, $y2, $y3) {
1754             $_ += $yoffset;
1755             }
1756             }
1757             if ($self->{coords} eq 'svg') {
1758             $self->{pdf}->bezier(
1759             x1 => $x1, y1 => $ytotal-$y1,
1760             x2 => $x2, y2 => $ytotal-$y2,
1761             x3 => $x3, y3 => $ytotal-$y3,
1762             );
1763             }
1764             else {
1765             $self->{pdf}->bezier(
1766             x1 => $x1, y1 => $y1,
1767             x2 => $x2, y2 => $y2,
1768             x3 => $x3, y3 => $y3,
1769             );
1770             }
1771             ($last_reflect_x, $last_reflect_y) = ($x2, $y2);
1772             ($x, $y) = ($x3, $y3);
1773             }
1774             $xoffset = $x; $yoffset = $y;
1775             }
1776             elsif (lc($type) eq 's') { # shorthand/smooth curveto
1777             if (@coords % 4) {
1778             warn("shorthand curveto coords must be in 4's, skipping.\n");
1779             next;
1780             }
1781            
1782             $need_to_close = 1;
1783              
1784             while (@coords) {
1785             my ($x2, $y2, $x3, $y3) = splice(@coords, 0, 4);
1786             if ($relative) {
1787             $x2 += $xoffset;
1788             $x3 += $xoffset;
1789             $y2 += $yoffset;
1790             $y3 += $yoffset;
1791             }
1792             my ($x1, $y1);
1793             if (defined($last_reflect_x)) {
1794             $x1 = $xoffset - ($last_reflect_x - $xoffset);
1795             $y1 = $yoffset - ($last_reflect_y - $yoffset);
1796             }
1797             else {
1798             $x1 = $xoffset;
1799             $y1 = $yoffset;
1800             }
1801             if ($self->{coords} eq 'svg') {
1802             $self->{pdf}->bezier(
1803             x1 => $x1, y1 => $ytotal-$y1,
1804             x2 => $x2, y2 => $ytotal-$y2,
1805             x3 => $x3, y3 => $ytotal-$y3,
1806             );
1807             }
1808             else {
1809             $self->{pdf}->bezier(
1810             x1 => $x1, y1 => $y1,
1811             x2 => $x2, y2 => $y2,
1812             x3 => $x3, y3 => $y3,
1813             );
1814             }
1815             ($last_reflect_x, $last_reflect_y) = ($x2, $y2);
1816             ($x, $y) = ($x3, $y3);
1817             }
1818             $xoffset = $x; $yoffset = $y;
1819             }
1820             elsif (lc($type) eq 'q') { # quadratic bezier curveto
1821             if (@coords % 4) {
1822             warn("quadratic curveto coords must be in 4's, skipping.\n");
1823             next;
1824             }
1825            
1826             $need_to_close = 1;
1827              
1828             while (@coords) {
1829             my ($x1, $y1, $x3, $y3) = splice(@coords, 0, 4);
1830             if ($relative) {
1831             for ($x1, $x3) {
1832             $_ += $xoffset;
1833             }
1834             for ($y1, $y3) {
1835             $_ += $yoffset;
1836             }
1837             }
1838             my ($x2, $y2) = ($x1, $y1);
1839             if ($self->{coords} eq 'svg') {
1840             $self->{pdf}->bezier(
1841             x1 => $x1, y1 => $ytotal-$y1,
1842             x2 => $x2, y2 => $ytotal-$y2,
1843             x3 => $x3, y3 => $ytotal-$y3,
1844             );
1845             }
1846             else {
1847             $self->{pdf}->bezier(
1848             x1 => $x1, y1 => $y1,
1849             x2 => $x2, y2 => $y2,
1850             x3 => $x3, y3 => $y3,
1851             );
1852             }
1853             ($last_reflect_x, $last_reflect_y) = ($x2, $y2);
1854             ($x, $y) = ($x3, $y3);
1855             }
1856             $xoffset = $x; $yoffset = $y;
1857             }
1858             elsif (lc($type) eq 't') { # shorthand/smooth quadratic bezier curveto
1859             if (@coords % 2) {
1860             warn("shorthand quadratic curveto coords must be in pairs, skipping.\n");
1861             next;
1862             }
1863            
1864             $need_to_close = 1;
1865              
1866             while (@coords) {
1867             my ($x3, $y3) = splice(@coords, 0, 2);
1868             if ($relative) {
1869             $x3 += $xoffset;
1870             $y3 += $yoffset;
1871             }
1872             my ($x1, $y1, $x2, $y2);
1873             if (defined($last_reflect_x)) {
1874             $x1 = $xoffset - ($last_reflect_x - $xoffset);
1875             $y1 = $yoffset - ($last_reflect_y - $yoffset);
1876             }
1877             else {
1878             $x1 = $xoffset;
1879             $y1 = $yoffset;
1880             }
1881             ($x2, $y2) = ($x1, $y1);
1882             if ($self->{coords} eq 'svg') {
1883             $self->{pdf}->bezier(
1884             x1 => $x1, y1 => $ytotal-$y1,
1885             x2 => $x2, y2 => $ytotal-$y2,
1886             x3 => $x3, y3 => $ytotal-$y3,
1887             );
1888             }
1889             else {
1890             $self->{pdf}->bezier(
1891             x1 => $x1, y1 => $y1,
1892             x2 => $x2, y2 => $y2,
1893             x3 => $x3, y3 => $y3,
1894             );
1895             }
1896             ($last_reflect_x, $last_reflect_y) = ($x2, $y2);
1897             ($x, $y) = ($x3, $y3);
1898             }
1899             $xoffset = $x; $yoffset = $y;
1900             }
1901             elsif (lc($type) eq 'a') { # elliptical arc
1902             if (@coords % 7) {
1903             warn("elliptical arc coords must be in 7's, skipping.\n");
1904             next;
1905             }
1906            
1907             while (@coords) {
1908             my ($rx, $ry, $rot, $large_arc_flag, $sweep_flag, $x2, $y2) =
1909             splice(@coords, 0, 7);
1910              
1911             if ($relative) {
1912             $x2 += $xoffset;
1913             $y2 += $yoffset;
1914             }
1915              
1916             # warn("arc($xoffset,$yoffset $rest)\n");
1917              
1918             #print STDERR "arc from $xoffset,$yoffset to $x2,$y2 ($large_arc_flag,$sweep_flag)\n";
1919              
1920             my @curves = convert_from_svg(
1921             $xoffset, $yoffset,
1922             $rx, $ry,
1923             $rot, int($large_arc_flag), int($sweep_flag),
1924             $x2, $y2);
1925              
1926             foreach my $curve (@curves) {
1927             #$self->{pdf}->move_to($$curve[0],$ytotal-$$curve[1]);
1928             #print STDERR " bezier: ($$curve[0],$$curve[1]) -> ($$curve[6],$$curve[7])\n";
1929             if ($self->{coords} eq 'svg') {
1930             $self->{pdf}->bezier(
1931             x1 => $$curve[2],
1932             y1 => $ytotal-$$curve[3],
1933             x2 => $$curve[4],
1934             y2 => $ytotal-$$curve[5],
1935             x3 => $$curve[6],
1936             y3 => $ytotal-$$curve[7],
1937             );
1938             }
1939             else {
1940             $self->{pdf}->bezier(
1941             x1 => $$curve[2],
1942             y1 => $ytotal-$$curve[3],
1943             x2 => $$curve[4],
1944             y2 => $ytotal-$$curve[5],
1945             x3 => $$curve[6],
1946             y3 => $ytotal-$$curve[7],
1947             );
1948             }
1949             }
1950              
1951             ($x, $y) = ($x2, $y2);
1952             }
1953             $xoffset = $x; $yoffset = $y;
1954             }
1955             else {
1956             warn("Unknown SVG path command: $type in $data");
1957             }
1958             }
1959              
1960             if ($need_to_close) {
1961             if ($self->{fill} && $self->{stroke}) {
1962             $self->{pdf}->fill_stroke;
1963             }
1964             elsif ($self->{fill}) {
1965             $self->{pdf}->fill;
1966             }
1967             elsif ($self->{stroke}) {
1968             $self->{pdf}->stroke;
1969             }
1970             }
1971             }
1972              
1973             sub slide_end_element {
1974             my ($self, $el) = @_;
1975              
1976             my $name = $el->{LocalName};
1977              
1978             #warn("slide_end_ $name ".join(",",map { $_."=>".$el->{Attributes}{$_}->{Value} } keys %{$el->{Attributes}})."\n");
1979              
1980             $el = $self->{SlideCurrent};
1981              
1982             if ($name =~ /^(point|plain|source[_-]code)$/) {
1983             # finish bounding box
1984             my ($x, $y) = $self->{bb}->get_text_pos;
1985             $self->{bb}->finish;
1986             $self->{pdf}->set_text_pos($self->{bb}->{x}, $y - 4);
1987             my $bb = delete $self->{bb};
1988             $self->{pdf}->print_line("");
1989             }
1990              
1991             if ($name eq 'title') {
1992             if ($self->{pagetype} ne 'empty') {
1993             my ($x, $y) = $self->{bb}->get_text_pos;
1994             $self->{bb}->finish;
1995             $self->{pdf}->set_text_pos($self->{bb}->{x}, $y - 4);
1996             my $bb = delete $self->{bb};
1997             $self->{pdf}->print_line("");
1998             }
1999             # create bookmarks
2000             if (!$self->{transitional}) {
2001             my $text = $self->gathered_text;
2002             $self->{values}->{'slide-title'} = $text;
2003             $self->push_bookmark(
2004             $self->{pdf}->add_bookmark(
2005             text => $self->{text_encoder}->convert($text),
2006             level => 3,
2007             parent_of => $self->top_bookmark,
2008             )
2009             );
2010             }
2011             if ($self->{pagetype} ne 'empty') {
2012             my ($x, $y) = $self->{pdf}->get_text_pos();
2013             $self->{pdf}->add_link(
2014             link => $el->{Attributes}{"{}href"}{Value},
2015             x => 20, y => $y + $self->{pdf}->get_value('leading'),
2016             w => 570, h => 24) if exists($el->{Attributes}{"{}href"});
2017             $self->{pdf}->set_text_pos(60, $y);
2018             }
2019             $self->{chars_ok} = 0;
2020             }
2021             elsif ($name eq 'slide') {
2022             $self->pop_bookmark unless $self->{transitional};
2023             }
2024             elsif ($name eq 'i' || $name eq 'b' || $name eq 'span' || $name eq 'g' || $name eq 'u') {
2025             $self->pop_font();
2026             }
2027             elsif ($name eq 'point') {
2028             $self->{chars_ok} = 0;
2029             my ($x, $y) = $self->{pdf}->get_text_pos();
2030             $self->{pdf}->add_link(
2031             link => $el->{Attributes}{"{}href"}{Value},
2032             x => 20, y => $y + $self->{pdf}->get_value('leading'),
2033             w => 570, h => 24) if exists($el->{Attributes}{"{}href"});
2034             }
2035             elsif ($name eq 'plain') {
2036             $self->{chars_ok} = 0;
2037             }
2038             elsif ($name eq 'source_code' || $name eq 'source-code') {
2039             $self->{chars_ok} = 0;
2040             $self->pop_font();
2041             }
2042             elsif ($name eq 'image') {
2043             $self->{image_id}++;
2044             }
2045             elsif ($name eq 'colour' || $name eq 'color') {
2046             $self->pop_font();
2047             }
2048             elsif ($name eq 'table') {
2049             shift @{$self->{extents}};
2050             }
2051             elsif ($name eq 'box') {
2052             shift @{$self->{extents}};
2053             $self->{pdf}->set_text_pos(@{$self->{boxlast}});
2054             if (!$self->{transitional}) {
2055             if ($self->{boxtransition}[0]) {
2056             shift @{$self->{default_transition}};
2057             }
2058             shift @{$self->{boxtransition}};
2059             }
2060             }
2061             elsif ($name eq 'row') {
2062             $self->{row_number}++;
2063             $self->{pdf}->set_text_pos($self->{row_start}[0], $self->{max_height});
2064             }
2065             elsif ($name eq 'col') {
2066             $self->{col_number}++;
2067             $self->{pdf}->print_line("");
2068             my ($x, $y) = $self->{pdf}->get_text_pos;
2069             # warn("end-col: $y < $self->{max_height} ???");
2070             $self->{max_height} = $y if $y < $self->{max_height};
2071             }
2072             elsif ($name eq 'text') {
2073             my $text = $self->gathered_text;
2074             $self->{chars_ok} = 0;
2075             $self->{pdf}->print($text);
2076             $self->{pdf}->restore_graphics_state();
2077             $self->pop_font();
2078             $self->pop_font();
2079             }
2080             elsif ($name eq 'list') {
2081             pop @{$self->{list_index}};
2082             }
2083             elsif ($name =~ /^(circle|ellipse|line|rect|path)$/) {
2084             $self->{pdf}->restore_graphics_state();
2085             $self->pop_font();
2086             }
2087              
2088             if ($name =~ m/^(table|list|image|source[-_]code)$/ && $el->{Attributes}{'{}caption'}) {
2089             $self->push_font();
2090             $self->{pdf}->set_font(face => $self->{normal_font}, italic => 1, size => 14);
2091             my ($x, $y) = $self->{pdf}->get_text_pos;
2092             my $indent = 80 * ($self->{extents}[0]{w} / $self->{extents}[-1]{w});
2093             $self->{pdf}->set_text_pos($self->{bb}?$self->{bb}->{x}:$self->{extents}[0]{x}+$indent, $y);
2094             $self->{pdf}->print_line("");
2095             $self->{pdf}->print($el->{Attributes}{'{}caption'}{Value});
2096             $self->pop_font();
2097             }
2098              
2099              
2100             $self->{SlideCurrent} = $el->{Parent};
2101             }
2102              
2103             sub slide_characters {
2104             my ($self, $chars) = @_;
2105              
2106             return unless $self->{chars_ok};
2107              
2108             $self->{gathered_text} .= $chars->{Data};
2109              
2110             my $name = $self->{SlideCurrent}->{LocalName};
2111             my $text = $chars->{Data};
2112             return unless $text && $self->{bb};
2113             my $leftover = $self->{bb}->print($self->{text_encoder}->convert($text));
2114             if (defined $leftover && $leftover =~ m/\S/) {
2115             die "Could not print: $leftover\nof: $text\n";
2116             }
2117             }
2118              
2119             1;
2120             __END__