File Coverage

blib/lib/Math/Geometry/Construction/Draw/TikZ.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 Math::Geometry::Construction::Draw::TikZ;
2 1     1   2409 use Moose;
  0            
  0            
3             extends 'Math::Geometry::Construction::Draw';
4              
5             use 5.008008;
6              
7             use Carp;
8             use LaTeX::TikZ as => 'TikZ';
9              
10             =head1 NAME
11              
12             C<Math::Geometry::Construction::Draw::TikZ> - TikZ output
13              
14             =head1 VERSION
15              
16             Version 0.021
17              
18             =cut
19              
20             our $VERSION = '0.021';
21              
22              
23             ###########################################################################
24             # #
25             # Accessors #
26             # #
27             ###########################################################################
28              
29             has 'svg_mode' => (isa => 'Bool',
30             is => 'ro',
31             default => 0);
32              
33             has 'math_mode' => (isa => 'Bool',
34             is => 'ro',
35             default => 1);
36              
37             sub BUILD {
38             my ($self, $args) = @_;
39             my $seq = TikZ->seq;
40              
41             # add clip
42             my $rect = TikZ->rectangle
43             (TikZ->point(0, 0),
44             TikZ->point($self->width, $self->height));
45             $seq->clip(TikZ->path($rect));
46             $self->_output($seq);
47             }
48              
49             ###########################################################################
50             # #
51             # Generate Output #
52             # #
53             ###########################################################################
54              
55             override 'transform_coordinates' => sub {
56             my ($self, $x, $y) = @_;
57              
58             ($x, $y) = super();
59              
60             if($self->svg_mode) { return($x, $self->height - $y) }
61             else { return($x, $y) }
62             };
63              
64             override 'is_flipped' => sub {
65             return(super() == 1 ? 0 : 1);
66             };
67              
68             sub process_style {
69             my ($self, $element, %style) = @_;
70             my $svg_mode = $self->svg_mode;
71              
72             if($element eq 'text') {
73             if(exists($style{fill})) {
74             $style{color} = $style{fill} if($svg_mode);
75             delete($style{fill});
76             }
77             }
78             else {
79             if(exists($style{stroke})) {
80             $style{color} = $style{stroke} if($svg_mode);
81             delete($style{stroke});
82             }
83              
84             # for some reason 'none' does not work although it should
85             if($style{color} and $style{color} eq 'none') {
86             if($style{fill} and $style{fill} ne 'none') {
87             $style{color} = $style{fill};
88             }
89             else {
90             delete($style{color})
91             }
92             }
93             }
94              
95             while(my ($key, $value) = each(%style)) {
96             if($value and ref($value) eq 'ARRAY' and @$value == 3) {
97             $style{$key} = sprintf('{rgb,255:red,%d;green,%d;blue,%d}',
98             @$value);
99             }
100             if($svg_mode) {
101             if($key eq 'stroke-dasharray') {
102             if($value) {
103             my $wsp = qr/[\x{20}\x{9}\x{D}\x{A}]/;
104             my $split_pattern = qr/(?:$wsp+\,?$wsp*|\,$wsp*)/;
105             my @sections = split($split_pattern, $value);
106             my $cmd = 'on';
107             foreach(@sections) {
108             $_ = "$cmd $_";
109             $cmd = $cmd eq 'on' ? 'off' : 'on';
110             }
111             $style{'dash pattern'} = join(' ', @sections);
112             }
113             delete($style{'stroke-dasharray'});
114             }
115             if($key eq 'stroke-dashoffset') {
116             $style{'dash phase'} = $style{'stroke-dashoffset'};
117             delete($style{'stroke-dashoffset'});
118             }
119             }
120             }
121              
122             return %style;
123             }
124              
125             sub set_background {
126             my ($self, $color) = @_;
127             }
128              
129             sub line {
130             my ($self, %args) = @_;
131              
132             my $line = TikZ->line
133             ([$self->transform_coordinates($args{x1}, $args{y1})],
134             [$self->transform_coordinates($args{x2}, $args{y2})]);
135              
136             my %style = $self->process_style('line', %{$args{style} || {}});
137             while(my ($key, $value) = each(%style)) {
138             $line->mod(TikZ->raw_mod("$key=$value"));
139             }
140              
141             $self->output->add($line);
142             }
143              
144             sub circle {
145             my ($self, %args) = @_;
146             my @raw = ();
147              
148             ($args{cx}, $args{cy}) = $self->transform_coordinates
149             ($args{cx}, $args{cy});
150             $args{rx} = $self->transform_x_length($args{r});
151             $args{ry} = $self->transform_y_length($args{r});
152             if(defined($args{x1}) and defined($args{y1}) and
153             defined($args{x2}) and defined($args{y2}))
154             {
155             my @boundary = $self->is_flipped
156             ? ([$self->transform_coordinates($args{x2}, $args{y2})],
157             [$self->transform_coordinates($args{x1}, $args{y1})])
158             : ([$self->transform_coordinates($args{x1}, $args{y1})],
159             [$self->transform_coordinates($args{x2}, $args{y2})]);
160              
161             my @alpha = (); # angles in °
162             foreach(@boundary) {
163             my $angle = atan2($_->[1] - $args{cy}, $_->[0] - $args{cx});
164             $angle += 6.28318530717959 if($angle < 0);
165             push(@alpha, $angle / 3.14159265358979 * 180);
166             }
167              
168             =pod
169              
170             =begin problematic
171              
172             TexLive crashes on this TikZ construct.
173              
174             my $delta_alpha = $alpha[1] - $alpha[0];
175             $delta_alpha += 360 if($delta_alpha < 0);
176             my $template =
177             '(%f, %f) arc '.
178             '[start angle=%f, delta angle=%f, '.
179             'x radius=%f, y radius=%f]';
180             $raw = TikZ->raw(sprintf($template,
181             @{$boundary[0]},
182             $alpha[0], $delta_alpha,
183             $args{rx}, $args{ry}));
184            
185             =end problematic
186              
187             =cut
188              
189              
190             if($alpha[0] > $alpha[1]) {
191             push(@raw, TikZ->raw(sprintf('(%f, %f) arc (%f:%f:%f and %f)',
192             @{$boundary[0]},
193             $alpha[0], 360,
194             $args{rx}, $args{ry})));
195             push(@raw, TikZ->raw(sprintf('(%f, %f) arc (%f:%f:%f and %f)',
196             $args{cx} + $args{rx}, $args{cy},
197             0, $alpha[1],
198             $args{rx}, $args{ry})));
199             }
200             else {
201             push(@raw, TikZ->raw(sprintf('(%f, %f) arc (%f:%f:%f and %f)',
202             @{$boundary[0]},
203             $alpha[0], $alpha[1],
204             $args{rx}, $args{ry})));
205             }
206             }
207             else {
208             push(@raw, TikZ->raw(sprintf('(%f, %f) ellipse (%f and %f)',
209             $args{cx}, $args{cy},
210             $args{rx}, $args{ry})));
211             }
212            
213             my %style = $self->process_style('circle', %{$args{style} || {}});
214             while(my ($key, $value) = each(%style)) {
215             next if($key eq 'fill');
216             foreach(@raw) {
217             $_->mod(TikZ->raw_mod("$key=$value"));
218             }
219             }
220             if($style{fill}) {
221             foreach(@raw) {
222             $_->mod(TikZ->fill($style{fill}));
223             }
224             }
225            
226             foreach(@raw) {
227             $self->output->add($_);
228             }
229             }
230              
231             sub text {
232             my ($self, %args) = @_;
233             my $svg_mode = $self->svg_mode;
234             my $template = $self->math_mode
235             ? '(%f, %f) node {$%s$}' : '(%f, %f) node {%s}';
236              
237             my $content = sprintf
238             ($template,
239             $self->transform_coordinates($args{x}, $args{y}),
240             $args{text});
241             my $raw = TikZ->raw($content);
242             my %style = $self->process_style('text', %{$args{style} || {}});
243             while(my ($key, $value) = each(%style)) {
244             $raw->mod(TikZ->raw_mod("$key=$value"));
245             }
246             $self->output->add($raw);
247             }
248              
249             1;
250              
251              
252             __END__
253              
254             =pod
255              
256             =head1 SYNOPSIS
257              
258             use Math::Geometry::Construction;
259              
260             my $construction = Math::Geometry::Construction->new;
261             my $p1 = $construction->add_point('x' => 100, 'y' => 150);
262             my $p2 = $construction->add_point('x' => 130, 'y' => 110);
263              
264             my $l1 = $construction->add_line(extend => 10,
265             support => [$p1, $p2]);
266              
267             my $tikz = $construction->as_tikz(width => 8,
268             height => 3,
269             view_box => [0, 0, 800, 300],
270             svg_mode => 1);
271              
272             my (undef, undef, $body) = Tikz->formatter->render($tikz);
273             my $string = sprintf("%s\n", join("\n", @$body));
274              
275             print <<END_OF_TEX;
276             \\documentclass{article}
277             \\usepackage{tikz}
278             \\begin{document}
279             $string\\end{document}
280             END_OF_TEX
281              
282             =head1 DESCRIPTION
283              
284             This class implements the
285             L<Math::Geometry::Construction::Draw|Math::Geometry::Construction::Draw>
286             interface in order to generate C<TikZ> code to be used in
287             C<LaTeX>. It is instantiated by the L<draw
288             method|Math::Geometry::Construction/draw> in
289             C<Math::Geometry::Construction>.
290              
291             The output created by this class will be a
292             C<LaTeX::TikZ::Set::Sequence> object. See C<SYNOPSIS>.
293              
294             Key/value pairs in the style settings of lines, circles etc. are
295             translated into C<raw_mod> calls
296              
297             while(my ($key, $value) = each(%style)) {
298             $raw->mod(TikZ->raw_mod("$key=$value"));
299             }
300              
301             See L<LaTeX::TikZ|LaTeX::TikZ> if you want to know what this code
302             exactly does. Anyway, the important part is that you should be able
303             to use any modifier that C<TikZ> understands. See also
304             C<svg_mode|/svg_mode>.
305              
306             =head1 INTERFACE
307              
308             =head2 Public Attributes
309              
310             =head3 svg_mode
311              
312             Defaults to C<0>. If set to a true value, C<SVG> style attributes
313             are mapped to C<TikZ> attributes internally. The idea behind this is
314             that you might want to use the same construction including style
315             settings for both C<SVG> and C<TikZ> export. This feature is
316             experimental and will probably never cover the full C<SVG> and/or
317             C<TikZ> functionality.
318              
319             Currently, only C<stroke> is mapped to C<color>, and this is done
320             literally. It will therefore only work for named colors which exist
321             in both output formats.
322              
323             =head3 math_mode
324              
325             Defaults to C<0>. If set to a true value, all text is printed in
326             C<LaTeX>'s math mode. Again, this is to enable the same code to be
327             used for C<TikZ> along side other output formats while still
328             typesetting labels in math mode.
329              
330             =head2 Methods
331              
332             See
333             L<Math::Geometry::Construction::Draw|Math::Geometry::Construction::Draw>.
334              
335              
336             =head1 SEE ALSO
337              
338             =over 4
339              
340             =item * L<LaTeX::TikZ|LaTeX::TikZ>
341              
342             =item * L<http://en.wikipedia.org/wiki/PGF/TikZ>
343              
344             =item * L<http://www.ctan.org/tex-archive/graphics/pgf/base/doc/generic/pgf>
345              
346             =back
347              
348              
349             =head1 AUTHOR
350              
351             Lutz Gehlen, C<< <perl at lutzgehlen.de> >>
352              
353              
354             =head1 LICENSE AND COPYRIGHT
355              
356             Copyright 2011,2013 Lutz Gehlen.
357              
358             This program is free software; you can redistribute it and/or modify it
359             under the terms of either: the GNU General Public License as published
360             by the Free Software Foundation; or the Artistic License.
361              
362             See http://dev.perl.org/licenses/ for more information.
363              
364              
365             =cut
366