]> git.donarmstrong.com Git - lilypond.git/blob - lily/paper-book.cc
Add '-dcrop' option to ps and svg backends
[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 #include "ly-module.hh"
34 #include "lily-imports.hh"
35
36
37 Paper_book::Paper_book ()
38 {
39   header_ = SCM_EOL;
40   header_0_ = SCM_EOL;
41   pages_ = SCM_BOOL_F;
42   scores_ = SCM_EOL;
43   bookparts_ = SCM_EOL;
44   performances_ = SCM_EOL;
45   systems_ = SCM_BOOL_F;
46
47   paper_ = 0;
48   parent_ = 0;
49   smobify_self ();
50 }
51
52 Paper_book::~Paper_book ()
53 {
54 }
55
56 const char * const Paper_book::type_p_name_ = "ly:paper-book?";
57
58 SCM
59 Paper_book::mark_smob () const
60 {
61   if (paper_)
62     scm_gc_mark (paper_->self_scm ());
63   if (parent_)
64     scm_gc_mark (parent_->self_scm ());
65   scm_gc_mark (header_);
66   scm_gc_mark (header_0_);
67   scm_gc_mark (pages_);
68   scm_gc_mark (performances_);
69   scm_gc_mark (scores_);
70   scm_gc_mark (bookparts_);
71   return systems_;
72 }
73
74 Output_def *
75 Paper_book::top_paper ()
76 {
77   Output_def *paper = paper_;
78   while (paper->parent_)
79     paper = paper->parent_;
80   return paper;
81 }
82
83 SCM
84 dump_fields ()
85 {
86   SCM fields = SCM_EOL;
87   for (vsize i = dump_header_fieldnames_global.size (); i--;)
88     fields
89       = scm_cons (ly_symbol2scm (dump_header_fieldnames_global[i].c_str ()),
90                   fields);
91   return fields;
92 }
93
94 void
95 Paper_book::add_score (SCM s)
96 {
97   scores_ = scm_cons (s, scores_);
98 }
99
100 void
101 Paper_book::add_bookpart (SCM p)
102 {
103   bookparts_ = scm_cons (p, bookparts_);
104 }
105
106 void
107 Paper_book::add_performance (SCM s)
108 {
109   performances_ = scm_cons (s, performances_);
110 }
111
112 long
113 Paper_book::output_aux (SCM output_channel,
114                         bool is_last,
115                         long *first_page_number,
116                         long *first_performance_number)
117 {
118   long page_nb = 0;
119   if (scm_is_pair (performances_))
120     {
121       Lily::write_performances_midis (performances (),
122                                       output_channel,
123                                       scm_from_long (*first_performance_number));
124       *first_performance_number += scm_ilength (performances_);
125     }
126
127   if (scm_is_pair (bookparts_))
128     {
129       for (SCM p = bookparts_; scm_is_pair (p); p = scm_cdr (p))
130         if (Paper_book *pbookpart = unsmob<Paper_book> (scm_car (p)))
131           {
132             bool is_last_part = (is_last && !scm_is_pair (scm_cdr (p)));
133             page_nb += pbookpart->output_aux (output_channel,
134                                               is_last_part,
135                                               first_page_number,
136                                               first_performance_number);
137           }
138     }
139   else
140     {
141       if (scm_is_null (scores_))
142         return 0;
143       paper_->set_variable (ly_symbol2scm ("first-page-number"),
144                             scm_from_long (*first_page_number));
145       paper_->set_variable (ly_symbol2scm ("is-last-bookpart"),
146                             ly_bool2scm (is_last));
147       /* Generate all stencils to trigger font loads.  */
148       page_nb = scm_ilength (pages ());
149       *first_page_number += page_nb;
150     }
151   return page_nb;
152 }
153
154 void
155 Paper_book::output (SCM output_channel)
156 {
157   long first_page_number
158     = robust_scm2int (paper_->c_variable ("first-page-number"), 1);
159   long first_performance_number = 0;
160
161   /* FIXME: We need a line-width for ps output (framework-ps.scm:92).
162      If we don't have any, we take the paper-width unless we know
163      better which line-width to choose (e.g. if there are \bookparts
164      with different line-widths) and why we need it at all.
165   */
166
167   if (SCM_UNBNDP (paper_->c_variable ("line-width")))
168     paper_->set_variable (ly_symbol2scm ("line-width"),
169                           paper_->c_variable ("paper-width"));
170
171   if (!output_aux (output_channel,
172                    true,
173                    &first_page_number,
174                    &first_performance_number))
175     return;
176
177   SCM scopes = SCM_EOL;
178   if (ly_is_module (header_))
179     scopes = scm_cons (header_, scopes);
180
181   string mod_nm = "scm framework-" + get_output_backend_name ();
182
183   SCM mod = scm_c_resolve_module (mod_nm.c_str ());
184
185   if (get_program_option ("print-pages"))
186     {
187       SCM framework = ly_module_lookup (mod,
188                                         ly_symbol2scm ("output-framework"));
189
190       if (scm_is_true (framework))
191         {
192           SCM func = scm_variable_ref (framework);
193           scm_call_4 (func,
194                       output_channel,
195                       self_scm (),
196                       scopes,
197                       dump_fields ());
198         }
199       else
200         warning (_f ("program option -dprint-pages not supported by backend `%s'",
201                      get_output_backend_name ()));
202     }
203
204   if (get_program_option ("preview"))
205     {
206       SCM framework
207         = ly_module_lookup (mod, ly_symbol2scm ("output-preview-framework"));
208
209       if (scm_is_true (framework))
210         {
211           SCM func = scm_variable_ref (framework);
212           scm_call_4 (func,
213                       output_channel,
214                       self_scm (),
215                       scopes,
216                       dump_fields ());
217         }
218       else
219         warning (_f ("program option -dpreview not supported by backend `%s'",
220                      get_output_backend_name ()));
221     }
222
223   if (get_program_option ("crop"))
224     {
225       SCM framework
226         = ly_module_lookup (mod, ly_symbol2scm ("output-crop-framework"));
227
228       if (scm_is_true (framework))
229         {
230           SCM func = scm_variable_ref (framework);
231           scm_call_4 (func,
232                       output_channel,
233                       self_scm (),
234                       scopes,
235                       dump_fields ());
236         }
237       else
238         warning (_f ("program option -dcrop not supported by backend `%s'",
239                      get_output_backend_name ()));
240     }
241 }
242
243 void
244 Paper_book::classic_output_aux (SCM output,
245                                 long *first_performance_number)
246 {
247   if (scm_is_pair (performances_))
248     {
249       Lily::write_performances_midis (performances (),
250                                       output,
251                                       scm_from_long (*first_performance_number));
252       *first_performance_number += scm_ilength (performances_);
253     }
254
255   /* Generate all stencils to trigger font loads.  */
256   systems ();
257 }
258
259 void
260 Paper_book::classic_output (SCM output)
261 {
262   long first_performance_number = 0;
263   classic_output_aux (output, &first_performance_number);
264
265   SCM scopes = SCM_EOL;
266   if (ly_is_module (header_))
267     scopes = scm_cons (header_, scopes);
268
269   if (ly_is_module (header_0_))
270     scopes = scm_cons (header_0_, scopes);
271
272   string format = get_output_backend_name ();
273   string mod_nm = "scm framework-" + format;
274
275   SCM mod = scm_c_resolve_module (mod_nm.c_str ());
276   SCM func = scm_c_module_lookup (mod, "output-classic-framework");
277
278   func = scm_variable_ref (func);
279   scm_call_4 (func,
280               output,
281               self_scm (),
282               scopes,
283               dump_fields ());
284   progress_indication ("\n");
285 }
286
287 /* TODO: resurrect more complex user-tweaks for titling?  */
288 Stencil
289 Paper_book::book_title ()
290 {
291   SCM title_func = paper_->lookup_variable (ly_symbol2scm ("book-title"));
292   Stencil title;
293
294   SCM scopes = SCM_EOL;
295   if (ly_is_module (header_))
296     scopes = scm_cons (header_, scopes);
297
298   SCM tit = SCM_EOL;
299   if (ly_is_procedure (title_func))
300     tit = scm_call_2 (title_func,
301                       paper_->self_scm (),
302                       scopes);
303
304   if (unsmob<Stencil> (tit))
305     title = *unsmob<Stencil> (tit);
306
307   if (!title.is_empty ())
308     title.align_to (Y_AXIS, UP);
309
310   return title;
311 }
312
313 Stencil
314 Paper_book::score_title (SCM header)
315 {
316   SCM title_func = paper_->lookup_variable (ly_symbol2scm ("score-title"));
317
318   Stencil title;
319
320   SCM scopes = SCM_EOL;
321   if (ly_is_module (header_))
322     scopes = scm_cons (header_, scopes);
323
324   if (ly_is_module (header))
325     scopes = scm_cons (header, scopes);
326
327   SCM tit = SCM_EOL;
328   if (ly_is_procedure (title_func))
329     tit = scm_call_2 (title_func,
330                       paper_->self_scm (),
331                       scopes);
332
333   if (unsmob<Stencil> (tit))
334     title = *unsmob<Stencil> (tit);
335
336   if (!title.is_empty ())
337     title.align_to (Y_AXIS, UP);
338
339   return title;
340 }
341
342 void
343 set_page_permission (SCM sys, SCM symbol, SCM permission)
344 {
345   if (Paper_score *ps = unsmob<Paper_score> (sys))
346     {
347       vector<Grob *> cols = ps->get_columns ();
348       if (cols.size ())
349         {
350           Paper_column *col = dynamic_cast<Paper_column *> (cols.back ());
351           col->set_property (symbol, permission);
352           col->find_prebroken_piece (LEFT)->set_property (symbol, permission);
353         }
354     }
355   else if (Prob *pb = unsmob<Prob> (sys))
356     pb->set_property (symbol, permission);
357 }
358
359 /* read the breakbefore property of a score block and set up the preceding
360    system-spec to honour it. That is, SYS should be the system spec that
361    immediately precedes the score (from which HEADER is taken)
362    in the get_system_specs () list */
363 void
364 set_system_penalty (SCM sys, SCM header)
365 {
366   if (ly_is_module (header))
367     {
368       SCM force = ly_module_lookup (header, ly_symbol2scm ("breakbefore"));
369       if (SCM_VARIABLEP (force)
370           && scm_is_bool (SCM_VARIABLE_REF (force)))
371         {
372           if (to_boolean (SCM_VARIABLE_REF (force)))
373             {
374               set_page_permission (sys, ly_symbol2scm ("page-break-permission"),
375                                    ly_symbol2scm ("force"));
376               set_page_permission (sys, ly_symbol2scm ("line-break-permission"),
377                                    ly_symbol2scm ("force"));
378             }
379           else
380             set_page_permission (sys, ly_symbol2scm ("page-break-permission"),
381                                  SCM_EOL);
382         }
383     }
384 }
385
386 void
387 set_labels (SCM sys, SCM labels)
388 {
389   if (Paper_score *ps = unsmob<Paper_score> (sys))
390     {
391       vector<Grob *> cols = ps->get_columns ();
392       if (cols.size ())
393         {
394           Paper_column *col = dynamic_cast<Paper_column *> (cols[0]);
395           col->set_property ("labels",
396                              scm_append_x (scm_list_2 (col->get_property ("labels"),
397                                                        labels)));
398           Paper_column *col_right
399             = dynamic_cast<Paper_column *> (col->find_prebroken_piece (RIGHT));
400           col_right->set_property ("labels",
401                                    scm_append_x (scm_list_2 (col_right->get_property ("labels"),
402                                                              labels)));
403         }
404     }
405   else if (Prob *pb = unsmob<Prob> (sys))
406     pb->set_property ("labels",
407                       scm_append_x (scm_list_2 (pb->get_property ("labels"),
408                                                 labels)));
409 }
410
411 SCM
412 Paper_book::get_score_title (SCM header)
413 {
414   Stencil title = score_title (header);
415   if (title.is_empty ())
416     title = score_title (header_);
417   if (!title.is_empty ())
418     {
419       /*
420         TODO: this should come from the \layout {} block, which should
421         override settings from \paper {}
422       */
423       SCM props
424         = paper_->lookup_variable (ly_symbol2scm ("score-title-properties"));
425       Prob *ps = make_paper_system (props);
426       paper_system_set_stencil (ps, title);
427
428       return ps->self_scm ();
429     }
430
431   return SCM_BOOL_F;
432 }
433
434 SCM
435 Paper_book::get_system_specs ()
436 {
437   SCM system_specs = SCM_EOL;
438
439   Stencil title = book_title ();
440   if (!title.is_empty ())
441     {
442       SCM props
443         = paper_->lookup_variable (ly_symbol2scm ("book-title-properties"));
444       Prob *ps = make_paper_system (props);
445       paper_system_set_stencil (ps, title);
446
447       system_specs = scm_cons (ps->self_scm (), system_specs);
448       ps->unprotect ();
449     }
450
451   SCM page_properties
452     = Lily::layout_extract_page_properties (paper_->self_scm ());
453
454   SCM header = SCM_EOL;
455   SCM labels = SCM_EOL;
456   for (SCM s = scm_reverse (scores_); scm_is_pair (s); s = scm_cdr (s))
457     {
458       if (ly_is_module (scm_car (s)))
459         {
460           header = scm_car (s);
461           if (scm_is_null (header_0_))
462             header_0_ = header;
463         }
464       else if (Page_marker *page_marker = unsmob<Page_marker> (scm_car (s)))
465         {
466           /* page markers are used to set page breaking/turning permission,
467              or to place bookmarking labels */
468           if (scm_is_symbol (page_marker->permission_symbol ()))
469             {
470               /* set previous element page break or turn permission */
471               if (scm_is_pair (system_specs))
472                 set_page_permission (scm_car (system_specs),
473                                      page_marker->permission_symbol (),
474                                      page_marker->permission_value ());
475             }
476           if (scm_is_symbol (page_marker->label ()))
477             {
478               /* The next element label is to be set */
479               labels = scm_cons (page_marker->label (), labels);
480             }
481         }
482       else if (Music_output *mop = unsmob<Music_output> (scm_car (s)))
483         {
484           if (Paper_score *pscore = dynamic_cast<Paper_score *> (mop))
485             {
486               SCM title = get_score_title (header);
487
488               if (scm_is_pair (system_specs))
489                 set_system_penalty (scm_car (system_specs), header);
490
491               if (unsmob<Prob> (title))
492                 {
493                   system_specs = scm_cons (title, system_specs);
494                   unsmob<Prob> (title)->unprotect ();
495                 }
496
497               header = SCM_EOL;
498               system_specs = scm_cons (pscore->self_scm (), system_specs);
499               if (scm_is_pair (labels))
500                 {
501                   set_labels (scm_car (system_specs), labels);
502                   labels = SCM_EOL;
503                 }
504             }
505           else
506             {
507               /*
508                 Ignore MIDI
509               */
510             }
511         }
512       else if (Text_interface::is_markup_list (scm_car (s)))
513         {
514           SCM texts = Lily::interpret_markup_list (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 (scm_is_eq (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 (scm_is_true (systems_))
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               = unsmob<Paper_score> (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 (scm_is_null (perm))
627                 next->set_property ("penalty", scm_from_int (10001));
628               else if (scm_is_eq (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_is_true (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 (scm_is_false (systems_))
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 }