]> git.vpit.fr Git - perl/modules/LaTeX-TikZ.git/blob - lib/LaTeX/TikZ/Scope.pm
c048a1c700fdbd9bcba99b84d990d30f84d54b1a
[perl/modules/LaTeX-TikZ.git] / lib / LaTeX / TikZ / Scope.pm
1 package LaTeX::TikZ::Scope;
2
3 use strict;
4 use warnings;
5
6 =head1 NAME
7
8 LaTeX::TikZ::Scope - An object modeling a TikZ scope or layer.
9
10 =head1 VERSION
11
12 Version 0.01
13
14 =cut
15
16 our $VERSION = '0.01';
17
18 use Sub::Name ();
19
20 use LaTeX::TikZ::Tools;
21
22 use Any::Moose;
23
24 has '_mods' => (
25  is       => 'ro',
26  isa      => 'Maybe[ArrayRef[LaTeX::TikZ::Mod::Formatted]]',
27  init_arg => undef,
28  default  => sub { [ ] },
29 );
30
31 sub mods { @{$_[0]->_mods} }
32
33 has '_mods_cache' => (
34  is       => 'ro',
35  isa      => 'Maybe[HashRef[LaTeX::TikZ::Mod::Formatted]]',
36  init_arg => undef,
37  default  => sub { +{ } },
38 );
39
40 has '_body' => (
41  is       => 'rw',
42  isa      => 'LaTeX::TikZ::Scope|ArrayRef[Str]',
43  init_arg => 'body',
44 );
45
46 my $my_tc    = LaTeX::TikZ::Tools::type_constraint(__PACKAGE__);
47 my $ltmf_tc  = LaTeX::TikZ::Tools::type_constraint('LaTeX::TikZ::Mod::Formatted');
48 my $_body_tc = __PACKAGE__->meta->find_attribute_by_name('_body')
49                                 ->type_constraint;
50
51 sub mod {
52  my $scope = shift;
53
54  my $cache = $scope->_mods_cache;
55
56  for (@_) {
57   my $mod = $ltmf_tc->check($_) ? $_ : $ltmf_tc->coerce($_);
58   my $tag = $mod->tag;
59   next if exists $cache->{$tag};
60   $cache->{$tag} = $mod;
61   push @{$scope->_mods}, $mod;
62  }
63
64  $scope;
65 }
66
67 sub body {
68  my $scope = shift;
69
70  if (@_) {
71   $scope->_body($_[0]);
72   $scope;
73  } else {
74   @{$scope->_body};
75  }
76 }
77
78 use overload (
79  '@{}' => 'dereference',
80 );
81
82 sub flatten {
83  my ($scope) = @_;
84
85  do {
86   my $body = $scope->_body;
87   return $scope unless $my_tc->check($body);
88   $scope = $scope->new
89                  ->mod ($scope->mods, $body->mods)
90                  ->body($body->_body)
91  } while (1);
92 }
93
94 my $inter = Sub::Name::subname('inter' => sub {
95  my ($lh, $rh) = @_;
96
97  my (@left, @common, @right);
98  my %where;
99
100  --$where{$_} for keys %$lh;
101  ++$where{$_} for keys %$rh;
102
103  while (my ($key, $where) = each %where) {
104   if ($where < 0) {
105    push @left,   $lh->{$key};
106   } elsif ($where > 0) {
107    push @right,  $rh->{$key};
108   } else {
109    push @common, $rh->{$key};
110   }
111  }
112
113  return \@left, \@common, \@right;
114 });
115
116 sub instantiate {
117  my ($scope) = @_;
118
119  $scope = $scope->flatten;
120
121  my ($layer, @clips, @raw_mods);
122  for ($scope->mods) {
123   my $type = $_->type;
124   if ($type eq 'clip') {
125    unshift @clips, $_->content;
126   } elsif ($type eq 'layer') {
127    confess("Can't apply two layers in a row") if defined $layer;
128    $layer = $_->content;
129   } else { # raw
130    push @raw_mods, $_->content;
131   }
132  }
133
134  my @body = $scope->body;
135
136  my $mods_string = @raw_mods ? ' [' . join(',', @raw_mods) . ']' : undef;
137
138  if (@raw_mods and @body == 1 and $body[0] =~ /^\s*\\draw\b\s*([^\[].*)\s*$/) {
139   $body[0]     = "\\draw$mods_string $1"; # Has trailing semicolon
140   $mods_string = undef;                   # Done with mods
141  }
142
143  for (0 .. $#clips) {
144   my $clip        = $clips[$_];
145   my $clip_string = "\\clip $clip ;";
146   my $mods_string = ($_ == $#clips and defined $mods_string)
147                      ? $mods_string : '';
148   unshift @body, "\\begin{scope}$mods_string",
149                  $clip_string;
150   push    @body, "\\end{scope}",
151  }
152
153  if (not @clips and defined $mods_string) {
154   unshift @body, "\\begin{scope}$mods_string";
155   push    @body, "\\end{scope}";
156  }
157
158  if (defined $layer) {
159   unshift @body, "\\begin{pgfonlayer}{$layer}";
160   push    @body, "\\end{pgfonlayer}";
161  }
162
163  return @body;
164 }
165
166 sub dereference { [ $_[0]->instantiate ] }
167
168 sub fold {
169  my ($left, $right, $rev) = @_;
170
171  my (@left, @right);
172
173  if ($my_tc->check($left)) {
174   $left = $left->flatten;
175
176   if ($my_tc->check($right)) {
177    $right = $right->flatten;
178
179    my ($only_left, $common, $only_right) = $inter->(
180     $left->_mods_cache,
181     $right->_mods_cache,
182    );
183
184    my $has_different_layers;
185    for (@$only_left) {
186     if ($_->type eq 'layer') {
187      $has_different_layers = 1;
188      last;
189     }
190    }
191    unless ($has_different_layers) {
192     for (@$only_right) {
193      if ($_->type eq 'layer') {
194       $has_different_layers = 1;
195       last;
196      }
197     }
198    }
199
200    if (!$has_different_layers and @$common) {
201     my $x = $left->new
202                  ->mod(@$only_left)
203                  ->body($left->_body);
204     my $y = $left->new
205                  ->mod(@$only_right)
206                  ->body($right->_body);
207     return $left->new
208                 ->mod(@$common)
209                 ->body(fold($x, $y, $rev));
210    } else {
211     @right = $right->instantiate;
212    }
213   } else {
214    $_body_tc->assert_valid($right);
215    @right = @$right;
216   }
217
218   @left = $left->instantiate;
219  } else {
220   if ($my_tc->check($right)) {
221    return fold($right, $left, 1);
222   } else {
223    $_body_tc->assert_valid($_) for $left, $right;
224    @left  = @$left;
225    @right = @$right;
226   }
227  }
228
229  $rev ? [ @right, @left ] : [ @left, @right ];
230 }
231
232 __PACKAGE__->meta->make_immutable;
233
234 =head1 AUTHOR
235
236 Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
237
238 You can contact me by mail or on C<irc.perl.org> (vincent).
239
240 =head1 BUGS
241
242 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>.
243 I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
244
245 =head1 SUPPORT
246
247 You can find documentation for this module with the perldoc command.
248
249     perldoc LaTeX::TikZ
250
251 =head1 COPYRIGHT & LICENSE
252
253 Copyright 2010 Vincent Pit, all rights reserved.
254
255 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
256
257 =cut
258
259 1; # End of LaTeX::TikZ::Scope