File Coverage

lib/Pod/PseudoPod/DOM/Role/LaTeX.pm
Criterion Covered Total %
statement 224 229 97.8
branch 43 56 76.7
condition 2 2 100.0
subroutine 54 56 96.4
pod 0 44 0.0
total 323 387 83.4


line stmt bran cond sub pod time code
1             package Pod::PseudoPod::DOM::Role::LaTeX;
2             # ABSTRACT: an LaTeX formatter role for PseudoPod DOM trees
3              
4 11     11   8612 use strict;
  11         26  
  11         344  
5 11     11   59 use warnings;
  11         25  
  11         304  
6              
7 11     11   62 use Moose::Role;
  11         22  
  11         111  
8 11     11   66457 use File::Basename;
  11         26  
  11         25318  
9              
10             requires 'type';
11             has 'tables', is => 'rw', default => sub { {} };
12             has 'filename', is => 'ro', default => '';
13             has 'emit_environments', is => 'ro', default => sub { {} };
14              
15 11     11 0 143 sub accept_targets { 'latex' }
16              
17             sub add_table
18             {
19 11     11 0 52 my ($self, $table) = @_;
20              
21             # TeX includes are RELATIVE
22 11         478 my $filename = basename($self->filename);
23 11         496 my $tables = $self->tables;
24 11         145 my $count = keys %$tables;
25 11         146 (my $id = $filename)
26 11         1339 =~ s/\.(\w+)$/'_table' . $count . '.tex'/e;
27              
28 11         67 $tables->{$id} = $table;
29 11         49 return $id;
30             }
31              
32             sub emit
33             {
34 3710     3710 0 6212 my $self = shift;
35 3710         112562 my $type = $self->type;
36 3710         7812 my $emit = 'emit_' . $type;
37              
38 3710         11249 $self->$emit( @_ );
39             }
40              
41             sub emit_document
42             {
43 11     11 0 35 my $self = shift;
44 11         58 return $self->emit_kids( document => $self );
45             }
46              
47             sub emit_kids
48             {
49 1300     1300 0 2238 my $self = shift;
50 1300         2142 join '', map { $_->emit( @_ ) } @{ $self->children }
  2962         8334  
  1300         41433  
51             }
52              
53             sub emit_header
54             {
55 88     88 0 195 my $self = shift;
56 88         2875 my $level = $self->level;
57 88         275 my $text = $self->emit_kids;
58 88 100       390 my $suppress = $text =~ s/^\*// ? '*' : '';
59 88 100       3102 my $anchor = $self->anchor ? $self->anchor->emit_anchor : '';
60              
61 88 100       371 return qq|\\chapter${suppress}{$text}\n\n$anchor| if $level == 0;
62              
63 66         201 my $subs = 'sub' x ($level - 1);
64              
65 66         311 return qq|\\${subs}section${suppress}{$text}\n\n$anchor|;
66             }
67              
68             sub emit_plaintext
69             {
70 2015     2015 0 4996 my ($self, %args) = @_;
71 2015 50       65672 my $content = defined $self->content ? $self->content : '';
72              
73 2015 100       5128 if (my $encode = $args{encode})
74             {
75 617         1193 my $method = 'encode_' . $encode;
76 617         2121 return $self->$method( $content, %args );
77             }
78              
79 1398         3989 return $self->encode_text( $content, %args );
80             }
81              
82 44     44 0 394 sub encode_none { return $_[1] }
83              
84             sub encode_split
85             {
86 33     33 0 151 my ($self, $content, %args) = @_;
87 33         76 my $target = $args{target};
88             return join $args{joiner},
89 33         359 map { $self->encode_text( $_ ) } split /\Q$target\E/, $content;
  66         150  
90             }
91              
92             sub encode_index_anchor
93             {
94 187     187 0 420 my ($self, $text) = @_;
95              
96 187         380 $text =~ s/"/""/g;
97 187         427 $text = $self->escape_characters( $text );
98 187         516 $text =~ s/([!|@])/"$1/g;
99              
100 187         730 return $text;
101             }
102              
103             sub encode_label_text
104             {
105 88     88 0 210 my ($self, $text) = @_;
106 88         294 $text =~ s/[^\w:]/-/g;
107              
108 88         592 return $text;
109             }
110              
111             sub encode_verbatim_text
112             {
113 187     187 0 445 my ($self, $text) = @_;
114              
115 187         457 $text = $self->escape_characters( $text );
116 187         437 $text =~ s/--/-\\mbox{}-/g;
117              
118 187         1100 return $text;
119             }
120              
121             sub encode_text
122             {
123 1553     1553 0 3146 my ($self, $text) = @_;
124              
125 1553         3464 $text = $self->escape_characters( $text );
126 1553         3053 $text =~ s/(\\textbackslash)/\$$1\$/g; # add unescaped dollars
127              
128             # use the right beginning quotes
129 1553         2822 $text =~ s/(^|\s)"/$1``/g;
130              
131             # and the right ending quotes
132 1553         2565 $text =~ s/"(\W|$)/''$1/g;
133              
134             # fix the ellipses
135 1553         2707 $text =~ s/\.{3}\s*/\\ldots /g;
136              
137             # fix the ligatures
138 1553 50       4677 $text =~ s/f([fil])/f\\mbox{}$1/g unless $self->{keep_ligatures};
139              
140             # fix emdashes
141 1553         2792 $text =~ s/\s--\s/---/g;
142              
143             # suggest hyphenation points for module names
144 1553         2463 $text =~ s/::/::\\-/g;
145              
146 1553         9444 return $text;
147             }
148              
149             sub escape_characters
150             {
151 1927     1927 0 3642 my ($self, $text) = @_;
152              
153             # Escape LaTeX-specific characters
154 1927         4053 $text =~ s/([{}])/\\$1/g;
155 1927         3562 $text =~ s/\\(?![{}])/\\textbackslash{}/g; # backslashes are special
156 1927         3930 $text =~ s/([#\$&%_])/\\$1/g;
157 1927         3255 $text =~ s/(\^)/\\char94{}/g; # carets are special
158 1927         3019 $text =~ s/</\\textless{}/g;
159 1927         3082 $text =~ s/>/\\textgreater{}/g;
160 1927         3106 $text =~ s/~/\\textasciitilde{}/g;
161 1927         3160 $text =~ s/'/\\textquotesingle{}/g;
162              
163 1927         3996 return $text;
164             }
165              
166             sub emit_literal
167             {
168 11     11 0 41 my $self = shift;
169              
170 11 50       358 if (my $title = $self->title)
171             {
172 11         51 my $target = $title->emit_kids( encode => 'none' );
173             return join "\n\n",
174             map
175             {
176 11         62 $_->emit_kids(
177             encode => 'split', target => $target, joiner => "\\\\\n"
178             )
179 11         43 } @{ $self->children };
  11         366  
180             }
181              
182             return qq||
183 0         0 . join( "\\\\\n", map { $_->emit_kids( @_ ) } @{ $self->children } )
  0         0  
  0         0  
184             . qq|\n|;
185             }
186              
187             sub emit_anchor
188             {
189 44     44 0 100 my $self = shift;
190 44         135 return '\\label{' . $self->emit_kids( encode => 'label_text' ) . qq|}\n\n|;
191             }
192              
193             sub emit_italics
194             {
195 110     110 0 230 my $self = shift;
196 110         326 return '\\emph{' . $self->emit_kids( @_ ) . '}';
197             }
198              
199             sub emit_number_item
200             {
201 33     33 0 75 my $self = shift;
202 33         1162 my $marker = $self->marker;
203 33 50       130 my $number = $marker ? qq| number="$marker"| : '';
204 33         106 return "\\item " . $self->emit_kids( @_ ) . "\n\n";
205             }
206              
207             sub emit_text_item
208             {
209 121     121 0 235 my $self = shift;
210 121         3793 my $kids = $self->children;
211 121 50       472 return qq|\\item[]\n| unless @$kids;
212              
213 121         378 my $first = (shift @$kids)->emit;
214 121 100       597 my $prelude = $first =~ /\D/
215             ? q|\\item[] | . $first
216             : qq|\\item[$first]|;
217              
218 121         13721 return $prelude . "\n\n" . join( '', map { $_->emit } @$kids );
  88         274  
219             }
220              
221             sub emit_bullet_item
222             {
223 99     99 0 193 my $self = shift;
224 99         3175 my $kids = $self->children;
225 99 50       271 return qq|\\item\n| unless @$kids;
226              
227 99         227 return q|\\item | . join( '', map { $_->emit } @$kids ) . qq|\n\n|;
  110         302  
228             }
229              
230             sub emit_code
231             {
232 121     121 0 342 my ($self, %args) = @_;
233 121         325 my $kids = $self->emit_kids( encode => 'verbatim_text' );
234 121         1004 my $tag = '\\texttt{' . $kids . '}';
235              
236 121   100     528 $args{encode} ||= '';
237 121 100       497 return $tag unless $args{encode} =~ /^index_/;
238 55         245 return $kids . '@' . $tag;
239             }
240              
241             sub emit_footnote
242             {
243 11     11 0 35 my $self = shift;
244 11         55 return '\\footnote{' . $self->emit_kids( @_ ) . '}';
245             }
246              
247             sub emit_url
248             {
249 11     11 0 36 my $self = shift;
250 11         60 return q|\\url{| . $self->emit_kids( encode => 'verbatim_text' ) . '}';
251             }
252              
253             sub emit_link
254             {
255 44     44 0 97 my $self = shift;
256 44         152 return qq|\\ppodxref{| . $self->emit_kids( encode => 'label_text' ). q|}|;
257             }
258              
259             sub emit_superscript
260             {
261 11     11 0 38 my $self = shift;
262 11         60 return '$^{' . $self->emit_kids( @_ ) . '}$';
263             }
264              
265             sub emit_subscript
266             {
267 11     11 0 36 my $self = shift;
268 11         53 return '$_{' . $self->emit_kids( @_ ) . '}$';
269             }
270              
271             sub emit_bold
272             {
273 22     22 0 69 my $self = shift;
274 22         96 return '\\textbf{' . $self->emit_kids( @_ ) . '}';
275             }
276              
277             sub emit_file
278             {
279 22     22 0 58 my $self = shift;
280 22         88 return '\\emph{' . $self->emit_kids( @_ ) . '}';
281             }
282              
283             sub emit_paragraph
284             {
285 385     385 0 697 my $self = shift;
286 385         616 my $has_visible_text = grep { $_->type ne 'index' } @{ $self->children };
  1199         34615  
  385         11990  
287 385 50       1129 return $self->emit_kids( @_ ) . ( $has_visible_text ? "\n\n" : '' );
288             }
289              
290 11     11   115 use constant { BEFORE => 0, AFTER => 1 };
  11         27  
  11         3032  
291             my $escapes = "commandchars=\\\\\\{\\}";
292              
293             my %parent_items =
294             (
295             text_list => [ qq|\\begin{description}\n\n|,
296             qq|\\end{description}| ],
297             bullet_list => [ qq|\\begin{itemize}\n\n|,
298             qq|\\end{itemize}| ],
299             number_list => [ qq|\\begin{enumerate}\n\n|,
300             qq|\\end{enumerate}| ],
301             map { $_ => [ qq|\\begin{$_}\n|, qq|\\end{$_}\n\n| ] }
302             qw( epigraph blockquote )
303             );
304              
305             while (my ($tag, $values) = each %parent_items)
306             {
307             my $sub = sub
308             {
309 0     0   0 my $self = shift;
  99     99   218  
        99      
        99      
        99      
        99      
310 0         0 return $values->[BEFORE]
  99         329  
311             . $self->emit_kids( @_ )
312             . $values->[AFTER] . "\n\n";
313             };
314              
315 11     11   96 do { no strict 'refs'; *{ 'emit_' . $tag } = $sub };
  11         24  
  11         18518  
316             }
317              
318             sub emit_programlisting
319             {
320 11     11 0 37 my $self = shift;
321              
322             # should be only a single Verbatim; may need to fix with hoisting
323 11         491 my $kid = $self->children->[0];
324              
325 11         401 return qq|\\begin{CodeListing}\n|
326             . $kid->emit_kids( encode => 'verbatim_text' )
327             . qq|\n\\end{CodeListing}\n|;
328             }
329              
330             sub emit_verbatim
331             {
332 11     11 0 32 my $self = shift;
333 11         70 return qq|\\begin{Verbatim}[$escapes]\n|
334             . $self->emit_kids( encode => 'verbatim_text' )
335             . qq|\n\\end{Verbatim}\n|;
336             }
337              
338             sub emit_screen
339             {
340 11     11 0 38 my $self = shift;
341             # should be only a single Verbatim; may need to fix with hoisting
342 11         386 my $kid = $self->children->[0];
343              
344 11         51 return qq|\\begin{Screen}\n|
345             . $kid->emit_kids( encode => 'verbatim_text' )
346             . qq|\n\\end{Screen}\n|;
347             }
348              
349             my %characters = (
350             acute => sub { qq|\\'| . shift },
351             grave => sub { qq|\\`| . shift },
352             uml => sub { qq|\\"| . shift },
353             cedilla => sub { '\c{' . shift . '}' }, # cedilla
354             opy => sub { '\copyright' }, # copy
355             dash => sub { '---' }, # mdash
356             lusmn => sub { '\pm' }, # plusmn
357             mp => sub { '\&' }, # amp
358             rademark => sub { '\texttrademark' }
359             );
360              
361             sub emit_character
362             {
363 110     110 0 224 my $self = shift;
364              
365 110         205 my $content = eval { $self->emit_kids( @_ ) };
  110         325  
366 110 50       317 return unless defined $content;
367              
368 110 50       588 if (my ($char, $class) = $content =~ /(\w)(\w+)/)
369             {
370 110 100       489 return $characters{$class}->($char) if exists $characters{$class};
371             }
372              
373 33         174 return Pod::Escapes::e2char( $content );
374             }
375              
376             sub emit_index
377             {
378 154     154 0 285 my $self = shift;
379              
380 154         243 my $content;
381 154         320 for my $kid (@{ $self->children })
  154         5144  
382             {
383 242 100       7966 if ($kid->type eq 'plaintext')
384             {
385 165         548 my $kid_content = $kid->emit( encode => 'index_anchor' );
386 165         531 $kid_content =~ s/\s*;\s*/!/g;
387 165         459 $content .= $kid_content;
388             }
389             else
390             {
391 77         216 $content .= $kid->emit( encode => 'index_anchor' );
392             }
393             }
394              
395 154         950 $content =~ s/^\s+|\s+$//g;
396              
397 154         570 return '\\index{' . $content . '}';
398             }
399              
400             sub emit_latex
401             {
402 22     22 0 65 my $self = shift;
403 22         85 return $self->emit_kids( encode => 'none' ) . "\n";
404             }
405              
406             sub emit_block
407             {
408 78     78 0 176 my $self = shift;
409 78 100       2558 my $title = $self->title ? $self->title->emit_kids( encode => 'text' ) :'';
410 78         2628 my $target = $self->target;
411              
412 78 100       2751 if (my $environment = $self->emit_environments->{$target})
    100          
413             {
414 1         3 $target = $environment;
415             }
416             elsif (my $meth = $self->can( 'emit_' . $target))
417             {
418 66         276 return $self->$meth( @_ );
419             }
420              
421 12         409 return $self->make_basic_block( $self->target, $title, @_ );
422             }
423              
424             sub make_basic_block
425             {
426 14     14 0 65 my ($self, $target, $title, @rest) = @_;
427              
428 14 100       107 $title = defined $title ? qq|[$title]| : '';
429              
430 14         88 return qq|\\begin{$target}$title\{\n|
431             . $self->emit_kids( @rest )
432             . qq|}\\end{$target}\n|;
433             }
434              
435       0 0   sub encode_E_contents {}
436              
437             sub emit_sidebar
438             {
439 22     22 0 54 my $self = shift;
440 22         776 my $title = $self->title;
441 22         762 my $env = $self->emit_environments;
442              
443             return $self->make_basic_block( $env->{sidebar}, $title, @_ )
444 22 100       96 if exists $env->{sidebar};
445              
446 20 100       72 if ($title)
447             {
448 10         49 $title = <<END_TITLE;
449             \\begin{center}
450             \\large{\\bfseries{$title}}
451             \\end{center}
452             END_TITLE
453             }
454             else
455             {
456 10         35 $title = '';
457             }
458              
459 20         123 return <<END_HEADER . $self->emit_kids( @_ ) . <<END_FOOTER;
460             \\begin{figure}[H]
461             \\begin{center}
462             \\begin{Sbox}
463             \\begin{minipage}{\\linewidth}
464             $title
465             END_HEADER
466             \\end{minipage}
467             \\end{Sbox}
468             \\framebox{\\TheSbox}
469             \\end{center}
470             \\end{figure}
471             END_FOOTER
472              
473             }
474              
475             sub emit_table
476             {
477 11     11 0 55 my ($self, %args) = @_;
478 11 50       401 my $title = $self->title
479             ? $self->title->emit_kids( encode => 'text' )
480             : '';
481 11         156 my $num_cols = $self->num_cols;
482 11         63 my $width = 1.0 / $num_cols;
483 11         54 my $cols = join ' | ', map { 'X' } 1 .. $num_cols;
  22         106  
484              
485 11         54 my $document = $args{document};
486 11 50       85 my $caption = length $title
487             ? "\\caption{" . $title . "}\n"
488             : '';
489              
490 11         38 my $start = "\\begin{longtable}{| $cols |}\n";
491 11         56 my $end = "$caption\\end{longtable}\n";
492 11         61 my $id = $document->add_table( $start . $self->emit_kids( @_ ) . $end );
493              
494 11         91 return <<TABLE_REFERENCE;
495             \\begin{center}
496             \\LTXtable{\\linewidth}{$id}
497             \\end{center}
498             TABLE_REFERENCE
499             }
500              
501             sub emit_headrow
502             {
503 11     11 0 35 my $self = shift;
504 11         63 my $row = $self->emit_row;
505 11         103 $row =~ s{(\\hline\n)$}{\\endhead$1}s;
506 11         70 return "\\hline\n\\rowcolor[gray]{.9}\n$row";
507             }
508              
509             sub emit_row
510             {
511 33     33 0 69 my $self = shift;
512 33         66 my $contents = join ' & ', map { $_->emit } @{ $self->children };
  66         205  
  33         1042  
513 33         261 return $contents . "\\\\\\hline\n";
514             }
515              
516             sub emit_cell
517             {
518 66     66 0 116 my $self = shift;
519 66         131 my @contents;
520              
521 66         111 for my $child (@{ $self->children })
  66         2445  
522             {
523 99         325 my $contents = $child->emit( @_ );
524 99         265 $contents =~ s/\n+$//g;
525 99 100       372 next unless $contents =~ /\S/;
526 66         194 push @contents, $contents;
527             }
528              
529 66         362 return join '\\newline\\newline ', @contents;
530             }
531              
532             sub emit_figure
533             {
534 11     11 0 42 my $self = shift;
535 11         1313 my $caption = $self->caption;
536 11 50       86 $caption = defined $caption
537             ? '\\caption{' . $self->encode_text( $caption ) . "}\n"
538             : '';
539              
540 11         108 my $anchor = $self->anchor;
541 11 50       72 $anchor = defined $anchor ? $anchor->emit : '';
542              
543 11         100 my $file = $self->file->emit_kids( encode => 'none' );
544              
545 11         70 return <<END_FIGURE;
546             \\begin{figure}[H]
547             \\centering
548             \\includegraphics[width=\\linewidth]{$file}
549             $caption$anchor\\end{figure}
550             END_FIGURE
551             }
552              
553             1;
554              
555             __END__
556              
557             =pod
558              
559             =encoding UTF-8
560              
561             =head1 NAME
562              
563             Pod::PseudoPod::DOM::Role::LaTeX - an LaTeX formatter role for PseudoPod DOM trees
564              
565             =head1 VERSION
566              
567             version 1.20210620.2040
568              
569             =head1 AUTHOR
570              
571             chromatic <chromatic@wgz.org>
572              
573             =head1 COPYRIGHT AND LICENSE
574              
575             This software is copyright (c) 2021 by chromatic.
576              
577             This is free software; you can redistribute it and/or modify it under
578             the same terms as the Perl 5 programming language system itself.
579              
580             =cut