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 |
||||||
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 |
||||||
105 | |||||||
106 | =item tt | ||||||
107 | |||||||
108 | The L |
||||||
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 |
||||||
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 |
||||||
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 |
||||||
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 |
||||||
646 | C |
||||||
647 | |||||||
648 | With no arguments, this method enforces the options C |
||||||
649 | and C |
||||||
650 | the imposed output, unless C |
||||||
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 |
||||||
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 |
||||||
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 | |
||||||
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 |
||||||
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 |
||||||
1274 | Otherwise print to the standard output. | ||||||
1275 | |||||||
1276 | =item log_fatal(@strings) | ||||||
1277 | |||||||
1278 | Calls C |
||||||
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; |