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