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 |
||||||
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 | 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 |
||||||
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 |
||||||
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 |
||||||
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 | 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 |
||||||
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 |
||||||
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 | |
||||||
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 |
||||||
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 |
||||||
1271 | Otherwise print to the standard output. | ||||||
1272 | |||||||
1273 | =item log_fatal(@strings) | ||||||
1274 | |||||||
1275 | Calls C |
||||||
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; |