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