File Coverage

blib/lib/Text/Amuse/Compile/File.pm
Criterion Covered Total %
statement 558 663 84.1
branch 170 268 63.4
condition 40 63 63.4
subroutine 77 89 86.5
pod 33 33 100.0
total 878 1116 78.6


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

$author

\n" if $text->wants_preamble;
1069             }
1070 69         4742 my $muse_header = $self->file_header;
1071 69         1118 foreach my $aut ($muse_header->authors_as_html_list) {
1072 5         712 $epub->add_author($self->_clean_html($aut));
1073             }
1074 69         467 foreach my $topic ($muse_header->topics_as_html_list) {
1075 11         1805 $epub->add_subject($self->_clean_html($topic));
1076             }
1077 69 50       430 if ($text->header_defined->{title}) {
1078 69         1036 my $t = $header->{title};
1079 69         480 $epub->add_title($self->_clean_html($t));
1080 69 100       18913 $titlepage .= "

$t

\n" if $text->wants_preamble;
1081             }
1082             else {
1083 0         0 $epub->add_title('Untitled');
1084             }
1085              
1086 69 100       1412 if ($text->header_defined->{subtitle}) {
1087 2         21 my $st = $header->{subtitle};
1088 2 50       8 $titlepage .= "

$st

\n" if $text->wants_preamble;
1089             }
1090 69 100       943 if ($text->header_defined->{date}) {
1091 1 50       5 if ($header->{date} =~ m/([0-9]{4})/) {
1092 0         0 $epub->add_date($1);
1093             }
1094 1 50       3 $titlepage .= "

$header->{date}

" if $text->wants_preamble;
1095             }
1096              
1097 69         864 $epub->add_language($text->language_code);
1098              
1099 69         18352 $titlepage .= qq{
\n};
1100              
1101 69 50 66     319 if ($text->header_defined->{seriesname} && $text->header_defined->{seriesnumber}) {
1102             $titlepage .= qq{
}
1103             . $header->{seriesname} . ' ' . $header->{seriesnumber}
1104 2         114 . qq{};
1105             }
1106              
1107 69         2017 my @impressum_map = (
1108             [ source => [qw/add_source/], ],
1109             [ notes => [qw/add_description/], ],
1110             [ rights => [qw/add_rights/], ],
1111             [ isbn => [qw/add_identifier ISBN/], ],
1112             [ publisher => [qw/add_publisher/], ],
1113             [ colophon => [] ],
1114             );
1115              
1116 69         284 foreach my $imp (@impressum_map) {
1117 414         3422 my $k = $imp->[0];
1118 414 100       926 if ($text->header_defined->{$k}) {
1119 24         355 my $str = $header->{$k};
1120 24         48 my ($method, @additional_args) = @{$imp->[1]};
  24         74  
1121 24 100       74 if ($method) {
1122 22         87 $epub->$method($self->_clean_html($str), @additional_args);
1123             }
1124 24 100       3805 if ($k eq 'isbn') {
1125 2         8 $str = 'ISBN ' . $str;
1126             }
1127 24 100       122 $titlepage .= qq{
$str
\n}
1128             if $text->wants_postamble;
1129             }
1130             }
1131 69         657 $titlepage .= "\n\n";
1132             # create the front page
1133 69         346 my $firstpage = '';
1134             $self->tt->process($self->templates->minimal_html,
1135             {
1136 69 50 50     2385 title => $self->_remove_tags($header->{title} || 'Untitled'),
1137             text => $titlepage,
1138             html_direction => $text->html_direction,
1139             language_code => $text->language_code,
1140             },
1141             \$firstpage)
1142             or $self->log_fatal($self->tt->error);
1143              
1144 69         30100 my $tpid = $epub->add_xhtml("titlepage.xhtml", $firstpage);
1145              
1146             # main loop
1147             push @navpoints, {
1148 69   50     87271 label => $self->_clean_html($header->{title} || 'Untitled'),
1149             id => $tpid,
1150             content => "titlepage.xhtml",
1151             play_order => ++$order,
1152             level => 1,
1153             };
1154              
1155 69         189 my %internal_links;
1156             {
1157 69         143 my $piecenumber = 0;
  69         189  
1158 69         244 foreach my $piece (@pieces) {
1159             # we insert these in Text::Amuse, so it's not a wild regexp.
1160 313         1583 while ($piece->{text} =~ m/<\/a>/g) {
1161 86         210 my $label = $1;
1162             $internal_links{$label} =
1163 86         312 $self->_format_epub_fragment($toc[$piecenumber]{index});
1164             }
1165 313         550 $piecenumber++;
1166             }
1167             }
1168             my $fix_link = sub {
1169 123     123   298 my ($target) = @_;
1170 123 50       299 die unless $target;
1171 123 100       333 if (my $file = $internal_links{$target}) {
1172 109         803 return $file . '#' . $target;
1173             }
1174             else {
1175             # broken link
1176 14         102 return '#' . $target;
1177             }
1178 69         862 };
1179 69         296 while (@pieces) {
1180 313         691 my $piece = shift @pieces;
1181 313         727 my $index = shift @toc;
1182 313         694 my $xhtml = "";
1183             # print Dumper($index);
1184 313         1265 my $filename = $self->_format_epub_fragment($index->{index});
1185 313         1041 my $prefix = '*' x $index->{level};
1186 313         801 my $title = $prefix . " " . $index->{string};
1187 313         1213 $piece->{text} =~ s/(($2) . '"'/ge;
  123         273  
1188              
1189 313 50       9117 $self->tt->process($self->templates->minimal_html,
1190             {
1191             title => $self->_remove_tags($title),
1192             %$piece,
1193             },
1194             \$xhtml)
1195             or $self->log_fatal($self->tt->error);
1196              
1197 313         119273 my $id = $epub->add_xhtml($filename, $xhtml);
1198             push @navpoints, {
1199             label => $self->_clean_html($index->{string}),
1200             content => $filename,
1201             id => $id,
1202             play_order => ++$order,
1203             level => $index->{level},
1204 313         216514 };
1205             }
1206 69         439 $self->_epub_create_toc($epub, \@navpoints);
1207              
1208             # attachments
1209 69         484 foreach my $att ($text->attachments) {
1210 6 100       40099 $self->log_fatal("Referenced file $att does not exist!") unless -f $att;
1211 5         40 $epub->copy_file($att, $att, $self->_mime_for_attachment($att));
1212             }
1213             # finish
1214 68         473981 $epub->pack_zip($epubname);
1215 68         3604039 return $epubname;
1216             }
1217              
1218             sub _epub_create_toc {
1219 69     69   224 my ($self, $epub, $navpoints) = @_;
1220 69         181 my %levelnavs;
1221             # print Dumper($navpoints);
1222             NAVPOINT:
1223 69         449 foreach my $navpoint (@$navpoints) {
1224 389         2357 my %nav = %$navpoint;
1225 389         1031 my $level = delete $nav{level};
1226 389 50       1086 die "Shouldn't happen: false level: $level" unless $level;
1227 389 50       1657 die "Shouldn't happen either: $level not 1-4" unless $level =~ m/\A[1-4]\z/;
1228 389         764 my $checklevel = $level - 1;
1229              
1230 389         677 my $current;
1231 389         1064 while ($checklevel > 0) {
1232 264 100       780 if (my $parent = $levelnavs{$checklevel}) {
1233 234         1024 $current = $parent->add_navpoint(%nav);
1234 234         42020 last;
1235             }
1236 30         83 $checklevel--;
1237             }
1238 389 100       1177 unless ($current) {
1239 155         4290 $current = $epub->add_navpoint(%nav);
1240             }
1241 389         109738 for my $clear ($level..4) {
1242 1190         2479 delete $levelnavs{$clear};
1243             }
1244 389         2042 $levelnavs{$level} = $current;
1245             }
1246             # probably not needed, but let's be sure we don't leave circular
1247             # refs.
1248 69         259 foreach my $k (keys %levelnavs) {
1249 149         386 delete $levelnavs{$k};
1250             }
1251             }
1252              
1253             sub _remove_tags {
1254 494     494   1275 my ($self, $string) = @_;
1255 494 50       1306 return "" unless defined $string;
1256 494         2090 $string =~ s/<.+?>//g;
1257 494         4107 return $string;
1258             }
1259              
1260             sub _clean_html {
1261 505     505   1517 my ($self, $string) = @_;
1262 505 50       1558 return "" unless defined $string;
1263 505         1693 $string =~ s/<.+?>//g;
1264 505         1102 $string =~ s/</
1265 505         1014 $string =~ s/>/>/g;
1266 505         931 $string =~ s/"/"/g;
1267 505         910 $string =~ s/'/'/g;
1268 505         863 $string =~ s/ / /g;
1269 505         846 $string =~ s/ / /g;
1270 505         887 $string =~ s/&/&/g;
1271 505         6294 return $string;
1272             }
1273              
1274             =head2 Logging
1275              
1276             While the C accessor holds a reference to a sub, but could be
1277             very well be empty, the object uses these two methods:
1278              
1279             =over 4
1280              
1281             =item log_info(@strings)
1282              
1283             If C exists, it will call it passing the strings as arguments.
1284             Otherwise print to the standard output.
1285              
1286             =item log_fatal(@strings)
1287              
1288             Calls C, remove the lock and dies.
1289              
1290             =item parse_tex_log_file($logfile)
1291              
1292             (Internal) Parse the produced logfile for missing characters.
1293              
1294             =back
1295              
1296             =head1 INTERNAL CONSTANTS
1297              
1298             =head2 DEBUG
1299              
1300             Set from AMW_DEBUG environment.
1301              
1302             =cut
1303              
1304              
1305              
1306             sub log_info {
1307 27     27 1 2711 my ($self, @info) = @_;
1308 27         135 my $logger = $self->logger;
1309 27 100       132 if ($logger) {
1310 26         158 $logger->(@info);
1311             }
1312             else {
1313 1         68 print @info;
1314             }
1315             }
1316              
1317             sub log_fatal {
1318 1     1 1 3 my ($self, @info) = @_;
1319 1         23 $self->log_info(@info);
1320 1   50     4 my $failure = join("\n", @info) || "Fatal exception";
1321 1         43 die "$failure\n";
1322             }
1323              
1324             sub parse_tex_log_file {
1325 1     1 1 77 my ($self, $logfile) = @_;
1326 1 50       5 die "Missing file argument!" unless $logfile;
1327 1 50       38 if (-f $logfile) {
1328             # if you're wandering why we open this in raw mode: The log
1329             # file produced by XeLaTeX is utf8, but it splits the output
1330             # at 80 bytes or so. This of course sometimes, expecially
1331             # working with cyrillic scripts, cut the multibyte character
1332             # in half, producing invalid utf8 octects.
1333 1 50       76 open (my $fh, '<:raw', $logfile)
1334             or $self->log_fatal("Couldn't open $logfile $!");
1335              
1336 1         4 my %errors;
1337 1         3 my $continue = 0;
1338              
1339 1         72 while (my $line = <$fh>) {
1340 1257         2042 chomp $line;
1341 1257 100       5044 if ($line =~ m/^missing character/i) {
    100          
    100          
1342             # if we get the warning, nothing we can do about it,
1343             # but shouldn't happen.
1344 4         23 $errors{$line} = 1;
1345             }
1346             elsif ($line =~ m/^Overfull/) {
1347 2         34 $self->log_info(decode_utf8($line) . "\n");
1348 2         22 $continue++;
1349             }
1350             elsif ($continue) {
1351 2         19 $self->log_info(decode_utf8($line) . "\n\n");
1352 2         13 $continue = 0;
1353             }
1354             }
1355 1         16 close $fh;
1356 1         9 foreach my $error (sort keys %errors) {
1357 4         35 $self->log_info(decode_utf8($error) . "...\n");
1358             }
1359             }
1360             }
1361              
1362             sub cleanup {
1363 5     5 1 20012 my $self = shift;
1364 5 50       37 if (my $f = $self->status_file) {
1365 5 100       130 if (-f $f) {
1366 4 50       721 unlink $f or $self->log_fatal("Couldn't unlink $f $!");
1367             }
1368             else {
1369 1         94 $self->log_info("Couldn't find " . File::Spec->rel2abs($f));
1370             }
1371             }
1372             }
1373              
1374             sub _process_template {
1375 371     371   7978 my ($self, $template_ref, $tokens, $outfile) = @_;
1376 371         915 eval {
1377 371         1076 my $out = '';
1378 371 50 33     3368 die "Wrong usage" unless ($template_ref && $tokens && $outfile);
      33        
1379 371         4354 $self->tt->process($template_ref, $tokens, \$out);
1380 371 50       144814032 open (my $fh, '>:encoding(UTF-8)', $outfile) or die "Couldn't open $outfile $!";
1381 371         123400 print $fh $out, "\n";
1382 371         18243 close $fh;
1383             };
1384 371 50       2314 if ($@) {
1385 0         0 $self->log_fatal("Error processing template for $outfile: $@");
1386             };
1387 371         24052 return $outfile;
1388             }
1389              
1390              
1391             # method for options to pass to the tex template
1392             sub _prepare_tex_tokens {
1393 253     253   1882 my ($self, %args) = @_;
1394 253         7971 my $doc = $self->document;
1395 253         29144 my $is_slide = delete $args{is_slide};
1396 253         770 my $template_body = delete $args{template_body};
1397 253 50       1020 die "Missing required argument template_body " unless $template_body;
1398 253         548 my %tokens = %{ $self->tex_options };
  253         6215  
1399 253         12754 my $escaped_args = $self->_escape_options_hashref(ltx => \%args);
1400 253         982 foreach my $k (keys %$escaped_args) {
1401 46         96 $tokens{$k} = $escaped_args->{$k};
1402             }
1403             # now tokens have the unparsed options
1404             # now validate the options against the new shiny module
1405 253         606 my %options = (%{ $self->full_options }, %args);
  253         7106  
1406             # print Dumper($self->full_options);
1407 253         9680 my $template_options = eval { Text::Amuse::Compile::TemplateOptions->new(%options) };
  253         8807  
1408 253 100       11863 unless ($template_options) {
1409 12         296 $template_options = Text::Amuse::Compile::TemplateOptions->new;
1410 12         429 $self->log_info("# Validation failed: $@, setting one by one\n");
1411 12         125 foreach my $method ($template_options->config_setters) {
1412 504 100       6361 if (exists $options{$method}) {
1413 160         255 eval { $template_options->$method($options{$method}) };
  160         3979  
1414             }
1415             }
1416             }
1417 253         1843 my $safe_options =
1418             $self->_escape_options_hashref(ltx => $template_options->config_output);
1419              
1420             # defaults
1421 253         21179 my %parsed = (%$safe_options,
1422             class => 'scrbook',
1423             lang => 'english',
1424             mainlanguage_script => '',
1425             wants_toc => 0,
1426             );
1427              
1428              
1429 253         2515 my $fonts = $self->fonts;
1430              
1431             # not used but for legacy templates
1432 253         2733 $parsed{mainfont} = $fonts->main->name;
1433 253         1869 $parsed{sansfont} = $fonts->sans->name;
1434 253         1581 $parsed{monofont} = $fonts->mono->name;
1435 253         1319 $parsed{fontsize} = $fonts->size;
1436              
1437 253         17299 my $latex_body = $self->_interpolate_magic_comments($template_options->format_id, $doc);
1438              
1439 253         1287 my $enable_secondary_footnotes = $latex_body =~ m/\\footnoteB\{/;
1440              
1441             # check if the template body support this conditional, which is new. If not,
1442             # always setup bigfoot
1443             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1444 253 100       3219 if (index($$template_body, '[% IF enable_secondary_footnotes %]', 0) < 0) {
1445 1         4 $enable_secondary_footnotes = 1;
1446             }
1447             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1448              
1449 253         1743 my $main_is_rtl = Text::Amuse::Utils::lang_code_is_rtl($doc->language_code);
1450              
1451 253   100     9636 my $tex_setup_langs = $fonts
1452             ->compose_polyglossia_fontspec_stanza(lang => $doc->language,
1453             others => $doc->other_languages || [],
1454             enable_secondary_footnotes => $enable_secondary_footnotes,
1455             bidi => $doc->is_bidi,
1456             main_is_rtl => $main_is_rtl,
1457             has_ruby => $doc->has_ruby,
1458             is_slide => $is_slide,
1459             captions => Text::Amuse::Utils::language_code_locale_captions($doc->language_code),
1460             );
1461              
1462 253         1190 my @indexes;
1463 253 100       1829 if (my @raw_indexes = $self->document_indexes) {
1464             my $indexer = Text::Amuse::Compile::Indexer->new(latex_body => $latex_body,
1465             language_code => $doc->language_code,
1466 0     0   0 logger => $self->logger || sub { print @_ },
1467 5   33     27 index_specs => \@raw_indexes);
1468 5         10450 $latex_body = $indexer->indexed_tex_body;
1469 5         122 my %xindy_langs = (
1470             bg => 'bulgarian',
1471             cs => 'czech',
1472             da => 'danish',
1473             de => 'german-din', # ae is sorted like ae. alternative -duden
1474             el => 'greek',
1475             en => 'english',
1476             es => 'spanish-modern',
1477             et => 'estonian',
1478             fi => 'finnish',
1479             fr => 'french',
1480             hr => 'croatian',
1481             hu => 'hungarian',
1482             is => 'icelandic',
1483             it => 'italian',
1484             lv => 'latvian',
1485             lt => 'lithuanian',
1486             mk => 'macedonian',
1487             # nl => 'dutch', # unclear why missing
1488             no => 'norwegian',
1489             sr => 'croatian', # serbian is cyrillic
1490             ro => 'romanian',
1491             ru => 'russian',
1492             sk => 'slovak-small', # exists also slovak-large
1493             sl => 'slovenian',
1494             pl => 'polish',
1495             pt => 'portuguese',
1496             sq => 'albanian',
1497             sv => 'swedish',
1498             tr => 'turkish',
1499             uk => 'ukrainian',
1500             vi => 'vietnamese',
1501             );
1502             @indexes = map { +{
1503             name => $_->index_name,
1504             title => $_->index_label,
1505 7   50     132 language => $xindy_langs{$doc->language_code} || 'general',
1506             } }
1507 5         12 @{ $indexer->specifications };
  5         169  
1508             }
1509 253 100       8445 $self->_set_indexes(@indexes ? \@indexes : undef);
1510             # no cover page if header or compiler says so, or
1511             # if coverpage_only_if_toc is set and doc doesn't have a toc.
1512 253 100 100     11968 if ($self->nocoverpage or
      100        
1513             ($self->coverpage_only_if_toc && !$doc->wants_toc)) {
1514 22         1137 $parsed{nocoverpage} = 1;
1515 22         79 $parsed{class} = 'scrartcl';
1516 22         81 delete $parsed{opening}; # not needed for article.
1517             }
1518              
1519              
1520 253 100       14510 unless ($parsed{notoc}) {
1521 239 100       1350 if ($doc->wants_toc) {
1522 159         10641 $parsed{wants_toc} = 1;
1523             }
1524             }
1525              
1526             return {
1527 253         9474 options => \%tokens,
1528             safe_options => \%parsed,
1529             doc => $doc,
1530             tex_setup_langs => $tex_setup_langs,
1531             latex_body => $latex_body,
1532             enable_secondary_footnotes => $enable_secondary_footnotes,
1533             tex_metadata => $self->file_header->tex_metadata,
1534             tex_indexes => \@indexes,
1535             # in case we need it for volumes
1536             format_id => $template_options->format_id,
1537             };
1538             }
1539              
1540             sub _interpolate_magic_comments {
1541 258     258   3700 my ($self, $format, $doc) = @_;
1542 258   100     1192 $format ||= 'DEFAULT';
1543 258         2199 my $latex = $doc->as_latex;
1544             # format is validated.
1545             # switch is gmx, no "s", we are line-based here
1546 258         9697374 my $prefix = qr{
1547             \%
1548             \x{20}+
1549             \:
1550             (?:
1551             \Q$format\E | \* | ALL
1552             )
1553             \:
1554             \x{20}+
1555             \\textbackslash\{\}
1556             }x;
1557 258         1355 my $size = qr{-?[1-9][0-9]*(?:mm|cm|pt|em)}x;
1558              
1559 258         8767 $latex =~ s/^
1560             $prefix
1561             ( # permitted commands
1562             sloppy |
1563             fussy |
1564             newpage |
1565             strut |
1566             flushbottom |
1567             raggedbottom |
1568             vfill |
1569             amusewiki[a-zA-Z]+ |
1570             clearpage |
1571             cleardoublepage |
1572             vskip \x{20}+ $size
1573             )
1574             \x{20}*
1575             $
1576             /\\$1/gmx;
1577 258         4563 $latex =~ s/^
1578             $prefix
1579             ( (this)? pagestyle )
1580             \\ \{
1581             ( plain | empty | headings | myheadings | scrheadings )
1582             \\ \}
1583             \x{20}*
1584             $
1585             /\\$1\{$3\}/gmx;
1586              
1587 258         4467 $latex =~ s/^
1588             $prefix
1589             ( enlargethispage )
1590             \\ \{
1591             ( $size )
1592             \\ \}
1593             \x{20}*
1594             $
1595             /\\$1\{$2\}/gmx;
1596              
1597 258         1139 my $regular = qr{[^\#\$\%\&\~\^\\\{\}_]+};
1598 258         5378 $latex =~ s/^
1599             $prefix
1600             markboth
1601             \\ \{
1602             ($regular)
1603             \\\}
1604             \\\{
1605             ($regular)
1606             \\\}
1607             \x{20}*
1608             $
1609             /\\markboth\{$1}\{$2\}/gmx;
1610 258         4003 $latex =~ s/^
1611             $prefix
1612             markright
1613             \\ \{
1614             ($regular)
1615             \\\}
1616             \x{20}*
1617             $
1618             /\\markright\{$1}/gmx;
1619              
1620             # with looseness, we need to attach it to the next paragraph, so
1621             # eat all the space and replace with a single \n
1622              
1623 258         3656 $latex =~ s/^
1624             $prefix
1625             looseness\=(-?[0-9])
1626             $
1627             \s*
1628             /\\looseness=$1\n/gmx;
1629              
1630             # add to toc
1631 258         5486 $latex =~ s/^
1632             $prefix
1633             addcontentsline
1634             \\\{
1635             (toc|lof|lot)
1636             \\\}
1637             \\\{
1638             (part|chapter|section|subsection)
1639             \\\}
1640             \\\{
1641             ($regular)
1642             \\\}
1643             \x{20}*
1644             $
1645             /\\addcontentsline{$1}{$2}{$3}/gmx;
1646              
1647 258         1669 return $latex;
1648             }
1649              
1650             sub _looks_like_a_sane_name {
1651 926     926   2951 my ($self, $name) = @_;
1652 926 50       3337 return unless defined $name;
1653 926         2043 my $out;
1654 926         2099 eval {
1655 926         6138 $out = Text::Amuse::Compile::TemplateOptions::check_filename($name);
1656             };
1657 926 100 66     4301 if (!$out || $@) {
1658 784         1620 $self->log_info("$name is not good: $@") if DEBUG;
1659 784         3624 return;
1660             }
1661             else {
1662 142         272 $self->log_info("$name is good") if DEBUG;
1663 142         627 return $out;
1664             }
1665             }
1666              
1667             sub _mime_for_attachment {
1668 12     12   73 my ($self, $att) = @_;
1669 12 50       53 die "Missing argument" unless $att;
1670 12         26 my $mime;
1671 12 100       187 if ($att =~ m/\.jpe?g$/) {
    50          
1672 4         18 $mime = "image/jpeg";
1673             }
1674             elsif ($att =~ m/\.png$/) {
1675 8         19 $mime = "image/png";
1676             }
1677             else {
1678 0         0 $self->log_fatal("Unrecognized attachment $att!");
1679             }
1680 12         92 return $mime;
1681             }
1682              
1683             sub _format_epub_fragment {
1684 399     399   886 my ($self, $index) = @_;
1685 399   100     2851 return sprintf('piece%06d.xhtml', $index || 0);
1686             }
1687              
1688             sub document_indexes {
1689 260     260 1 402180 my ($self) = @_;
1690 260 100       8082 my @docs = ($self->virtual ? ($self->document->docs) : ( $self->document ));
1691 14         164 my @comments = grep { /\AINDEX +([a-z]+): (.+)/ }
1692 14         123 map { $_->string }
1693 9098         1038368 grep { $_->type eq 'comment' }
1694 260         3094 map { $_->document->elements } @docs;
  291         2006  
1695 260         3423 return @comments;
1696             }
1697              
1698              
1699             1;