File Coverage

blib/lib/Text/Amuse/Compile/File.pm
Criterion Covered Total %
statement 558 660 84.5
branch 170 268 63.4
condition 40 62 64.5
subroutine 77 89 86.5
pod 33 33 100.0
total 878 1112 78.9


line stmt bran cond sub pod time code
1             package Text::Amuse::Compile::File;
2              
3 59     59   156942 use strict;
  59         187  
  59         1757  
4 59     59   321 use warnings;
  59         130  
  59         1352  
5 59     59   310 use utf8;
  59         129  
  59         291  
6              
7 59     59   2047 use constant { DEBUG => $ENV{AMW_DEBUG} };
  59         155  
  59         4352  
8              
9             # core
10             # use Data::Dumper;
11 59     59   1259 use File::Copy qw/move/;
  59         6308  
  59         3118  
12 59     59   30869 use Encode qw/decode_utf8/;
  59         527624  
  59         4086  
13              
14             # needed
15 59     59   25364 use Template::Tiny;
  59         72597  
  59         2560  
16 59     59   36306 use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
  59         2754113  
  59         9737  
17 59     59   28806 use EBook::EPUB::Lite;
  59         12679906  
  59         2359  
18 59     59   535 use File::Copy;
  59         146  
  59         3320  
19 59     59   415 use File::Spec;
  59         161  
  59         1313  
20 59     59   57946 use IPC::Run qw(run);
  59         1493104  
  59         3130  
21 59     59   484 use File::Basename ();
  59         752  
  59         882  
22 59     59   49551 use Path::Tiny ();
  59         735066  
  59         1742  
23              
24             # ours
25 59     59   28016 use PDF::Imposition;
  59         15225368  
  59         2534  
26 59     59   1637 use Text::Amuse;
  59         62902  
  59         1949  
27 59         4809 use Text::Amuse::Functions qw/muse_fast_scan_header
28             muse_to_object
29 59     59   1175 muse_format_line/;
  59         3875  
30 59     59   428 use Text::Amuse::Utils;
  59         137  
  59         1651  
31              
32 59     59   32048 use Text::Amuse::Compile::Templates;
  59         197  
  59         2071  
33 59     59   28715 use Text::Amuse::Compile::TemplateOptions;
  59         285  
  59         3165  
34 59     59   29721 use Text::Amuse::Compile::MuseHeader;
  59         214  
  59         2243  
35 59     59   27747 use Text::Amuse::Compile::Indexer;
  59         210  
  59         2613  
36 59     59   442 use Types::Standard qw/Str Bool Object Maybe CodeRef HashRef InstanceOf ArrayRef/;
  59         163  
  59         420  
37 59     59   75077 use Moo;
  59         173  
  59         330  
