File Coverage

blib/lib/LaTeX/TikZ/Formatter.pm
Criterion Covered Total %
statement 66 78 84.6
branch 12 16 75.0
condition 1 3 33.3
subroutine 16 17 94.1
pod 6 7 85.7
total 101 121 83.4


line stmt bran cond sub pod time code
1             package LaTeX::TikZ::Formatter;
2              
3 10     10   54 use strict;
  10         16  
  10         340  
4 10     10   56 use warnings;
  10         18  
  10         457  
5              
6             =head1 NAME
7              
8             LaTeX::TikZ::Formatter - LaTeX::TikZ formatter object.
9              
10             =head1 VERSION
11              
12             Version 0.02
13              
14             =cut
15              
16             our $VERSION = '0.02';
17              
18             =head1 DESCRIPTION
19              
20             A formatter object turns a L<LaTeX::TikZ::Set> tree into the actual TikZ code, depending on some parameters such as the scale, the unit or the origin.
21              
22             =cut
23              
24 10     10   53 use Sub::Name ();
  10         19  
  10         186  
25              
26 10     10   5854 use LaTeX::TikZ::Point;
  10         32  
  10         346  
27              
28 10     10   83 use LaTeX::TikZ::Interface;
  10         20  
  10         249  
29              
30 10     10   5762 use LaTeX::TikZ::Tools;
  10         26  
  10         266  
31              
32 10     10   59 use Any::Moose;
  10         22  
  10         44  
33 10     10   4824 use Any::Moose 'Util::TypeConstraints';
  10         22  
  10         43  
