File Coverage

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


line stmt bran cond sub pod time code
1             package Text::Amuse::Compile::File;
2              
3 58     58   142316 use strict;
  58         133  
  58         1495  
4 58     58   271 use warnings;
  58         115  
  58         1160  
5 58     58   244 use utf8;
  58         147  
  58         262  
6              
7 58     58   1824 use constant { DEBUG => $ENV{AMW_DEBUG} };
  58         107  
  58         3691  
8              
9             # core
10             # use Data::Dumper;
11 58     58   1164 use File::Copy qw/move/;
  58         5965  
  58         2715  
12 58     58   27269 use Encode qw/decode_utf8/;
  58         455598  
  58         3755  
13              
14             # needed
15 58     58   21498 use Template::Tiny;
  58         62385  
  58         2298  
16 58     58   31327 use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
  58         2384083  
  58         7808  
17 58     58   24459 use EBook::EPUB::Lite;
  58         10988527  
  58         2113  
18 58     58   493 use File::Copy;
  58         116  
  58         2986  
19 58     58   300 use File::Spec;
  58         119  
  58         1150  
20 58     58   50906 use IPC::Run qw(run);
  58         1299239  
  58         2846  
21 58     58   437 use File::Basename ();
  58         641  
  58         754  
22 58     58   41813 use Path::Tiny ();
  58         592628  
  58         1560  
23              
24             # ours
25 58     58   26399 use PDF::Imposition;
  58         13299042  
  58         2221  
26 58     58   1427 use Text::Amuse;
  58         58579  
  58         1769  
27 58         4246 use Text::Amuse::Functions qw/muse_fast_scan_header
28             muse_to_object
29 58     58   1112 muse_format_line/;
  58         3569  
30 58     58   370 use Text::Amuse::Utils;
  58         125  
  58         1494  
31              
32 58     58   29722 use Text::Amuse::Compile::Templates;
  58         193  
  58         1881  
33 58     58   26921 use Text::Amuse::Compile::TemplateOptions;
  58         228  
  58         2672  
34 58     58   26971 use Text::Amuse::Compile::MuseHeader;
  58         192  
  58         2102  
35 58     58   26085 use Text::Amuse::Compile::Indexer;
  58         191  
  58         2413  
36 58     58   398 use Types::Standard qw/Str Bool Object Maybe CodeRef HashRef InstanceOf ArrayRef/;
  58         123  
  58         417  