38              
39             =encoding utf8
40              
41             =head1 NAME
42              
43             Text::Amuse::Compile::File - Object for file scheduled for compilation
44              
45             =head1 SYNOPSIS
46              
47             Everything here is pretty much private. It's used by
48             Text::Amuse::Compile in a forked and chdir'ed environment.
49              
50             =head1 ACCESSORS AND METHODS
51              
52             =head2 new(name => $basename, suffix => $suffix, templates => $templates)
53              
54             Constructor. Accepts the following named parameters:
55              
56             =over 4
57              
58             =item name
59              
60             =item virtual
61              
62             If it's a virtual file which doesn't exit on the disk (a merged one)
63              
64             =item suffix
65              
66             =item ttdir
67              
68             The directory with the custom templates.
69              
70             =item fileobj
71              
72             An optional L object (for partials)
73              
74             =item standalone
75              
76             When set to true, the tex output will obey bcor and twoside/oneside.
77              
78             =item options
79              
80             An hashref with the options to pass to the templates.
81              
82             =item include_paths
83              
84             Include paths arrayref.
85              
86             =back
87              
88             =head1 INTERNALS
89              
90             =over 4
91              
92             =item is_deleted
93              
94             =item status_file
95              
96             =item check_status
97              
98             =item purged_extensions
99              
100             =item muse_file
101              
102             =item document
103              
104             The L object
105              
106             =item tt
107              
108             The L object
109              
110             =item logger
111              
112             The logger subroutine set in the constructor.
113              
114             =item cleanup
115              
116             Remove auxiliary files (like the complete file and the status file)
117              
118             =item luatex
119              
120             Use luatex instead of xetex
121              
122             =item fonts
123              
124             The L object (required).
125              
126             =item epub_embed_fonts
127              
128             Boolean (default to true) which triggers the epub font embedding.
129              
130             =item coverpage_only_if_toc
131              
132             Boolean (default to false). Activates the conditional article output.
133              
134             =item document_indexes
135              
136             The raw, unparsed indexes found in the muse comments
137              
138             =item indexes
139              
140             If present, the parsed indexes are stored here
141              
142             =back
143              
144             =cut
145              
146             has luatex => (is => 'ro', isa => Bool, default => sub { 0 });
147             has name => (is => 'ro', isa => Str, required => 1);
148             has suffix => (is => 'ro', isa => Str, required => 1);
149              
150             has ttdir => (is => 'ro', isa => Maybe[Str]);
151             has templates => (is => 'lazy', isa => Object);
152              
153             sub _build_templates {
154 291     291   4756 my $self = shift;
155             return Text::Amuse::Compile::Templates->new(ttdir => $self->ttdir,
156 291         5238 format_id => $self->options->{format_id});
157             }
158              
159             has virtual => (is => 'ro', isa => Bool, default => sub { 0 });
160             has standalone => (is => 'ro', isa => Bool, default => sub { 0 });
161             has tt => (is => 'ro', isa => Object, default => sub { Template::Tiny->new });
162             has logger => (is => 'ro', isa => Maybe[CodeRef]);
163             has fileobj => (is => 'ro', isa => Maybe[Object]);
164             has document => (is => 'lazy', isa => Object);
165             has options => (is => 'ro', isa => HashRef, default => sub { +{} });
166             has full_options => (is => 'lazy', isa => HashRef);
167             has tex_options => (is => 'lazy', isa => HashRef);
168             has html_options => (is => 'lazy', isa => HashRef);
169             has wants_slides => (is => 'lazy', isa => Bool);
170             has is_deleted => (is => 'lazy', isa => Bool);
171             has file_header => (is => 'lazy', isa => Object);
172             has coverpage_only_if_toc => (is => 'ro', isa => Bool, default => sub { 0 });
173             has fonts => (is => 'ro', required => 1, isa => InstanceOf['Text::Amuse::Compile::Fonts::Selected']);
174             has epub_embed_fonts => (is => 'ro', isa => Bool, default => sub { 1 });
175             has indexes => (is => 'rwp', isa => Maybe[ArrayRef]);
176             has include_paths => (is => 'ro', isa => ArrayRef, default => sub { [] });
177             has volumes => (is => 'lazy', isa => ArrayRef);
178              
179             sub _build_file_header {
180 307     307   4387 my $self = shift;
181 307         676 my $header;
182 307 100       2018 if ($self->virtual) {
183 19         356 $header = { $self->document->headers };
184             }
185             else {
186 288         1304 $header = muse_fast_scan_header($self->muse_file);
187 288 50 33     145152 $self->log_fatal("Not a muse file!") unless $header && %$header;
188             }
189 307         8929 return Text::Amuse::Compile::MuseHeader->new($header);
190             }
191              
192             sub _build_is_deleted {
193 301     301   10984 return shift->file_header->is_deleted;
194             }
195              
196             sub _build_wants_slides {
197 16     16   439 return shift->file_header->wants_slides;
198             }
199              
200             sub _build_document {
201 280     280   4896 my $self = shift;
202 280         583 my %args;
203 280 50       1193 die "virtual files need an already built document" if $self->virtual;
204 280 100       1531 if (my $fileobj = $self->fileobj) {
205 275         1438 %args = $fileobj->text_amuse_constructor;
206             }
207             else {
208 5         26 %args = (file => $self->muse_file);
209             }
210 280         3466 return Text::Amuse->new(%args,
211             include_paths => $self->include_paths,
212             );
213             }
214              
215             sub _build_tex_options {
216 250     250   4169 my $self = shift;
217 250         4821 return $self->_escape_options_hashref(ltx => $self->full_options);
218             }
219              
220             sub _build_html_options {
221 113     113   2354 my $self = shift;
222 113         2170 return $self->_escape_options_hashref(html => $self->full_options);
223             }
224              
225             sub _build_full_options {
226 292     292   3821 my $self = shift;
227             # merge the options with the ones found in the header.
228             # print "Building full options\n" if DEBUG;
229 292         608 my %options = %{ $self->options };
  292         1698  
230             # these values are picked from the file, if not provided by the compiler
231 292         1351 foreach my $override (qw/cover coverwidth nocoverpage notoc
232             impressum
233             continuefootnotes
234             centerchapter
235             centersection
236             nofinalpage/) {
237 2628         77229 $options{$override} = $self->$override;
238             }
239 292         15067 return \%options;
240             }
241              
242             sub _build_volumes {
243 242     242   3603 my $self = shift;
244 242         685 my @volumes;
245 242 100 66     2090 if (!$self->virtual and -f $self->muse_file) {
246 226         1349 my @lines = Path::Tiny::path($self->muse_file)->lines_utf8;
247              
248 226 100       164133 if (grep { /^; +;;;#\w+/ } @lines) {
  18687         33298  
249 2         9 my @current;
250             my @current_meta;
251 2         0 my @original_meta;
252              
253             # muse starts with the directives
254 2         4 my $in_meta = 1;
255 2         4 my $in_volume_meta = 0;
256             LINE:
257 2         7 while (@lines) {
258 70         110 my $line = shift @lines;
259             # accumulate in the current pile until there's a blank line
260 70         164 my $blank = $line =~ m/\A\s*\z/;
261              
262 70 100       209 if ($line =~ m/\A; +;;;(#[A-Za-z0-9_-]+\w+.*)\z/s) {
    100          
263 6         45 my $directive = $1;
264 6         12 $in_meta = 0;
265 6 100       26 if (!$in_volume_meta) {
266             # entered a new volume
267 5         6 $in_volume_meta = 1;
268 5 100       14 if (@current) {
269 3         13 push @volumes, [ @current_meta, @original_meta, @current ];
270             }
271 5         13 @current = @current_meta = ();
272             }
273 6         10 push @current_meta, $directive;
274 6         17 next LINE;
275             }
276             elsif (!$blank) {
277 31         41 $in_volume_meta = 0;
278             }
279              
280 64 100       97 if ($in_meta) {
281 12         29 push @original_meta, $line;
282             }
283             else {
284 52         140 push @current, $line;
285             }
286             }
287             # end of loop, flush the stack
288 2 50       6 if (@current) {
289 2         12 push @volumes, [ @current_meta, @original_meta, @current ];
290             }
291             # print Dumper(\@original_meta, \@volumes);
292             }
293             }
294 242         6626 return \@volumes;
295             }
296              
297             sub cover {
298 386     386 1 1025 my $self = shift;
299             # options passed take precendence
300 386 100       2012 if (exists $self->options->{cover}) {
301 48 100       267 if ($self->_looks_like_a_sane_name($self->options->{cover})) {
302 26         185 return $self->options->{cover};
303             }
304             else {
305 22         157 return '';
306             }
307             }
308 338 100       6821 if (my $cover = $self->file_header->cover) {
309             # already validated by the MuseHeader class
310 37         1526 return $cover;
311             }
312             }
313              
314             sub coverwidth {
315 292     292 1 723 my $self = shift;
316             # print "Building coverwidth\n";
317             # validation here is not crucial, as the TeX routine will pass it
318             # through the class.
319 292 100       1352 if (exists $self->options->{coverwidth}) {
320             # print "Picking coverwidth from options\n";
321 8         65 return $self->options->{coverwidth};
322             }
323             # obey this thing only if the file set the cover
324 284 100       5229 if ($self->file_header->cover) {
325             # print "Picking coverwidth from file\n";
326 43   50     2162 return $self->file_header->coverwidth || 1;
327             }
328 241         7226 return 1;
329             }
330              
331             sub nocoverpage {
332 545     545 1 2517 shift->_look_at_header('nocoverpage');
333             }
334              
335             sub notoc {
336 292     292 1 975 shift->_look_at_header('notoc');
337             }
338              
339             sub nofinalpage {
340 292     292 1 1293 shift->_look_at_header('nofinalpage');
341             }
342              
343             sub impressum {
344 292     292 1 1058 shift->_look_at_header('impressum');
345             }
346              
347 292     292 1 1046 sub continuefootnotes { shift->_look_at_header('continuefootnotes') }
348 473     473 1 9446 sub centerchapter { shift->_look_at_header('centerchapter') }
349 473     473 1 1711 sub centersection { shift->_look_at_header('centersection') }
350              
351             sub _look_at_header {
352 2659     2659   6512 my ($self, $method) = @_;
353             # these are booleans, so we enforce them
354 2659 100 100     46324 !!$self->file_header->$method || !!$self->options->{$method} || 0;
355             }
356              
357             =head2 Options which are looked up in the file headers first
358              
359             See L for the explanation.
360              
361             =over 4
362              
363             =item cover
364              
365             =item coverwidth
366              
367             =item nocoverpage
368              
369             =item notoc
370              
371             =item nofinalpage
372              
373             =item impressum
374              
375             =item continuefootnotes
376              
377             =item centerchapter
378              
379             =item centersection
380              
381             =back
382              
383             =cut
384              
385             sub _escape_options_hashref {
386 869     869   11339 my ($self, $format, $ref) = @_;
387 869 50 33     4677 die "Wrong usage of internal method" unless $format && $ref;
388 869         1756 my %out;
389 869         5248 foreach my $k (keys %$ref) {
390 14514 100       4507592 if (defined $ref->{$k}) {
391 13089 100 100     54604 if ($k eq 'logo' or $k eq 'cover') {
    100          
392 878 100       4010 if (my $checked = $self->_looks_like_a_sane_name($ref->{$k})) {
393 116         447 $out{$k} = $checked;
394             }
395             }
396             elsif (ref($ref->{$k})) {
397             # pass it verbatim
398 287         1044 $out{$k} = $ref->{$k};
399             }
400             else {
401 11924         32495 $out{$k} = muse_format_line($format, $ref->{$k});
402             }
403             }
404             else {
405 1425         3681 $out{$k} = undef;
406             }
407             }
408 869         228062 return \%out;
409             }
410              
411              
412             sub muse_file {
413 745     745 1 1681 my $self = shift;
414 745         12961 return $self->name . $self->suffix;
415             }
416              
417             sub status_file {
418 305     305 1 2356 return shift->name . '.status';
419             }
420              
421             =head2 purge_all
422              
423             Remove all the output files related to basename
424              
425             =head2 purge_slides
426              
427             Remove all the files produces by the C call, i.e. file.sl.pdf
428             and file.sl.log and all the leftovers (.sl.toc, .sl.aux, etc.).
429              
430             =head2 purge_latex
431              
432             Remove files left by previous latex compilation, i.e. file.pdf and
433             file.log and all the leftovers (toc, aux, etc.).
434              
435             =head2 purge_latex_leftovers
436              
437             Remove the latex leftover files (toc, aux, etc.).
438              
439             =head2 purge_slides_leftovers
440              
441             Remove the latex leftover files (.sl.toc, .sl.aux, etc.).
442              
443             =head2 purge('.epub', ...)
444              
445             Remove the files associated with this file, by extension.
446              
447             =cut
448              
449             sub _compiled_extensions {
450 320     320   2033 return qw/.sl.tex .tex .a4.pdf .lt.pdf .ok .html .bare.html .epub .zip/;
451             }
452              
453             sub _latex_extensions {
454 640     640   2356 return qw/.pdf .log/;
455             }
456              
457             sub _slides_extensions {
458 320     320   774 my $self = shift;
459 320         887 return map { '.sl' . $_ } $self->_latex_extensions;
  640         2882  
460             }
461              
462             sub _latex_leftover_extensions {
463 640     640   2758 return qw/.aux .nav .out .snm .toc .tuc .vrb/;
464             }
465              
466             sub _slides_leftover_extensions {
467 320     320   817 my $self = shift;
468 320         919 return map { '.sl' . $_ } $self->_latex_leftover_extensions;
  2240         4773  
469             }
470              
471             sub purged_extensions {
472 320     320 1 3142 my $self = shift;
473 320         1427 my @exts = (
474             $self->_compiled_extensions,
475             $self->_latex_extensions,
476             $self->_latex_leftover_extensions,
477             $self->_slides_extensions,
478             $self->_slides_leftover_extensions,
479             );
480 320         2841 return @exts;
481             }
482              
483             sub purge {
484 769     769 1 3301 my ($self, @exts) = @_;
485 769         1485 $self->log_info("Started purging\n") if DEBUG;
486 769         2692 my $basename = $self->name;
487 769         2290 foreach my $ext (@exts) {
488 8621 50       23637 $self->log_fatal("wtf? Refusing to purge " . $basename . $ext)
489             if ($ext eq '.muse');
490 8621         17552 my $target = $basename . $ext;
491 8621 100       103471 if (-f $target) {
492 139         361 $self->log_info("Removing target $target\n") if DEBUG;
493 139 50       7629 unlink $target or $self->log_fatal("Couldn't unlink $target $!");
494             }
495             }
496 769         4677 $self->log_info("Ended purging\n") if DEBUG;
497             }
498              
499             sub purge_all {
500 302     302 1 14873 my $self = shift;
501 302         1554 $self->purge($self->purged_extensions);
502             }
503              
504             sub purge_latex {
505 0     0 1 0 my $self = shift;
506 0         0 $self->purge($self->_latex_extensions, $self->_latex_leftover_extensions);
507             }
508              
509             sub purge_slides {
510 0     0 1 0 my $self = shift;
511 0         0 $self->purge($self->_slides_extensions, $self->_slides_leftover_extensions);
512             }
513              
514             sub purge_latex_leftovers {
515 0     0 1 0 my $self = shift;
516 0         0 $self->purge($self->_latex_leftover_extensions);
517             }
518              
519             sub purge_slides_leftovers {
520 0     0 1 0 my $self = shift;
521 0         0 $self->purge($self->_slides_leftover_extensions);
522             }
523              
524             sub _write_file {
525 0     0   0 my ($self, $target, @strings) = @_;
526 0 0       0 open (my $fh, ">:encoding(UTF-8)", $target)
527             or $self->log_fatal("Couldn't open $target $!");
528              
529 0         0 print $fh @strings;
530              
531 0 0       0 close $fh or $self->log_fatal("Couldn't close $target");
532 0         0 return;
533             }
534              
535              
536             =head1 METHODS
537              
538             =head2 Formats
539              
540             Emit the respective format, saving it in a file. Return value is
541             meaningless, but exceptions could be raised.
542              
543             =over 4
544              
545             =item html
546              
547             =item bare_html
548              
549             =item pdf
550              
551             =item epub
552              
553             =item lt_pdf
554              
555             =item a4_pdf
556              
557             =item zip
558              
559             The zipped sources. Beware that if you don't call html or tex before
560             this, the attachments (if any) are ignored if both html and tex files
561             exist. Hence, the muse-compile.pl scripts forces the --tex and --html
562             switches.
563              
564             =cut
565              
566             sub _render_css {
567 181     181   1010 my ($self, %tokens) = @_;
568 181         514 my $out = '';
569 181         4221 $self->tt->process($self->templates->css, {
570             fonts => $self->fonts,
571             centersection => $self->centersection,
572             centerchapter => $self->centerchapter,
573             %tokens
574             }, \$out);
575 181         2819175 return $out;
576             }
577              
578              
579             sub html {
580 112     112 1 33300 my $self = shift;
581 112         473 $self->purge('.html');
582 112         635 my $outfile = $self->name . '.html';
583 112         3116 my $doc = $self->document;
584 112   50     10305 my $title = $doc->header_as_html->{title} || 'Untitled';
585             $self->_process_template($self->templates->html,
586             {
587             doc => $doc,
588             title => $self->_remove_tags($title),
589             css => $self->_render_css(html => 1),
590 112         974536 options => { %{$self->html_options} },
  112         3863  
591             },
592             $outfile);
593             }
594              
595             sub bare_html {
596 8     8 1 1746 my $self = shift;
597 8         31 $self->purge('.bare.html');
598 8         48 my $outfile = $self->name . '.bare.html';
599             $self->_process_template($self->templates->bare_html,
600             {
601             doc => $self->document,
602 8         238 options => { %{$self->html_options} },
  8         1087  
603             },
604             $outfile);
605             }
606              
607             sub a4_pdf {
608 0     0 1 0 my $self = shift;
609 0         0 $self->_compile_imposed('a4');
610             }
611              
612             sub lt_pdf {
613 0     0 1 0 my $self = shift;
614 0         0 $self->_compile_imposed('lt');
615             }
616              
617             sub _compile_imposed {
618 0     0   0 my ($self, $size) = @_;
619 0 0       0 $self->log_fatal("Missing size") unless $size;
620             # the trick: first call tex with an argument, then pdf, then
621             # impose, then rename.
622 0         0 $self->tex(papersize => "half-$size");
623 0         0 my $pdf = $self->pdf;
624 0         0 my $outfile = $self->name . ".$size.pdf";
625 0 0       0 if ($pdf) {
626 0         0 my $imposer = PDF::Imposition->new(
627             file => $pdf,
628             schema => '2up',
629             signature => '40-80',
630             cover => 1,
631             outfile => $outfile
632             );
633 0         0 $imposer->impose;
634             }
635             else {
636 0         0 $self->log_fatal("PDF was not produced!");
637             }
638 0         0 return $outfile;
639             }
640              
641              
642             =item tex
643              
644             This method is a bit tricky, because it's called with arguments
645             internally by C and C, and with no arguments before
646             C.
647              
648             With no arguments, this method enforces the options C
649             and C, effectively ignoring the global options which affect
650             the imposed output, unless C is set to true.
651              
652             This means that the twoside and binding correction options follow this
653             logic: if you have some imposed format, they are ignored for the
654             standalone PDF but applied for the imposed ones. If you have only
655             the standalone PDF, they are applied to it.
656              
657             =cut
658              
659             sub tex {
660 244     244 1 21424 my ($self, @args) = @_;
661 244         1251 my $texfile = $self->name . '.tex';
662 244 50       1104 $self->log_fatal("Wrong usage") if @args % 2;
663 244         774 my %arguments = @args;
664 244 100 100     1856 unless (@args || $self->standalone) {
665 7         41 %arguments = (
666             twoside => 0,
667             oneside => 1,
668             bcor => '0mm',
669             );
670             }
671 244         1109 $self->purge('.tex');
672 244         7141 my $template_body = $self->templates->latex;
673 244         1557 my $tokens = $self->_prepare_tex_tokens(%arguments, template_body => $template_body);
674              
675             # - if there's the ; ;;;#title magic cookie, split the volume. X
676             # - process the template normally. X
677             # - split the body between PREAMBLE \begin{document} BODY \end{document} END X
678             # - determine if the indexes and the toc go at the end or at the beginning, looking at the template X
679             # - split the muse body and create temporary files, adding the headers in the magic comments. X
680             # - process them, but override the wants_toc / wants_indexes depending on previous steps X
681             # - discard everything outside the \begin{document} and \end{document} X
682             # - concatenate the initial preamble, these bodies, and the end, and return it. X
683              
684 244         14905 my $volumes = $self->volumes;
685 244 100 66     9658 if ($volumes and @$volumes > 1) {
686 2         11 my $tex_parse = qr{\A(.*\\begin\{document\})(.*)(\\end\{document\}.*)}s;
687 2         6 my $full;
688 2         22 $self->tt->process($template_body, $tokens, \$full);
689             # print $full;
690 2 50       814079 if ($full =~ m/$tex_parse/s) {
691 2         18 my ($preamble, $body, $end) = ($1, $2, $3);
692             # print Dumper([$preamble, $body, $end ]);
693 2         7 my @pieces = ($preamble);
694 2         7 my $last = scalar $#$volumes;
695              
696             # check if the template is custom
697 2 50       42 my $toc_i = $$template_body =~ m/latex_body.*tableofcontents/s ? $last : 0;
698 2 50       24 my $idx_i = $$template_body =~ m/printindex.*latex_body/s ? 0 : $last;
699              
700 2         12 for (my $i = 0; $i <= $last; $i++) {
701 5         750 my $vol = $volumes->[$i];
702 5         77 my $doc = muse_to_object(join('', @$vol));
703 5         4893 my $latex = $self->_interpolate_magic_comments($tokens->{format_id}, $doc);
704              
705 5 100       34 if (my @raw_indexes = $self->document_indexes) {
706             my $indexer = Text::Amuse::Compile::Indexer->new(latex_body => $latex,
707             language_code => $doc->language_code,
708       2     logger => sub {}, # silence here
709 2         15 index_specs => \@raw_indexes);
710 2         340 $latex = $indexer->indexed_tex_body;
711             }
712              
713             my %partial_tokens = (
714 5         77 options => { %{ $tokens->{options} } },
715 5         202 safe_options => { %{ $tokens->{safe_options} } },
716             tex_setup_langs => 'DUMMY', # irrelevant
717             doc => $doc,
718             latex_body => $latex,
719 5         17 tex_indexes => [ @{ $tokens->{tex_indexes} } ],
  5         39  
720             );
721 5 100       34 if ($i != $toc_i) {
722 3         11 $partial_tokens{safe_options}{wants_toc} = 0;
723             }
724 5 100       20 if ($i != $idx_i) {
725 3         9 $partial_tokens{tex_indexes} = [];
726             }
727              
728             # print Dumper(\%partial_tokens);
729              
730              
731             # here clear wants_toc / indexes
732              
733 5         10 my $out;
734 5         47 $self->tt->process($template_body, \%partial_tokens, \$out);
735 5 50       2029677 if ($out =~ m/$tex_parse/s) {
736 5         984 push @pieces, $2;
737             }
738             }
739 2         495 push @pieces, $end;
740 2         28 Path::Tiny::path($texfile)->spew_utf8(@pieces);
741 2         2015 return $texfile;
742             }
743             }
744 242         1565 $self->_process_template($template_body, $tokens, $texfile);
745             }
746              
747             =item sl_tex
748              
749             Produce a file with extension C<.sl.tex>, a LaTeX Beamer source file.
750             If the source muse file doesn't require slides, do nothing.
751              
752             =item sl_pdf
753              
754             Compiles the file produced by C (if any) and generate the
755             slides with extension C<.sl.pdf>
756              
757             =back
758              
759             =cut
760              
761             sub sl_tex {
762 9     9 1 35 my ($self) = @_;
763             # no slides for virtual files
764 9 50       51 return if $self->virtual;
765 9         43 $self->purge('.sl.tex');
766 9         76 my $texfile = $self->name . '.sl.tex';
767 9 50       215 return unless $self->wants_slides;
768 9         257 my $template_body = $self->templates->slides;
769 9         53 return $self->_process_template($template_body,
770             $self->_prepare_tex_tokens(is_slide => 1,
771             template_body => $template_body,
772             ),
773             $texfile);
774             }
775              
776             sub sl_pdf {
777 0     0 1 0 my $self = shift;
778 0         0 $self->purge_slides; # remove .sl.pdf and .sl.log
779 0         0 my $source = $self->name . '.sl.tex';
780 0 0       0 unless (-f $source) {
781 0         0 $source = $self->sl_tex;
782             }
783 0 0       0 if ($source) {
784 0 0       0 $self->log_fatal("Missing source file $source!") unless -f $source;
785 0 0       0 if (my $out = $self->_compile_pdf($source)) {
786 0         0 $self->purge_slides_leftovers;
787 0         0 return $out;
788             }
789             }
790 0         0 return;
791             }
792              
793             sub pdf {
794 0     0 1 0 my ($self, %opts) = @_;
795 0         0 my $source = $self->name . '.tex';
796 0 0       0 unless (-f $source) {
797 0         0 $self->tex;
798             }
799 0 0       0 $self->log_fatal("Missing source file $source!") unless -f $source;
800 0         0 $self->purge_latex;
801 0 0       0 if (my $out = $self->_compile_pdf($source)) {
802 0         0 $self->purge_latex_leftovers;
803 0         0 return $out;
804             }
805 0         0 return;
806             }
807              
808             sub _compile_pdf {
809 0     0   0 my ($self, $source) = @_;
810 0         0 my ($output, $logfile);
811 0 0       0 die "Missing $source!" unless $source;
812 0 0       0 if ($source =~ m/(.+)\.tex$/) {
813 0         0 my $name = $1;
814 0         0 $output = $name . '.pdf';
815 0         0 $logfile = $name . '.log';
816             }
817             else {
818 0         0 die "Source must be a tex source file\n";
819             }
820 0         0 $self->log_info("Compiling $source to $output\n") if DEBUG;
821 0         0 my $max = 3;
822 0         0 my @run_xindy;
823             # maybe a check on the toc if more runs are needed?
824             # 1. create the toc
825             # 2. insert the toc
826             # 3. adjust the toc. Should be ok, right?
827 0 0       0 foreach my $idx (@{ $self->indexes || [] }) {
  0         0  
828             push @run_xindy, [
829             texindy => '--quiet',
830             -L => $idx->{language},
831             -I => 'xelatex',
832             -C => 'utf8',
833 0         0 $idx->{name} . '.idx',
834             ];
835             }
836 0 0       0 if (@run_xindy) {
837 0         0 $max++;
838             }
839 0         0 foreach my $i (1..$max) {
840 0 0 0     0 if ($i > 2 and @run_xindy) {
841 0         0 foreach my $exec (@run_xindy) {
842 0         0 $self->log_info("Executing " . join(" ", @$exec) . "\n");
843 0 0       0 system(@$exec) == 0 or $self->log_fatal("Errors running " . join(" ", @$exec) ."\n");
844             }
845             }
846 0 0       0 my $latexname = $self->luatex ? 'LuaLaTeX' : 'XeLaTeX';
847 0 0       0 my $latex = $self->luatex ? 'lualatex' : 'xelatex';
848 0         0 my @run = ($latex, '-interaction=nonstopmode', $source);
849 0         0 my ($in, $out, $err);
850 0         0 my $ok = run \@run, \$in, \$out, \$err;
851 0         0 my $shitout;
852 0         0 foreach my $line (split(/\n/, $out)) {
853 0 0       0 if ($line =~ m/^[!#]/) {
854 0 0       0 if ($line =~ m/^! Paragraph ended before/) {
855 0         0 $self->log_info("***** WARNING *****\n"
856             . "It is possible that you have a multiparagraph footnote\n"
857             . "inside an header or inside a em or strong tag.\n"
858             . "Unfortunately this is not supported in the PDF output.\n"
859             . "Please correct it.\n");
860             }
861 0 0       0 if ($line =~ m/^! LaTeX Error: Unknown option.*fragile.*for package.*bigfoot/) {
862 0         0 my $help =<
863             It appears that your TeX installation has an obsolete version of the
864             bigfoot package. You can upgrade this package following this
865             procedure (per user, not global).
866              
867             cd /tmp/
868             mkdir -p `kpsewhich -var-value TEXMFHOME`/tex/latex/bigfoot
869             wget http://mirrors.ctan.org/macros/latex/contrib/bigfoot.zip
870             unzip bigfoot.zip
871             cd bigfoot
872             make
873             mv *.sty `kpsewhich -var-value TEXMFHOME`/tex/latex/bigfoot
874             texhash `kpsewhich -var-value TEXMFHOME`
875              
876             Please contact the sys-admin if the commands above mean nothing to you.
877             HELP
878 0         0 $self->log_info("***** WARNING *****\n" . $help);
879             }
880 0         0 $shitout++;
881             }
882 0 0       0 if ($shitout) {
883             # List of CHECK values
884             # FB_DEFAULT
885             # I = Encode::FB_DEFAULT ( == 0)
886             # If CHECK is 0, encoding and decoding replace any
887             # malformed character with a substitution character.
888             # When you encode, SUBCHAR is used. When you decode,
889             # the Unicode REPLACEMENT CHARACTER, code point
890             # U+FFFD, is used. If the data is supposed to be
891             # UTF-8, an optional lexical warning of warning
892             # category "utf8" is given.
893 0         0 $self->log_info(decode_utf8($line));
894             }
895             }
896 0 0       0 unless ($ok) {
897 0         0 $self->log_info("$latexname compilation failed\n");
898 0 0       0 if (-f $logfile) {
899             # if we have a .pdf file, this means something was
900             # produced. Hence, remove the .pdf
901 0         0 unlink $output;
902 0         0 $self->log_fatal("Bailing out\n");
903             }
904             else {
905 0         0 $self->log_info("Skipping PDF generation\n");
906 0         0 return;
907             }
908             }
909             }
910 0         0 $self->parse_tex_log_file($logfile);
911 0         0 $self->log_info("Compilation over\n") if DEBUG;
912 0         0 return $output;
913             }
914              
915              
916              
917             sub zip {
918 25     25 1 2268 my $self = shift;
919 25         179 $self->purge('.zip');
920 25         129 my $zipname = $self->name . '.zip';
921 25         391 my $tempdir = File::Temp->newdir;
922 25         13055 my $tempdirname = $tempdir->dirname;
923 25         580 foreach my $todo (qw/tex html/) {
924 50         12211 my $target = $self->name . '.' . $todo;
925 50 100       737 unless (-f $target) {
926 24         204 $self->$todo;
927             }
928 50 50       834 $self->log_fatal("Couldn't produce $target") unless -f $target;
929 50 50       558 copy($target, $tempdirname)
930             or $self->log_fatal("Couldn't copy $target in $tempdirname $!");
931             }
932 25         10830 copy ($self->name . '.muse', $tempdirname);
933              
934 25         10300 my $text = $self->document;
935 25         410 foreach my $attach ($text->attachments) {
936 0 0       0 copy($attach, $tempdirname)
937             or $self->log_fatal("Couldn't copy $attach to $tempdirname $!");
938             }
939 25 100       1136 if (my $cover = $self->cover) {
940 8 100       128 if (-f $cover) {
941 6 50       38 copy($cover, $tempdirname)
942             or $self->log_info("Cannot find the cover to attach");
943             }
944             }
945 25         3240 my $zip = Archive::Zip->new;
946 25 50       1312 $zip->addTree($tempdirname, $self->name) == AZ_OK
947             or $self->log_fatal("Failure zipping $tempdirname");
948 25 50       247639 $zip->writeToFileNamed($zipname) == AZ_OK
949             or $self->log_fatal("Failure writing $zipname");
950 25         215151 return $zipname;
951             }
952              
953              
954             sub epub {
955 69     69 1 1753 my $self = shift;
956 69         355 $self->purge('.epub');
957 69         458 my $epubname = $self->name . '.epub';
958              
959 69         2123 my $text = $self->document;
960              
961 69         5007 my @pieces;
962 69 100       607 if ($text->can('as_splat_html_with_attrs')) {
963 10         48 @pieces = $text->as_splat_html_with_attrs;
964             }
965             else {
966             @pieces = map {
967 59         598 +{
968 176         499117 text => $_,
969             language_code => $text->language_code,
970             html_direction => $text->html_direction,
971             }
972             } $text->as_splat_html;
973             }
974 69         3088 my @toc = $text->raw_html_toc;
975             # fixed in 0.51
976 69 50       412409 if (my $missing = scalar(@pieces) - scalar(@toc)) {
977 0         0 $self->log_fatal("This shouldn't happen: missing pieces: $missing");
978             }
979 69         2535 my $epub = EBook::EPUB::Lite->new;
980              
981             # embedded CSS
982 69 100       548151 if ($self->epub_embed_fonts) {
983             # pass all
984 67 50       623 if (my $fonts = $self->fonts) {
985 67         183 my %done;
986 67         164 foreach my $family (@{ $fonts->families }) {
  67         541  
987 201 100       6972 if ($family->has_files) {
988 12         209 foreach my $ff (@{ $family->font_files }) {
  12         51  
989             # do not produce duplicate entries when using
990             # the same file
991 48 50       434 unless ($done{$ff->basename}) {
992 48         1152 $epub->copy_file($ff->file,
993             $ff->basename,
994             $ff->mimetype);
995 48         35677 $done{$ff->basename}++;
996             }
997             }
998             }
999             }
1000             }
1001             }
1002 69         1778 my $css = $self->_render_css(
1003             epub => 1,
1004             epub_embed_fonts => $self->epub_embed_fonts,
1005             );
1006 69         752 $epub->add_stylesheet("stylesheet.css" => $css);
1007              
1008             # build the title page and some metadata
1009 69         44156 my $header = $text->header_as_html;
1010              
1011 69         82584 my @navpoints;
1012 69         211 my $order = 0;
1013              
1014 69 100       535 if (my $cover = $self->cover) {
1015 9 100       162 if (-f $cover) {
1016 7 50       419 if (my $basename = File::Basename::basename($cover)) {
1017 7         28 my $coverpage = <<'HTML';
1018            
1019            
1020            
1021            
1022             Cover
1023            
1027            
1028            
1029            
1030             width="100%" height="100%" viewBox="0 0 573 800" preserveAspectRatio="xMidYMid meet">
1031            
1032            
1033            
1034            
1035             HTML
1036 7         133 $coverpage =~ s/__IMAGE__/$basename/;
1037 7         45 my $cover_id = $epub->copy_file($cover, $basename,
1038             $self->_mime_for_attachment($basename));
1039 7         5129 $epub->add_meta_item(cover => $cover_id);
1040 7         5782 my $cpid = $epub->add_xhtml("coverpage.xhtml", $coverpage);
1041 7         7705 $epub->guide->add_reference(type => 'cover', href => "coverpage.xhtml");
1042 7         5417 push @navpoints, {
1043             label => 'Cover',
1044             id => $cpid,
1045             content => "coverpage.xhtml",
1046             play_order => ++$order,
1047             level => 1,
1048             };
1049             }
1050             }
1051             }
1052              
1053 69         2563 my $titlepage = qq{
\n};
1054              
1055 69 100       510 if ($text->header_defined->{author}) {
1056 16         438 my $author = $header->{author};
1057 16         86 $epub->add_author($self->_clean_html($author));
1058 16 100       3322 $titlepage .= "

$author

\n" if $text->wants_preamble;
1059             }
1060 69         3933 my $muse_header = $self->file_header;
1061 69         1146 foreach my $aut ($muse_header->authors_as_html_list) {
1062 5         586 $epub->add_author($self->_clean_html($aut));
1063             }
1064 69         520 foreach my $topic ($muse_header->topics_as_html_list) {
1065 11         1247 $epub->add_subject($self->_clean_html($topic));
1066             }
1067 69 50       415 if ($text->header_defined->{title}) {
1068 69         1177 my $t = $header->{title};
1069 69         468 $epub->add_title($self->_clean_html($t));
1070 69 100       17323 $titlepage .= "

$t

\n" if $text->wants_preamble;
1071             }
1072             else {
1073 0         0 $epub->add_title('Untitled');
1074             }
1075              
1076 69 100       1331 if ($text->header_defined->{subtitle}) {
1077 2         15 my $st = $header->{subtitle};
1078 2 50       6 $titlepage .= "

$st

\n" if $text->wants_preamble;
1079             }
1080 69 100       940 if ($text->header_defined->{date}) {
1081 1 50       6 if ($header->{date} =~ m/([0-9]{4})/) {
1082 0         0 $epub->add_date($1);
1083             }
1084 1 50       4 $titlepage .= "

$header->{date}

" if $text->wants_preamble;
1085             }
1086              
1087 69         948 $epub->add_language($text->language_code);
1088              
1089 69         12477 $titlepage .= qq{
\n};
1090              
1091 69 50 66     499 if ($text->header_defined->{seriesname} && $text->header_defined->{seriesnumber}) {
1092             $titlepage .= qq{
}
1093             . $header->{seriesname} . ' ' . $header->{seriesnumber}
1094 2         78 . qq{};
1095             }
1096              
1097 69         1786 my @impressum_map = (
1098             [ source => [qw/add_source/], ],
1099             [ notes => [qw/add_description/], ],
1100             [ rights => [qw/add_rights/], ],
1101             [ isbn => [qw/add_identifier ISBN/], ],
1102             [ publisher => [qw/add_publisher/], ],
1103             [ colophon => [] ],
1104             );
1105              
1106 69         300 foreach my $imp (@impressum_map) {
1107 414         3336 my $k = $imp->[0];
1108 414 100       987 if ($text->header_defined->{$k}) {
1109 24         303 my $str = $header->{$k};
1110 24         45 my ($method, @additional_args) = @{$imp->[1]};
  24         79  
1111 24 100       84 if ($method) {
1112 22         70 $epub->$method($self->_clean_html($str), @additional_args);
1113             }
1114 24 100       3187 if ($k eq 'isbn') {
1115 2         6 $str = 'ISBN ' . $str;
1116             }
1117 24 100       100 $titlepage .= qq{
$str
\n}
1118             if $text->wants_postamble;
1119             }
1120             }
1121 69         714 $titlepage .= "\n\n";
1122             # create the front page
1123 69         303 my $firstpage = '';
1124             $self->tt->process($self->templates->minimal_html,
1125             {
1126 69 50 50     1862 title => $self->_remove_tags($header->{title} || 'Untitled'),
1127             text => $titlepage,
1128             html_direction => $text->html_direction,
1129             language_code => $text->language_code,
1130             },
1131             \$firstpage)
1132             or $self->log_fatal($self->tt->error);
1133              
1134 69         21295 my $tpid = $epub->add_xhtml("titlepage.xhtml", $firstpage);
1135              
1136             # main loop
1137             push @navpoints, {
1138 69   50     65917 label => $self->_clean_html($header->{title} || 'Untitled'),
1139             id => $tpid,
1140             content => "titlepage.xhtml",
1141             play_order => ++$order,
1142             level => 1,
1143             };
1144              
1145 69         402 my %internal_links;
1146             {
1147 69         172 my $piecenumber = 0;
  69         208  
1148 69         224 foreach my $piece (@pieces) {
1149             # we insert these in Text::Amuse, so it's not a wild regexp.
1150 313         1493 while ($piece->{text} =~ m/<\/a>/g) {
1151 86         284 my $label = $1;
1152             $internal_links{$label} =
1153 86         278 $self->_format_epub_fragment($toc[$piecenumber]{index});
1154             }
1155 313         597 $piecenumber++;
1156             }
1157             }
1158             my $fix_link = sub {
1159 123     123   272 my ($target) = @_;
1160 123 50       244 die unless $target;
1161 123 100       338 if (my $file = $internal_links{$target}) {
1162 109         731 return $file . '#' . $target;
1163             }
1164             else {
1165             # broken link
1166 14         92 return '#' . $target;
1167             }
1168 69         753 };
1169 69         287 while (@pieces) {
1170 313         659 my $piece = shift @pieces;
1171 313         705 my $index = shift @toc;
1172 313         633 my $xhtml = "";
1173             # print Dumper($index);
1174 313         1026 my $filename = $self->_format_epub_fragment($index->{index});
1175 313         1195 my $prefix = '*' x $index->{level};
1176 313         897 my $title = $prefix . " " . $index->{string};
1177 313         1390 $piece->{text} =~ s/(($2) . '"'/ge;
  123         275  
1178              
1179 313 50       6837 $self->tt->process($self->templates->minimal_html,
1180             {
1181             title => $self->_remove_tags($title),
1182             %$piece,
1183             },
1184             \$xhtml)
1185             or $self->log_fatal($self->tt->error);
1186              
1187 313         80721 my $id = $epub->add_xhtml($filename, $xhtml);
1188             push @navpoints, {
1189             label => $self->_clean_html($index->{string}),
1190             content => $filename,
1191             id => $id,
1192             play_order => ++$order,
1193             level => $index->{level},
1194 313         144015 };
1195             }
1196 69         476 $self->_epub_create_toc($epub, \@navpoints);
1197              
1198             # attachments
1199 69         442 foreach my $att ($text->attachments) {
1200 6 100       16047 $self->log_fatal("Referenced file $att does not exist!") unless -f $att;
1201 5         31 $epub->copy_file($att, $att, $self->_mime_for_attachment($att));
1202             }
1203             # finish
1204 68         386338 $epub->pack_zip($epubname);
1205 68         2961095 return $epubname;
1206             }
1207              
1208             sub _epub_create_toc {
1209 69     69   259 my ($self, $epub, $navpoints) = @_;
1210 69         155 my %levelnavs;
1211             # print Dumper($navpoints);
1212             NAVPOINT:
1213 69         231 foreach my $navpoint (@$navpoints) {
1214 389         2138 my %nav = %$navpoint;
1215 389         926 my $level = delete $nav{level};
1216 389 50       907 die "Shouldn't happen: false level: $level" unless $level;
1217 389 50       1640 die "Shouldn't happen either: $level not 1-4" unless $level =~ m/\A[1-4]\z/;
1218 389         884 my $checklevel = $level - 1;
1219              
1220 389         642 my $current;
1221 389         1074 while ($checklevel > 0) {
1222 264 100       909 if (my $parent = $levelnavs{$checklevel}) {
1223 234         1077 $current = $parent->add_navpoint(%nav);
1224 234         36375 last;
1225             }
1226 30         76 $checklevel--;
1227             }
1228 389 100       1030 unless ($current) {
1229 155         3485 $current = $epub->add_navpoint(%nav);
1230             }
1231 389         95103 for my $clear ($level..4) {
1232 1190         2244 delete $levelnavs{$clear};
1233             }
1234 389         1728 $levelnavs{$level} = $current;
1235             }
1236             # probably not needed, but let's be sure we don't leave circular
1237             # refs.
1238 69         397 foreach my $k (keys %levelnavs) {
1239 149         368 delete $levelnavs{$k};
1240             }
1241             }
1242              
1243             sub _remove_tags {
1244 494     494   1283 my ($self, $string) = @_;
1245 494 50       1303 return "" unless defined $string;
1246 494         1289 $string =~ s/<.+?>//g;
1247 494         3667 return $string;
1248             }
1249              
1250             sub _clean_html {
1251 505     505   1294 my ($self, $string) = @_;
1252 505 50       1332 return "" unless defined $string;
1253 505         1475 $string =~ s/<.+?>//g;
1254 505         1316 $string =~ s/</
1255 505         871 $string =~ s/>/>/g;
1256 505         807 $string =~ s/"/"/g;
1257 505         804 $string =~ s/'/'/g;
1258 505         785 $string =~ s/ / /g;
1259 505         805 $string =~ s/ / /g;
1260 505         840 $string =~ s/&/&/g;
1261 505         4895 return $string;
1262             }
1263              
1264             =head2 Logging
1265              
1266             While the C accessor holds a reference to a sub, but could be
1267             very well be empty, the object uses these two methods:
1268              
1269             =over 4
1270              
1271             =item log_info(@strings)
1272              
1273             If C exists, it will call it passing the strings as arguments.
1274             Otherwise print to the standard output.
1275              
1276             =item log_fatal(@strings)
1277              
1278             Calls C, remove the lock and dies.
1279              
1280             =item parse_tex_log_file($logfile)
1281              
1282             (Internal) Parse the produced logfile for missing characters.
1283              
1284             =back
1285              
1286             =head1 INTERNAL CONSTANTS
1287              
1288             =head2 DEBUG
1289              
1290             Set from AMW_DEBUG environment.
1291              
1292             =cut
1293              
1294              
1295              
1296             sub log_info {
1297 27     27 1 2134 my ($self, @info) = @_;
1298 27         177 my $logger = $self->logger;
1299 27 100       121 if ($logger) {
1300 26         130 $logger->(@info);
1301             }
1302             else {
1303 1         82 print @info;
1304             }
1305             }
1306              
1307             sub log_fatal {
1308 1     1 1 7 my ($self, @info) = @_;
1309 1         6 $self->log_info(@info);
1310 1   50     9 my $failure = join("\n", @info) || "Fatal exception";
1311 1         59 die "$failure\n";
1312             }
1313              
1314             sub parse_tex_log_file {
1315 1     1 1 48 my ($self, $logfile) = @_;
1316 1 50       4 die "Missing file argument!" unless $logfile;
1317 1 50       25 if (-f $logfile) {
1318             # if you're wandering why we open this in raw mode: The log
1319             # file produced by XeLaTeX is utf8, but it splits the output
1320             # at 80 bytes or so. This of course sometimes, expecially
1321             # working with cyrillic scripts, cut the multibyte character
1322             # in half, producing invalid utf8 octects.
1323 1 50       51 open (my $fh, '<:raw', $logfile)
1324             or $self->log_fatal("Couldn't open $logfile $!");
1325              
1326 1         4 my %errors;
1327 1         2 my $continue = 0;
1328              
1329 1         48 while (my $line = <$fh>) {
1330 1257         1393 chomp $line;
1331 1257 100       2993 if ($line =~ m/^missing character/i) {
    100          
    100          
1332             # if we get the warning, nothing we can do about it,
1333             # but shouldn't happen.
1334 4         13 $errors{$line} = 1;
1335             }
1336             elsif ($line =~ m/^Overfull/) {
1337 2         9 $self->log_info(decode_utf8($line) . "\n");
1338 2         12 $continue++;
1339             }
1340             elsif ($continue) {
1341 2         5 $self->log_info(decode_utf8($line) . "\n\n");
1342 2         10 $continue = 0;
1343             }
1344             }
1345 1         14 close $fh;
1346 1         9 foreach my $error (sort keys %errors) {
1347 4         15 $self->log_info(decode_utf8($error) . "...\n");
1348             }
1349             }
1350             }
1351              
1352             sub cleanup {
1353 5     5 1 12357 my $self = shift;
1354 5 50       34 if (my $f = $self->status_file) {
1355 5 100       115 if (-f $f) {
1356 4 50       311 unlink $f or $self->log_fatal("Couldn't unlink $f $!");
1357             }
1358             else {
1359 1         127 $self->log_info("Couldn't find " . File::Spec->rel2abs($f));
1360             }
1361             }
1362             }
1363              
1364             sub _process_template {
1365 371     371   7320 my ($self, $template_ref, $tokens, $outfile) = @_;
1366 371         926 eval {
1367 371         993 my $out = '';
1368 371 50 33     3108 die "Wrong usage" unless ($template_ref && $tokens && $outfile);
      33        
1369 371         3486 $self->tt->process($template_ref, $tokens, \$out);
1370 371 50       94133552 open (my $fh, '>:encoding(UTF-8)', $outfile) or die "Couldn't open $outfile $!";
1371 371         110639 print $fh $out, "\n";
1372 371         15048 close $fh;
1373             };
1374 371 50       2468 if ($@) {
1375 0         0 $self->log_fatal("Error processing template for $outfile: $@");
1376             };
1377 371         12811 return $outfile;
1378             }
1379              
1380              
1381             # method for options to pass to the tex template
1382             sub _prepare_tex_tokens {
1383 253     253   1262 my ($self, %args) = @_;
1384 253         5419 my $doc = $self->document;
1385 253         23539 my $is_slide = delete $args{is_slide};
1386 253         755 my $template_body = delete $args{template_body};
1387 253 50       1076 die "Missing required argument template_body " unless $template_body;
1388 253         587 my %tokens = %{ $self->tex_options };
  253         5306  
1389 253         11149 my $escaped_args = $self->_escape_options_hashref(ltx => \%args);
1390 253         1149 foreach my $k (keys %$escaped_args) {
1391 46         110 $tokens{$k} = $escaped_args->{$k};
1392             }
1393             # now tokens have the unparsed options
1394             # now validate the options against the new shiny module
1395 253         601 my %options = (%{ $self->full_options }, %args);
  253         5224  
1396             # print Dumper($self->full_options);
1397 253         4350 my $template_options = eval { Text::Amuse::Compile::TemplateOptions->new(%options) };
  253         6850  
1398 253 100       22548 unless ($template_options) {
1399 12         235 $template_options = Text::Amuse::Compile::TemplateOptions->new;
1400 12         642 $self->log_info("# Validation failed: $@, setting one by one\n");
1401 12         195 foreach my $method ($template_options->config_setters) {
1402 504 100       6087 if (exists $options{$method}) {
1403 160         237 eval { $template_options->$method($options{$method}) };
  160         3383  
1404             }
1405             }
1406             }
1407 253         1724 my $safe_options =
1408             $self->_escape_options_hashref(ltx => $template_options->config_output);
1409              
1410             # defaults
1411 253         9211 my %parsed = (%$safe_options,
1412             class => 'scrbook',
1413             lang => 'english',
1414             mainlanguage_script => '',
1415             wants_toc => 0,
1416             );
1417              
1418              
1419 253         1942 my $fonts = $self->fonts;
1420              
1421             # not used but for legacy templates
1422 253         1780 $parsed{mainfont} = $fonts->main->name;
1423 253         1397 $parsed{sansfont} = $fonts->sans->name;
1424 253         1317 $parsed{monofont} = $fonts->mono->name;
1425 253         1105 $parsed{fontsize} = $fonts->size;
1426              
1427 253         7557 my $latex_body = $self->_interpolate_magic_comments($template_options->format_id, $doc);
1428              
1429 253         1343 my $enable_secondary_footnotes = $latex_body =~ m/\\footnoteB\{/;
1430              
1431             # check if the template body support this conditional, which is new. If not,
1432             # always setup bigfoot
1433             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1434 253 100       9665 if (index($$template_body, '[% IF enable_secondary_footnotes %]', 0) < 0) {
1435 1         2 $enable_secondary_footnotes = 1;
1436             }
1437             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1438              
1439 253         1589 my $main_is_rtl = Text::Amuse::Utils::lang_code_is_rtl($doc->language_code);
1440              
1441 253   100     7830 my $tex_setup_langs = $fonts
1442             ->compose_polyglossia_fontspec_stanza(lang => $doc->language,
1443             others => $doc->other_languages || [],
1444             enable_secondary_footnotes => $enable_secondary_footnotes,
1445             bidi => $doc->is_bidi,
1446             main_is_rtl => $main_is_rtl,
1447             has_ruby => $doc->has_ruby,
1448             is_slide => $is_slide,
1449             captions => Text::Amuse::Utils::language_code_locale_captions($doc->language_code),
1450             );
1451              
1452 253         945 my @indexes;
1453 253 100       1563 if (my @raw_indexes = $self->document_indexes) {
1454             my $indexer = Text::Amuse::Compile::Indexer->new(latex_body => $latex_body,
1455             language_code => $doc->language_code,
1456 0     0   0 logger => $self->logger || sub { print @_ },
1457 5   50     35 index_specs => \@raw_indexes);
1458 5         7134 $latex_body = $indexer->indexed_tex_body;
1459 5         106 my %xindy_langs = (
1460             bg => 'bulgarian',
1461             cs => 'czech',
1462             da => 'danish',
1463             de => 'german-din', # ae is sorted like ae. alternative -duden
1464             el => 'greek',
1465             en => 'english',
1466             es => 'spanish-modern',
1467             et => 'estonian',
1468             fi => 'finnish',
1469             fr => 'french',
1470             hr => 'croatian',
1471             hu => 'hungarian',
1472             is => 'icelandic',
1473             it => 'italian',
1474             lv => 'latvian',
1475             lt => 'lithuanian',
1476             mk => 'macedonian',
1477             # nl => 'dutch', # unclear why missing
1478             no => 'norwegian',
1479             sr => 'croatian', # serbian is cyrillic
1480             ro => 'romanian',
1481             ru => 'russian',
1482             sk => 'slovak-small', # exists also slovak-large
1483             sl => 'slovenian',
1484             pl => 'polish',
1485             pt => 'portuguese',
1486             sq => 'albanian',
1487             sv => 'swedish',
1488             tr => 'turkish',
1489             uk => 'ukrainian',
1490             vi => 'vietnamese',
1491             );
1492             @indexes = map { +{
1493             name => $_->index_name,
1494             title => $_->index_label,
1495 7   50     169 language => $xindy_langs{$doc->language_code} || 'general',
1496             } }
1497 5         14 @{ $indexer->specifications };
  5         114  
1498             }
1499 253 100       6600 $self->_set_indexes(@indexes ? \@indexes : undef);
1500             # no cover page if header or compiler says so, or
1501             # if coverpage_only_if_toc is set and doc doesn't have a toc.
1502 253 100 100     9904 if ($self->nocoverpage or
      100        
1503             ($self->coverpage_only_if_toc && !$doc->wants_toc)) {
1504 22         1068 $parsed{nocoverpage} = 1;
1505 22         74 $parsed{class} = 'scrartcl';
1506 22         69 delete $parsed{opening}; # not needed for article.
1507             }
1508              
1509              
1510 253 100       12641 unless ($parsed{notoc}) {
1511 239 100       1178 if ($doc->wants_toc) {
1512 159         8877 $parsed{wants_toc} = 1;
1513             }
1514             }
1515              
1516             return {
1517 253         8093 options => \%tokens,
1518             safe_options => \%parsed,
1519             doc => $doc,
1520             tex_setup_langs => $tex_setup_langs,
1521             latex_body => $latex_body,
1522             enable_secondary_footnotes => $enable_secondary_footnotes,
1523             tex_metadata => $self->file_header->tex_metadata,
1524             tex_indexes => \@indexes,
1525             # in case we need it for volumes
1526             format_id => $template_options->format_id,
1527             };
1528             }
1529              
1530             sub _interpolate_magic_comments {
1531 258     258   3281 my ($self, $format, $doc) = @_;
1532 258   100     1156 $format ||= 'DEFAULT';
1533 258         1795 my $latex = $doc->as_latex;
1534             # format is validated.
1535             # switch is gmx, no "s", we are line-based here
1536 258         7464586 my $prefix = qr{
1537             \%
1538             \x{20}+
1539             \:
1540             (?:
1541             \Q$format\E | \* | ALL
1542             )
1543             \:
1544             \x{20}+
1545             \\textbackslash\{\}
1546             }x;
1547 258         1286 my $size = qr{-?[1-9][0-9]*(?:mm|cm|pt|em)}x;
1548              
1549 258         7092 $latex =~ s/^
1550             $prefix
1551             ( # permitted commands
1552             sloppy |
1553             fussy |
1554             newpage |
1555             strut |
1556             flushbottom |
1557             raggedbottom |
1558             vfill |
1559             amusewiki[a-zA-Z]+ |
1560             clearpage |
1561             cleardoublepage |
1562             vskip \x{20}+ $size
1563             )
1564             \x{20}*
1565             $
1566             /\\$1/gmx;
1567 258         4567 $latex =~ s/^
1568             $prefix
1569             ( (this)? pagestyle )
1570             \\ \{
1571             ( plain | empty | headings | myheadings | scrheadings )
1572             \\ \}
1573             \x{20}*
1574             $
1575             /\\$1\{$3\}/gmx;
1576              
1577 258         4204 $latex =~ s/^
1578             $prefix
1579             ( enlargethispage )
1580             \\ \{
1581             ( $size )
1582             \\ \}
1583             \x{20}*
1584             $
1585             /\\$1\{$2\}/gmx;
1586              
1587 258         1217 my $regular = qr{[^\#\$\%\&\~\^\\\{\}_]+};
1588 258         4524 $latex =~ s/^
1589             $prefix
1590             markboth
1591             \\ \{
1592             ($regular)
1593             \\\}
1594             \\\{
1595             ($regular)
1596             \\\}
1597             \x{20}*
1598             $
1599             /\\markboth\{$1}\{$2\}/gmx;
1600 258         3826 $latex =~ s/^
1601             $prefix
1602             markright
1603             \\ \{
1604             ($regular)
1605             \\\}
1606             \x{20}*
1607             $
1608             /\\markright\{$1}/gmx;
1609              
1610             # with looseness, we need to attach it to the next paragraph, so
1611             # eat all the space and replace with a single \n
1612              
1613 258         3585 $latex =~ s/^
1614             $prefix
1615             looseness\=(-?[0-9])
1616             $
1617             \s*
1618             /\\looseness=$1\n/gmx;
1619              
1620             # add to toc
1621 258         5006 $latex =~ s/^
1622             $prefix
1623             addcontentsline
1624             \\\{
1625             (toc|lof|lot)
1626             \\\}
1627             \\\{
1628             (part|chapter|section|subsection)
1629             \\\}
1630             \\\{
1631             ($regular)
1632             \\\}
1633             \x{20}*
1634             $
1635             /\\addcontentsline{$1}{$2}{$3}/gmx;
1636              
1637 258         1575 return $latex;
1638             }
1639              
1640             sub _looks_like_a_sane_name {
1641 926     926   2790 my ($self, $name) = @_;
1642 926 50       2778 return unless defined $name;
1643 926         1715 my $out;
1644 926         1774 eval {
1645 926         4346 $out = Text::Amuse::Compile::TemplateOptions::check_filename($name);
1646             };
1647 926 100 66     3989 if (!$out || $@) {
1648 784         1378 $self->log_info("$name is not good: $@") if DEBUG;
1649 784         3028 return;
1650             }
1651             else {
1652 142         243 $self->log_info("$name is good") if DEBUG;
1653 142         556 return $out;
1654             }
1655             }
1656              
1657             sub _mime_for_attachment {
1658 12     12   37 my ($self, $att) = @_;
1659 12 50       43 die "Missing argument" unless $att;
1660 12         26 my $mime;
1661 12 100       219 if ($att =~ m/\.jpe?g$/) {
    50          
1662 4         30 $mime = "image/jpeg";
1663             }
1664             elsif ($att =~ m/\.png$/) {
1665 8         25 $mime = "image/png";
1666             }
1667             else {
1668 0         0 $self->log_fatal("Unrecognized attachment $att!");
1669             }
1670 12         92 return $mime;
1671             }
1672              
1673             sub _format_epub_fragment {
1674 399     399   1101 my ($self, $index) = @_;
1675 399   100     2775 return sprintf('piece%06d.xhtml', $index || 0);
1676             }
1677              
1678             sub document_indexes {
1679 260     260 1 346775 my ($self) = @_;
1680 260 100       6712 my @docs = ($self->virtual ? ($self->document->docs) : ( $self->document ));
1681 14         155 my @comments = grep { /\AINDEX +([a-z]+): (.+)/ }
1682 14         112 map { $_->string }
1683 9098         777542 grep { $_->type eq 'comment' }
1684 260         3148 map { $_->document->elements } @docs;
  291         1694  
1685 260         3299 return @comments;
1686             }
1687              
1688              
1689             1;