File Coverage

blib/lib/Document/Writer.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 Document::Writer;
2 2     2   67055 use Moose;
  0            
  0            
3             use MooseX::AttributeHelpers;
4              
5             use Carp;
6             use Forest;
7             use Paper::Specs units => 'pt';
8              
9             use Document::Writer::Page;
10              
11             our $AUTHORITY = 'cpan:GPHAT';
12             our $VERSION = '0.13';
13              
14             has 'components' => (
15             metaclass => 'Collection::Array',
16             is => 'rw',
17             isa => 'ArrayRef[Graphics::Primitive::Component]',
18             default => sub { [] },
19             provides => {
20             'clear'=> 'clear_components',
21             'count'=> 'component_count',
22             'get' => 'get_component',
23             'push' => 'add_component',
24             'first'=> 'first_component',
25             'last' => 'last_component'
26             }
27             );
28             has 'last_page' => (
29             is => 'rw',
30             isa => 'Document::Writer::Page',
31             );
32              
33             sub draw {
34             my ($self, $driver) = @_;
35              
36             my @pages;
37             foreach my $c (@{ $self->components }) {
38              
39             next unless(defined($c));
40              
41             $driver->prepare($c);
42              
43             if($c->isa('Document::Writer::Page')) {
44             $c->layout_manager->do_layout($c);
45             push(@pages, $c);
46             } elsif($c->isa('Document::Writer::TextArea')) {
47             my $page = $self->last_page;
48              
49             my $avail = $page->body->inside_height - $page->body->layout_manager->used->[1];
50             $c->width($page->body->inside_width);
51              
52             my $tl = $driver->get_textbox_layout($c);
53              
54             my $tlh = $tl->height;
55             my $used = 0;
56              
57             # This is around to keep control of runaway page adding. We
58             # should never end up with an empty page.
59             my $newpage = 0;
60             # So. We need to 'use' all of the TextLayout we got. The height
61             # is $tlh and we have $avail space available on the page.
62             while($used < $tlh) {
63             # We've not yet used all of the TextLayout...
64             if($avail <= 0) {
65              
66             # Stop runaway page adding.
67             if($newpage >= 1) {
68             last;
69             }
70              
71             # But we ran out of available space. So we need to add a
72             # new page. We do this at the top so that we don't add
73             # a new page on the last iteration and then never use it!
74             $page = $self->add_page_break($driver);
75             $avail = $page->body->inside_height - $page->body->layout_manager->used->[1];
76             $newpage += 1;
77             } else {
78             $newpage = 0;
79             }
80              
81             # Ask the TL to slice off a chunk. Ask it for however much
82             # space we have available ($avail). If the TL doesn't have
83             # that much, it will give us all it has. If it has more, we'll
84             # re-loop.
85             my $new_ta = $tl->slice($used, $avail);
86              
87             if(defined($new_ta)) {
88             $used += $new_ta->height;
89             # Add whatever we got to the page body.
90             $page->body->add_component($new_ta, 'n');
91             # Relayout the page.
92             $page->layout_manager->do_layout($page);
93             # Get the new avail
94             $avail = $page->body->inside_height - $page->body->layout_manager->used->[1];
95             } else {
96             $avail = 0;
97             }
98             }
99              
100             } elsif($c->isa('Graphics::Primitive::Container')) {
101              
102             my $page = $self->last_page;
103             my $avail = $page->body->inside_height - $page->body->layout_manager->used->[1];
104              
105             $driver->prepare($c);
106             if($avail < $c->minimum_height) {
107             $page = $self->add_page_break($driver);
108             $avail = $page->body->inside_height;
109             }
110             $page->body->add_component($c, 'n');
111             $page->layout_manager->do_layout($page);
112             }
113             }
114              
115             foreach my $p (@pages) {
116             # Prepare all the pages...
117             $driver->prepare($p);
118             # Layout each page...
119              
120             if($p->layout_manager) {
121             $p->layout_manager->do_layout($p);
122             $p->body->layout_manager->do_layout($p->body);
123             }
124             $driver->finalize($p);
125             $driver->reset;
126             $driver->draw($p);
127             }
128              
129             return \@pages;
130             }
131              
132             sub find {
133             my ($self, $predicate) = @_;
134              
135             my $newlist = Graphics::Primitive::ComponentList->new;
136             foreach my $c (@{ $self->components }) {
137              
138             return unless(defined($c));
139              
140             unless($c->can('components')) {
141             return $newlist;
142             }
143             my $list = $c->find($predicate);
144             if(scalar(@{ $list->components })) {
145             $newlist->push_components(@{ $list->components });
146             $newlist->push_constraints(@{ $list->constraints });
147             }
148             }
149              
150             return $newlist;
151             }
152              
153             sub get_paper_dimensions {
154             my ($self, $name) = @_;
155              
156             my $form = Paper::Specs->find(brand => 'standard', code => uc($name));
157             if(defined($form)) {
158             return $form->sheet_size;
159             } else {
160             return (undef, undef);
161             }
162             }
163              
164             sub get_tree {
165             my ($self) = @_;
166              
167             my $tree = Forest::Tree->new(node => $self);
168              
169             foreach my $c (@{ $self->components }) {
170             $tree->add_child($c->get_tree);
171             }
172              
173             return $tree;
174             }
175              
176             sub add_page_break {
177             my ($self, $driver, $page) = @_;
178              
179             my $newpage;
180             if(defined($page)) {
181             $newpage = $page;
182             } else {
183             die('Must add a first page to create implicit ones') unless defined($self->last_page);
184             my $last = $self->last_page;
185             $newpage = Document::Writer::Page->new(
186             color => $last->color,
187             width => $last->width,
188             height => $last->height,
189             );
190             }
191              
192             $driver->prepare($newpage);
193             $newpage->layout_manager->do_layout($newpage);
194              
195             $self->add_component($newpage);
196             $self->last_page($newpage);
197             return $newpage;
198             }
199              
200             __PACKAGE__->meta->make_immutable;
201              
202             1;
203             __END__
204             =head1 NAME
205              
206             Document::Writer - Library agnostic document creation
207              
208             =head1 SYNOPSIS
209              
210             use Document::Writer;
211             use Graphics::Color::RGB;
212             # Use whatever you like, but this is best!
213             use Graphics::Primitive::Driver::CairoPango;
214              
215             my $doc = Document::Writer->new;
216             my $driver = Graphics::Primitive::Driver::CairoPango->new(format => 'pdf');
217            
218             # Create the first page
219             my @dim = Document::Writer->get_paper_dimensions('letter');
220             my $p = Document::Writer::Page->new(
221             color => Graphics::Color::RGB->new(red => 0, green => 0, blue => 0),
222             width => $dim[0], height => $dim[1]
223             );
224              
225             $doc->add_page_break($driver, $page);
226             ...
227             my $textarea = Document::Writer::TextArea->new(
228             text => 'Lorem ipsum...'
229             );
230             $textarea->font->size(13);
231             $textarea->padding(10);
232             $textarea->line_height(17);
233              
234             $doc->add_component($textarea);
235             $doc->draw($driver);
236             $driver->write('/Users/gphat/foo.pdf');
237              
238             =head1 DESCRIPTION
239              
240             Document::Writer is a document creation library that is built on the
241             L<Graphics::Primitive> stack. It aims to provide convenient abstractions for
242             creating documents and a library-agnostic base for the embedding of other
243             components that use Graphics::Primitive.
244              
245             When you create a new Document::Writer, it has no pages. You can add a page
246             to the document using C<add_page_break($driver, [ $page ])>. The first
247             time this is called, a page must be supplied. Subsequent calls will clone the
248             last page that was passed in. If you add components to the document, then
249             they will automatically be paginated at render time, if necessary. You only
250             need to add the first page and any manual page breaks.
251              
252             =head1 NOTICE
253              
254             Document::Writer is a hobby project that I work on in my spare time. It's
255             yet to be used for any real work and it's likely I've forgotten something
256             important. Free free to contact me via IRC or email if you run into problems.
257              
258             =head1 METHODS
259              
260             =head2 add_component
261              
262             Add a component to this document.
263              
264             =head2 add_page_break ($driver, [ $page ])
265              
266             Add a page break to the document. The first time this is called, a page must
267             be supplied. Subsequent calls will clone the last page that was passed in.
268              
269             =head2 clear_components
270              
271             Remove all pages from this document.
272              
273             =head2 draw ($driver)
274              
275             Convenience method that hides all the Graphics::Primitive magic when you
276             give it a driver. After this method completes the entire document will have
277             been rendered into the driver. You can retrieve the output by using
278             L<Driver's|Graphics::Primitive::Driver> I<data> or I<write> methods. Returns
279             the list of Page's as an arrayref.
280              
281             =head2 find ($CODEREF)
282              
283             Compatability and convenience method matching C<find> in
284             Graphics::Primitive::Container.
285              
286             Returns a new ComponentList containing only the components for which the
287             supplied CODEREF returns true. The coderef is called for each component and
288             is passed the component and it's constraints. Undefined components (the ones
289             left around after a remove_component) are automatically skipped.
290              
291             my $flist = $list->find(
292             sub{
293             my ($component, $constraint) = @_; return $comp->class eq 'foo'
294             }
295             );
296              
297             If no matching components are found then a new list is returned so that simple
298             calls liked $container->find(...)->each(...) don't explode.
299              
300             =head2 get_paper_dimensions
301              
302             Given a paper name, such as letter or a4, returns a height and width in points
303             as an array. Uses L<Paper::Specs>.
304              
305             =head2 get_tree
306              
307             Returns a L<Forest::Tree> object with this document at it's root and each
308             page (and it's children) as children. Provided for convenience.
309              
310             =head2 last_component
311              
312             The last component in the list.
313              
314             =head1 SEE ALSO
315              
316             L<Graphics::Primitive>, L<Paper::Specs>
317              
318             =head1 AUTHOR
319              
320             Cory Watson, C<< <gphat@cpan.org> >>
321              
322             =head1 BUGS
323              
324             Please report any bugs or feature requests to C<bug-geometry-primitive at rt.cpan.org>, or through
325             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Geometry-Primitive>. I will be notified, and then you'll
326             automatically be notified of progress on your bug as I make changes.
327              
328             =head1 COPYRIGHT & LICENSE
329              
330             This program is free software; you can redistribute it and/or modify it
331             under the same terms as Perl itself.