34              
35             =head1 ATTRIBUTES
36              
37             =head2 C<unit>
38              
39             The unit in which lengths are printed.
40             Valid units are C<cm> for centimeters and C<pt> for points.
41              
42             Defaults to C<cm>.
43              
44             =cut
45              
46             has 'unit' => (
47             is => 'ro',
48             isa => enum([ qw/cm pt/ ]),
49             default => 'cm',
50             );
51              
52             =head2 C<format>
53              
54             The format used to print the numbers.
55              
56             Defaults to C<%s>.
57              
58             =cut
59              
60             has 'format' => (
61             is => 'ro',
62             isa => 'Str',
63             default => '%s',
64             );
65              
66             =head2 C<scale>
67              
68             The scale of the drawing.
69              
70             Defaults to C<1>.
71              
72             =cut
73              
74             has 'scale' => (
75             is => 'rw',
76             isa => 'Num',
77             default => 1,
78             );
79              
80             =head2 C<width>
81              
82             The width of the drawing area.
83              
84             Defaults to C<undef> for none.
85              
86             =cut
87              
88             has 'width' => (
89             is => 'rw',
90             isa => 'Maybe[Num]',
91             );
92              
93             =head2 C<height>
94              
95             The height of the drawing area.
96              
97             Defaults to C<undef> for none.
98              
99             =cut
100              
101             has 'height' => (
102             is => 'rw',
103             isa => 'Maybe[Num]',
104             );
105              
106             =head2 C<origin>
107              
108             A point coerced into a L<LaTeX::TikZ::Point> object that represents the logical origin of the printed area.
109             If L</width> and L</height> are set, the canvas will be equivalent to a rectangle whose lower left corner at C<-$origin> and of given width and length.
110              
111             Defaults to C<(0, 0)>, meaning that the drawing area goes from C<(0, 0)> to C<($width, $height)>.
112              
113             =cut
114              
115             has 'origin' => (
116             is => 'rw',
117             isa => 'LaTeX::TikZ::Point::Autocoerce',
118             coerce => 1,
119             );
120              
121             =head1 METHODS
122              
123             =head2 C<id>
124              
125             An unique identifier of the formatter object.
126              
127             =cut
128              
129             sub id {
130 17     17 1 23 my $tikz = shift;
131              
132 17         39 my $origin = $tikz->origin;
133 17 50       35 if (defined $origin) {
134 0         0 my ($x, $y) = map $origin->$_, qw/x y/;
135 0         0 $origin = "($x;$y)";
136             } else {
137 17         23 $origin = "(0;0)";
138             }
139              
140 102 100       282 join $;, map {
141 17         154 defined() ? "$_" : '(undef)';
142             } map($tikz->$_, qw/unit format scale width height/), $origin;
143             }
144              
145             =head2 C<render @sets>
146              
147             Processes all the L<LaTeX::TikZ::Set> objects given in C<@sets> to produce the actual TikZ code to insert in the LaTeX file.
148             First, all the mods applied to the sets and their subsets are collected, and a declaration is emitted if needed for each of them by calling L<LaTeX::TikZ::Mod/declare>.
149             Then, the image code is generated for each set.
150              
151             This method returns a list of array references :
152              
153             =over 4
154              
155             =item *
156              
157             The first one contains the header lines to include between the C<\documentclass> and the C<\begin{document}>.
158              
159             =item *
160              
161             The second one contains the mod declaration lines to put inside the document, between C<\begin{document}> and C<\end{document}>.
162              
163             =item *
164              
165             Finally, there's one array reference for each given TikZ set, which contain the lines for the actual TikZ pictures.
166              
167             =back
168              
169             The lines returned by L</render> don't end with a line feed.
170              
171             my ($header, $declarations, $set1_body, $set2_body) = $formatter->render($set1, $set2);
172              
173             open my $tex, '>', 'test.tex' or die "open('>test.tex'): $!";
174              
175             print $tex "$_\n" for (
176             "\\documentclass[12pt]{article}",
177             @$header,
178             "\\begin{document}",
179             "\\pagestyle{empty}",
180             @$declarations,
181             "First set :"
182             "\\begin{center}",
183             @$set1_body,
184             "\\end{center}",
185             "Second set :"
186             "\\begin{center}",
187             @$set2_body,
188             "\\end{center}",
189             "\\end{document}",
190             );
191              
192             =cut
193              
194             my $find_mods = do {
195             our %seen;
196              
197             my $find_mods_rec;
198             $find_mods_rec = do {
199 10     10   5103 no warnings 'recursion';
  10         20  
  10         10137  
200              
201             Sub::Name::subname('find_mods_rec' => sub {
202             my ($set, $layers, $others) = @_;
203              
204             for ($set->mods) {
205             my $tag = $_->tag;
206             next if $seen{$tag}++;
207              
208             if ($_->isa('LaTeX::TikZ::Mod::Layer')) {
209             push @$layers, $_;
210             } else {
211             push @$others, $_;
212             }
213             }
214              
215             my @subsets = $set->isa('LaTeX::TikZ::Set::Sequence')
216             ? $set->kids
217             : $set->isa('LaTeX::TikZ::Set::Path')
218             ? $set->ops
219             : ();
220              
221             $find_mods_rec->($_, $layers, $others) for @subsets;
222             });
223             };
224              
225             Sub::Name::subname('find_mods' => sub {
226             local %seen = ();
227              
228             $find_mods_rec->(@_);
229             });
230             };
231              
232             my $translate;
233              
234             sub render {
235 92     92 1 37177 my ($tikz, @sets) = @_;
236              
237 92 100       365 unless ($translate) {
238 8         74 require LaTeX::TikZ::Functor;
239             $translate = LaTeX::TikZ::Functor->new(
240             rules => [
241             'LaTeX::TikZ::Set::Point' => sub {
242 7     7   14 my ($functor, $set, $v) = @_;
243              
244 7         22 $set->new(
245             point => [
246             $set->x + $v->x,
247             $set->y + $v->y,
248             ],
249             label => $set->label,
250             pos => $set->pos,
251             );
252             },
253 8         129 ],
254             );
255             }
256              
257 92         605 my $origin = $tikz->origin;
258 92 100       265 if (defined $origin) {
259 1         7 @sets = map $_->$translate($origin), @sets;
260             }
261              
262 92         142 my (@layers, @other_mods);
263 92         1095 my $seq = LaTeX::TikZ::Set::Sequence->new(kids => \@sets);
264 92         6789 $find_mods->($seq, \@layers, \@other_mods);
265              
266 92         395 my $w = $tikz->width;
267 92         279 my $h = $tikz->height;
268 92         140 my $canvas = '';
269 92 50 33     301 if (defined $w and defined $h) {
270 0         0 require LaTeX::TikZ::Set::Rectangle;
271 0         0 for (@sets) {
272 0         0 $_->clip(LaTeX::TikZ::Set::Rectangle->new(
273             from => 0,
274             width => $w,
275             height => $h,
276             ));
277             }
278 0         0 $_ = $tikz->len($_) for $w, $h;
279 0         0 $canvas = ",papersize={$w,$h},body={$w,$h}";
280             }
281              
282 92         359 my @header = (
283             "\\usepackage[pdftex,hcentering,vcentering$canvas]{geometry}",
284             "\\usepackage{tikz}",
285             "\\usetikzlibrary{patterns}",
286             );
287              
288 92         131 my @decls;
289 92 100       288 push @decls, LaTeX::TikZ::Mod::Layer->declare(@layers) if @layers;
290 92         414 push @decls, $_->declare($tikz) for @other_mods;
291              
292 92         338 my @bodies = map [
293             "\\begin{tikzpicture}",
294 92         205 @{ $_->draw($tikz) },
295             "\\end{tikzpicture}",
296             ], @sets;
297              
298 92         909 return \@header, \@decls, @bodies;
299             }
300              
301             =head2 C<len $len>
302              
303             Format the given length according to the formatter options.
304              
305             =cut
306              
307             sub len {
308 215     215 1 330 my ($tikz, $len) = @_;
309              
310 215 100       563 $len = 0 if LaTeX::TikZ::Tools::numeq($len, 0);
311              
312 215         1780 sprintf $tikz->format . $tikz->unit, $len * $tikz->scale;
313             }
314              
315             =head2 C<angle $theta>
316              
317             Format the given angle (in radians) according to the formatter options.
318              
319             =cut
320              
321             sub angle {
322 0     0 1 0 my ($tikz, $a) = @_;
323              
324 0         0 $a = ($a * 180) / CORE::atan2(0, -1);
325 0 0       0 $a += 360 if LaTeX::TikZ::Tools::numcmp($a, 0) < 0;
326              
327 0         0 require POSIX;
328 0         0 sprintf $tikz->format, POSIX::ceil($a);
329             }
330              
331             =head2 C<label $name, $pos>
332              
333             Returns the TikZ code for a point labeled C<$name> at position C<$pos> according to the formatter options.
334              
335             =cut
336              
337             sub label {
338 2     2 1 4 my ($tikz, $name, $pos) = @_;
339              
340 2         10 my $scale = sprintf '%0.2f', $tikz->scale / 5;
341              
342 2         10 "node[scale=$scale,$pos] {$name}";
343             }
344              
345             =head2 C<thickness>
346              
347             Format the given line thickness according to the formatter options.
348              
349             =cut
350              
351             sub thickness {
352 61     61 1 109 my ($tikz, $width) = @_;
353              
354             # width=1 is 0.4 points for a scale of 2.5
355 61         871 0.8 * $width * ($tikz->scale / 5);
356             }
357              
358             LaTeX::TikZ::Interface->register(
359             formatter => sub {
360 10     10 0 2444 shift;
361              
362 10         277 __PACKAGE__->new(@_);
363             },
364             );
365              
366             __PACKAGE__->meta->make_immutable;
367              
368             =head1 SEE ALSO
369              
370             L<LaTeX::TikZ>.
371              
372             =head1 AUTHOR
373              
374             Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
375              
376             You can contact me by mail or on C<irc.perl.org> (vincent).
377              
378             =head1 BUGS
379              
380             Please report any bugs or feature requests to C<bug-latex-tikz at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=LaTeX-TikZ>.
381             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
382              
383             =head1 SUPPORT
384              
385             You can find documentation for this module with the perldoc command.
386              
387             perldoc LaTeX::TikZ
388              
389             =head1 COPYRIGHT & LICENSE
390              
391             Copyright 2010 Vincent Pit, all rights reserved.
392              
393             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
394              
395             =cut
396              
397             1; # End of LaTeX::TikZ::Formatter