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