File Coverage

blib/lib/Graphics/Primitive/Driver/CairoPango.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Graphics::Primitive::Driver::CairoPango;
2 1     1   24775 use Moose;
  0            
  0            
3             use Moose::Util::TypeConstraints;
4              
5             use Cairo;
6             use Carp;
7             use Geometry::Primitive::Point;
8             use Geometry::Primitive::Rectangle;
9             use Graphics::Primitive::Driver::CairoPango::TextLayout;
10             use Pango;
11             use IO::File;
12             use Math::Trig ':pi';
13              
14             extends 'Graphics::Primitive::Driver::Cairo';
15              
16             # with 'Graphics::Primitive::Driver';
17              
18             our $AUTHORITY = 'cpan:GPHAT';
19             our $VERSION = '0.63';
20              
21             enum 'Graphics::Primitive::Driver::CairoPango::AntialiasModes' => [
22             qw(default none gray subpixel)
23             ];
24              
25             enum 'Graphics::Primitive::Driver::CairoPango::Format' => [
26             qw(PDF PS PNG SVG pdf ps png svg)
27             ];
28              
29             sub _draw_textbox {
30             my ($self, $comp) = @_;
31              
32             return unless(defined($comp->text));
33              
34             my $context = $self->cairo;
35              
36             $context->save;
37             $self->_draw_component($comp);
38             $context->restore;
39              
40             my $bbox = $comp->inside_bounding_box;
41             my $width = $bbox->width;
42              
43             $context->set_source_rgba($comp->color->as_array_with_alpha);
44              
45             my $origin = $bbox->origin;
46             my $x = $origin->x;
47             my $y = $origin->y;
48              
49             if(defined($comp->lines)) {
50             my $start = $comp->lines->{start};
51             my $count = $comp->lines->{count};
52             my $endline = $start + $count;
53              
54             my $layout = $comp->layout->_layout;
55             my $iter = $layout->get_iter;
56              
57             my $startpos = 0;
58              
59             my $line_index = 0;
60             while($line_index < $endline) {
61             if($line_index < $start) {
62             $iter->next_line;
63             $line_index++;
64             next;
65             }
66              
67             my $line = $iter->get_line_readonly;
68             my ($ink, $log) = $iter->get_line_extents;
69             my $baseline = $iter->get_baseline;
70              
71             if($start == $line_index) {
72             $startpos = $log->{y} / 1024;
73             }
74              
75             $context->move_to($x + ($log->{x} / 1024), $baseline / 1024 - $startpos + $y);
76              
77             Pango::Cairo::show_layout_line($context, $line);
78             $line_index++;
79             $iter->next_line;
80             };
81             } else {
82             my $layout = $comp->layout->_layout;
83              
84             my $angle = $comp->angle;
85             if($angle) {
86             my $tw2 = $comp->width / 2;
87             my $th2 = $comp->height / 2;
88              
89             $context->translate($tw2, $th2);
90             $context->rotate($angle);
91             $context->translate(-$tw2, -$th2);
92              
93             # Get the un-rotated extents so we can position it
94             my ($ink, $log) = $layout->get_pixel_extents;
95             $context->move_to(
96             $tw2 - $log->{width} / 2,
97             $th2 - $log->{height} / 2
98             );
99             } else {
100             my ($ink, $log) = $layout->get_pixel_extents;
101             if($comp->vertical_alignment eq 'bottom') {
102             $y = $bbox->height - $log->{height} / 2;
103             } elsif($comp->vertical_alignment eq 'center') {
104             $y = $bbox->height / 2 - $log->{height} / 2;
105             }
106             $context->move_to($x, $y);
107             }
108             Pango::Cairo::update_layout($context, $layout);
109             Pango::Cairo::show_layout($context, $layout);
110             }
111             }
112              
113             sub get_textbox_layout {
114             my ($self, $comp) = @_;
115              
116             my $tl = Graphics::Primitive::Driver::CairoPango::TextLayout->new(
117             component => $comp,
118             );
119            
120             unless(defined($comp->text)) {
121             $tl->height(0);
122             return $tl;
123             }
124              
125             my $context = $self->cairo;
126              
127             my $font = $comp->font;
128              
129             my $fontmap = Pango::Cairo::FontMap->get_default;
130              
131             my $desc = Pango::FontDescription->new;
132             $desc->set_family($font->family);
133             $desc->set_variant($font->variant);
134             $desc->set_style($font->slant);
135             $desc->set_weight($font->weight);
136             $desc->set_size(Pango::units_from_double($font->size));
137              
138             my $layout = Pango::Cairo::create_layout($context);
139              
140             $layout->set_font_description($desc);
141             $layout->set_markup($comp->text);
142             $layout->set_indent(Pango::units_from_double($comp->indent));
143             $layout->set_alignment($comp->horizontal_alignment);
144             $layout->set_justify($comp->justify);
145             if(defined($comp->wrap_mode)) {
146             $layout->set_wrap($comp->wrap_mode);
147             }
148             if(defined($comp->ellipsize_mode)) {
149             $layout->set_ellipsize($comp->ellipsize_mode);
150             }
151              
152             if(defined($comp->line_height)) {
153             $layout->set_spacing(Pango::units_from_double($comp->line_height - $comp->font->size));
154             }
155              
156             my $pcontext = $layout->get_context;
157              
158             $fontmap->set_resolution(72);
159             my $options = Cairo::FontOptions->create;
160             $options->set_antialias($font->antialias_mode);
161             $options->set_subpixel_order($font->subpixel_order);
162             $options->set_hint_style($font->hint_style);
163             $options->set_hint_metrics($font->hint_metrics);
164             Pango::Cairo::Context::set_font_options($pcontext, $options);
165              
166             if(defined($comp->direction)) {
167             $pcontext->set_base_dir($comp->direction);
168             }
169              
170             my $width = $comp->width ? $comp->inside_width : $comp->minimum_inside_width;
171             $width = -1 if(!defined($width) || ($width == 0));
172             $layout->set_width(Pango::units_from_double($width));
173              
174             if($comp->height) {
175             # $layout->set_height(Pango::units_from_double($comp->height));
176             }
177              
178             if($comp->angle) {
179             $context->save;
180              
181             my $tw2 = $comp->width / 2;
182             my $th2 = $comp->height / 2;
183              
184             $context->translate($tw2, $th2);
185             $context->rotate($comp->angle);
186             $context->translate(-$tw2, -$th2);
187              
188             Pango::Cairo::update_context($context, $pcontext);
189             Pango::Cairo::update_layout($context, $layout);
190              
191             my ($rw, $rh) = $self->_get_bounding_box($context, $layout);
192              
193             $tl->width($rw);
194             $tl->height($rh);
195              
196             $context->restore;
197             } else {
198              
199             Pango::Cairo::update_context($context, $pcontext);
200             Pango::Cairo::update_layout($context, $layout);
201              
202             my ($ink, $log) = $layout->get_pixel_extents;
203              
204             $tl->width($log->{width});
205             $tl->height($log->{height});
206             }
207              
208             $tl->_layout($layout);
209              
210             return $tl;
211             }
212              
213             sub _get_bounding_box {
214             my ($self, $context, $layout) = @_;
215              
216             my ($lw, $lh) = Pango::Layout::get_size($layout);
217              
218             my $matrix = $context->get_matrix;
219             my @corners = ([0,0], [$lw/1024,0], [$lw/1024,$lh/1024], [0,$lh/1024]);
220              
221             # Transform each of the four corners, the find the maximum X and Y
222             # coordinates to create a bounding box
223              
224             my @points;
225             foreach my $pt (@corners) {
226             my ($x, $y) = $matrix->transform_point($pt->[0], $pt->[1]);
227             push(@points, [ $x, $y ]);
228             }
229              
230             my $maxX = $points[0]->[0];
231             my $maxY = $points[0]->[1];
232             my $minX = $points[0]->[0];
233             my $minY = $points[0]->[1];
234              
235             foreach my $pt (@points) {
236              
237             if($pt->[0] > $maxX) {
238             $maxX = $pt->[0];
239             } elsif($pt->[0] < $minX) {
240             $minX = $pt->[0];
241             }
242              
243             if($pt->[1] > $maxY) {
244             $maxY = $pt->[1];
245             } elsif($pt->[1] < $minY) {
246             $minY = $pt->[1];
247             }
248             }
249              
250             my $bw = $maxX - $minX;
251             my $bh = $maxY - $minY;
252              
253             return ($bw, $bh);
254             }
255              
256             no Moose;
257             1;
258             __END__
259              
260             =head1 NAME
261              
262             Graphics::Primitive::Driver::CairoPango - Cairo/Pango backend for Graphics::Primitive
263              
264             =head1 SYNOPSIS
265              
266             use Graphics::Pritive::Component;
267             use Graphics::Pritive::Component;
268             use Graphics::Primitive::Driver::CairoPango;
269              
270             my $driver = Graphics::Primitive::Driver::CairoPango->new;
271             my $container = Graphics::Primitive::Container->new(
272             width => $form->sheet_width,
273             height => $form->sheet_height
274             );
275             $container->border->width(1);
276             $container->border->color($black);
277             $container->padding(
278             Graphics::Primitive::Insets->new(top => 5, bottom => 5, left => 5, right => 5)
279             );
280             my $comp = Graphics::Primitive::Component->new;
281             $comp->background_color($black);
282             $container->add_component($comp, 'c');
283              
284             my $lm = Layout::Manager::Compass->new;
285             $lm->do_layout($container);
286              
287             my $driver = Graphics::Primitive::Driver::CairoPango->new(
288             format => 'PDF'
289             );
290             $driver->draw($container);
291             $driver->write('/Users/gphat/foo.pdf');
292              
293             =head1 DESCRIPTION
294              
295             This module draws Graphics::Primitive objects using Cairo and Pango. This
296             is a separate distribution due to the Pango requirement. The Pango specific
297             bits will be rolled into the normal Cairo driver at some point.
298              
299             =head1 IMPLEMENTATION DETAILS
300              
301             =over 4
302              
303             =item B<Borders>
304              
305             Borders are drawn clockwise starting with the top one. Since cairo can't do
306             line-joins on different colored lines, each border overlaps those before it.
307             This is not the way I'd like it to work, but i'm opting to fix this later.
308             Consider yourself warned.
309              
310             =back
311              
312             =head1 METHODS
313              
314             =head2 Constructor
315              
316             =over 4
317              
318             =item I<new>
319              
320             Creates a new Graphics::Primitive::Driver::CairoPango object. Requires a format.
321              
322             my $driver = Graphics::Primitive::Driver::CairoPango->new(format => 'PDF');
323              
324             =back
325              
326             =head2 Instance Methods
327              
328             =over 4
329              
330             =item I<antialias_mode>
331              
332             Set/Get the antialias mode of this driver. Options are default, none, gray and
333             subpixel.
334              
335             =item I<cairo>
336              
337             This driver's Cairo::Context object
338              
339             =item I<data>
340              
341             Get the data in a scalar for this driver.
342              
343             =item I<draw>
344              
345             Draws the specified component. Container's components are drawn recursively.
346              
347             =item I<format>
348              
349             Get the format for this driver.
350              
351             =item I<get_textbox_layout ($font, $textbox)>
352              
353             Returns this driver's implementation of a
354             L<TextLayout|Graphics::Primitive::Driver::TextLayout>.
355              
356             =item I<reset>
357              
358             Reset the driver.
359              
360             =item I<surface>
361              
362             Get/Set the surface on which this driver is operating.
363              
364             =item I<write>
365              
366             Write this driver's data to the specified file.
367              
368             =back
369              
370             =head1 AUTHOR
371              
372             Cory Watson, C<< <gphat@cpan.org> >>
373              
374             Infinity Interactive, L<http://www.iinteractive.com>
375              
376             =head1 BUGS
377              
378             Please report any bugs or feature requests to C<bug-geometry-primitive at rt.cpan.org>, or through
379             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Geometry-Primitive>. I will be notified, and then you'll
380             automatically be notified of progress on your bug as I make changes.
381              
382             =head1 COPYRIGHT & LICENSE
383              
384             Copyright 2008 by Infinity Interactive, Inc.
385              
386             L<http://www.iinteractive.com>
387              
388             This program is free software; you can redistribute it and/or modify it
389             under the same terms as Perl itself.