File Coverage

blib/lib/Text/Amuse/Compile/File.pm
Criterion Covered Total %
statement 556 658 84.5
branch 168 266 63.1
condition 40 62 64.5
subroutine 77 89 86.5
pod 33 33 100.0
total 874 1108 78.8


line stmt bran cond sub pod time code
1             package Text::Amuse::Compile::File;
2              
3 58     58   143145 use strict;
  58         132  
  58         1522  
4 58     58   261 use warnings;
  58         111  
  58         1156  
5 58     58   245 use utf8;
  58         94  
  58         254  
6              
7 58     58   1869 use constant { DEBUG => $ENV{AMW_DEBUG} };
  58         104  
  58         3714  
8              
9             # core
10             # use Data::Dumper;
11 58     58   1140 use File::Copy qw/move/;
  58         5854  
  58         2825  
12 58     58   27032 use Encode qw/decode_utf8/;
  58         451389  
  58         3624  
13              
14             # needed
15 58     58   21233 use Template::Tiny;
  58         61536  
  58         2248  
16 58     58   30435 use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
  58         2348707  
  58         7683  
17 58     58   24301 use EBook::EPUB::Lite;
  58         10808162  
  58         2182  
18 58     58   470 use File::Copy;
  58         113  
  58         3068  
19 58     58   322 use File::Spec;
  58         106  
  58         1090  
20 58     58   49310 use IPC::Run qw(run);
  58         1241864  
  58         2832  
21 58     58   449 use File::Basename ();
  58         666  
  58         787  
22 58     58   40343 use Path::Tiny ();
  58         578705  
  58         1544  
23              
24             # ours
25 58     58   25539 use PDF::Imposition;
  58         12894867  
  58         2135  
26 58     58   1305 use Text::Amuse;
  58         57957  
  58         1620  
27 58         4241 use Text::Amuse::Functions qw/muse_fast_scan_header
28             muse_to_object
29 58     58   1047 muse_format_line/;
  58         3623  
30 58     58   345 use Text::Amuse::Utils;
  58         106  
  58         1221  
31              
32 58     58   28881 use Text::Amuse::Compile::Templates;
  58         157  
  58         1833  
33 58     58   25374 use Text::Amuse::Compile::TemplateOptions;
  58         216  
  58         2643  
34 58     58   25517 use Text::Amuse::Compile::MuseHeader;
  58         185  
  58         1995  
35 58     58   24887 use Text::Amuse::Compile::Indexer;
  58         193  
  58         2502  
36 58     58   408 use Types::Standard qw/Str Bool Object Maybe CodeRef HashRef InstanceOf ArrayRef/;
  58         115  
  58         403  