37 58     58   64937 use Moo;
  58         152  
  58         301  
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 287     287   4352 my $self = shift;
155             return Text::Amuse::Compile::Templates->new(ttdir => $self->ttdir,
156 287         4880 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 303     303   3672 my $self = shift;
181 303         590 my $header;
182 303 100       1537 if ($self->virtual) {
183 19         325 $header = { $self->document->headers };
184             }
185             else {
186 284         1182 $header = muse_fast_scan_header($self->muse_file);
187 284 50 33     126375 $self->log_fatal("Not a muse file!") unless $header && %$header;
188             }
189 303         8246 return Text::Amuse::Compile::MuseHeader->new($header);
190             }
191              
192             sub _build_is_deleted {
193 297     297   9710 return shift->file_header->is_deleted;
194             }
195              
196             sub _build_wants_slides {
197 16     16   415 return shift->file_header->wants_slides;
198             }
199              
200             sub _build_document {
201 276     276   4550 my $self = shift;
202 276         648 my %args;
203 276 50       1092 die "virtual files need an already built document" if $self->virtual;
204 276 100       1297 if (my $fileobj = $self->fileobj) {
205 271         1412 %args = $fileobj->text_amuse_constructor;
206             }
207             else {
208 5         22 %args = (file => $self->muse_file);
209             }
210 276         3043 return Text::Amuse->new(%args,
211             include_paths => $self->include_paths,
212             );
213             }
214              
215             sub _build_tex_options {
216 246     246   3853 my $self = shift;
217 246         3902 return $self->_escape_options_hashref(ltx => $self->full_options);
218             }
219              
220             sub _build_html_options {
221 113     113   2293 my $self = shift;
222 113         1975 return $self->_escape_options_hashref(html => $self->full_options);
223             }
224              
225             sub _build_full_options {
226 288     288   3539 my $self = shift;
227             # merge the options with the ones found in the header.
228             # print "Building full options\n" if DEBUG;
229 288         615 my %options = %{ $self->options };
  288         1598  
230             # these values are picked from the file, if not provided by the compiler
231 288         1236 foreach my $override (qw/cover coverwidth nocoverpage notoc
232             impressum
233             continuefootnotes
234             centerchapter
235             centersection
236             nofinalpage/) {
237 2592         69771 $options{$override} = $self->$override;
238             }
239 288         12934 return \%options;
240             }
241              
242             sub _build_volumes {
243 238     238   3440 my $self = shift;
244 238         521 my @volumes;
245 238 100 66     1855 if (!$self->virtual and -f $self->muse_file) {
246 222         1181 my @lines = Path::Tiny::path($self->muse_file)->lines_utf8;
247              
248 222 100       131821 if (grep { /^; +;;;#\w+/ } @lines) {
  18647         28843  
249 2         10 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         5 while (@lines) {
258 70         91 my $line = shift @lines;
259             # accumulate in the current pile until there's a blank line
260 70         169 my $blank = $line =~ m/\A\s*\z/;
261              
262 70 100       141 if ($line =~ m/\A; +;;;(#[A-Za-z0-9_-]+\w+.*)\z/s) {
    100          
263 6         20 my $directive = $1;
264 6         9 $in_meta = 0;
265 6 100       13 if (!$in_volume_meta) {
266             # entered a new volume
267 5         7 $in_volume_meta = 1;
268 5 100       21 if (@current) {
269 3         17 push @volumes, [ @current_meta, @original_meta, @current ];
270             }
271 5         10 @current = @current_meta = ();
272             }
273 6         10 push @current_meta, $directive;
274 6         11 next LINE;
275             }
276             elsif (!$blank) {
277 31         36 $in_volume_meta = 0;
278             }
279              
280 64 100       79 if ($in_meta) {
281 12         24 push @original_meta, $line;
282             }
283             else {
284 52         95 push @current, $line;
285             }
286             }
287             # end of loop, flush the stack
288 2 50       8 if (@current) {
289 2         11 push @volumes, [ @current_meta, @original_meta, @current ];
290             }
291             # print Dumper(\@original_meta, \@volumes);
292             }
293             }
294 238         5402 return \@volumes;
295             }
296              
297             sub cover {
298 382     382 1 831 my $self = shift;
299             # options passed take precendence
300 382 100       1718 if (exists $self->options->{cover}) {
301 48 100       312 if ($self->_looks_like_a_sane_name($self->options->{cover})) {
302 26         174 return $self->options->{cover};
303             }
304             else {
305 22         129 return '';
306             }
307             }
308 334 100       5757 if (my $cover = $self->file_header->cover) {
309             # already validated by the MuseHeader class
310 37         1346 return $cover;
311             }
312             }
313              
314             sub coverwidth {
315 288     288 1 660 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 288 100       1355 if (exists $self->options->{coverwidth}) {
320             # print "Picking coverwidth from options\n";
321 8         29 return $self->options->{coverwidth};
322             }
323             # obey this thing only if the file set the cover
324 280 100       4409 if ($self->file_header->cover) {
325             # print "Picking coverwidth from file\n";
326 43   50     1889 return $self->file_header->coverwidth || 1;
327             }
328 237         6393 return 1;
329             }
330              
331             sub nocoverpage {
332 537     537 1 2154 shift->_look_at_header('nocoverpage');
333             }
334              
335             sub notoc {
336 288     288 1 863 shift->_look_at_header('notoc');
337             }
338              
339             sub nofinalpage {
340 288     288 1 991 shift->_look_at_header('nofinalpage');
341             }
342              
343             sub impressum {
344 288     288 1 936 shift->_look_at_header('impressum');
345             }
346              
347 288     288 1 931 sub continuefootnotes { shift->_look_at_header('continuefootnotes') }
348 469     469 1 8188 sub centerchapter { shift->_look_at_header('centerchapter') }
349 469     469 1 1566 sub centersection { shift->_look_at_header('centersection') }
350              
351             sub _look_at_header {
352 2627     2627   5280 my ($self, $method) = @_;
353             # these are booleans, so we enforce them
354 2627 100 100     38594 !!$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 857     857   10551 my ($self, $format, $ref) = @_;
387 857 50 33     4104 die "Wrong usage of internal method" unless $format && $ref;
388 857         1482 my %out;
389 857         4555 foreach my $k (keys %$ref) {
390 14306 100       3719340 if (defined $ref->{$k}) {
391 12905 100 100     46549 if ($k eq 'logo' or $k eq 'cover') {
    100          
392 866 100       3926 if (my $checked = $self->_looks_like_a_sane_name($ref->{$k})) {
393 116         364 $out{$k} = $checked;
394             }
395             }
396             elsif (ref($ref->{$k})) {
397             # pass it verbatim
398 283         1077 $out{$k} = $ref->{$k};
399             }
400             else {
401 11756         27605 $out{$k} = muse_format_line($format, $ref->{$k});
402             }
403             }
404             else {
405 1401         3364 $out{$k} = undef;
406             }
407             }
408 857         184957 return \%out;
409             }
410              
411              
412             sub muse_file {
413 733     733 1 1408 my $self = shift;
414 733         12268 return $self->name . $self->suffix;
415             }
416              
417             sub status_file {
418 301     301 1 2221 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 316     316   1822 return qw/.sl.tex .tex .a4.pdf .lt.pdf .ok .html .bare.html .epub .zip/;
451             }
452              
453             sub _latex_extensions {
454 632     632   2166 return qw/.pdf .log/;
455             }
456              
457             sub _slides_extensions {
458 316     316   681 my $self = shift;
459 316         785 return map { '.sl' . $_ } $self->_latex_extensions;
  632         2531  
460             }
461              
462             sub _latex_leftover_extensions {
463 632     632   2548 return qw/.aux .nav .out .snm .toc .tuc .vrb/;
464             }
465              
466             sub _slides_leftover_extensions {
467 316     316   722 my $self = shift;
468 316         831 return map { '.sl' . $_ } $self->_latex_leftover_extensions;
  2212         4116  
469             }
470              
471             sub purged_extensions {
472 316     316 1 2741 my $self = shift;
473 316         1278 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 316         2703 return @exts;
481             }
482              
483             sub purge {
484 761     761 1 2766 my ($self, @exts) = @_;
485 761         1303 $self->log_info("Started purging\n") if DEBUG;
486 761         2308 my $basename = $self->name;
487 761         1897 foreach my $ext (@exts) {
488 8509 50       19031 $self->log_fatal("wtf? Refusing to purge " . $basename . $ext)
489             if ($ext eq '.muse');
490 8509         14226 my $target = $basename . $ext;
491 8509 100       85136 if (-f $target) {
492 139         326 $self->log_info("Removing target $target\n") if DEBUG;
493 139 50       6774 unlink $target or $self->log_fatal("Couldn't unlink $target $!");
494             }
495             }
496 761         3967 $self->log_info("Ended purging\n") if DEBUG;
497             }
498              
499             sub purge_all {
500 298     298 1 12683 my $self = shift;
501 298         1350 $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   1176 my ($self, %tokens) = @_;
568 181         582 my $out = '';
569 181         3502 $self->tt->process($self->templates->css, {
570             fonts => $self->fonts,
571             centersection => $self->centersection,
572             centerchapter => $self->centerchapter,
573             %tokens
574             }, \$out);
575 181         2346243 return $out;
576             }
577              
578              
579             sub html {
580 112     112 1 27633 my $self = shift;
581 112         380 $self->purge('.html');
582 112         656 my $outfile = $self->name . '.html';
583 112         2810 my $doc = $self->document;
584 112   50     9362 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         834486 options => { %{$self->html_options} },
  112         4138  
591             },
592             $outfile);
593             }
594              
595             sub bare_html {
596 8     8 1 1518 my $self = shift;
597 8         32 $self->purge('.bare.html');
598 8         42 my $outfile = $self->name . '.bare.html';
599             $self->_process_template($self->templates->bare_html,
600             {
601             doc => $self->document,
602 8         196 options => { %{$self->html_options} },
  8         902  
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 240     240 1 18363 my ($self, @args) = @_;
661 240         1210 my $texfile = $self->name . '.tex';
662 240 50       1094 $self->log_fatal("Wrong usage") if @args % 2;
663 240         662 my %arguments = @args;
664 240 100 100     1853 unless (@args || $self->standalone) {
665 7         33 %arguments = (
666             twoside => 0,
667             oneside => 1,
668             bcor => '0mm',
669             );
670             }
671 240         988 $self->purge('.tex');
672 240         6150 my $template_body = $self->templates->latex;
673 240         1414 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 240         13498 my $volumes = $self->volumes;
685 240 100 66     8554 if ($volumes and @$volumes > 1) {
686 2         9 my $tex_parse = qr{\A(.*\\begin\{document\})(.*)(\\end\{document\}.*)}s;
687 2         4 my $full;
688 2         14 $self->tt->process($template_body, $tokens, \$full);
689             # print $full;
690 2 50       665444 if ($full =~ m/$tex_parse/s) {
691 2         13 my ($preamble, $body, $end) = ($1, $2, $3);
692             # print Dumper([$preamble, $body, $end ]);
693 2         7 my @pieces = ($preamble);
694 2         10 my $last = scalar $#$volumes;
695              
696             # check if the template is custom
697 2 50       39 my $toc_i = $$template_body =~ m/latex_body.*tableofcontents/s ? $last : 0;
698 2 50       20 my $idx_i = $$template_body =~ m/printindex.*latex_body/s ? 0 : $last;
699              
700 2         7 for (my $i = 0; $i <= $last; $i++) {
701 5         964 my $vol = $volumes->[$i];
702 5         59 my $doc = muse_to_object(join('', @$vol));
703 5         4097 my $latex = $self->_interpolate_magic_comments($tokens->{format_id}, $doc);
704              
705 5 100       25 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         9 index_specs => \@raw_indexes);
710 2         270 $latex = $indexer->indexed_tex_body;
711             }
712              
713             my %partial_tokens = (
714 5         63 options => { %{ $tokens->{options} } },
715 5         141 safe_options => { %{ $tokens->{safe_options} } },
716             tex_setup_langs => 'DUMMY', # irrelevant
717             doc => $doc,
718             latex_body => $latex,
719 5         12 tex_indexes => [ @{ $tokens->{tex_indexes} } ],
  5         32  
720             );
721 5 100       23 if ($i != $toc_i) {
722 3         7 $partial_tokens{safe_options}{wants_toc} = 0;
723             }
724 5 100       15 if ($i != $idx_i) {
725 3         7 $partial_tokens{tex_indexes} = [];
726             }
727              
728             # print Dumper(\%partial_tokens);
729              
730              
731             # here clear wants_toc / indexes
732              
733 5         8 my $out;
734 5         34 $self->tt->process($template_body, \%partial_tokens, \$out);
735 5 50       1653105 if ($out =~ m/$tex_parse/s) {
736 5         131 push @pieces, $2;
737             }
738             }
739 2         2673 push @pieces, $end;
740 2         17 Path::Tiny::path($texfile)->spew_utf8(@pieces);
741 2         1321 return $texfile;
742             }
743             }
744 238         1406 $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 27 my ($self) = @_;
763             # no slides for virtual files
764 9 50       43 return if $self->virtual;
765 9         41 $self->purge('.sl.tex');
766 9         58 my $texfile = $self->name . '.sl.tex';
767 9 50       184 return unless $self->wants_slides;
768 9         261 my $template_body = $self->templates->slides;
769 9         52 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 1761 my $self = shift;
919 25         160 $self->purge('.zip');
920 25         115 my $zipname = $self->name . '.zip';
921 25         313 my $tempdir = File::Temp->newdir;
922 25         11913 my $tempdirname = $tempdir->dirname;
923 25         225 foreach my $todo (qw/tex html/) {
924 50         11014 my $target = $self->name . '.' . $todo;
925 50 100       569 unless (-f $target) {
926 24         210 $self->$todo;
927             }
928 50 50       687 $self->log_fatal("Couldn't produce $target") unless -f $target;
929 50 50       392 copy($target, $tempdirname)
930             or $self->log_fatal("Couldn't copy $target in $tempdirname $!");
931             }
932 25         32087 copy ($self->name . '.muse', $tempdirname);
933              
934 25         8496 my $text = $self->document;
935 25         313 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       833 if (my $cover = $self->cover) {
940 8 100       146 if (-f $cover) {
941 6 50       37 copy($cover, $tempdirname)
942             or $self->log_info("Cannot find the cover to attach");
943             }
944             }
945 25         2747 my $zip = Archive::Zip->new;
946 25 50       1261 $zip->addTree($tempdirname, $self->name) == AZ_OK
947             or $self->log_fatal("Failure zipping $tempdirname");
948 25 50       228824 $zip->writeToFileNamed($zipname) == AZ_OK
949             or $self->log_fatal("Failure writing $zipname");
950 25         177399 return $zipname;
951             }
952              
953              
954             sub epub {
955 69     69 1 960 my $self = shift;
956 69         288 $self->purge('.epub');
957 69         496 my $epubname = $self->name . '.epub';
958              
959 69         1795 my $text = $self->document;
960              
961 69         4329 my @pieces;
962 69 100       540 if ($text->can('as_splat_html_with_attrs')) {
963 10         60 @pieces = $text->as_splat_html_with_attrs;
964             }
965             else {
966             @pieces = map {
967 59         505 +{
968 176         433650 text => $_,
969             language_code => $text->language_code,
970             html_direction => $text->html_direction,
971             }
972             } $text->as_splat_html;
973             }
974 69         2717 my @toc = $text->raw_html_toc;
975             # fixed in 0.51
976 69 50       348978 if (my $missing = scalar(@pieces) - scalar(@toc)) {
977 0         0 $self->log_fatal("This shouldn't happen: missing pieces: $missing");
978             }
979 69         2337 my $epub = EBook::EPUB::Lite->new;
980              
981             # embedded CSS
982 69 100       481858 if ($self->epub_embed_fonts) {
983             # pass all
984 67 50       615 if (my $fonts = $self->fonts) {
985 67         167 my %done;
986 67         236 foreach my $family (@{ $fonts->families }) {
  67         524  
987 201 100       5792 if ($family->has_files) {
988 12         197 foreach my $ff (@{ $family->font_files }) {
  12         35  
989             # do not produce duplicate entries when using
990             # the same file
991 48 50       349 unless ($done{$ff->basename}) {
992 48         955 $epub->copy_file($ff->file,
993             $ff->basename,
994             $ff->mimetype);
995 48         31421 $done{$ff->basename}++;
996             }
997             }
998             }
999             }
1000             }
1001             }
1002 69         1556 my $css = $self->_render_css(
1003             epub => 1,
1004             epub_embed_fonts => $self->epub_embed_fonts,
1005             );
1006 69         724 $epub->add_stylesheet("stylesheet.css" => $css);
1007              
1008             # build the title page and some metadata
1009 69         37837 my $header = $text->header_as_html;
1010              
1011 69         77237 my @navpoints;
1012 69         187 my $order = 0;
1013              
1014 69 100       346 if (my $cover = $self->cover) {
1015 9 100       133 if (-f $cover) {
1016 7 50       332 if (my $basename = File::Basename::basename($cover)) {
1017 7         21 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         89 $coverpage =~ s/__IMAGE__/$basename/;
1037 7         61 my $cover_id = $epub->copy_file($cover, $basename,
1038             $self->_mime_for_attachment($basename));
1039 7         4701 $epub->add_meta_item(cover => $cover_id);
1040 7         5118 my $cpid = $epub->add_xhtml("coverpage.xhtml", $coverpage);
1041 7         7372 $epub->guide->add_reference(type => 'cover', href => "coverpage.xhtml");
1042 7         5079 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         2246 my $titlepage = qq{
\n};
1054              
1055 69 100       420 if ($text->header_defined->{author}) {
1056 16         384 my $author = $header->{author};
1057 16         81 $epub->add_author($self->_clean_html($author));
1058 16 100       2849 $titlepage .= "

$author

\n" if $text->wants_preamble;
1059             }
1060 69         3361 my $muse_header = $self->file_header;
1061 69         1016 foreach my $aut ($muse_header->authors_as_html_list) {
1062 5         463 $epub->add_author($self->_clean_html($aut));
1063             }
1064 69         471 foreach my $topic ($muse_header->topics_as_html_list) {
1065 11         985 $epub->add_subject($self->_clean_html($topic));
1066             }
1067 69 50       376 if ($text->header_defined->{title}) {
1068 69         846 my $t = $header->{title};
1069 69         575 $epub->add_title($self->_clean_html($t));
1070 69 100       14703 $titlepage .= "

$t

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

$st

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

$header->{date}

" if $text->wants_preamble;
1085             }
1086              
1087 69         766 $epub->add_language($text->language_code);
1088              
1089 69         10315 $titlepage .= qq{
\n};
1090              
1091 69 50 66     258 if ($text->header_defined->{seriesname} && $text->header_defined->{seriesnumber}) {
1092             $titlepage .= qq{
}
1093             . $header->{seriesname} . ' ' . $header->{seriesnumber}
1094 2         172 . qq{};
1095             }
1096              
1097 69         1729 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         250 foreach my $imp (@impressum_map) {
1107 414         2749 my $k = $imp->[0];
1108 414 100       815 if ($text->header_defined->{$k}) {
1109 24         311 my $str = $header->{$k};
1110 24         108 my ($method, @additional_args) = @{$imp->[1]};
  24         65  
1111 24 100       82 if ($method) {
1112 22         58 $epub->$method($self->_clean_html($str), @additional_args);
1113             }
1114 24 100       2709 if ($k eq 'isbn') {
1115 2         8 $str = 'ISBN ' . $str;
1116             }
1117 24 100       103 $titlepage .= qq{
$str
\n}
1118             if $text->wants_postamble;
1119             }
1120             }
1121 69         623 $titlepage .= "\n\n";
1122             # create the front page
1123 69         160 my $firstpage = '';
1124             $self->tt->process($self->templates->minimal_html,
1125             {
1126 69 50 50     1378 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         17975 my $tpid = $epub->add_xhtml("titlepage.xhtml", $firstpage);
1135              
1136             # main loop
1137             push @navpoints, {
1138 69   50     58976 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         221 my %internal_links;
1146             {
1147 69         278 my $piecenumber = 0;
  69         148  
1148 69         196 foreach my $piece (@pieces) {
1149             # we insert these in Text::Amuse, so it's not a wild regexp.
1150 313         1294 while ($piece->{text} =~ m/<\/a>/g) {
1151 86         171 my $label = $1;
1152             $internal_links{$label} =
1153 86         349 $self->_format_epub_fragment($toc[$piecenumber]{index});
1154             }
1155 313         523 $piecenumber++;
1156             }
1157             }
1158             my $fix_link = sub {
1159 123     123   248 my ($target) = @_;
1160 123 50       200 die unless $target;
1161 123 100       295 if (my $file = $internal_links{$target}) {
1162 109         678 return $file . '#' . $target;
1163             }
1164             else {
1165             # broken link
1166 14         87 return '#' . $target;
1167             }
1168 69         640 };
1169 69         261 while (@pieces) {
1170 313         600 my $piece = shift @pieces;
1171 313         975 my $index = shift @toc;
1172 313         614 my $xhtml = "";
1173             # print Dumper($index);
1174 313         885 my $filename = $self->_format_epub_fragment($index->{index});
1175 313         891 my $prefix = '*' x $index->{level};
1176 313         838 my $title = $prefix . " " . $index->{string};
1177 313         1211 $piece->{text} =~ s/(($2) . '"'/ge;
  123         247  
1178              
1179 313 50       5661 $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         67363 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         120927 };
1195             }
1196 69         514 $self->_epub_create_toc($epub, \@navpoints);
1197              
1198             # attachments
1199 69         431 foreach my $att ($text->attachments) {
1200 6 100       16638 $self->log_fatal("Referenced file $att does not exist!") unless -f $att;
1201 5         32 $epub->copy_file($att, $att, $self->_mime_for_attachment($att));
1202             }
1203             # finish
1204 68         326183 $epub->pack_zip($epubname);
1205 68         2624545 return $epubname;
1206             }
1207              
1208             sub _epub_create_toc {
1209 69     69   224 my ($self, $epub, $navpoints) = @_;
1210 69         132 my %levelnavs;
1211             # print Dumper($navpoints);
1212             NAVPOINT:
1213 69         361 foreach my $navpoint (@$navpoints) {
1214 389         2149 my %nav = %$navpoint;
1215 389         787 my $level = delete $nav{level};
1216 389 50       770 die "Shouldn't happen: false level: $level" unless $level;
1217 389 50       1411 die "Shouldn't happen either: $level not 1-4" unless $level =~ m/\A[1-4]\z/;
1218 389         639 my $checklevel = $level - 1;
1219              
1220 389         497 my $current;
1221 389         1177 while ($checklevel > 0) {
1222 264 100       786 if (my $parent = $levelnavs{$checklevel}) {
1223 234         1100 $current = $parent->add_navpoint(%nav);
1224 234         30593 last;
1225             }
1226 30         70 $checklevel--;
1227             }
1228 389 100       900 unless ($current) {
1229 155         2953 $current = $epub->add_navpoint(%nav);
1230             }
1231 389         82492 for my $clear ($level..4) {
1232 1190         1845 delete $levelnavs{$clear};
1233             }
1234 389         1583 $levelnavs{$level} = $current;
1235             }
1236             # probably not needed, but let's be sure we don't leave circular
1237             # refs.
1238 69         379 foreach my $k (keys %levelnavs) {
1239 149         327 delete $levelnavs{$k};
1240             }
1241             }
1242              
1243             sub _remove_tags {
1244 494     494   1095 my ($self, $string) = @_;
1245 494 50       1193 return "" unless defined $string;
1246 494         1130 $string =~ s/<.+?>//g;
1247 494         3410 return $string;
1248             }
1249              
1250             sub _clean_html {
1251 505     505   1148 my ($self, $string) = @_;
1252 505 50       1130 return "" unless defined $string;
1253 505         1566 $string =~ s/<.+?>//g;
1254 505         835 $string =~ s/</
1255 505         821 $string =~ s/>/>/g;
1256 505         827 $string =~ s/"/"/g;
1257 505         742 $string =~ s/'/'/g;
1258 505         694 $string =~ s/ / /g;
1259 505         695 $string =~ s/ / /g;
1260 505         706 $string =~ s/&/&/g;
1261 505         4733 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 1877 my ($self, @info) = @_;
1298 27         104 my $logger = $self->logger;
1299 27 100       69 if ($logger) {
1300 26         94 $logger->(@info);
1301             }
1302             else {
1303 1         62 print @info;
1304             }
1305             }
1306              
1307             sub log_fatal {
1308 1     1 1 6 my ($self, @info) = @_;
1309 1         5 $self->log_info(@info);
1310 1   50     14 my $failure = join("\n", @info) || "Fatal exception";
1311 1         45 die "$failure\n";
1312             }
1313              
1314             sub parse_tex_log_file {
1315 1     1 1 53 my ($self, $logfile) = @_;
1316 1 50       5 die "Missing file argument!" unless $logfile;
1317 1 50       38 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       68 open (my $fh, '<:raw', $logfile)
1324             or $self->log_fatal("Couldn't open $logfile $!");
1325              
1326 1         3 my %errors;
1327 1         3 my $continue = 0;
1328              
1329 1         45 while (my $line = <$fh>) {
1330 1257         1340 chomp $line;
1331 1257 100       3171 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         23 $errors{$line} = 1;
1335             }
1336             elsif ($line =~ m/^Overfull/) {
1337 2         14 $self->log_info(decode_utf8($line) . "\n");
1338 2         15 $continue++;
1339             }
1340             elsif ($continue) {
1341 2         7 $self->log_info(decode_utf8($line) . "\n\n");
1342 2         9 $continue = 0;
1343             }
1344             }
1345 1         22 close $fh;
1346 1         13 foreach my $error (sort keys %errors) {
1347 4         16 $self->log_info(decode_utf8($error) . "...\n");
1348             }
1349             }
1350             }
1351              
1352             sub cleanup {
1353 5     5 1 9653 my $self = shift;
1354 5 50       22 if (my $f = $self->status_file) {
1355 5 100       85 if (-f $f) {
1356 4 50       264 unlink $f or $self->log_fatal("Couldn't unlink $f $!");
1357             }
1358             else {
1359 1         74 $self->log_info("Couldn't find " . File::Spec->rel2abs($f));
1360             }
1361             }
1362             }
1363              
1364             sub _process_template {
1365 367     367   6401 my ($self, $template_ref, $tokens, $outfile) = @_;
1366 367         763 eval {
1367 367         844 my $out = '';
1368 367 50 33     2874 die "Wrong usage" unless ($template_ref && $tokens && $outfile);
      33        
1369 367         3258 $self->tt->process($template_ref, $tokens, \$out);
1370 367 50       76476472 open (my $fh, '>:encoding(UTF-8)', $outfile) or die "Couldn't open $outfile $!";
1371 367         260304 print $fh $out, "\n";
1372 367         14341 close $fh;
1373             };
1374 367 50       2260 if ($@) {
1375 0         0 $self->log_fatal("Error processing template for $outfile: $@");
1376             };
1377 367         12909 return $outfile;
1378             }
1379              
1380              
1381             # method for options to pass to the tex template
1382             sub _prepare_tex_tokens {
1383 249     249   1067 my ($self, %args) = @_;
1384 249         4721 my $doc = $self->document;
1385 249         20660 my $is_slide = delete $args{is_slide};
1386 249         677 my $template_body = delete $args{template_body};
1387 249 50       902 die "Missing required argument template_body " unless $template_body;
1388 249         508 my %tokens = %{ $self->tex_options };
  249         4429  
1389 249         10043 my $escaped_args = $self->_escape_options_hashref(ltx => \%args);
1390 249         1106 foreach my $k (keys %$escaped_args) {
1391 46         103 $tokens{$k} = $escaped_args->{$k};
1392             }
1393             # now tokens have the unparsed options
1394             # now validate the options against the new shiny module
1395 249         922 my %options = (%{ $self->full_options }, %args);
  249         4694  
1396             # print Dumper($self->full_options);
1397 249         3866 my $template_options = eval { Text::Amuse::Compile::TemplateOptions->new(%options) };
  249         6341  
1398 249 100       22159 unless ($template_options) {
1399 12         198 $template_options = Text::Amuse::Compile::TemplateOptions->new;
1400 12         525 $self->log_info("# Validation failed: $@, setting one by one\n");
1401 12         140 foreach my $method ($template_options->config_setters) {
1402 504 100       5386 if (exists $options{$method}) {
1403 160         223 eval { $template_options->$method($options{$method}) };
  160         2890  
1404             }
1405             }
1406             }
1407 249         1645 my $safe_options =
1408             $self->_escape_options_hashref(ltx => $template_options->config_output);
1409              
1410             # defaults
1411 249         9004 my %parsed = (%$safe_options,
1412             class => 'scrbook',
1413             lang => 'english',
1414             mainlanguage_script => '',
1415             wants_toc => 0,
1416             );
1417              
1418              
1419 249         1785 my $fonts = $self->fonts;
1420              
1421             # not used but for legacy templates
1422 249         1801 $parsed{mainfont} = $fonts->main->name;
1423 249         1302 $parsed{sansfont} = $fonts->sans->name;
1424 249         1163 $parsed{monofont} = $fonts->mono->name;
1425 249         1036 $parsed{fontsize} = $fonts->size;
1426              
1427 249         7404 my $latex_body = $self->_interpolate_magic_comments($template_options->format_id, $doc);
1428              
1429 249         1329 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 249 100       8298 if (index($$template_body, '[% IF enable_secondary_footnotes %]', 0) < 0) {
1435 1         3 $enable_secondary_footnotes = 1;
1436             }
1437             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1438              
1439 249   100     1562 my $tex_setup_langs = $fonts
1440             ->compose_polyglossia_fontspec_stanza(lang => $doc->language,
1441             others => $doc->other_languages || [],
1442             enable_secondary_footnotes => $enable_secondary_footnotes,
1443             bidi => $doc->is_bidi,
1444             has_ruby => $doc->has_ruby,
1445             is_slide => $is_slide,
1446             captions => Text::Amuse::Utils::language_code_locale_captions($doc->language_code),
1447             );
1448              
1449 249         805 my @indexes;
1450 249 100       1276 if (my @raw_indexes = $self->document_indexes) {
1451             my $indexer = Text::Amuse::Compile::Indexer->new(latex_body => $latex_body,
1452             language_code => $doc->language_code,
1453 0     0   0 logger => $self->logger || sub { print @_ },
1454 5   50     27 index_specs => \@raw_indexes);
1455 5         6691 $latex_body = $indexer->indexed_tex_body;
1456 5         123 my %xindy_langs = (
1457             bg => 'bulgarian',
1458             cs => 'czech',
1459             da => 'danish',
1460             de => 'german-din', # ae is sorted like ae. alternative -duden
1461             el => 'greek',
1462             en => 'english',
1463             es => 'spanish-modern',
1464             et => 'estonian',
1465             fi => 'finnish',
1466             fr => 'french',
1467             hr => 'croatian',
1468             hu => 'hungarian',
1469             is => 'icelandic',
1470             it => 'italian',
1471             lv => 'latvian',
1472             lt => 'lithuanian',
1473             mk => 'macedonian',
1474             # nl => 'dutch', # unclear why missing
1475             no => 'norwegian',
1476             sr => 'croatian', # serbian is cyrillic
1477             ro => 'romanian',
1478             ru => 'russian',
1479             sk => 'slovak-small', # exists also slovak-large
1480             sl => 'slovenian',
1481             pl => 'polish',
1482             pt => 'portuguese',
1483             sq => 'albanian',
1484             sv => 'swedish',
1485             tr => 'turkish',
1486             uk => 'ukrainian',
1487             vi => 'vietnamese',
1488             );
1489             @indexes = map { +{
1490             name => $_->index_name,
1491             title => $_->index_label,
1492 7   50     106 language => $xindy_langs{$doc->language_code} || 'general',
1493             } }
1494 5         14 @{ $indexer->specifications };
  5         86  
1495             }
1496 249 100       5978 $self->_set_indexes(@indexes ? \@indexes : undef);
1497             # no cover page if header or compiler says so, or
1498             # if coverpage_only_if_toc is set and doc doesn't have a toc.
1499 249 100 100     8897 if ($self->nocoverpage or
      100        
1500             ($self->coverpage_only_if_toc && !$doc->wants_toc)) {
1501 22         978 $parsed{nocoverpage} = 1;
1502 22         67 $parsed{class} = 'scrartcl';
1503 22         70 delete $parsed{opening}; # not needed for article.
1504             }
1505              
1506              
1507 249 100       11018 unless ($parsed{notoc}) {
1508 239 100       1122 if ($doc->wants_toc) {
1509 159         8068 $parsed{wants_toc} = 1;
1510             }
1511             }
1512              
1513             return {
1514 249         6879 options => \%tokens,
1515             safe_options => \%parsed,
1516             doc => $doc,
1517             tex_setup_langs => $tex_setup_langs,
1518             latex_body => $latex_body,
1519             enable_secondary_footnotes => $enable_secondary_footnotes,
1520             tex_metadata => $self->file_header->tex_metadata,
1521             tex_indexes => \@indexes,
1522             # in case we need it for volumes
1523             format_id => $template_options->format_id,
1524             };
1525             }
1526              
1527             sub _interpolate_magic_comments {
1528 254     254   3393 my ($self, $format, $doc) = @_;
1529 254   100     903 $format ||= 'DEFAULT';
1530 254         1551 my $latex = $doc->as_latex;
1531             # format is validated.
1532             # switch is gmx, no "s", we are line-based here
1533 254         6246854 my $prefix = qr{
1534             \%
1535             \x{20}+
1536             \:
1537             (?:
1538             \Q$format\E | \* | ALL
1539             )
1540             \:
1541             \x{20}+
1542             \\textbackslash\{\}
1543             }x;
1544 254         1090 my $size = qr{-?[1-9][0-9]*(?:mm|cm|pt|em)}x;
1545              
1546 254         6011 $latex =~ s/^
1547             $prefix
1548             ( # permitted commands
1549             sloppy |
1550             fussy |
1551             newpage |
1552             strut |
1553             flushbottom |
1554             raggedbottom |
1555             vfill |
1556             amusewiki[a-zA-Z]+ |
1557             clearpage |
1558             cleardoublepage |
1559             vskip \x{20}+ $size
1560             )
1561             \x{20}*
1562             $
1563             /\\$1/gmx;
1564 254         3890 $latex =~ s/^
1565             $prefix
1566             ( (this)? pagestyle )
1567             \\ \{
1568             ( plain | empty | headings | myheadings | scrheadings )
1569             \\ \}
1570             \x{20}*
1571             $
1572             /\\$1\{$3\}/gmx;
1573              
1574 254         3738 $latex =~ s/^
1575             $prefix
1576             ( enlargethispage )
1577             \\ \{
1578             ( $size )
1579             \\ \}
1580             \x{20}*
1581             $
1582             /\\$1\{$2\}/gmx;
1583              
1584 254         1087 my $regular = qr{[^\#\$\%\&\~\^\\\{\}_]+};
1585 254         3853 $latex =~ s/^
1586             $prefix
1587             markboth
1588             \\ \{
1589             ($regular)
1590             \\\}
1591             \\\{
1592             ($regular)
1593             \\\}
1594             \x{20}*
1595             $
1596             /\\markboth\{$1}\{$2\}/gmx;
1597 254         3354 $latex =~ s/^
1598             $prefix
1599             markright
1600             \\ \{
1601             ($regular)
1602             \\\}
1603             \x{20}*
1604             $
1605             /\\markright\{$1}/gmx;
1606              
1607             # with looseness, we need to attach it to the next paragraph, so
1608             # eat all the space and replace with a single \n
1609              
1610 254         2872 $latex =~ s/^
1611             $prefix
1612             looseness\=(-?[0-9])
1613             $
1614             \s*
1615             /\\looseness=$1\n/gmx;
1616              
1617             # add to toc
1618 254         4780 $latex =~ s/^
1619             $prefix
1620             addcontentsline
1621             \\\{
1622             (toc|lof|lot)
1623             \\\}
1624             \\\{
1625             (part|chapter|section|subsection)
1626             \\\}
1627             \\\{
1628             ($regular)
1629             \\\}
1630             \x{20}*
1631             $
1632             /\\addcontentsline{$1}{$2}{$3}/gmx;
1633              
1634 254         1430 return $latex;
1635             }
1636              
1637             sub _looks_like_a_sane_name {
1638 914     914   2523 my ($self, $name) = @_;
1639 914 50       2721 return unless defined $name;
1640 914         1615 my $out;
1641 914         1706 eval {
1642 914         4005 $out = Text::Amuse::Compile::TemplateOptions::check_filename($name);
1643             };
1644 914 100 66     3436 if (!$out || $@) {
1645 772         1247 $self->log_info("$name is not good: $@") if DEBUG;
1646 772         2831 return;
1647             }
1648             else {
1649 142         269 $self->log_info("$name is good") if DEBUG;
1650 142         508 return $out;
1651             }
1652             }
1653              
1654             sub _mime_for_attachment {
1655 12     12   40 my ($self, $att) = @_;
1656 12 50       36 die "Missing argument" unless $att;
1657 12         30 my $mime;
1658 12 100       134 if ($att =~ m/\.jpe?g$/) {
    50          
1659 4         34 $mime = "image/jpeg";
1660             }
1661             elsif ($att =~ m/\.png$/) {
1662 8         24 $mime = "image/png";
1663             }
1664             else {
1665 0         0 $self->log_fatal("Unrecognized attachment $att!");
1666             }
1667 12         95 return $mime;
1668             }
1669              
1670             sub _format_epub_fragment {
1671 399     399   713 my ($self, $index) = @_;
1672 399   100     2608 return sprintf('piece%06d.xhtml', $index || 0);
1673             }
1674              
1675             sub document_indexes {
1676 256     256 1 279143 my ($self) = @_;
1677 256 100       5549 my @docs = ($self->virtual ? ($self->document->docs) : ( $self->document ));
1678 14         134 my @comments = grep { /\AINDEX +([a-z]+): (.+)/ }
1679 14         94 map { $_->string }
1680 9062         634754 grep { $_->type eq 'comment' }
1681 256         2997 map { $_->document->elements } @docs;
  287         1692  
1682 256         3335 return @comments;
1683             }
1684              
1685              
1686             1;