File Coverage

blib/lib/ChordPro/Output/PDF/StringDiagrams.pm
Criterion Covered Total %
statement 103 139 74.1
branch 20 46 43.4
condition 11 31 35.4
subroutine 12 13 92.3
pod 0 10 0.0
total 146 239 61.0


line stmt bran cond sub pod time code
1             #! perl
2              
3 8     8   88 use strict;
  8         32  
  8         714  
4              
5             package main;
6              
7             our $config;
8              
9             package ChordPro::Output::PDF::StringDiagrams;
10              
11 8     8   71 use ChordPro::Chords;
  8         58  
  8         17429  
12              
13             sub new {
14 40     40 0 144 my ( $pkg, $ps ) = @_;
15              
16 40         133 my $ctl = $ps->{kbdiagrams};
17              
18 40         121 my $show = $ctl->{show};
19 40 50       382 unless ( $show =~ /^(?:top|bottom|right|below)$/i ) {
20 0         0 die("pdf.diagrams.show is \"$show\", must be one of ".
21             "\"top\", \"bottom\", \"right\", or \"below\"\n");
22             }
23              
24 40         219 bless { ps => $ps } => $pkg;
25             }
26              
27             # The vertical space the diagram requires.
28             sub vsp0 {
29 24     24 0 72 my ( $self, $elt, $ps ) = @_;
30             $ps->{fonts}->{diagram}->{size} * $ps->{spacing}->{diagramchords}
31             + 0.40 * $ps->{diagrams}->{width}
32 24         182 + $ps->{diagrams}->{vcells} * $ps->{diagrams}->{height};
33             }
34              
35             # The advance height.
36             sub vsp1 {
37 48     48 0 105 my ( $self, $elt, $ps ) = @_;
38 48         158 $ps->{diagrams}->{vspace} * $ps->{diagrams}->{height};
39             }
40              
41             # The vertical space the diagram requires, including advance height.
42             sub vsp {
43 24     24 0 73 my ( $self, $elt, $ps ) = @_;
44 24         93 $self->vsp0( $elt, $ps ) + $self->vsp1( $elt, $ps );
45             }
46              
47             # The horizontal space the diagram requires.
48             sub hsp0 {
49 48     48 0 108 my ( $self, $elt, $ps ) = @_;
50 48         205 ($config->diagram_strings - 1) * $ps->{diagrams}->{width};
51             }
52              
53             # The advance width.
54             sub hsp1 {
55 72     72 0 166 my ( $self, $elt, $ps ) = @_;
56 72         350 $ps->{diagrams}->{hspace} * $ps->{diagrams}->{width};
57             }
58              
59             # The horizontal space the diagram requires, including advance width.
60             sub hsp {
61 48     48 0 140 my ( $self, $elt, $ps ) = @_;
62 48         148 $self->hsp0( $elt, $ps ) + $self->hsp1( $elt, $ps );
63             }
64              
65             # my @Roman = qw( I II III IV V VI VI VII VIII IX X XI XII );
66              
67             sub font_bl {
68 48     48 0 11185 goto &ChordPro::Output::PDF::font_bl;
69             }
70              
71             # The actual draw method.
72             sub draw {
73 48     48 0 168 my ( $self, $info, $x, $y, $ps ) = @_;
74 48 50       173 return unless $info;
75              
76 48         133 my $x0 = $x;
77              
78 48         138 my $gw = $ps->{diagrams}->{width};
79 48         120 my $gh = $ps->{diagrams}->{height};
80 48         102 my $dot = 0.80 * $gw;
81 48   50     181 my $lw = ($ps->{diagrams}->{linewidth} || 0.10) * $gw;
82 48         111 my $pr = $ps->{pr};
83              
84 48   33     371 my $fg = $info->{diagram} // $config->{pdf}->{theme}->{foreground};
85 48         210 my $strings = $config->diagram_strings;
86 48         145 my $w = $gw * ($strings - 1);
87              
88             # Draw font name.
89 48         133 my $font = $ps->{fonts}->{diagram};
90 48         252 $pr->setfont($font);
91 48         11093 my $name = $info->chord_display;
92             # $name .= "*"
93             # unless $info->{origin} ne "user"
94             # || $::config->{diagrams}->{show} eq "user";
95             $name = "$name"
96 48 50       784 if $info->{diagram};
97 48         299 $pr->text( $name, $x + ($w - $pr->strwidth($name))/2, $y - font_bl($font) );
98 48         294 $y -= $font->{size} * $ps->{spacing}->{diagramchords} + $dot/2 + $lw;
99 48 50       219 if ( $info->{base} + $info->{baselabeloffset} > 1 ) {
100             # my $i = @Roman[$info->{base}] . " ";
101 0         0 my $i = sprintf("%d ", $info->{base} + $info->{baselabeloffset});
102 0         0 $pr->setfont( $ps->{fonts}->{diagram_base}, $gh );
103             $pr->text( $i, $x-$pr->strwidth($i),
104             $y-($info->{baselabeloffset}*$gh)-0.85*$gh,
105 0         0 $ps->{fonts}->{diagram_base}, $ps->{spacing}->{diagramchords}*$gh );
106 0         0 $pr->setfont($font);
107             }
108              
109 48         136 my $v = $ps->{diagrams}->{vcells};
110 48         108 my $h = $strings;
111              
112             # Draw the grid.
113 48         195 my $xo = $self->grid_xo( $ps, $fg );
114              
115             my $crosshairs = sub {
116 0     0   0 my ( $x, $y, $col ) = @_;
117 0         0 for ( $pr->{pdfgfx} ) {
118 0         0 $_->save;
119 0         0 $_->linewidth(0.1);
120 0   0     0 $_->strokecolor($col//"black");
121 0         0 $_->move($x-10,$y);
122 0         0 $_->hline($x+20);
123 0         0 $_->stroke;
124 0         0 $_->move($x,$y+10);
125 0         0 $_->vline($y-20);
126 0         0 $_->stroke;
127 0         0 $_->restore;
128             }
129 48         465 };
130              
131 48         355 $pr->{pdfgfx}->formimage( $xo, $x, $y-$v*$gh, 1 );
132              
133             # The numbercolor property of the chordfingers is used for the
134             # background of the underlying dot (the numbers are transparent).
135 48         21578 my $fcf = $ps->{fonts}->{chordfingers};
136 48         213 my $fbg = $pr->_bgcolor($fcf->{numbercolor});
137             # However, if none we should really use white.
138 48 50       209 $fbg = "white" if $fbg eq "none";
139              
140 48         107 my $fingers;
141 48 50       243 $fingers = $info->{fingers} if $ps->{diagrams}->{fingers};
142              
143             # Bar detection.
144 48         575 my $bar;
145 48 50 33     318 if ( $fingers && $fbg ne $fg ) {
146 48         89 my %h;
147 48         90 my $str = 0;
148 48         93 my $got = 0;
149 48         98 foreach ( @{ $fingers } ) {
  48         161  
150 0 0       0 $str++, next unless $info->{frets}->[$str] > 0;
151 0 0       0 if ( $bar->{$_} ) {
152             # Same finger on multiple strings -> bar.
153 0         0 $got++;
154 0         0 $bar->{$_}->[-1] = $str;
155             }
156             else {
157             # Register.
158 0         0 $bar->{$_} = [ $_, $info->{frets}->[$str], $str, $str ];
159             }
160 0         0 $str++;
161             }
162 48 50       172 if ( $got ) {
163 0         0 foreach (sort keys %$bar ) {
164 0         0 my @bi = @{ $bar->{$_} };
  0         0  
165 0 0       0 if ( $bi[-2] == $bi[-1] ) { # not a bar
166 0         0 delete $bar->{$_};
167 0         0 next;
168             }
169             # Print the bar line. Need linecap 0.
170 0         0 $pr->hline( $x+$bi[2]*$gw, $y-$bi[1]*$gh+$gh/2,
171             ($bi[3]-$bi[2])*$gw,
172             $dot, $fg, 0 );
173             }
174             }
175             }
176              
177             # Process the strings and fingers.
178 48         137 $x -= $gw/2;
179 48         107 my $oflo; # to detect out of range frets
180              
181 48         97 my $g_none = "/"; # unnumbered
182              
183             # All symbols from the chordfingers font are equal size: a circle
184             # of 824 (1000-2*88) centered horizontally in the box, with a
185             # decender of 55.
186             # To get it vertically centered we must lower it by 455 (1000/2-55).
187 48         199 $pr->setfont($fcf,$dot);
188 48         9925 my $g_width = $pr->strwidth("1");
189 48         15405 my $g_lower = -0.455*$g_width;
190             # warn("GW dot=$dot, width=$g_width, lower=$g_lower\n");
191             # my $e = $fcf->{fd}->{font}->extents("1",10);
192             # use DDumper; DDumper($e);
193              
194 48   50     131 for my $sx ( 0 .. @{ $info->{frets} // [] }-1 ) {
  48         300  
195 288         760 my $fret = $info->{frets}->[$sx];
196 288         556 my $fing = -1;
197 288 50 50     1308 $fing = $fingers->[$sx] // -1 if $fingers;
198              
199             # For bars, only the first and last finger.
200 288 0 33     1091 if ( $fing && $bar && $bar->{$fing} ) {
      33        
201             next unless $sx == $bar->{$fing}->[2]
202 0 0 0     0 || $sx == $bar->{$fing}->[3];
203             }
204              
205 288 100       881 if ( $fret > 0 ) {
    100          
    50          
206 192 50 33     617 if ( $fret > $v && !$oflo++ ) {
207 0         0 warn("Diagram $info->{name}: ",
208             "Fret position $fret exceeds diagram size $v\n");
209             }
210              
211 192         307 my $glyph;
212 192 50       1618 if ( $fbg eq $fg ) {
    50          
    50          
213 0         0 $glyph = $g_none;
214             }
215             elsif ( $fing =~ /^[A-Z0-9]$/ ) {
216             # Leave it to the user to interpret sensibly.
217 0         0 $glyph = $fing;
218             }
219             elsif ( $fing =~ /-\d+$/ ) {
220 192         424 $glyph = $g_none;
221             }
222             else {
223 0         0 warn("Diagram $info->{name}: ",
224             "Invalid finger position $fing, ignored\n");
225 0         0 $glyph = $g_none;
226             }
227              
228             # The glyphs are open, so we need am explicit
229             # background circle to prevent the grid peeping through.
230             # OTOH, for the unnumbered dot, we need a foreground circle.
231 192 50       1321 $pr->circle( $x+$gw/2, $y-$fret*$gh+$gh/2, $dot/2.2, 1,
232             $glyph eq $g_none ? $fg : $fbg,
233             "none");
234              
235 192         9565 $pr->setfont( $fcf, $dot );
236             $glyph = "$glyph"
237 192 50       39045 if $info->{diagram};
238 192         1044 $pr->text( $glyph,
239             $x,
240             $y - $fret*$gh + $gh/2 + $g_lower,
241             $fcf, $dot/0.8 );
242             }
243             elsif ( $fret < 0 ) {
244 48         392 $pr->cross( $x+$gw/2, $y+$lw+$gh/3, $dot/3, $lw,
245             $fg );
246             }
247             elsif ( $info->{base} > 0 ) {
248 48         366 $pr->circle( $x+$gw/2, $y+$lw+$gh/3, $dot/3, $lw,
249             undef, $fg );
250             }
251             }
252             continue {
253 288         5156 $x += $gw;
254             }
255              
256 48         804 return $gw * ( $ps->{diagrams}->{hspace} + $strings );
257             }
258              
259             sub grid_xo {
260 48     48 0 148 my ( $self, $ps, $fg ) = @_;
261              
262 48         122 my $gw = $ps->{diagrams}->{width};
263 48         100 my $gh = $ps->{diagrams}->{height};
264 48   50     174 my $lw = ($ps->{diagrams}->{linewidth} || 0.10) * $gw;
265 48         133 my $v = $ps->{diagrams}->{vcells};
266 48         220 my $strings = $config->diagram_strings;
267              
268             return $self->{grids}->{$gw,$gh,$lw,$v,$strings,$fg} //= do
269 48   66     723 {
270 24         83 my $w = $gw * ($strings - 1);
271 24         71 my $h = $strings;
272              
273 24         177 my $form = $ps->{pr}->{pdf}->xo_form;
274              
275             # Bounding box must take linewidth into account.
276 24         11202 my @bb = ( -$lw/2, -$lw/2, ($h-1)*$gw+$lw/2, $v*$gh+$lw/2 );
277 24         202 $form->bbox(@bb);
278              
279             # Pseudo-object to access low level drawing routines.
280 24         2356 my $dc = bless { pdfgfx => $form } =>
281             ChordPro::Output::PDF::Writer::;
282              
283             # Draw the grid.
284 24         61 $dc->rectxy( @bb, 0, 'red' ) if 0;
285 24         68 my $color = $fg;
286 24         231 $dc->hline( 0, ($v-$_)*$gh, $w, $lw, $color ) for 0..$v;
287 24         1328 $dc->vline( $_*$gw, $v*$gh, $gh*$v, $lw, $color) for 0..$h-1;
288              
289 24         1392 $form;
290             };
291             }
292              
293             1;