37 58     58   63195 use Moo;
  58         122  
  58         274  
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   3612 my $self = shift;
155             return Text::Amuse::Compile::Templates->new(ttdir => $self->ttdir,
156 287         4908 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   3835 my $self = shift;
181 303         570 my $header;
182 303 100       1464 if ($self->virtual) {
183 19         296 $header = { $self->document->headers };
184             }
185             else {
186 284         1175 $header = muse_fast_scan_header($self->muse_file);
187 284 50 33     125480 $self->log_fatal("Not a muse file!") unless $header && %$header;
188             }
189 303         8283 return Text::Amuse::Compile::MuseHeader->new($header);
190             }
191              
192             sub _build_is_deleted {
193 297     297   9277 return shift->file_header->is_deleted;
194             }
195              
196             sub _build_wants_slides {
197 16     16   395 return shift->file_header->wants_slides;
198             }
199              
200             sub _build_document {
201 276     276   3826 my $self = shift;
202 276         583 my %args;
203 276 50       1044 die "virtual files need an already built document" if $self->virtual;
204 276 100       1275 if (my $fileobj = $self->fileobj) {
205 271         1498 %args = $fileobj->text_amuse_constructor;
206             }
207             else {
208 5         22 %args = (file => $self->muse_file);
209             }
210 276         3035 return Text::Amuse->new(%args,
211             include_paths => $self->include_paths,
212             );
213             }
214              
215             sub _build_tex_options {
216 246     246   3395 my $self = shift;
217 246         3932 return $self->_escape_options_hashref(ltx => $self->full_options);
218             }
219              
220             sub _build_html_options {
221 113     113   2195 my $self = shift;
222 113         1985 return $self->_escape_options_hashref(html => $self->full_options);
223             }
224              
225             sub _build_full_options {
226 288     288   3374 my $self = shift;
227             # merge the options with the ones found in the header.
228             # print "Building full options\n" if DEBUG;
229 288         534 my %options = %{ $self->options };
  288         1674  
230             # these values are picked from the file, if not provided by the compiler
231 288         1227 foreach my $override (qw/cover coverwidth nocoverpage notoc
232             impressum
233             continuefootnotes
234             centerchapter
235             centersection
236             nofinalpage/) {
237 2592         65743 $options{$override} = $self->$override;
238             }
239 288         12123 return \%options;
240             }
241              
242             sub _build_volumes {
243 238     238   2888 my $self = shift;
244 238         482 my @volumes;
245 238 100 66     1716 if (!$self->virtual and -f $self->muse_file) {
246 222         1116 my @lines = Path::Tiny::path($self->muse_file)->lines_utf8;
247              
248 222 100       130892 if (grep { /^; +;;;#\w+/ } @lines) {
  18645         26966  
249 2         7 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         3 my $in_volume_meta = 0;
256             LINE:
257 2         6 while (@lines) {
258 70         124 my $line = shift @lines;
259             # accumulate in the current pile until there's a blank line
260 70         131 my $blank = $line =~ m/\A\s*\z/;
261              
262 70 100       139 if ($line =~ m/\A; +;;;(#[A-Za-z0-9_-]+\w+.*)\z/s) {
    100          
263 6         15 my $directive = $1;
264 6         8 $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       87 if (@current) {
269 3         13 push @volumes, [ @current_meta, @original_meta, @current ];
270             }
271 5         12 @current = @current_meta = ();
272             }
273 6         8 push @current_meta, $directive;
274 6         13 next LINE;
275             }
276             elsif (!$blank) {
277 31         37 $in_volume_meta = 0;
278             }
279              
280 64 100       88 if ($in_meta) {
281 12         22 push @original_meta, $line;
282             }
283             else {
284 52         87 push @current, $line;
285             }
286             }
287             # end of loop, flush the stack
288 2 50       6 if (@current) {
289 2         13 push @volumes, [ @current_meta, @original_meta, @current ];
290             }
291             # print Dumper(\@original_meta, \@volumes);
292             }
293             }
294 238         5310 return \@volumes;
295             }
296              
297             sub cover {
298 382     382 1 799 my $self = shift;
299             # options passed take precendence
300 382 100       1732 if (exists $self->options->{cover}) {
301 48 100       232 if ($self->_looks_like_a_sane_name($self->options->{cover})) {
302 26         138 return $self->options->{cover};
303             }
304             else {
305 22         151 return '';
306             }
307             }
308 334 100       5764 if (my $cover = $self->file_header->cover) {
309             # already validated by the MuseHeader class
310 37         1187 return $cover;
311             }
312             }
313              
314             sub coverwidth {
315 288     288 1 654 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       1296 if (exists $self->options->{coverwidth}) {
320             # print "Picking coverwidth from options\n";
321 8         26 return $self->options->{coverwidth};
322             }
323             # obey this thing only if the file set the cover
324 280 100       4397 if ($self->file_header->cover) {
325             # print "Picking coverwidth from file\n";
326 43   50     1634 return $self->file_header->coverwidth || 1;
327             }
328 237         5737 return 1;
329             }
330              
331             sub nocoverpage {
332 537     537 1 2203 shift->_look_at_header('nocoverpage');
333             }
334              
335             sub notoc {
336 288     288 1 868 shift->_look_at_header('notoc');
337             }
338              
339             sub nofinalpage {
340 288     288 1 818 shift->_look_at_header('nofinalpage');
341             }
342              
343             sub impressum {
344 288     288 1 896 shift->_look_at_header('impressum');
345             }
346              
347 288     288 1 911 sub continuefootnotes { shift->_look_at_header('continuefootnotes') }
348 469     469 1 8026 sub centerchapter { shift->_look_at_header('centerchapter') }
349 469     469 1 1638 sub centersection { shift->_look_at_header('centersection') }
350              
351             sub _look_at_header {
352 2627     2627   5061 my ($self, $method) = @_;
353             # these are booleans, so we enforce them
354 2627 100 100     37146 !!$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   9790 my ($self, $format, $ref) = @_;
387 857 50 33     4149 die "Wrong usage of internal method" unless $format && $ref;
388 857         1452 my %out;
389 857         4664 foreach my $k (keys %$ref) {
390 14306 100       3686563 if (defined $ref->{$k}) {
391 12905 100 100     45802 if ($k eq 'logo' or $k eq 'cover') {
    100          
392 866 100       3619 if (my $checked = $self->_looks_like_a_sane_name($ref->{$k})) {
393 116         350 $out{$k} = $checked;
394             }
395             }
396             elsif (ref($ref->{$k})) {
397             # pass it verbatim
398 283         759 $out{$k} = $ref->{$k};
399             }
400             else {
401 11756         27260 $out{$k} = muse_format_line($format, $ref->{$k});
402             }
403             }
404             else {
405 1401         3218 $out{$k} = undef;
406             }
407             }
408 857         177928 return \%out;
409             }
410              
411              
412             sub muse_file {
413 733     733 1 1340 my $self = shift;
414 733         12477 return $self->name . $self->suffix;
415             }
416              
417             sub status_file {
418 301     301 1 2145 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   1875 return qw/.sl.tex .tex .a4.pdf .lt.pdf .ok .html .bare.html .epub .zip/;
451             }
452              
453             sub _latex_extensions {
454 632     632   2056 return qw/.pdf .log/;
455             }
456              
457             sub _slides_extensions {
458 316     316   640 my $self = shift;
459 316         739 return map { '.sl' . $_ } $self->_latex_extensions;
  632         2349  
460             }
461              
462             sub _latex_leftover_extensions {
463 632     632   2256 return qw/.aux .nav .out .snm .toc .tuc .vrb/;
464             }
465              
466             sub _slides_leftover_extensions {
467 316     316   699 my $self = shift;
468 316         785 return map { '.sl' . $_ } $self->_latex_leftover_extensions;
  2212         3909  
469             }
470              
471             sub purged_extensions {
472 316     316 1 2757 my $self = shift;
473 316         1247 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         2416 return @exts;
481             }
482              
483             sub purge {
484 761     761 1 2878 my ($self, @exts) = @_;
485 761         1236 $self->log_info("Started purging\n") if DEBUG;
486 761         2292 my $basename = $self->name;
487 761         1881 foreach my $ext (@exts) {
488 8509 50       19187 $self->log_fatal("wtf? Refusing to purge " . $basename . $ext)
489             if ($ext eq '.muse');
490 8509         13902 my $target = $basename . $ext;
491 8509 100       81492 if (-f $target) {
492 139         307 $self->log_info("Removing target $target\n") if DEBUG;
493 139 50       8040 unlink $target or $self->log_fatal("Couldn't unlink $target $!");
494             }
495             }
496 761         3907 $self->log_info("Ended purging\n") if DEBUG;
497             }
498              
499             sub purge_all {
500 298     298 1 11790 my $self = shift;
501 298         1227 $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   751 my ($self, %tokens) = @_;
568 181         542 my $out = '';
569 181         3542 $self->tt->process($self->templates->css, {
570             fonts => $self->fonts,
571             centersection => $self->centersection,
572             centerchapter => $self->centerchapter,
573             %tokens
574             }, \$out);
575 181         2283945 return $out;
576             }
577              
578              
579             sub html {
580 112     112 1 29159 my $self = shift;
581 112         377 $self->purge('.html');
582 112         551 my $outfile = $self->name . '.html';
583 112         2642 my $doc = $self->document;
584 112   50     9702 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         837106 options => { %{$self->html_options} },
  112         4387  
591             },
592             $outfile);
593             }
594              
595             sub bare_html {
596 8     8 1 1017 my $self = shift;
597 8         30 $self->purge('.bare.html');
598 8         36 my $outfile = $self->name . '.bare.html';
599             $self->_process_template($self->templates->bare_html,
600             {
601             doc => $self->document,
602 8         207 options => { %{$self->html_options} },
  8         933  
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 17508 my ($self, @args) = @_;
661 240         1103 my $texfile = $self->name . '.tex';
662 240 50       914 $self->log_fatal("Wrong usage") if @args % 2;
663 240         625 my %arguments = @args;
664 240 100 100     1818 unless (@args || $self->standalone) {
665 7         31 %arguments = (
666             twoside => 0,
667             oneside => 1,
668             bcor => '0mm',
669             );
670             }
671 240         955 $self->purge('.tex');
672 240         5913 my $template_body = $self->templates->latex;
673 240         1363 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         13362 my $volumes = $self->volumes;
685 240 100 66     8033 if ($volumes and @$volumes > 1) {
686 2         12 my $tex_parse = qr{\A(.*\\begin\{document\})(.*)(\\end\{document\}.*)}s;
687 2         4 my $full;
688 2         18 $self->tt->process($template_body, $tokens, \$full);
689             # print $full;
690 2 50       663964 if ($full =~ m/$tex_parse/s) {
691 2         16 my ($preamble, $body, $end) = ($1, $2, $3);
692             # print Dumper([$preamble, $body, $end ]);
693 2         7 my @pieces = ($preamble);
694 2         6 my $last = scalar $#$volumes;
695              
696             # check if the template is custom
697 2 50       36 my $toc_i = $$template_body =~ m/latex_body.*tableofcontents/s ? $last : 0;
698 2 50       17 my $idx_i = $$template_body =~ m/printindex.*latex_body/s ? 0 : $last;
699              
700 2         12 for (my $i = 0; $i <= $last; $i++) {
701 5         667 my $vol = $volumes->[$i];
702 5         62 my $doc = muse_to_object(join('', @$vol));
703 5         7735 my $latex = $self->_interpolate_magic_comments($tokens->{format_id}, $doc);
704              
705 5 100       24 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         267 $latex = $indexer->indexed_tex_body;
711             }
712              
713             my %partial_tokens = (
714 5         61 options => { %{ $tokens->{options} } },
715 5         164 safe_options => { %{ $tokens->{safe_options} } },
716             tex_setup_langs => 'DUMMY', # irrelevant
717             doc => $doc,
718             latex_body => $latex,
719 5         13 tex_indexes => [ @{ $tokens->{tex_indexes} } ],
  5         31  
720             );
721 5 100       24 if ($i != $toc_i) {
722 3         8 $partial_tokens{safe_options}{wants_toc} = 0;
723             }
724 5 100       18 if ($i != $idx_i) {
725 3         8 $partial_tokens{tex_indexes} = [];
726             }
727              
728             # print Dumper(\%partial_tokens);
729              
730              
731             # here clear wants_toc / indexes
732              
733 5         9 my $out;
734 5         41 $self->tt->process($template_body, \%partial_tokens, \$out);
735 5 50       1655351 if ($out =~ m/$tex_parse/s) {
736 5         814 push @pieces, $2;
737             }
738             }
739 2         400 push @pieces, $end;
740 2         12 Path::Tiny::path($texfile)->spew_utf8(@pieces);
741 2         1285 return $texfile;
742             }
743             }
744 238         1271 $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 25 my ($self) = @_;
763             # no slides for virtual files
764 9 50       43 return if $self->virtual;
765 9         33 $self->purge('.sl.tex');
766 9         42 my $texfile = $self->name . '.sl.tex';
767 9 50       178 return unless $self->wants_slides;
768 9         198 my $template_body = $self->templates->slides;
769 9         48 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 1659 my $self = shift;
919 25         139 $self->purge('.zip');
920 25         105 my $zipname = $self->name . '.zip';
921 25         284 my $tempdir = File::Temp->newdir;
922 25         10455 my $tempdirname = $tempdir->dirname;
923 25         274 foreach my $todo (qw/tex html/) {
924 50         9218 my $target = $self->name . '.' . $todo;
925 50 100       480 unless (-f $target) {
926 24         149 $self->$todo;
927             }
928 50 50       712 $self->log_fatal("Couldn't produce $target") unless -f $target;
929 50 50       447 copy($target, $tempdirname)
930             or $self->log_fatal("Couldn't copy $target in $tempdirname $!");
931             }
932 25         8705 copy ($self->name . '.muse', $tempdirname);
933              
934 25         7175 my $text = $self->document;
935 25         301 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       747 if (my $cover = $self->cover) {
940 8 100       91 if (-f $cover) {
941 6 50       25 copy($cover, $tempdirname)
942             or $self->log_info("Cannot find the cover to attach");
943             }
944             }
945 25         2512 my $zip = Archive::Zip->new;
946 25 50       1090 $zip->addTree($tempdirname, $self->name) == AZ_OK
947             or $self->log_fatal("Failure zipping $tempdirname");
948 25 50       240308 $zip->writeToFileNamed($zipname) == AZ_OK
949             or $self->log_fatal("Failure writing $zipname");
950 25         163539 return $zipname;
951             }
952              
953              
954             sub epub {
955 69     69 1 893 my $self = shift;
956 69         294 $self->purge('.epub');
957 69         307 my $epubname = $self->name . '.epub';
958              
959 69         1747 my $text = $self->document;
960              
961 69         3951 my @pieces;
962 69 100       483 if ($text->can('as_splat_html_with_attrs')) {
963 10         44 @pieces = $text->as_splat_html_with_attrs;
964             }
965             else {
966             @pieces = map {
967 59         390 +{
968 176         409282 text => $_,
969             language_code => $text->language_code,
970             html_direction => $text->html_direction,
971             }
972             } $text->as_splat_html;
973             }
974 69         2677 my @toc = $text->raw_html_toc;
975             # fixed in 0.51
976 69 50       336928 if (my $missing = scalar(@pieces) - scalar(@toc)) {
977 0         0 $self->log_fatal("This shouldn't happen: missing pieces: $missing");
978             }
979 69         2336 my $epub = EBook::EPUB::Lite->new;
980              
981             # embedded CSS
982 69 100       448049 if ($self->epub_embed_fonts) {
983             # pass all
984 67 50       474 if (my $fonts = $self->fonts) {
985 67         147 my %done;
986 67         184 foreach my $family (@{ $fonts->families }) {
  67         536  
987 201 100       5168 if ($family->has_files) {
988 12         173 foreach my $ff (@{ $family->font_files }) {
  12         39  
989             # do not produce duplicate entries when using
990             # the same file
991 48 50       359 unless ($done{$ff->basename}) {
992 48         928 $epub->copy_file($ff->file,
993             $ff->basename,
994             $ff->mimetype);
995 48         30252 $done{$ff->basename}++;
996             }
997             }
998             }
999             }
1000             }
1001             }
1002 69         1363 my $css = $self->_render_css(
1003             epub => 1,
1004             epub_embed_fonts => $self->epub_embed_fonts,
1005             );
1006 69         766 $epub->add_stylesheet("stylesheet.css" => $css);
1007              
1008             # build the title page and some metadata
1009 69         37506 my $header = $text->header_as_html;
1010              
1011 69         67572 my @navpoints;
1012 69         161 my $order = 0;
1013              
1014 69 100       329 if (my $cover = $self->cover) {
1015 9 100       158 if (-f $cover) {
1016 7 50       347 if (my $basename = File::Basename::basename($cover)) {
1017 7         19 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         84 $coverpage =~ s/__IMAGE__/$basename/;
1037 7         32 my $cover_id = $epub->copy_file($cover, $basename,
1038             $self->_mime_for_attachment($basename));
1039 7         4113 $epub->add_meta_item(cover => $cover_id);
1040 7         4463 my $cpid = $epub->add_xhtml("coverpage.xhtml", $coverpage);
1041 7         6058 $epub->guide->add_reference(type => 'cover', href => "coverpage.xhtml");
1042 7         4290 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         1957 my $titlepage = qq{
\n};
1054              
1055 69 100       392 if ($text->header_defined->{author}) {
1056 16         368 my $author = $header->{author};
1057 16         83 $epub->add_author($self->_clean_html($author));
1058 16 100       2716 $titlepage .= "

$author

\n" if $text->wants_preamble;
1059             }
1060 69         3068 my $muse_header = $self->file_header;
1061 69         811 foreach my $aut ($muse_header->authors_as_html_list) {
1062 5         474 $epub->add_author($self->_clean_html($aut));
1063             }
1064 69         483 foreach my $topic ($muse_header->topics_as_html_list) {
1065 11         974 $epub->add_subject($self->_clean_html($topic));
1066             }
1067 69 50       360 if ($text->header_defined->{title}) {
1068 69         796 my $t = $header->{title};
1069 69         531 $epub->add_title($self->_clean_html($t));
1070 69 100       13582 $titlepage .= "

$t

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

$st

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

$header->{date}

" if $text->wants_preamble;
1085             }
1086              
1087 69         720 $epub->add_language($text->language_code);
1088              
1089 69         9523 $titlepage .= qq{
\n};
1090              
1091 69 50 66     204 if ($text->header_defined->{seriesname} && $text->header_defined->{seriesnumber}) {
1092             $titlepage .= qq{
}
1093             . $header->{seriesname} . ' ' . $header->{seriesnumber}
1094 2         193 . qq{};
1095             }
1096              
1097 69         1334 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             );
1104              
1105 69         246 foreach my $imp (@impressum_map) {
1106 345         2161 my $k = $imp->[0];
1107 345 100       591 if ($text->header_defined->{$k}) {
1108 22         278 my $str = $header->{$k};
1109 22         38 my ($method, @additional_args) = @{$imp->[1]};
  22         57  
1110 22         64 $epub->$method($self->_clean_html($str), @additional_args);
1111 22 100       2690 if ($k eq 'isbn') {
1112 2         7 $str = 'ISBN ' . $str;
1113             }
1114 22 100       77 $titlepage .= qq{
$str
\n}
1115             if $text->wants_postamble;
1116             }
1117             }
1118 69         513 $titlepage .= "\n\n";
1119             # create the front page
1120 69         228 my $firstpage = '';
1121             $self->tt->process($self->templates->minimal_html,
1122             {
1123 69 50 50     1430 title => $self->_remove_tags($header->{title} || 'Untitled'),
1124             text => $titlepage,
1125             html_direction => $text->html_direction,
1126             language_code => $text->language_code,
1127             },
1128             \$firstpage)
1129             or $self->log_fatal($self->tt->error);
1130              
1131 69         17047 my $tpid = $epub->add_xhtml("titlepage.xhtml", $firstpage);
1132              
1133             # main loop
1134             push @navpoints, {
1135 69   50     55983 label => $self->_clean_html($header->{title} || 'Untitled'),
1136             id => $tpid,
1137             content => "titlepage.xhtml",
1138             play_order => ++$order,
1139             level => 1,
1140             };
1141              
1142 69         167 my %internal_links;
1143             {
1144 69         130 my $piecenumber = 0;
  69         253  
1145 69         191 foreach my $piece (@pieces) {
1146             # we insert these in Text::Amuse, so it's not a wild regexp.
1147 313         1298 while ($piece->{text} =~ m/<\/a>/g) {
1148 86         197 my $label = $1;
1149             $internal_links{$label} =
1150 86         208 $self->_format_epub_fragment($toc[$piecenumber]{index});
1151             }
1152 313         489 $piecenumber++;
1153             }
1154             }
1155             my $fix_link = sub {
1156 123     123   234 my ($target) = @_;
1157 123 50       219 die unless $target;
1158 123 100       275 if (my $file = $internal_links{$target}) {
1159 109         605 return $file . '#' . $target;
1160             }
1161             else {
1162             # broken link
1163 14         69 return '#' . $target;
1164             }
1165 69         572 };
1166 69         237 while (@pieces) {
1167 313         553 my $piece = shift @pieces;
1168 313         481 my $index = shift @toc;
1169 313         635 my $xhtml = "";
1170             # print Dumper($index);
1171 313         818 my $filename = $self->_format_epub_fragment($index->{index});
1172 313         990 my $prefix = '*' x $index->{level};
1173 313         754 my $title = $prefix . " " . $index->{string};
1174 313         1172 $piece->{text} =~ s/(($2) . '"'/ge;
  123         220  
1175              
1176 313 50       5913 $self->tt->process($self->templates->minimal_html,
1177             {
1178             title => $self->_remove_tags($title),
1179             %$piece,
1180             },
1181             \$xhtml)
1182             or $self->log_fatal($self->tt->error);
1183              
1184 313         63926 my $id = $epub->add_xhtml($filename, $xhtml);
1185             push @navpoints, {
1186             label => $self->_clean_html($index->{string}),
1187             content => $filename,
1188             id => $id,
1189             play_order => ++$order,
1190             level => $index->{level},
1191 313         117361 };
1192             }
1193 69         358 $self->_epub_create_toc($epub, \@navpoints);
1194              
1195             # attachments
1196 69         362 foreach my $att ($text->attachments) {
1197 6 100       13911 $self->log_fatal("Referenced file $att does not exist!") unless -f $att;
1198 5         26 $epub->copy_file($att, $att, $self->_mime_for_attachment($att));
1199             }
1200             # finish
1201 68         317322 $epub->pack_zip($epubname);
1202 68         2399982 return $epubname;
1203             }
1204              
1205             sub _epub_create_toc {
1206 69     69   194 my ($self, $epub, $navpoints) = @_;
1207 69         128 my %levelnavs;
1208             # print Dumper($navpoints);
1209             NAVPOINT:
1210 69         305 foreach my $navpoint (@$navpoints) {
1211 389         1775 my %nav = %$navpoint;
1212 389         760 my $level = delete $nav{level};
1213 389 50       745 die "Shouldn't happen: false level: $level" unless $level;
1214 389 50       1304 die "Shouldn't happen either: $level not 1-4" unless $level =~ m/\A[1-4]\z/;
1215 389         605 my $checklevel = $level - 1;
1216              
1217 389         466 my $current;
1218 389         741 while ($checklevel > 0) {
1219 264 100       570 if (my $parent = $levelnavs{$checklevel}) {
1220 234         924 $current = $parent->add_navpoint(%nav);
1221 234         29580 last;
1222             }
1223 30         121 $checklevel--;
1224             }
1225 389 100       737 unless ($current) {
1226 155         3042 $current = $epub->add_navpoint(%nav);
1227             }
1228 389         76949 for my $clear ($level..4) {
1229 1190         1840 delete $levelnavs{$clear};
1230             }
1231 389         1614 $levelnavs{$level} = $current;
1232             }
1233             # probably not needed, but let's be sure we don't leave circular
1234             # refs.
1235 69         275 foreach my $k (keys %levelnavs) {
1236 149         284 delete $levelnavs{$k};
1237             }
1238             }
1239              
1240             sub _remove_tags {
1241 494     494   1130 my ($self, $string) = @_;
1242 494 50       1085 return "" unless defined $string;
1243 494         1064 $string =~ s/<.+?>//g;
1244 494         2976 return $string;
1245             }
1246              
1247             sub _clean_html {
1248 505     505   1090 my ($self, $string) = @_;
1249 505 50       1118 return "" unless defined $string;
1250 505         1210 $string =~ s/<.+?>//g;
1251 505         778 $string =~ s/</
1252 505         764 $string =~ s/>/>/g;
1253 505         722 $string =~ s/"/"/g;
1254 505         785 $string =~ s/'/'/g;
1255 505         620 $string =~ s/ / /g;
1256 505         631 $string =~ s/ / /g;
1257 505         694 $string =~ s/&/&/g;
1258 505         4048 return $string;
1259             }
1260              
1261             =head2 Logging
1262              
1263             While the C accessor holds a reference to a sub, but could be
1264             very well be empty, the object uses these two methods:
1265              
1266             =over 4
1267              
1268             =item log_info(@strings)
1269              
1270             If C exists, it will call it passing the strings as arguments.
1271             Otherwise print to the standard output.
1272              
1273             =item log_fatal(@strings)
1274              
1275             Calls C, remove the lock and dies.
1276              
1277             =item parse_tex_log_file($logfile)
1278              
1279             (Internal) Parse the produced logfile for missing characters.
1280              
1281             =back
1282              
1283             =head1 INTERNAL CONSTANTS
1284              
1285             =head2 DEBUG
1286              
1287             Set from AMW_DEBUG environment.
1288              
1289             =cut
1290              
1291              
1292              
1293             sub log_info {
1294 27     27 1 2114 my ($self, @info) = @_;
1295 27         106 my $logger = $self->logger;
1296 27 100       81 if ($logger) {
1297 26         105 $logger->(@info);
1298             }
1299             else {
1300 1         53 print @info;
1301             }
1302             }
1303              
1304             sub log_fatal {
1305 1     1 1 4 my ($self, @info) = @_;
1306 1         6 $self->log_info(@info);
1307 1   50     9 my $failure = join("\n", @info) || "Fatal exception";
1308 1         84 die "$failure\n";
1309             }
1310              
1311             sub parse_tex_log_file {
1312 1     1 1 46 my ($self, $logfile) = @_;
1313 1 50       5 die "Missing file argument!" unless $logfile;
1314 1 50       37 if (-f $logfile) {
1315             # if you're wandering why we open this in raw mode: The log
1316             # file produced by XeLaTeX is utf8, but it splits the output
1317             # at 80 bytes or so. This of course sometimes, expecially
1318             # working with cyrillic scripts, cut the multibyte character
1319             # in half, producing invalid utf8 octects.
1320 1 50       64 open (my $fh, '<:raw', $logfile)
1321             or $self->log_fatal("Couldn't open $logfile $!");
1322              
1323 1         4 my %errors;
1324 1         3 my $continue = 0;
1325              
1326 1         241 while (my $line = <$fh>) {
1327 1257         1329 chomp $line;
1328 1257 100       3006 if ($line =~ m/^missing character/i) {
    100          
    100          
1329             # if we get the warning, nothing we can do about it,
1330             # but shouldn't happen.
1331 4         15 $errors{$line} = 1;
1332             }
1333             elsif ($line =~ m/^Overfull/) {
1334 2         9 $self->log_info(decode_utf8($line) . "\n");
1335 2         14 $continue++;
1336             }
1337             elsif ($continue) {
1338 2         7 $self->log_info(decode_utf8($line) . "\n\n");
1339 2         9 $continue = 0;
1340             }
1341             }
1342 1         16 close $fh;
1343 1         9 foreach my $error (sort keys %errors) {
1344 4         17 $self->log_info(decode_utf8($error) . "...\n");
1345             }
1346             }
1347             }
1348              
1349             sub cleanup {
1350 5     5 1 7902 my $self = shift;
1351 5 50       27 if (my $f = $self->status_file) {
1352 5 100       83 if (-f $f) {
1353 4 50       263 unlink $f or $self->log_fatal("Couldn't unlink $f $!");
1354             }
1355             else {
1356 1         95 $self->log_info("Couldn't find " . File::Spec->rel2abs($f));
1357             }
1358             }
1359             }
1360              
1361             sub _process_template {
1362 367     367   6706 my ($self, $template_ref, $tokens, $outfile) = @_;
1363 367         747 eval {
1364 367         818 my $out = '';
1365 367 50 33     2830 die "Wrong usage" unless ($template_ref && $tokens && $outfile);
      33        
1366 367         3504 $self->tt->process($template_ref, $tokens, \$out);
1367 367 50       71262795 open (my $fh, '>:encoding(UTF-8)', $outfile) or die "Couldn't open $outfile $!";
1368 367         138548 print $fh $out, "\n";
1369 367         13925 close $fh;
1370             };
1371 367 50       2026 if ($@) {
1372 0         0 $self->log_fatal("Error processing template for $outfile: $@");
1373             };
1374 367         13278 return $outfile;
1375             }
1376              
1377              
1378             # method for options to pass to the tex template
1379             sub _prepare_tex_tokens {
1380 249     249   1006 my ($self, %args) = @_;
1381 249         4489 my $doc = $self->document;
1382 249         19928 my $is_slide = delete $args{is_slide};
1383 249         617 my $template_body = delete $args{template_body};
1384 249 50       895 die "Missing required argument template_body " unless $template_body;
1385 249         495 my %tokens = %{ $self->tex_options };
  249         4287  
1386 249         9410 my $escaped_args = $self->_escape_options_hashref(ltx => \%args);
1387 249         840 foreach my $k (keys %$escaped_args) {
1388 46         84 $tokens{$k} = $escaped_args->{$k};
1389             }
1390             # now tokens have the unparsed options
1391             # now validate the options against the new shiny module
1392 249         502 my %options = (%{ $self->full_options }, %args);
  249         4491  
1393             # print Dumper($self->full_options);
1394 249         3423 my $template_options = eval { Text::Amuse::Compile::TemplateOptions->new(%options) };
  249         5909  
1395 249 100       21439 unless ($template_options) {
1396 12         208 $template_options = Text::Amuse::Compile::TemplateOptions->new;
1397 12         563 $self->log_info("# Validation failed: $@, setting one by one\n");
1398 12         130 foreach my $method ($template_options->config_setters) {
1399 504 100       5175 if (exists $options{$method}) {
1400 160         197 eval { $template_options->$method($options{$method}) };
  160         2695  
1401             }
1402             }
1403             }
1404 249         1609 my $safe_options =
1405             $self->_escape_options_hashref(ltx => $template_options->config_output);
1406              
1407             # defaults
1408 249         8867 my %parsed = (%$safe_options,
1409             class => 'scrbook',
1410             lang => 'english',
1411             mainlanguage_script => '',
1412             wants_toc => 0,
1413             );
1414              
1415              
1416 249         1779 my $fonts = $self->fonts;
1417              
1418             # not used but for legacy templates
1419 249         1771 $parsed{mainfont} = $fonts->main->name;
1420 249         1196 $parsed{sansfont} = $fonts->sans->name;
1421 249         1135 $parsed{monofont} = $fonts->mono->name;
1422 249         955 $parsed{fontsize} = $fonts->size;
1423              
1424 249         7380 my $latex_body = $self->_interpolate_magic_comments($template_options->format_id, $doc);
1425              
1426 249         1207 my $enable_secondary_footnotes = $latex_body =~ m/\\footnoteB\{/;
1427              
1428             # check if the template body support this conditional, which is new. If not,
1429             # always setup bigfoot
1430             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1431 249 100       7550 if (index($$template_body, '[% IF enable_secondary_footnotes %]', 0) < 0) {
1432 1         3 $enable_secondary_footnotes = 1;
1433             }
1434             # print "SECONDARY FOOTNOTES ENABLED? $enable_secondary_footnotes\n";
1435              
1436 249   100     1415 my $tex_setup_langs = $fonts
1437             ->compose_polyglossia_fontspec_stanza(lang => $doc->language,
1438             others => $doc->other_languages || [],
1439             enable_secondary_footnotes => $enable_secondary_footnotes,
1440             bidi => $doc->is_bidi,
1441             has_ruby => $doc->has_ruby,
1442             is_slide => $is_slide,
1443             captions => Text::Amuse::Utils::language_code_locale_captions($doc->language_code),
1444             );
1445              
1446 249         911 my @indexes;
1447 249 100       1300 if (my @raw_indexes = $self->document_indexes) {
1448             my $indexer = Text::Amuse::Compile::Indexer->new(latex_body => $latex_body,
1449             language_code => $doc->language_code,
1450 0     0   0 logger => $self->logger || sub { print @_ },
1451 5   50     29 index_specs => \@raw_indexes);
1452 5         6824 $latex_body = $indexer->indexed_tex_body;
1453 5         116 my %xindy_langs = (
1454             bg => 'bulgarian',
1455             cs => 'czech',
1456             da => 'danish',
1457             de => 'german-din', # ae is sorted like ae. alternative -duden
1458             el => 'greek',
1459             en => 'english',
1460             es => 'spanish-modern',
1461             et => 'estonian',
1462             fi => 'finnish',
1463             fr => 'french',
1464             hr => 'croatian',
1465             hu => 'hungarian',
1466             is => 'icelandic',
1467             it => 'italian',
1468             lv => 'latvian',
1469             lt => 'lithuanian',
1470             mk => 'macedonian',
1471             # nl => 'dutch', # unclear why missing
1472             no => 'norwegian',
1473             sr => 'croatian', # serbian is cyrillic
1474             ro => 'romanian',
1475             ru => 'russian',
1476             sk => 'slovak-small', # exists also slovak-large
1477             sl => 'slovenian',
1478             pl => 'polish',
1479             pt => 'portuguese',
1480             sq => 'albanian',
1481             sv => 'swedish',
1482             tr => 'turkish',
1483             uk => 'ukrainian',
1484             vi => 'vietnamese',
1485             );
1486             @indexes = map { +{
1487             name => $_->index_name,
1488             title => $_->index_label,
1489 7   50     102 language => $xindy_langs{$doc->language_code} || 'general',
1490             } }
1491 5         14 @{ $indexer->specifications };
  5         103  
1492             }
1493 249 100       5590 $self->_set_indexes(@indexes ? \@indexes : undef);
1494             # no cover page if header or compiler says so, or
1495             # if coverpage_only_if_toc is set and doc doesn't have a toc.
1496 249 100 100     7889 if ($self->nocoverpage or
      100        
1497             ($self->coverpage_only_if_toc && !$doc->wants_toc)) {
1498 22         862 $parsed{nocoverpage} = 1;
1499 22         58 $parsed{class} = 'scrartcl';
1500 22         59 delete $parsed{opening}; # not needed for article.
1501             }
1502              
1503              
1504 249 100       10424 unless ($parsed{notoc}) {
1505 239 100       1115 if ($doc->wants_toc) {
1506 159         7753 $parsed{wants_toc} = 1;
1507             }
1508             }
1509              
1510             return {
1511 249         6587 options => \%tokens,
1512             safe_options => \%parsed,
1513             doc => $doc,
1514             tex_setup_langs => $tex_setup_langs,
1515             latex_body => $latex_body,
1516             enable_secondary_footnotes => $enable_secondary_footnotes,
1517             tex_metadata => $self->file_header->tex_metadata,
1518             tex_indexes => \@indexes,
1519             # in case we need it for volumes
1520             format_id => $template_options->format_id,
1521             };
1522             }
1523              
1524             sub _interpolate_magic_comments {
1525 254     254   3099 my ($self, $format, $doc) = @_;
1526 254   100     901 $format ||= 'DEFAULT';
1527 254         1610 my $latex = $doc->as_latex;
1528             # format is validated.
1529             # switch is gmx, no "s", we are line-based here
1530 254         6220230 my $prefix = qr{
1531             \%
1532             \x{20}+
1533             \:
1534             (?:
1535             \Q$format\E | \* | ALL
1536             )
1537             \:
1538             \x{20}+
1539             \\textbackslash\{\}
1540             }x;
1541 254         1043 my $size = qr{-?[1-9][0-9]*(?:mm|cm|pt|em)}x;
1542              
1543 254         5934 $latex =~ s/^
1544             $prefix
1545             ( # permitted commands
1546             sloppy |
1547             fussy |
1548             newpage |
1549             strut |
1550             flushbottom |
1551             raggedbottom |
1552             vfill |
1553             amusewiki[a-zA-Z]+ |
1554             clearpage |
1555             cleardoublepage |
1556             vskip \x{20}+ $size
1557             )
1558             \x{20}*
1559             $
1560             /\\$1/gmx;
1561 254         3591 $latex =~ s/^
1562             $prefix
1563             ( (this)? pagestyle )
1564             \\ \{
1565             ( plain | empty | headings | myheadings | scrheadings )
1566             \\ \}
1567             \x{20}*
1568             $
1569             /\\$1\{$3\}/gmx;
1570              
1571 254         3429 $latex =~ s/^
1572             $prefix
1573             ( enlargethispage )
1574             \\ \{
1575             ( $size )
1576             \\ \}
1577             \x{20}*
1578             $
1579             /\\$1\{$2\}/gmx;
1580              
1581 254         994 my $regular = qr{[^\#\$\%\&\~\^\\\{\}_]+};
1582 254         3558 $latex =~ s/^
1583             $prefix
1584             markboth
1585             \\ \{
1586             ($regular)
1587             \\\}
1588             \\\{
1589             ($regular)
1590             \\\}
1591             \x{20}*
1592             $
1593             /\\markboth\{$1}\{$2\}/gmx;
1594 254         3095 $latex =~ s/^
1595             $prefix
1596             markright
1597             \\ \{
1598             ($regular)
1599             \\\}
1600             \x{20}*
1601             $
1602             /\\markright\{$1}/gmx;
1603              
1604             # with looseness, we need to attach it to the next paragraph, so
1605             # eat all the space and replace with a single \n
1606              
1607 254         2767 $latex =~ s/^
1608             $prefix
1609             looseness\=(-?[0-9])
1610             $
1611             \s*
1612             /\\looseness=$1\n/gmx;
1613              
1614             # add to toc
1615 254         4185 $latex =~ s/^
1616             $prefix
1617             addcontentsline
1618             \\\{
1619             (toc|lof|lot)
1620             \\\}
1621             \\\{
1622             (part|chapter|section|subsection)
1623             \\\}
1624             \\\{
1625             ($regular)
1626             \\\}
1627             \x{20}*
1628             $
1629             /\\addcontentsline{$1}{$2}{$3}/gmx;
1630              
1631 254         1274 return $latex;
1632             }
1633              
1634             sub _looks_like_a_sane_name {
1635 914     914   2499 my ($self, $name) = @_;
1636 914 50       2481 return unless defined $name;
1637 914         1468 my $out;
1638 914         1659 eval {
1639 914         4143 $out = Text::Amuse::Compile::TemplateOptions::check_filename($name);
1640             };
1641 914 100 66     3385 if (!$out || $@) {
1642 772         1188 $self->log_info("$name is not good: $@") if DEBUG;
1643 772         2862 return;
1644             }
1645             else {
1646 142         237 $self->log_info("$name is good") if DEBUG;
1647 142         461 return $out;
1648             }
1649             }
1650              
1651             sub _mime_for_attachment {
1652 12     12   39 my ($self, $att) = @_;
1653 12 50       40 die "Missing argument" unless $att;
1654 12         22 my $mime;
1655 12 100       150 if ($att =~ m/\.jpe?g$/) {
    50          
1656 4         30 $mime = "image/jpeg";
1657             }
1658             elsif ($att =~ m/\.png$/) {
1659 8         21 $mime = "image/png";
1660             }
1661             else {
1662 0         0 $self->log_fatal("Unrecognized attachment $att!");
1663             }
1664 12         78 return $mime;
1665             }
1666              
1667             sub _format_epub_fragment {
1668 399     399   774 my ($self, $index) = @_;
1669 399   100     2359 return sprintf('piece%06d.xhtml', $index || 0);
1670             }
1671              
1672             sub document_indexes {
1673 256     256 1 281389 my ($self) = @_;
1674 256 100       5742 my @docs = ($self->virtual ? ($self->document->docs) : ( $self->document ));
1675 14         160 my @comments = grep { /\AINDEX +([a-z]+): (.+)/ }
1676 14         100 map { $_->string }
1677 9062         640471 grep { $_->type eq 'comment' }
1678 256         2565 map { $_->document->elements } @docs;
  287         1550  
1679 256         2664 return @comments;
1680             }
1681              
1682              
1683             1;