]> git.donarmstrong.com Git - lilypond.git/blob - lily/paper-book.cc
restore regression tests for time-signature styles; issue 4176
[lilypond.git] / lily / paper-book.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2004--2015 Jan Nieuwenhuizen <janneke@gnu.org>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "paper-book.hh"
21
22 #include "grob.hh"
23 #include "international.hh"
24 #include "main.hh"
25 #include "output-def.hh"
26 #include "paper-column.hh"
27 #include "paper-score.hh"
28 #include "paper-system.hh"
29 #include "text-interface.hh"
30 #include "warn.hh"
31 #include "program-option.hh"
32 #include "page-marker.hh"
33
34
35 Paper_book::Paper_book ()
36 {
37   header_ = SCM_EOL;
38   header_0_ = SCM_EOL;
39   pages_ = SCM_BOOL_F;
40   scores_ = SCM_EOL;
41   bookparts_ = SCM_EOL;
42   performances_ = SCM_EOL;
43   systems_ = SCM_BOOL_F;
44
45   paper_ = 0;
46   parent_ = 0;
47   smobify_self ();
48 }
49
50 Paper_book::~Paper_book ()
51 {
52 }
53
54 const char Paper_book::type_p_name_[] = "ly:paper-book?";
55
56 SCM
57 Paper_book::mark_smob ()
58 {
59   if (paper_)
60     scm_gc_mark (paper_->self_scm ());
61   if (parent_)
62     scm_gc_mark (parent_->self_scm ());
63   scm_gc_mark (header_);
64   scm_gc_mark (header_0_);
65   scm_gc_mark (pages_);
66   scm_gc_mark (performances_);
67   scm_gc_mark (scores_);
68   scm_gc_mark (bookparts_);
69   return systems_;
70 }
71
72 Output_def *
73 Paper_book::top_paper ()
74 {
75   Output_def *paper = paper_;
76   while (paper->parent_)
77     paper = paper->parent_;
78   return paper;
79 }
80
81 SCM
82 dump_fields ()
83 {
84   SCM fields = SCM_EOL;
85   for (vsize i = dump_header_fieldnames_global.size (); i--;)
86     fields
87       = scm_cons (ly_symbol2scm (dump_header_fieldnames_global[i].c_str ()),
88                   fields);
89   return fields;
90 }
91
92 void
93 Paper_book::add_score (SCM s)
94 {
95   scores_ = scm_cons (s, scores_);
96 }
97
98 void
99 Paper_book::add_bookpart (SCM p)
100 {
101   bookparts_ = scm_cons (p, bookparts_);
102 }
103
104 void
105 Paper_book::add_performance (SCM s)
106 {
107   performances_ = scm_cons (s, performances_);
108 }
109
110 long
111 Paper_book::output_aux (SCM output_channel,
112                         bool is_last,
113                         long *first_page_number,
114                         long *first_performance_number)
115 {
116   long page_nb = 0;
117   if (scm_is_pair (performances_))
118     {
119       SCM proc = ly_lily_module_constant ("write-performances-midis");
120
121       scm_call_3 (proc,
122                   performances (),
123                   output_channel,
124                   scm_from_long (*first_performance_number));
125       *first_performance_number += scm_ilength (performances_);
126     }
127
128   if (scm_is_pair (bookparts_))
129     {
130       for (SCM p = bookparts_; scm_is_pair (p); p = scm_cdr (p))
131         if (Paper_book *pbookpart = Paper_book::unsmob (scm_car (p)))
132           {
133             bool is_last_part = (is_last && !scm_is_pair (scm_cdr (p)));
134             page_nb += pbookpart->output_aux (output_channel,
135                                               is_last_part,
136                                               first_page_number,
137                                               first_performance_number);
138           }
139     }
140   else
141     {
142       if (scores_ == SCM_EOL)
143         return 0;
144       paper_->set_variable (ly_symbol2scm ("first-page-number"),
145                             scm_from_long (*first_page_number));
146       paper_->set_variable (ly_symbol2scm ("is-last-bookpart"),
147                             ly_bool2scm (is_last));
148       /* Generate all stencils to trigger font loads.  */
149       page_nb = scm_ilength (pages ());
150       *first_page_number += page_nb;
151     }
152   return page_nb;
153 }
154
155 void
156 Paper_book::output (SCM output_channel)
157 {
158   long first_page_number
159     = robust_scm2int (paper_->c_variable ("first-page-number"), 1);
160   long first_performance_number = 0;
161
162   /* FIXME: We need a line-width for ps output (framework-ps.scm:92).
163      If we don't have any, we take the paper-width unless we know
164      better which line-width to choose (e.g. if there are \bookparts
165      with different line-widths) and why we need it at all.
166   */
167
168   if (paper_->c_variable ("line-width") == SCM_UNDEFINED)
169     paper_->set_variable (ly_symbol2scm ("line-width"),
170                           paper_->c_variable ("paper-width"));
171
172   if (!output_aux (output_channel,
173                    true,
174                    &first_page_number,
175                    &first_performance_number))
176     return;
177
178   SCM scopes = SCM_EOL;
179   if (ly_is_module (header_))
180     scopes = scm_cons (header_, scopes);
181
182   string mod_nm = "scm framework-" + get_output_backend_name ();
183
184   SCM mod = scm_c_resolve_module (mod_nm.c_str ());
185
186   if (get_program_option ("print-pages"))
187     {
188       SCM framework = ly_module_lookup (mod,
189                                         ly_symbol2scm ("output-framework"));
190
191       if (framework != SCM_BOOL_F)
192         {
193           SCM func = scm_variable_ref (framework);
194           scm_call_4 (func,
195                       output_channel,
196                       self_scm (),
197                       scopes,
198                       dump_fields ());
199         }
200       else
201         warning (_f ("program option -dprint-pages not supported by backend `%s'",
202                      get_output_backend_name ()));
203     }
204
205   if (get_program_option ("preview"))
206     {
207       SCM framework
208         = ly_module_lookup (mod, ly_symbol2scm ("output-preview-framework"));
209
210       if (framework != SCM_BOOL_F)
211         {
212           SCM func = scm_variable_ref (framework);
213           scm_call_4 (func,
214                       output_channel,
215                       self_scm (),
216                       scopes,
217                       dump_fields ());
218         }
219       else
220         warning (_f ("program option -dpreview not supported by backend `%s'",
221                      get_output_backend_name ()));
222     }
223 }
224
225 void
226 Paper_book::classic_output_aux (SCM output,
227                                 long *first_performance_number)
228 {
229   if (scm_is_pair (performances_))
230     {
231       SCM proc = ly_lily_module_constant ("write-performances-midis");
232       scm_call_3 (proc,
233                   performances (),
234                   output,
235                   scm_from_long (*first_performance_number));
236       *first_performance_number += scm_ilength (performances_);
237     }
238
239   /* Generate all stencils to trigger font loads.  */
240   systems ();
241 }
242
243 void
244 Paper_book::classic_output (SCM output)
245 {
246   long first_performance_number = 0;
247   classic_output_aux (output, &first_performance_number);
248
249   SCM scopes = SCM_EOL;
250   if (ly_is_module (header_))
251     scopes = scm_cons (header_, scopes);
252
253   if (ly_is_module (header_0_))
254     scopes = scm_cons (header_0_, scopes);
255
256   string format = get_output_backend_name ();
257   string mod_nm = "scm framework-" + format;
258
259   SCM mod = scm_c_resolve_module (mod_nm.c_str ());
260   SCM func = scm_c_module_lookup (mod, "output-classic-framework");
261
262   func = scm_variable_ref (func);
263   scm_call_4 (func,
264               output,
265               self_scm (),
266               scopes,
267               dump_fields ());
268   progress_indication ("\n");
269 }
270
271 /* TODO: resurrect more complex user-tweaks for titling?  */
272 Stencil
273 Paper_book::book_title ()
274 {
275   SCM title_func = paper_->lookup_variable (ly_symbol2scm ("book-title"));
276   Stencil title;
277
278   SCM scopes = SCM_EOL;
279   if (ly_is_module (header_))
280     scopes = scm_cons (header_, scopes);
281
282   SCM tit = SCM_EOL;
283   if (ly_is_procedure (title_func))
284     tit = scm_call_2 (title_func,
285                       paper_->self_scm (),
286                       scopes);
287
288   if (Stencil::is_smob (tit))
289     title = *Stencil::unsmob (tit);
290
291   if (!title.is_empty ())
292     title.align_to (Y_AXIS, UP);
293
294   return title;
295 }
296
297 Stencil
298 Paper_book::score_title (SCM header)
299 {
300   SCM title_func = paper_->lookup_variable (ly_symbol2scm ("score-title"));
301
302   Stencil title;
303
304   SCM scopes = SCM_EOL;
305   if (ly_is_module (header_))
306     scopes = scm_cons (header_, scopes);
307
308   if (ly_is_module (header))
309     scopes = scm_cons (header, scopes);
310
311   SCM tit = SCM_EOL;
312   if (ly_is_procedure (title_func))
313     tit = scm_call_2 (title_func,
314                       paper_->self_scm (),
315                       scopes);
316
317   if (Stencil::is_smob (tit))
318     title = *Stencil::unsmob (tit);
319
320   if (!title.is_empty ())
321     title.align_to (Y_AXIS, UP);
322
323   return title;
324 }
325
326 void
327 set_page_permission (SCM sys, SCM symbol, SCM permission)
328 {
329   if (Paper_score *ps = dynamic_cast<Paper_score *> (Music_output::unsmob (sys)))
330     {
331       vector<Grob *> cols = ps->get_columns ();
332       if (cols.size ())
333         {
334           Paper_column *col = dynamic_cast<Paper_column *> (cols.back ());
335           col->set_property (symbol, permission);
336           col->find_prebroken_piece (LEFT)->set_property (symbol, permission);
337         }
338     }
339   else if (Prob *pb = Prob::unsmob (sys))
340     pb->set_property (symbol, permission);
341 }
342
343 /* read the breakbefore property of a score block and set up the preceding
344    system-spec to honour it. That is, SYS should be the system spec that
345    immediately precedes the score (from which HEADER is taken)
346    in the get_system_specs () list */
347 void
348 set_system_penalty (SCM sys, SCM header)
349 {
350   if (ly_is_module (header))
351     {
352       SCM force = ly_module_lookup (header, ly_symbol2scm ("breakbefore"));
353       if (SCM_VARIABLEP (force)
354           && scm_is_bool (SCM_VARIABLE_REF (force)))
355         {
356           if (to_boolean (SCM_VARIABLE_REF (force)))
357             {
358               set_page_permission (sys, ly_symbol2scm ("page-break-permission"),
359                                    ly_symbol2scm ("force"));
360               set_page_permission (sys, ly_symbol2scm ("line-break-permission"),
361                                    ly_symbol2scm ("force"));
362             }
363           else
364             set_page_permission (sys, ly_symbol2scm ("page-break-permission"),
365                                  SCM_EOL);
366         }
367     }
368 }
369
370 void
371 set_labels (SCM sys, SCM labels)
372 {
373   if (Paper_score *ps = dynamic_cast<Paper_score *> (Music_output::unsmob (sys)))
374     {
375       vector<Grob *> cols = ps->get_columns ();
376       if (cols.size ())
377         {
378           Paper_column *col = dynamic_cast<Paper_column *> (cols[0]);
379           col->set_property ("labels",
380                              scm_append_x (scm_list_2 (col->get_property ("labels"),
381                                                        labels)));
382           Paper_column *col_right
383             = dynamic_cast<Paper_column *> (col->find_prebroken_piece (RIGHT));
384           col_right->set_property ("labels",
385                                    scm_append_x (scm_list_2 (col_right->get_property ("labels"),
386                                                              labels)));
387         }
388     }
389   else if (Prob *pb = Prob::unsmob (sys))
390     pb->set_property ("labels",
391                       scm_append_x (scm_list_2 (pb->get_property ("labels"),
392                                                 labels)));
393 }
394
395 SCM
396 Paper_book::get_score_title (SCM header)
397 {
398   Stencil title = score_title (header);
399   if (title.is_empty ())
400     title = score_title (header_);
401   if (!title.is_empty ())
402     {
403       /*
404         TODO: this should come from the \layout {} block, which should
405         override settings from \paper {}
406       */
407       SCM props
408         = paper_->lookup_variable (ly_symbol2scm ("score-title-properties"));
409       Prob *ps = make_paper_system (props);
410       paper_system_set_stencil (ps, title);
411
412       return ps->self_scm ();
413     }
414
415   return SCM_BOOL_F;
416 }
417
418 SCM
419 Paper_book::get_system_specs ()
420 {
421   SCM system_specs = SCM_EOL;
422
423   Stencil title = book_title ();
424   if (!title.is_empty ())
425     {
426       SCM props
427         = paper_->lookup_variable (ly_symbol2scm ("book-title-properties"));
428       Prob *ps = make_paper_system (props);
429       paper_system_set_stencil (ps, title);
430
431       system_specs = scm_cons (ps->self_scm (), system_specs);
432       ps->unprotect ();
433     }
434
435   SCM page_properties
436     = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
437                   paper_->self_scm ());
438
439   SCM interpret_markup_list = ly_lily_module_constant ("interpret-markup-list");
440   SCM header = SCM_EOL;
441   SCM labels = SCM_EOL;
442   for (SCM s = scm_reverse (scores_); scm_is_pair (s); s = scm_cdr (s))
443     {
444       if (ly_is_module (scm_car (s)))
445         {
446           header = scm_car (s);
447           if (header_0_ == SCM_EOL)
448             header_0_ = header;
449         }
450       else if (Page_marker *page_marker = Page_marker::unsmob (scm_car (s)))
451         {
452           /* page markers are used to set page breaking/turning permission,
453              or to place bookmarking labels */
454           if (scm_is_symbol (page_marker->permission_symbol ()))
455             {
456               /* set previous element page break or turn permission */
457               if (scm_is_pair (system_specs))
458                 set_page_permission (scm_car (system_specs),
459                                      page_marker->permission_symbol (),
460                                      page_marker->permission_value ());
461             }
462           if (scm_is_symbol (page_marker->label ()))
463             {
464               /* The next element label is to be set */
465               labels = scm_cons (page_marker->label (), labels);
466             }
467         }
468       else if (Music_output *mop = Music_output::unsmob (scm_car (s)))
469         {
470           if (Paper_score *pscore = dynamic_cast<Paper_score *> (mop))
471             {
472               SCM title = get_score_title (header);
473
474               if (scm_is_pair (system_specs))
475                 set_system_penalty (scm_car (system_specs), header);
476
477               if (Prob::is_smob (title))
478                 {
479                   system_specs = scm_cons (title, system_specs);
480                   Prob::unsmob (title)->unprotect ();
481                 }
482
483               header = SCM_EOL;
484               system_specs = scm_cons (pscore->self_scm (), system_specs);
485               if (scm_is_pair (labels))
486                 {
487                   set_labels (scm_car (system_specs), labels);
488                   labels = SCM_EOL;
489                 }
490             }
491           else
492             {
493               /*
494                 Ignore MIDI
495               */
496             }
497         }
498       else if (Text_interface::is_markup_list (scm_car (s)))
499         {
500           SCM texts = scm_call_3 (interpret_markup_list,
501                                   paper_->self_scm (),
502                                   page_properties,
503                                   scm_car (s));
504           Prob *first = 0;
505           Prob *last = 0;
506           for (SCM list = texts; scm_is_pair (list); list = scm_cdr (list))
507             {
508               SCM t = scm_car (list);
509               // TODO: init props
510               Prob *ps = make_paper_system (SCM_EOL);
511               ps->set_property ("page-break-permission",
512                                 ly_symbol2scm ("allow"));
513               ps->set_property ("page-turn-permission",
514                                 ly_symbol2scm ("allow"));
515               ps->set_property ("last-markup-line", SCM_BOOL_F);
516               ps->set_property ("first-markup-line", SCM_BOOL_F);
517
518               paper_system_set_stencil (ps, *Stencil::unsmob (t));
519
520               SCM footnotes = get_footnotes (Stencil::unsmob (t)->expr ());
521               ps->set_property ("footnotes", footnotes);
522               ps->set_property ("is-title", SCM_BOOL_T);
523               if (list == texts)
524                 first = ps;
525               else
526                 {
527                   // last line so far, in a multi-line paragraph
528                   last = ps;
529                   //Place closely to previous line, no stretching.
530                   ps->set_property ("tight-spacing", SCM_BOOL_T);
531                 }
532               system_specs = scm_cons (ps->self_scm (), system_specs);
533               ps->unprotect ();
534
535               if (scm_is_pair (labels))
536                 {
537                   set_labels (scm_car (system_specs), labels);
538                   labels = SCM_EOL;
539                 }
540               // FIXME: figure out penalty.
541               //set_system_penalty (ps, scores_[i].header_);
542             }
543           /* Set properties to avoid widowed/orphaned lines.
544              Single-line markup_lists are excluded, but in future
545              we may want to add the case of a very short, single line. */
546           if (first && last)
547             {
548               last->set_property ("last-markup-line", SCM_BOOL_T);
549               first->set_property ("first-markup-line", SCM_BOOL_T);
550             }
551         }
552       else
553         assert (0);
554     }
555
556   system_specs = scm_reverse_x (system_specs, SCM_EOL);
557   return system_specs;
558 }
559
560 SCM
561 Paper_book::systems ()
562 {
563   if (systems_ != SCM_BOOL_F)
564     return systems_;
565
566   systems_ = SCM_EOL;
567   if (scm_is_pair (bookparts_))
568     {
569       SCM system_list = SCM_EOL;
570       for (SCM p = bookparts_; scm_is_pair (p); p = scm_cdr (p))
571         if (Paper_book *pbookpart = Paper_book::unsmob (scm_car (p)))
572           system_list = scm_cons (pbookpart->systems (), system_list);
573       systems_ = scm_append (scm_reverse_x (system_list, SCM_EOL));
574     }
575   else
576     {
577       SCM specs = get_system_specs ();
578       for (SCM s = specs; scm_is_pair (s); s = scm_cdr (s))
579         {
580           if (Paper_score * pscore
581               = dynamic_cast<Paper_score *> (Music_output::unsmob (scm_car (s))))
582             {
583               SCM system_list
584                 = scm_vector_to_list (pscore->get_paper_systems ());
585
586               systems_ = scm_reverse_x (system_list, systems_);
587             }
588           else
589             {
590               systems_ = scm_cons (scm_car (s), systems_);
591             }
592         }
593       systems_ = scm_reverse_x (systems_, SCM_EOL);
594
595       /* backwards compatibility for the old page breaker */
596       int i = 0;
597       Prob *last = 0;
598       for (SCM s = systems_; scm_is_pair (s); s = scm_cdr (s))
599         {
600           Prob *ps = Prob::unsmob (scm_car (s));
601           ps->set_property ("number", scm_from_int (++i));
602
603           if (last
604               && to_boolean (last->get_property ("is-title"))
605               && !scm_is_number (ps->get_property ("penalty")))
606             ps->set_property ("penalty", scm_from_int (10000));
607           last = ps;
608
609           if (scm_is_pair (scm_cdr (s)))
610             {
611               SCM perm = ps->get_property ("page-break-permission");
612               Prob *next = Prob::unsmob (scm_cadr (s));
613               if (perm == SCM_EOL)
614                 next->set_property ("penalty", scm_from_int (10001));
615               else if (perm == ly_symbol2scm ("force"))
616                 next->set_property ("penalty", scm_from_int (-10001));
617             }
618         }
619     }
620
621   return systems_;
622 }
623
624 SCM
625 Paper_book::pages ()
626 {
627   if (SCM_BOOL_F != pages_)
628     return pages_;
629
630   pages_ = SCM_EOL;
631   if (scm_is_pair (bookparts_))
632     {
633       for (SCM p = bookparts_; scm_is_pair (p); p = scm_cdr (p))
634         if (Paper_book *pbookpart = Paper_book::unsmob (scm_car (p)))
635           pages_ = scm_cons (pbookpart->pages (), pages_);
636       pages_ = scm_append (scm_reverse_x (pages_, SCM_EOL));
637     }
638   else if (scm_is_pair (scores_))
639     {
640       SCM page_breaking = paper_->c_variable ("page-breaking");
641       pages_ = scm_call_1 (page_breaking, self_scm ());
642
643       // Create all the page stencils.
644       SCM page_module = scm_c_resolve_module ("scm page");
645       SCM page_stencil = scm_c_module_lookup (page_module, "page-stencil");
646       page_stencil = scm_variable_ref (page_stencil);
647       for (SCM pages = pages_; scm_is_pair (pages); pages = scm_cdr (pages))
648         scm_call_1 (page_stencil, scm_car (pages));
649
650       // Perform any user-supplied post-processing.
651       SCM post_process = paper_->c_variable ("page-post-process");
652       if (ly_is_procedure (post_process))
653         scm_call_2 (post_process, paper_->self_scm (), pages_);
654
655       /* set systems_ from the pages */
656       if (systems_ == SCM_BOOL_F)
657         {
658           systems_ = SCM_EOL;
659           for (SCM p = pages_; scm_is_pair (p); p = scm_cdr (p))
660             {
661               Prob *page = Prob::unsmob (scm_car (p));
662               SCM systems = page->get_property ("lines");
663               systems_ = scm_cons (systems, systems_);
664             }
665           systems_ = scm_append (scm_reverse_x (systems_, SCM_EOL));
666         }
667     }
668   return pages_;
669 }
670
671 SCM
672 Paper_book::performances () const
673 {
674   return scm_reverse (performances_);
675 }