File Coverage

blib/lib/Graphics/Primitive/Driver/Cairo.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Graphics::Primitive::Driver::Cairo;
2             $Graphics::Primitive::Driver::Cairo::VERSION = '0.47';
3 2     2   2225364 use Moose;
  2         566172  
  2         20  
4 2     2   17156 use Moose::Util::TypeConstraints;
  2         5  
  2         21  
5              
6             # ABSTRACT: Cairo backend for Graphics::Primitive
7              
8 2     2   20923 use Cairo;
  0            
  0            
9             use Carp;
10             use Geometry::Primitive::Point;
11             use Geometry::Primitive::Rectangle;
12             use Graphics::Primitive::Driver::Cairo::TextLayout;
13             use IO::File;
14             use Math::Trig ':pi';
15              
16             with 'Graphics::Primitive::Driver';
17              
18             enum 'Graphics::Primitive::Driver::Cairo::AntialiasModes' => [
19             qw(default none gray subpixel)
20             ];
21              
22             enum 'Graphics::Primitive::Driver::Cairo::Format' => [
23             qw(PDF PS PNG SVG pdf ps png svg)
24             ];
25              
26              
27             # If we encounter an operation with 'preserve' set to true we'll set this attr
28             # to the number of primitives in that path. On each iteration we'll check
29             # this attribute. If it's true, we'll skip that many primitives in the
30             # current path and then reset the value. This allows us to leverage cairo's
31             # fill_preserve and stroke_perserve and avoid wasting time redrawing.
32             has '_preserve_count' => (
33             isa => 'Str',
34             is => 'rw',
35             default => sub { 0 }
36             );
37              
38              
39             has 'antialias_mode' => (
40             is => 'rw',
41             isa => 'Graphics::Primitive::Driver::Cairo::AntialiasModes'
42             );
43              
44              
45             has 'cairo' => (
46             is => 'rw',
47             isa => 'Cairo::Context',
48             clearer => 'clear_cairo',
49             lazy => 1,
50             default => sub {
51             my $self = shift;
52             my $ctx = Cairo::Context->create($self->surface);
53              
54             if(defined($self->antialias_mode)) {
55             $ctx->set_antialias($self->antialias_mode);
56             }
57              
58             return $ctx;
59             }
60             );
61              
62              
63             has 'format' => (
64             is => 'ro',
65             isa => 'Graphics::Primitive::Driver::Cairo::Format',
66             default => sub { 'PNG' }
67             );
68              
69              
70             has 'surface' => (
71             is => 'rw',
72             clearer => 'clear_surface',
73             lazy => 1,
74             default => sub {
75             # Lazily create our surface based on the format they are required
76             # to've chosen when creating this object
77             my $self = shift;
78              
79             my $surface;
80              
81             my $width = $self->width;
82             my $height = $self->height;
83              
84             if(uc($self->format) eq 'PNG') {
85             $surface = Cairo::ImageSurface->create(
86             'argb32', $width, $height
87             );
88             } elsif(uc($self->format) eq 'PDF') {
89             croak('Your Cairo does not have PostScript support!')
90             unless Cairo::HAS_PDF_SURFACE;
91             $surface = Cairo::PdfSurface->create_for_stream(
92             sub { $self->{DATA} .= $_[1] }, $self, $width, $height
93             # $self->can('append_surface_data'), $self, $width, $height
94             );
95             } elsif(uc($self->format) eq 'PS') {
96             croak('Your Cairo does not have PostScript support!')
97             unless Cairo::HAS_PS_SURFACE;
98             $surface = Cairo::PsSurface->create_for_stream(
99             sub { $self->{DATA} .= $_[1] }, $self, $width, $height
100             # $self->can('append_surface_data'), $self, $width, $height
101             );
102             } elsif(uc($self->format) eq 'SVG') {
103             croak('Your Cairo does not have SVG support!')
104             unless Cairo::HAS_SVG_SURFACE;
105             $surface = Cairo::SvgSurface->create_for_stream(
106             sub { $self->{DATA} .= $_[1] }, $self, $width, $height
107             # $self->can('append_surface_data'), $self, $width, $height
108             );
109             } else {
110             croak("Unknown format '".$self->format."'");
111             }
112              
113             return $surface;
114             }
115             );
116              
117              
118             sub data {
119             my ($self) = @_;
120              
121             my $cr = $self->cairo;
122              
123             if(uc($self->format) eq 'PNG') {
124             my $buff;
125             $self->surface->write_to_png_stream(sub {
126             my ($closure, $data) = @_;
127             $buff .= $data;
128             });
129             return $buff;
130             }
131              
132             $cr->show_page;
133              
134             $cr = undef;
135             $self->clear_cairo;
136             $self->clear_surface;
137              
138             return $self->{DATA};
139             }
140              
141             around('draw', sub {
142             my ($cont, $class, $comp) = @_;
143              
144             my $cairo = $class->cairo;
145              
146             $cairo->save;
147              
148             $cairo->translate($comp->origin->x, $comp->origin->y);
149             $cairo->rectangle(0, 0, $comp->width, $comp->height);
150             $cairo->clip;
151              
152             $cont->($class, $comp);
153              
154             $cairo->restore;
155             });
156              
157              
158             sub write {
159             my ($self, $file) = @_;
160              
161             my $fh = IO::File->new($file, 'w')
162             or die("Unable to open '$file' for writing: $!");
163             $fh->binmode;
164             $fh->print($self->data);
165             $fh->close;
166             }
167              
168             sub _draw_component {
169             my ($self, $comp) = @_;
170              
171             my $width = $comp->width;
172             my $height = $comp->height;
173              
174             my $context = $self->cairo;
175              
176             if(defined($comp->background_color)) {
177             my ($mt, $mr, $mb, $ml) = $comp->margins->as_array;
178             $context->set_source_rgba($comp->background_color->as_array_with_alpha);
179             $context->rectangle(
180             $mr, $mt, $comp->width - $mr - $ml, $comp->height - $mt - $mb
181             );
182             $context->fill;
183             }
184              
185             if(defined($comp->border)) {
186              
187             my $border = $comp->border;
188              
189             if($border->homogeneous) {
190             # Don't bother if there's no width
191             if($border->top->width) {
192             $self->_draw_simple_border($comp);
193             }
194             } else {
195             $self->_draw_complex_border($comp);
196             }
197             }
198             }
199              
200             sub _draw_complex_border {
201             my ($self, $comp) = @_;
202              
203             my ($mt, $mr, $mb, $ml) = $comp->margins->as_array;
204              
205             my $context = $self->cairo;
206             my $border = $comp->border;
207              
208             my $width = $comp->width;
209             my $height = $comp->height;
210              
211             my $bt = $border->top;
212             my $thalf = (defined($bt) && defined($bt->color))
213             ? $bt->width / 2: 0;
214              
215             my $br = $border->right;
216             my $rhalf = (defined($br) && defined($br->color))
217             ? $br->width / 2: 0;
218              
219             my $bb = $border->bottom;
220             my $bhalf = (defined($bb) && defined($bb->color))
221             ? $bb->width / 2 : 0;
222              
223             my $bl = $border->left;
224             my $lhalf = (defined($bl) && defined($bl->color))
225             ? $bl->width / 2 : 0;
226              
227             if($thalf) {
228             $context->move_to($ml, $mt + $thalf);
229             $context->set_source_rgba($bt->color->as_array_with_alpha);
230              
231             $context->set_line_width($bt->width);
232             $context->rel_line_to($width - $mr - $ml, 0);
233              
234             my $dash = $bt->dash_pattern;
235             if(defined($dash) && scalar(@{ $dash })) {
236             $context->set_dash(0, @{ $dash });
237             }
238              
239             $context->stroke;
240              
241             $context->set_dash(0, []);
242             }
243              
244             if($rhalf) {
245             $context->move_to($width - $mr - $rhalf, $mt);
246             $context->set_source_rgba($br->color->as_array_with_alpha);
247              
248             $context->set_line_width($br->width);
249             $context->rel_line_to(0, $height - $mb);
250              
251             my $dash = $br->dash_pattern;
252             if(defined($dash) && scalar(@{ $dash })) {
253             $context->set_dash(0, @{ $dash });
254             }
255              
256             $context->stroke;
257             $context->set_dash(0, []);
258             }
259              
260             if($bhalf) {
261             $context->move_to($width - $mr, $height - $bhalf - $mb);
262             $context->set_source_rgba($bb->color->as_array_with_alpha);
263              
264             $context->set_line_width($bb->width);
265             $context->rel_line_to(-($width - $mr - $ml), 0);
266              
267             my $dash = $bb->dash_pattern;
268             if(defined($dash) && scalar(@{ $dash })) {
269             $context->set_dash(0, @{ $dash });
270             }
271              
272             $context->stroke;
273             }
274              
275             if($lhalf) {
276             $context->move_to($ml + $lhalf, $mt);
277             $context->set_source_rgba($bl->color->as_array_with_alpha);
278              
279             $context->set_line_width($bl->width);
280             $context->rel_line_to(0, $height - $mb);
281              
282             my $dash = $bl->dash_pattern;
283             if(defined($dash) && scalar(@{ $dash })) {
284             $context->set_dash(0, @{ $dash });
285             }
286              
287             $context->stroke;
288             $context->set_dash(0, []);
289             }
290             }
291              
292             sub _draw_simple_border {
293             my ($self, $comp) = @_;
294              
295             my $context = $self->cairo;
296              
297             my $border = $comp->border;
298             my $top = $border->top;
299             my $bswidth = $top->width;
300              
301             $context->set_source_rgba($top->color->as_array_with_alpha);
302              
303             my @margins = $comp->margins->as_array;
304              
305             $context->set_line_width($bswidth);
306             $context->set_line_cap($top->line_cap);
307             $context->set_line_join($top->line_join);
308              
309             $context->new_path;
310             my $swhalf = $bswidth / 2;
311             my $width = $comp->width;
312             my $height = $comp->height;
313              
314             my $dash = $top->dash_pattern;
315             if(defined($dash) && scalar(@{ $dash })) {
316             $context->set_dash(0, @{ $dash });
317             }
318              
319             $context->rectangle(
320             $margins[3] + $swhalf, $margins[0] + $swhalf,
321             $width - $bswidth - $margins[3] - $margins[1],
322             $height - $bswidth - $margins[2] - $margins[0]
323             );
324             $context->stroke;
325              
326             # Reset dashing
327             $context->set_dash(0, []);
328             }
329              
330             sub _draw_textbox {
331             my ($self, $comp) = @_;
332              
333             return unless defined($comp->text);
334              
335             $self->_draw_component($comp);
336              
337             my $bbox = $comp->inside_bounding_box;
338              
339             my $height = $bbox->height;
340             my $height2 = $height / 2;
341             my $width = $bbox->width;
342             my $width2 = $width / 2;
343              
344             my $halign = $comp->horizontal_alignment;
345             my $valign = $comp->vertical_alignment;
346              
347             my $context = $self->cairo;
348              
349             my $font = $comp->font;
350             my $fsize = $font->size;
351             $context->select_font_face(
352             $font->face, $font->slant, $font->weight
353             );
354             $context->set_font_size($fsize);
355              
356             my $options = Cairo::FontOptions->create;
357             $options->set_antialias($font->antialias_mode);
358             $options->set_subpixel_order($font->subpixel_order);
359             $options->set_hint_style($font->hint_style);
360             $options->set_hint_metrics($font->hint_metrics);
361             $context->set_font_options($options);
362              
363             my $angle = $comp->angle;
364              
365             $context->set_source_rgba($comp->color->as_array_with_alpha);
366              
367             my $lh = $comp->line_height;
368             $lh = $fsize unless(defined($lh));
369              
370             my $yaccum = $bbox->origin->y;
371              
372             foreach my $line (@{ $comp->layout->lines }) {
373             my $text = $line->{text};
374             my $tbox = $line->{box};
375              
376             my $o = $tbox->origin;
377             my $bbo = $bbox->origin;
378             my $twidth = $tbox->width;
379             my $theight = $tbox->height;
380              
381             my $x = $bbox->origin->x + $o->x;
382              
383             my $ydiff = $theight + $o->y;
384             my $xdiff = $twidth + $o->x;
385              
386             my $realh = $theight + $ydiff;
387             my $realw = $twidth + $xdiff;
388             my $theight2 = $realh / 2;
389             my $twidth2 = $twidth / 2;
390              
391             my $y = $yaccum + $theight;
392              
393             $context->save;
394              
395             if($angle) {
396             my $twidth2 = $twidth / 2;
397             my $cwidth2 = $width / 2;
398             my $cheight2 = $height / 2;
399              
400             $context->translate($cwidth2, $cheight2);
401             $context->rotate($angle);
402             $context->translate(-$cwidth2, -$cheight2);
403              
404             $context->move_to($cwidth2 - $twidth2, $cheight2 + $theight / 3.5);
405             $context->show_text($text);
406              
407             } else {
408             if($halign eq 'right') {
409             $x += $width - $twidth;
410             } elsif($halign eq 'center') {
411             $x += $width2 - $twidth2;
412             }
413              
414             if($valign eq 'bottom') {
415             $y = $height - $ydiff;
416             } elsif($valign eq 'center') {
417             $y += $height2 - $theight2;
418             } else {
419             $y -= $ydiff;
420             }
421              
422             $context->move_to($x, $y);
423             $context->show_text($text);
424             }
425              
426             $context->restore;
427             $yaccum += $lh;
428             }
429              
430             }
431              
432             sub _draw_arc {
433             my ($self, $arc) = @_;
434              
435             my $context = $self->cairo;
436             my $o = $arc->origin;
437             if($arc->angle_start > $arc->angle_end) {
438             $context->arc_negative(
439             $o->x, $o->y, $arc->radius, $arc->angle_start, $arc->angle_end
440             );
441             } else {
442             $context->arc(
443             $o->x, $o->y, $arc->radius, $arc->angle_start, $arc->angle_end
444             );
445             }
446             }
447              
448             sub _draw_bezier {
449             my ($self, $bezier) = @_;
450              
451             my $context = $self->cairo;
452             my $start = $bezier->start;
453             my $end = $bezier->end;
454             my $c1 = $bezier->control1;
455             my $c2 = $bezier->control2;
456              
457             $context->curve_to($c1->x, $c1->y, $c2->x, $c2->y, $end->x, $end->y);
458             }
459              
460             sub _draw_canvas {
461             my ($self, $comp) = @_;
462              
463             $self->_draw_component($comp);
464              
465             foreach (@{ $comp->paths }) {
466              
467             $self->_draw_path($_->{path}, $_->{op});
468             }
469             }
470              
471             sub _draw_circle {
472             my ($self, $circle) = @_;
473              
474             my $context = $self->cairo;
475             my $o = $circle->origin;
476             $context->new_sub_path;
477             $context->arc(
478             $o->x, $o->y, $circle->radius, 0, pi2
479             );
480             }
481              
482             sub _draw_ellipse {
483             my ($self, $ell) = @_;
484              
485             my $cairo = $self->cairo;
486             my $o = $ell->origin;
487              
488             $cairo->new_sub_path;
489             $cairo->save;
490             $cairo->translate($o->x, $o->y);
491             $cairo->scale($ell->width / 2, $ell->height / 2);
492             $cairo->arc(
493             $o->x, $o->y, 1, 0, pi2
494             );
495             $cairo->restore;
496             }
497              
498             sub _draw_image {
499             my ($self, $comp) = @_;
500              
501             $self->_draw_component($comp);
502              
503             my $cairo = $self->cairo;
504              
505             $cairo->save;
506              
507             my $imgs = Cairo::ImageSurface->create_from_png($comp->image);
508              
509             my $bb = $comp->inside_bounding_box;
510              
511             my $bumpx = 0;
512             my $bumpy = 0;
513             if($comp->horizontal_alignment eq 'center') {
514             $bumpx = $bb->width / 2;
515             if(defined($comp->scale)) {
516             $bumpx -= $comp->scale->[0] * ($imgs->get_width / 2);
517             } else {
518             $bumpx -= $imgs->get_width / 2;
519             }
520             } elsif($comp->horizontal_alignment eq 'right') {
521             $bumpx = $bb->width;
522             if(defined($comp->scale)) {
523             $bumpx -= $comp->scale->[0] * $imgs->get_width;
524             } else {
525             $bumpx -= $imgs->get_width;
526             }
527             }
528              
529             if($comp->vertical_alignment eq 'center') {
530             $bumpy = $bb->height / 2;
531             if(defined($comp->scale)) {
532             $bumpy -= $comp->scale->[1] * ($imgs->get_height / 2);
533             } else {
534             $bumpy -= $imgs->get_height / 2;
535             }
536             } elsif($comp->vertical_alignment eq 'bottom') {
537             $bumpy = $bb->height;
538             if(defined($comp->scale)) {
539             $bumpy -= $comp->scale->[1] * $imgs->get_height;
540             } else {
541             $bumpy -= $imgs->get_height;
542             }
543             }
544              
545             $cairo->translate($bb->origin->x + $bumpx, $bb->origin->y + $bumpy);
546             $cairo->rectangle(0, 0, $imgs->get_width, $imgs->get_height);
547             $cairo->clip;
548              
549             if(defined($comp->scale)) {
550             $cairo->scale($comp->scale->[0], $comp->scale->[1]);
551             }
552              
553             $cairo->rectangle(
554             0, 0, $imgs->get_width, $imgs->get_height
555             );
556              
557             $cairo->set_source_surface($imgs, 0, 0);
558              
559             $cairo->fill;
560              
561             $cairo->restore;
562             }
563              
564             sub _draw_path {
565             my ($self, $path, $op) = @_;
566              
567             my $context = $self->cairo;
568              
569             # If preserve count is set we've "preserved" a path that's made up
570             # of X primitives. Set the sentinel to the the count so we skip that
571             # many primitives
572             my $pc = $self->_preserve_count;
573             if($pc) {
574             $self->_preserve_count(0);
575             } else {
576             $context->new_path;
577             }
578              
579             my $pcount = $path->primitive_count;
580             for(my $i = $pc; $i < $pcount; $i++) {
581             my $prim = $path->get_primitive($i);
582             my $hints = $path->get_hint($i);
583              
584             if(defined($hints)) {
585             unless($hints->{contiguous}) {
586             my $ps = $prim->point_start;
587             $context->move_to(
588             $ps->x, $ps->y
589             );
590             }
591             }
592              
593             # FIXME Check::ISA
594             if($prim->isa('Geometry::Primitive::Line')) {
595             $self->_draw_line($prim);
596             } elsif($prim->isa('Geometry::Primitive::Rectangle')) {
597             $self->_draw_rectangle($prim);
598             } elsif($prim->isa('Geometry::Primitive::Arc')) {
599             $self->_draw_arc($prim);
600             } elsif($prim->isa('Geometry::Primitive::Bezier')) {
601             $self->_draw_bezier($prim);
602             } elsif($prim->isa('Geometry::Primitive::Circle')) {
603             $self->_draw_circle($prim);
604             } elsif($prim->isa('Geometry::Primitive::Ellipse')) {
605             $self->_draw_ellipse($prim);
606             } elsif($prim->isa('Geometry::Primitive::Polygon')) {
607             $self->_draw_polygon($prim);
608             }
609             }
610              
611             if($op->isa('Graphics::Primitive::Operation::Stroke')) {
612             $self->_do_stroke($op);
613             } elsif($op->isa('Graphics::Primitive::Operation::Fill')) {
614             $self->_do_fill($op);
615             }
616              
617             if($op->preserve) {
618             $self->_preserve_count($path->primitive_count);
619             }
620             }
621              
622             sub _draw_line {
623             my ($self, $line) = @_;
624              
625             my $context = $self->cairo;
626             my $end = $line->end;
627             $context->line_to($end->x, $end->y);
628             }
629              
630             sub _draw_polygon {
631             my ($self, $poly) = @_;
632              
633             my $context = $self->cairo;
634             for(my $i = 1; $i < $poly->point_count; $i++) {
635             my $p = $poly->get_point($i);
636             $context->line_to($p->x, $p->y);
637             }
638             $context->close_path;
639             }
640              
641             sub _draw_rectangle {
642             my ($self, $rect) = @_;
643              
644             my $context = $self->cairo;
645             $context->rectangle(
646             $rect->origin->x, $rect->origin->y,
647             $rect->width, $rect->height
648             );
649             }
650              
651             sub _do_fill {
652             my ($self, $fill) = @_;
653              
654             my $context = $self->cairo;
655             my $paint = $fill->paint;
656              
657             # FIXME Check::ISA?
658             if($paint->isa('Graphics::Primitive::Paint::Gradient')) {
659              
660             my $patt;
661             if($paint->isa('Graphics::Primitive::Paint::Gradient::Linear')) {
662             $patt = Cairo::LinearGradient->create(
663             $paint->line->start->x, $paint->line->start->y,
664             $paint->line->end->x, $paint->line->end->y,
665             );
666             } elsif($paint->isa('Graphics::Primitive::Paint::Gradient::Radial')) {
667             $patt = Cairo::RadialGradient->create(
668             $paint->start->origin->x, $paint->start->origin->y,
669             $paint->start->radius,
670             $paint->end->origin->x, $paint->end->origin->y,
671             $paint->end->radius
672             );
673             } else {
674             croak('Unknown gradient type: '.ref($paint));
675             }
676              
677             foreach my $stop ($paint->stops) {
678             my $color = $paint->get_stop($stop);
679             $patt->add_color_stop_rgba(
680             $stop, $color->red, $color->green,
681             $color->blue, $color->alpha
682             );
683             }
684             $context->set_source($patt);
685              
686             } elsif($paint->isa('Graphics::Primitive::Paint::Solid')) {
687             $context->set_source_rgba($paint->color->as_array_with_alpha);
688             }
689              
690             if($fill->preserve) {
691             $context->fill_preserve;
692             } else {
693             $context->fill;
694             }
695             }
696              
697             sub _do_stroke {
698             my ($self, $stroke) = @_;
699              
700             my $br = $stroke->brush;
701              
702             my $context = $self->cairo;
703             $context->set_source_rgba($br->color->as_array_with_alpha);
704             $context->set_line_cap($br->line_cap);
705             $context->set_line_join($br->line_join);
706             $context->set_line_width($br->width);
707              
708             my $dash = $br->dash_pattern;
709             if(defined($dash) && scalar(@{ $dash })) {
710             $context->set_dash(0, @{ $dash });
711             }
712              
713             if($stroke->preserve) {
714             $context->stroke_preserve;
715             } else {
716             $context->stroke;
717             }
718              
719             # Reset dashing
720             $context->set_dash(0, []);
721             }
722              
723             sub _finish_page {
724             my ($self) = @_;
725              
726             my $context = $self->cairo;
727             $context->show_page;
728             }
729              
730             sub _resize {
731             my ($self, $width, $height) = @_;
732              
733             # Don't resize unless we have to
734             if(($self->width != $width) || ($self->height != $height)) {
735             $self->surface->set_size($width, $height);
736             }
737             }
738              
739              
740             sub get_text_bounding_box {
741             my ($self, $tb, $text) = @_;
742              
743             my $context = $self->cairo;
744              
745             my $font = $tb->font;
746              
747             unless(defined($text)) {
748             $text = $tb->text;
749             }
750              
751             $context->new_path;
752              
753             my $fsize = $font->size;
754              
755             my $options = Cairo::FontOptions->create;
756             $options->set_antialias($font->antialias_mode);
757             $options->set_subpixel_order($font->subpixel_order);
758             $options->set_hint_style($font->hint_style);
759             $options->set_hint_metrics($font->hint_metrics);
760             $context->set_font_options($options);
761              
762             # my $key = "$text||".$font->face.'||'.$font->slant.'||'.$font->weight.'||'.$fsize;
763              
764             # If our text + font key is found, return the box we already made.
765             # if(exists($self->{TBCACHE}->{$key})) {
766             # return ($self->{TBCACHE}->{$key}->[0], $self->{TBCACHE}->{$key}->[1]);
767             # }
768              
769             # my @exts;
770             my $exts;
771             if($text eq '') {
772             # Catch empty lines. There's no sense trying to get it's height. We
773             # just set it to the height of the font and move on.
774             # @exts = (0, -$font->size, 0, 0);
775             $exts->{y_bearing} = 0;
776             $exts->{x_bearing} = 0;
777             $exts->{x_advance} = 0;
778             $exts->{width} = 0;
779             $exts->{height} = $fsize;
780             } else {
781             $context->select_font_face(
782             $font->face, $font->slant, $font->weight
783             );
784             $context->set_font_size($fsize);
785             $exts = $context->text_extents($text);
786             }
787              
788             my $tbr = Geometry::Primitive::Rectangle->new(
789             origin => Geometry::Primitive::Point->new(
790             x => $exts->{x_bearing},#$exts[0],
791             y => $exts->{y_bearing},#$exts[1],
792             ),
793             width => $exts->{width} + $exts->{x_bearing} + 1,#abs($exts[2]) + abs($exts[0]),
794             height => $exts->{height},#$tbsize
795             );
796              
797             my $cb = $tbr;
798             if($tb->angle) {
799              
800             $context->save;
801              
802             my $tw2 = $tb->width / 2;
803             my $th2 = $tb->height / 2;
804              
805             $context->translate($tw2, $th2);
806             $context->rotate($tb->angle);
807             $context->translate(-$tw2, -$th2);
808              
809             my ($rw, $rh) = $self->_get_bounding_box($context, $exts);
810              
811             $cb = Geometry::Primitive::Rectangle->new(
812             origin => $tbr->origin,
813             width => $rw,
814             height => $rh
815             );
816              
817             $context->restore;
818             }
819              
820             # $self->{TBCACHE}->{$key} = [ $cb, $tbr ];
821              
822             return ($cb, $tbr);
823             }
824              
825              
826             sub get_textbox_layout {
827             my ($self, $comp) = @_;
828              
829             my $tl = Graphics::Primitive::Driver::Cairo::TextLayout->new(
830             component => $comp
831             );
832             $tl->layout($self);
833             return $tl;
834             }
835              
836              
837             sub reset {
838             my ($self) = @_;
839              
840             $self->clear_cairo;
841             }
842              
843             sub _get_bounding_box {
844             my ($self, $context, $exts) = @_;
845              
846             my $lw = $exts->{width} + abs($exts->{x_bearing});
847             my $lh = $exts->{height} + abs($exts->{y_bearing});
848              
849             my $matrix = $context->get_matrix;
850             my @corners = ([0,0], [$lw,0], [$lw,$lh], [0,$lh]);
851              
852             # Transform each of the four corners, the find the maximum X and Y
853             # coordinates to create a bounding box
854              
855             my @points;
856             foreach my $pt (@corners) {
857             my ($x, $y) = $matrix->transform_point($pt->[0], $pt->[1]);
858             push(@points, [ $x, $y ]);
859             }
860              
861             my $maxX = $points[0]->[0];
862             my $maxY = $points[0]->[1];
863             my $minX = $points[0]->[0];
864             my $minY = $points[0]->[1];
865              
866             foreach my $pt (@points) {
867              
868             if($pt->[0] > $maxX) {
869             $maxX = $pt->[0];
870             } elsif($pt->[0] < $minX) {
871             $minX = $pt->[0];
872             }
873              
874             if($pt->[1] > $maxY) {
875             $maxY = $pt->[1];
876             } elsif($pt->[1] < $minY) {
877             $minY = $pt->[1];
878             }
879             }
880              
881             my $bw = $maxX - $minX;
882             my $bh = $maxY - $minY;
883              
884             return ($bw, $bh);
885             }
886              
887              
888             no Moose;
889             1;
890              
891             __END__
892              
893             =pod
894              
895             =head1 NAME
896              
897             Graphics::Primitive::Driver::Cairo - Cairo backend for Graphics::Primitive
898              
899             =head1 VERSION
900              
901             version 0.47
902              
903             =head1 SYNOPSIS
904              
905             use Graphics::Primitive::Component;
906             use Graphics::Primitive::Driver::Cairo;
907              
908             my $driver = Graphics::Primitive::Driver::Cairo->new;
909             my $container = Graphics::Primitive::Container->new(
910             width => 800,
911             height => 600
912             );
913             my $black = Graphics::Primitive::Color->new(red => 0, green => 0, blue => 0);
914             $container->border->width(1);
915             $container->border->color($black);
916             $container->padding(
917             Graphics::Primitive::Insets->new(top => 5, bottom => 5, left => 5, right => 5)
918             );
919             my $comp = Graphics::Primitive::Component->new;
920             $comp->background_color($black);
921             $container->add_component($comp, 'c');
922              
923             my $lm = Layout::Manager::Compass->new;
924             $lm->do_layout($container);
925              
926             my $driver = Graphics::Primitive::Driver::Cairo->new(
927             format => 'PDF'
928             );
929             $driver->draw($container);
930             $driver->write('/Users/gphat/foo.pdf');
931              
932             =head1 DESCRIPTION
933              
934             This module draws Graphics::Primitive objects using Cairo.
935              
936             =head1 IMPLEMENTATION DETAILS
937              
938             =over 4
939              
940             =item B<Borders>
941              
942             Borders are drawn clockwise starting with the top one. Since cairo can't do
943             line-joins on different colored lines, each border overlaps those before it.
944             This is not the way I'd like it to work, but i'm opting to fix this later.
945             Consider yourself warned.
946              
947             =back
948              
949             =head1 ATTRIBUTES
950              
951             =head2 antialias_mode
952              
953             Set/Get the antialias mode of this driver. Options are default, none, gray and
954             subpixel.
955              
956             =head2 cairo
957              
958             This driver's Cairo::Context object
959              
960             =head2 format
961              
962             Get the format for this driver.
963              
964             =head2 surface
965              
966             Get/Set the surface on which this driver is operating.
967              
968             =head1 METHODS
969              
970             =head2 data
971              
972             Get the data in a scalar for this driver.
973              
974             =head2 write ($file)
975              
976             Write this driver's data to the specified file.
977              
978             =head2 get_text_bounding_box ($font, $text, $angle)
979              
980             Returns two L<Rectangles|Graphics::Primitive::Rectangle> that encloses the
981             supplied text. The origin's x and y maybe negative, meaning that the glyphs in
982             the text extending left of x or above y.
983              
984             The first rectangle is the bounding box required for a container that wants to
985             contain the text. The second box is only useful if an optional angle is
986             provided. This second rectangle is the bounding box of the un-rotated text
987             that allows for a controlled rotation. If no angle is supplied then the
988             two rectangles are actually the same object.
989              
990             If the optional angle is supplied the text will be rotated by the supplied
991             amount in radians.
992              
993             =head2 get_textbox_layout ($tb)
994              
995             Returns a L<Graphics::Primitive::Driver::TextLayout> for the supplied
996             textbox.
997              
998             =head2 reset
999              
1000             Reset the driver.
1001              
1002             =head2 draw
1003              
1004             Draws the specified component. Container's components are drawn recursively.
1005              
1006             =head1 ACKNOWLEDGEMENTS
1007              
1008             Danny Luna
1009              
1010             =head1 AUTHOR
1011              
1012             Cory G Watson <gphat@cpan.org>
1013              
1014             =head1 COPYRIGHT AND LICENSE
1015              
1016             This software is copyright (c) 2016 by Cold Hard Code, LLC.
1017              
1018             This is free software; you can redistribute it and/or modify it under
1019             the same terms as the Perl 5 programming language system itself.
1020              
1021             =cut