File Coverage

lib/ChordPro/Output/PDF/StringDiagrams.pm
Criterion Covered Total %
statement 116 160 72.5
branch 24 54 44.4
condition 16 45 35.5
subroutine 12 13 92.3
pod 0 10 0.0
total 168 282 59.5


line stmt bran cond sub pod time code
1             #! perl
2              
3 8     8   69 use strict;
  8         29  
  8         625  
4              
5             package main;
6              
7             our $config;
8              
9             package ChordPro::Output::PDF::StringDiagrams;
10              
11 8     8   65 use ChordPro::Chords;
  8         32  
  8         21235  
12              
13             sub new {
14 40     40 0 138 my ( $pkg, $ps ) = @_;
15              
16 40         127 my $ctl = $ps->{kbdiagrams};
17              
18 40         104 my $show = $ctl->{show};
19 40 50       315 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         230 bless { ps => $ps } => $pkg;
25             }
26              
27             # The vertical space the diagram requires.
28             sub vsp0 {
29 24     24 0 74 my ( $self, $elt, $ps ) = @_;
30             $ps->{fonts}->{diagram}->{size} * $ps->{spacing}->{diagramchords}
31             + ( $ps->{diagrams}->{nutwidth} * ($ps->{diagrams}->{linewidth} || 0.10) + 0.40 )
32             * $ps->{diagrams}->{width}
33             + $ps->{diagrams}->{vcells} * $ps->{diagrams}->{height}
34 24 50 50     279 + ( $ps->{diagrams}->{fingers} eq "below" ? $ps->{fonts}->{diagram}->{size} : 0 )
35             ;
36             }
37              
38             # The advance height.
39             sub vsp1 {
40 48     48 0 630 my ( $self, $elt, $ps ) = @_;
41 48         183 $ps->{diagrams}->{vspace} * $ps->{diagrams}->{height};
42             }
43              
44             # The vertical space the diagram requires, including advance height.
45             sub vsp {
46 24     24 0 72 my ( $self, $elt, $ps ) = @_;
47 24         104 $self->vsp0( $elt, $ps ) + $self->vsp1( $elt, $ps );
48             }
49              
50             # The horizontal space the diagram requires.
51             sub hsp0 {
52 48     48 0 104 my ( $self, $elt, $ps ) = @_;
53 48         248 ($config->diagram_strings - 1) * $ps->{diagrams}->{width};
54             }
55              
56             # The advance width.
57             sub hsp1 {
58 72     72 0 163 my ( $self, $elt, $ps ) = @_;
59 72         349 $ps->{diagrams}->{hspace} * $ps->{diagrams}->{width};
60             }
61              
62             # The horizontal space the diagram requires, including advance width.
63             sub hsp {
64 48     48 0 112 my ( $self, $elt, $ps ) = @_;
65 48         154 $self->hsp0( $elt, $ps ) + $self->hsp1( $elt, $ps );
66             }
67              
68             sub font_bl {
69 48     48 0 10709 goto &ChordPro::Output::PDF::font_bl;
70             }
71              
72             # The actual draw method.
73             sub draw {
74 48     48 0 176 my ( $self, $info, $x, $y, $ps ) = @_;
75 48 50       167 return unless $info;
76              
77 48         114 my $x0 = $x;
78 48         79 my $y0 = $y;
79              
80 48         114 my $gw = $ps->{diagrams}->{width};
81 48         108 my $gh = $ps->{diagrams}->{height};
82 48         108 my $dot = $ps->{diagrams}->{dotsize} * $gw;
83 48         98 my $bsz = $ps->{diagrams}->{barwidth} * $dot;
84 48   50     170 my $lw = ($ps->{diagrams}->{linewidth} || 0.10) * $gw;
85 48         113 my $bflw = $ps->{diagrams}->{nutwidth} * $lw;
86 48         92 my $fsh = $ps->{diagrams}->{fingers}; # t / f / below
87 48         140 my $bfy = $bflw/3;
88              
89 48         102 my $pr = $ps->{pr};
90              
91 48   33     374 my $fg = $info->{diagram} // $config->{pdf}->{theme}->{foreground};
92 48         199 my $strings = $config->diagram_strings;
93 48         138 my $w = $gw * ($strings - 1);
94              
95             # Draw font name.
96 48         119 my $font = $ps->{fonts}->{diagram};
97 48         275 my $fasc = $font->{fd}->{font}->ascender/1000;
98 48         578 $pr->setfont($font);
99 48         10893 my $name = $info->chord_display;
100             # $name .= "*"
101             # unless $info->{origin} ne "user"
102             # || $::config->{diagrams}->{show} eq "user";
103             $name = "$name"
104 48 50       774 if $info->{diagram};
105 48         274 $pr->text( $name, $x + ($w - $pr->strwidth($name))/2, $y - font_bl($font) );
106 48         245 $y -= $font->{size} * $ps->{spacing}->{diagramchords} + $dot/2 + $lw;
107 48 50       190 if ( $info->{base} + $info->{baselabeloffset} > 1 ) {
108 0         0 my $i = sprintf("%d ", $info->{base} + $info->{baselabeloffset});
109 0         0 $pr->setfont( $ps->{fonts}->{diagram_base}, $gh );
110             $pr->text( $i, $x-$pr->strwidth($i),
111             $y-$bfy - $bflw/2 - ($info->{baselabeloffset}*$gh)-0.85*$gh,
112 0         0 $ps->{fonts}->{diagram_base}, $ps->{spacing}->{diagramchords}*$gh );
113 0         0 $pr->setfont($font);
114             }
115              
116 48         126 my $v = $ps->{diagrams}->{vcells};
117 48         137 my $h = $strings;
118              
119 48         105 my $basefretno = $info->{base} + $info->{baselabeloffset};
120             # Draw the grid.
121 48         183 my $xo = $self->grid_xo( $ps, $basefretno, $fg );
122              
123             my $crosshairs = sub {
124 0     0   0 my ( $x, $y, $col ) = @_;
125 0         0 for ( $pr->{pdfgfx} ) {
126 0         0 $_->save;
127 0         0 $_->linewidth(0.1);
128 0   0     0 $_->strokecolor($col//"black");
129 0         0 $_->move($x-10,$y);
130 0         0 $_->hline($x+20);
131 0         0 $_->stroke;
132 0         0 $_->move($x,$y+10);
133 0         0 $_->vline($y-20);
134 0         0 $_->stroke;
135 0         0 $_->restore;
136             }
137 48         401 };
138              
139 48         412 $pr->{pdfgfx}->formimage( $xo, $x, $y-$bfy-$v*$gh, 1 );
140              
141             # The numbercolor property of the chordfingers is used for the
142             # background of the underlying dot (the numbers are transparent).
143 48         20306 my $fcf = $ps->{fonts}->{chordfingers};
144 48         241 my $fbg = $pr->_bgcolor($fcf->{numbercolor});
145             # However, if none we should really use white.
146 48 50       179 $fbg = "white" if $fbg eq "none";
147              
148 48         88 my $fingers;
149 48 50       239 $fingers = $info->{fingers} if $fsh;
150              
151             # Bar detection.
152 48         533 my $bar;
153 48 50 33     254 if ( $fingers && $fbg ne $fg ) {
154 48         91 my %h;
155 48         88 my $str = 0;
156 48         93 my $got = 0;
157 48         86 foreach ( @{ $fingers } ) {
  48         164  
158 0 0       0 $str++, next unless $info->{frets}->[$str] > 0;
159 0 0       0 if ( $bar->{$_} ) {
160             # Same finger on multiple strings -> bar.
161 0         0 $got++;
162 0         0 $bar->{$_}->[-1] = $str;
163             }
164             else {
165             # Register.
166 0         0 $bar->{$_} = [ $_, $info->{frets}->[$str], $str, $str ];
167             }
168 0         0 $str++;
169             }
170 48 50       176 if ( $got ) {
171 0         0 foreach (sort keys %$bar ) {
172 0         0 my @bi = @{ $bar->{$_} };
  0         0  
173 0 0       0 if ( $bi[-2] == $bi[-1] ) { # not a bar
174 0         0 delete $bar->{$_};
175 0         0 next;
176             }
177             # Print the bar line. Need linecap 0.
178 0         0 $pr->hline( $x+$bi[2]*$gw, $y-$bfy -$bflw/2 -$bi[1]*$gh+$gh/2,
179             ($bi[3]-$bi[2])*$gw,
180             $bsz, $fg, 0 );
181             }
182             }
183             }
184              
185             # Process the strings and fingers.
186             # $x -= $gw/2;
187 48         83 my $oflo; # to detect out of range frets
188              
189 48         106 my $g_none = "/"; # unnumbered
190              
191             # All symbols from the chordfingers font are equal size: a circle
192             # of 824 (1000-2*88) centered horizontally in the box, with a
193             # decender of 55.
194             # To get it vertically centered we must lower it by 455 (1000/2-55).
195 48         208 $pr->setfont($fcf,$dot);
196 48         9690 my $g_width = $pr->strwidth("1");
197 48         15180 $x -= 0.65 * $g_width;
198 48         118 my $g_lower = -0.455*$g_width;
199             # warn("GW dot=$dot, width=$g_width, lower=$g_lower\n");
200             # my $e = $fcf->{fd}->{font}->extents("1",10);
201             # use DDumper; DDumper($e);
202              
203 48   50     127 for my $sx ( 0 .. @{ $info->{frets} // [] }-1 ) {
  48         271  
204 288         738 my $fret = $info->{frets}->[$sx];
205 288         487 my $fing = -1;
206 288 50 50     1358 $fing = $fingers->[$sx] // -1 if $fingers;
207              
208             # For bars, only the first and last finger.
209 288 0 33     1039 if ( $fing && $bar && $bar->{$fing} ) {
      33        
210 0 0 0     0 unless ( $sx == $bar->{$fing}->[2] || $sx == $bar->{$fing}->[3] ) {
211 0 0 0     0 if ( $fsh eq "below" && $fing =~ /^[A-Z0-9]$/ ){
212 0         0 $pr->setfont( $font, $dot );
213 0         0 my $w = $pr->strwidth($fing);
214 0         0 $pr->text( $fing,
215             $x + 0.65*$g_width -$w/2,
216             $y - $bfy - $bflw/2-($v+0.3)*$gh - $dot*1.4*$fasc,
217             $font, $dot*1.4 );
218             }
219 0         0 next;
220             }
221             }
222              
223 288 100       845 if ( $fret > 0 ) {
    100          
    50          
224 192 50 33     530 if ( $fret > $v && !$oflo++ ) {
225 0         0 warn("Diagram $info->{name}: ",
226             "Fret position $fret exceeds diagram size $v\n");
227             }
228              
229 192         335 my $glyph;
230 192 50 33     781 if ( $fbg eq $fg || $fsh eq "below" ) {
    50          
    50          
231 0         0 $glyph = $g_none;
232             }
233             elsif ( $fing =~ /^[A-Z0-9]$/ ) {
234             # Leave it to the user to interpret sensibly.
235 0         0 $glyph = $fing;
236             }
237             elsif ( $fing =~ /-\d+$/ ) {
238 192         3574 $glyph = $g_none;
239             }
240             else {
241 0         0 warn("Diagram $info->{name}: ",
242             "Invalid finger position $fing, ignored\n");
243 0         0 $glyph = $g_none;
244             }
245              
246             # The glyphs are open, so we need am explicit
247             # background circle to prevent the grid peeping through.
248 192 50       553 $pr->circle( $x+$gw/2, $y-$bfy-$bflw/2-$fret*$gh+$gh/2, $dot/2.2, 1,
249             $fbg, "none") unless $glyph eq $g_none;
250              
251 192         740 $pr->setfont( $fcf, $dot );
252             $glyph = "$glyph"
253 192 50       38294 if $info->{diagram};
254 192         1219 $pr->text( $glyph,
255             $x,
256             $y - $bfy - $bflw/2-$fret*$gh + $gh/2 + $g_lower,
257             $fcf, $dot/0.8 );
258              
259 192 50 33     870 if ( $fsh eq "below" && $fing =~ /^[A-Z0-9]$/ ){
260 0         0 $pr->setfont( $font, $dot*1.4 );
261 0         0 my $w = $pr->strwidth($fing);
262 0         0 $pr->text( $fing,
263             $x + 0.65*$g_width -$w/2,
264             $y - $bfy - $bflw/2-($v+0.3)*$gh - $dot*1.4*$fasc,
265             $font, $dot*1.4 );
266             }
267              
268             }
269             elsif ( $fret < 0 ) {
270 48         329 $pr->cross( $x+$gw/2, $y+$lw+$gh/3, $dot/3, $lw, $fg );
271             }
272             elsif ( $info->{base} > 0 ) {
273 48         322 $pr->circle( $x+$gw/2, $y+$lw+$gh/3, $dot/3, $lw, undef, $fg );
274             }
275             }
276             continue {
277 288         7951 $x += $gw;
278             }
279              
280 48         700 return $gw * ( $ps->{diagrams}->{hspace} + $strings );
281             }
282              
283             sub grid_xo {
284 48     48 0 145 my ( $self, $ps, $basefretno, $fg ) = @_;
285              
286 48         125 my $gw = $ps->{diagrams}->{width};
287 48         103 my $gh = $ps->{diagrams}->{height};
288 48   50     218 my $lw = ($ps->{diagrams}->{linewidth} || 0.10) * $gw;
289 48         112 my $bflw = $ps->{diagrams}->{nutwidth} * $lw;
290 48         98 my $bfno = $basefretno;
291 48         119 my $v = $ps->{diagrams}->{vcells};
292 48         219 my $strings = $config->diagram_strings;
293              
294             return $self->{grids}->{$gw,$gh,$lw, $bflw, $bfno, $fg, $v,$strings} //= do
295 48   66     822 {
296 24         70 my $w = $gw * ($strings - 1);
297 24         58 my $h = $strings;
298              
299 24         198 my $form = $ps->{pr}->{pdf}->xo_form;
300              
301             # Bounding box must take linewidth into account.
302 24         10317 my @bb = ( -$lw/2, -$lw/2 - $bflw/2,
303             ($h-1)*$gw+$lw/2, $v*$gh+$lw/2 + $bflw/2.5 );
304 24         199 $form->bbox(@bb);
305              
306             # Pseudo-object to access low level drawing routines.
307 24         2323 my $dc = bless { pdfgfx => $form } => ChordPro::Output::PDF::Writer::;
308              
309             # Draw the grid.
310 24         75 $dc->rectxy( @bb, 0, 'red' ) if 0;
311 24         71 my $color = $fg;
312 24         87 for ( 0 .. $v ) {
313 120 100 66     4778 if ( $bfno <= 1 && $_== 0 ) {
314 24         172 $dc->hline( 0, ($v-$_)*$gh, $w, $bflw, $color );
315             }
316             else {
317 96         473 $dc->hline( 0, ($v-$_)*$gh-$bflw/2, $w, $lw, $color );
318             }
319             }
320              
321 24         1408 $dc->vline( $_*$gw, $v*$gh+$bflw/4, $gh*$v+$bflw/1.5, $lw, $color) for 0..$h-1;
322              
323 24         1248 $form;
324             };
325             }
326              
327             1;