]> git.donarmstrong.com Git - lilypond.git/blob - lily/paper-book.cc
83653b6804325b4b72129c04ab40fe93235061e0
[lilypond.git] / lily / paper-book.cc
1 /*
2   paper-book.cc -- implement Paper_book
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2004 Jan Nieuwenhuizen <janneke@gnu.org>
7 */
8
9 #include <stdio.h>
10 #include <math.h>
11
12 #include "ly-module.hh"
13 #include "main.hh"
14 #include "paper-book.hh"
15 #include "paper-def.hh"
16 #include "paper-outputter.hh"
17 #include "paper-line.hh"
18 #include "paper-score.hh"
19 #include "stencil.hh"
20
21 static Real const MIN_COVERAGE = 0.66;
22 static Real const MAX_CRAMP = 0.05;
23
24 static SCM
25 stencil2line (Stencil* stil, bool is_title = false)
26 {
27   static SCM z;
28   if (!z)
29     z = scm_permanent_object (ly_offset2scm (Offset (0, 0)));
30   Offset dim = Offset (stil->extent (X_AXIS).length (),
31                        stil->extent (Y_AXIS).length ());
32   Paper_line pl (dim, scm_cons (stil->smobbed_copy (), SCM_EOL), is_title);
33   return pl.smobbed_copy ();
34 }
35
36 /* Simplistic page interface */
37 class Page
38 {
39 public:
40   Paper_def *paper_;
41   static int page_count_;
42   int number_;
43   int line_count_;
44
45   Protected_scm lines_;
46   Protected_scm header_;
47   Protected_scm footer_;
48   Protected_scm copyright_;
49   Protected_scm tagline_;
50   
51   Stencil *get_header () { return unsmob_stencil (header_); }
52   Stencil *get_copyright () { return unsmob_stencil (copyright_); }
53   Stencil *get_tagline () { return unsmob_stencil (tagline_); }
54   Stencil *get_footer () { return unsmob_stencil (footer_); }
55
56   /* actual height filled with text.  */
57   Real height_;
58   
59   // HMMM all this size stuff to paper/paper-outputter?
60   Real hsize_;
61   Real vsize_;
62   Real left_margin_;
63   Real top_margin_;
64   Real bottom_margin_;
65   Real foot_sep_;
66   Real head_sep_;
67   Real text_width_;
68
69   /* available area for text.  */
70   Real text_height ();
71
72   Page (Paper_def*, int);
73   void output (Paper_outputter*, bool);
74 };
75
76 int Page::page_count_ = 0;
77
78 Page::Page (Paper_def *paper, int number)
79 {
80   paper_ = paper;
81   number_ = number;
82   page_count_++;
83   
84   height_ = 0;
85   lines_ = SCM_EOL;
86   line_count_ = 0;
87
88   hsize_ = paper->get_realvar (ly_symbol2scm ("hsize"));
89   vsize_ = paper->get_realvar (ly_symbol2scm ("vsize"));
90   top_margin_ = paper->get_realvar (ly_symbol2scm ("top-margin"));
91   bottom_margin_ = paper->get_realvar (ly_symbol2scm ("bottom-margin"));
92   head_sep_ = paper->get_realvar (ly_symbol2scm ("head-sep"));
93   foot_sep_ = paper->get_realvar (ly_symbol2scm ("foot-sep"));
94   text_width_ = paper->get_realvar (ly_symbol2scm ("linewidth"));
95   left_margin_ = (hsize_ - text_width_) / 2;
96   
97   copyright_ = SCM_EOL;
98   tagline_ = SCM_EOL;
99   
100   SCM make_header = scm_primitive_eval (ly_symbol2scm ("make-header"));
101   SCM make_footer = scm_primitive_eval (ly_symbol2scm ("make-footer"));
102
103   header_ = scm_call_2 (make_header, paper_->smobbed_copy (),
104                         scm_int2num (number_));
105   // FIXME: why does this (generates Stencil) not trigger font load?
106   if (get_header ())
107     get_header ()->align_to (Y_AXIS, UP);
108     
109   footer_ = scm_call_2 (make_footer, paper_->smobbed_copy (),
110                         scm_int2num (number_));
111   if (get_footer ())
112     get_footer ()->align_to (Y_AXIS, UP);
113 }
114
115 void
116 Page::output (Paper_outputter *out, bool is_last)
117 {
118   progress_indication ("[" + to_string (number_));
119   out->output_scheme (scm_list_1 (ly_symbol2scm ("start-page")));
120   Offset o (left_margin_, top_margin_);
121   Real vfill = line_count_ > 1 ? (text_height () - height_) / (line_count_ - 1)
122     : 0;
123
124   Real coverage = height_ / text_height ();
125   if (coverage < MIN_COVERAGE)
126     /* Do not space out a badly filled page.  This is too simplistic
127        (ie broken), because this should not vary too much between
128        (subsequent?) pages in a book.  */
129     vfill = 0;
130
131   if (get_header ())
132     {
133       out->output_line (stencil2line (get_header ()), &o, false);
134       o[Y_AXIS] += head_sep_;
135     }
136   for (SCM s = lines_; gh_pair_p (s); s = ly_cdr (s))
137     {
138       SCM line = ly_car (s);
139       out->output_line (line, &o,
140                         is_last && gh_pair_p (ly_cdr (s)) && !get_copyright ()
141                         && !get_tagline () && !get_footer ());
142       if (gh_pair_p (ly_cdr (s)) && unsmob_paper_line (line)->is_title ())
143         o[Y_AXIS] += vfill;
144     }
145
146   o[Y_AXIS] = vsize_ - bottom_margin_;
147   if (get_copyright ())
148     o[Y_AXIS] -= get_copyright ()->extent (Y_AXIS).length ();
149   if (get_tagline ())
150     o[Y_AXIS] -= get_tagline ()->extent (Y_AXIS).length ();
151   if (get_footer ())
152     o[Y_AXIS] -= get_footer ()->extent (Y_AXIS).length ();
153
154   if (get_copyright ())
155     out->output_line (stencil2line (get_copyright ()), &o,
156                       is_last && !get_tagline () && !get_footer ());
157   if (get_tagline ())
158     out->output_line (stencil2line (get_tagline ()), &o,
159                       is_last && !get_footer ());
160   if (get_footer ())
161     out->output_line (stencil2line (get_footer ()), &o, is_last);
162   out->output_scheme (scm_list_2 (ly_symbol2scm ("stop-page"),
163                                   gh_bool2scm (is_last && !get_footer ())));
164   progress_indication ("]");
165 }
166
167 Real
168 Page::text_height ()
169 {
170   Real h = vsize_ - top_margin_ - bottom_margin_;
171   if (get_header ())
172     h -= get_header ()->extent (Y_AXIS).length () + head_sep_;
173   if (get_copyright () || get_tagline () || get_footer ())
174     h -= foot_sep_;
175   if (get_copyright ())
176     h -= get_copyright ()->extent (Y_AXIS).length ();
177   if (get_tagline ())
178     h -= get_tagline ()->extent (Y_AXIS).length ();
179   if (get_footer ())
180     h -= get_footer ()->extent (Y_AXIS).length ();
181   return h;
182 }
183
184 /****************************************************************/
185
186 /* Current global paper book.  Gives default_rendering access from
187    input-file-results.  */
188 Paper_book *paper_book;
189
190 Paper_book::Paper_book ()
191 {
192   copyright_ = SCM_EOL;
193   tagline_ = SCM_EOL;
194   
195   smobify_self ();
196 }
197
198 Paper_book::~Paper_book ()
199 {
200 }
201
202 void
203 Paper_book::output (String outname)
204 {
205   if (!papers_.size ())
206     return;
207     
208   /* Generate all stencils to trigger font loads.  */
209   Link_array<Page> *pages = get_pages ();
210
211   Paper_def *paper = papers_[0];
212   Paper_outputter *out = paper->get_paper_outputter (outname);
213   out->output_header (paper, get_scopes (0), pages->size ());
214
215   int page_count = pages->size ();
216   for (int i = 0; i < page_count; i++)
217     (*pages)[i]->output (out, i + 1 == page_count);
218
219   out->output_scheme (scm_list_1 (ly_symbol2scm ("end-output")));
220   progress_indication ("\n");
221 }
222
223 SCM
224 Paper_book::get_scopes (int i)
225 {
226   SCM scopes = SCM_EOL;
227   if (headers_[i])
228     scopes = scm_cons (headers_[i], scopes);
229   if (global_headers_[i] && global_headers_[i] != headers_[i])
230     scopes = scm_cons (global_headers_[i], scopes);
231   return scopes;
232 }
233
234 Stencil*
235 Paper_book::get_title (int i)
236 {
237   SCM user_title = scm_primitive_eval (ly_symbol2scm ("user-title"));
238   SCM book_title = scm_primitive_eval (ly_symbol2scm ("book-title"));
239   SCM score_title = scm_primitive_eval (ly_symbol2scm ("score-title"));
240   SCM field = (i == 0 ? ly_symbol2scm ("bookTitle")
241                : ly_symbol2scm ("scoreTitle"));
242
243   Stencil *title = 0;
244   SCM scopes = get_scopes (i);
245   SCM s = ly_modules_lookup (scopes, field);
246   if (s != SCM_UNDEFINED && scm_variable_bound_p (s) == SCM_BOOL_T)
247     title = unsmob_stencil (scm_call_2 (user_title,
248                                         papers_[0]->self_scm (),
249                                         scm_variable_ref (s)));
250   else
251     title = unsmob_stencil (scm_call_2 (i == 0 ? book_title : score_title,
252                                         papers_[0]->self_scm (),
253                                         scopes));
254   if (title)
255     title->align_to (Y_AXIS, UP);
256   
257   return title;
258 }
259
260 /* calculate book height, #lines, stencils.  */
261 void
262 Paper_book::init ()
263 {
264   int score_count = scores_.size ();
265
266   /* Calculate the full book height.  Hmm, can't we cache system
267      heights while making stencils?  */
268   height_ = 0;
269   for (int i = 0; i < score_count; i++)
270     {
271       Stencil *title = get_title (i);
272       if (title)
273         height_ += title->extent (Y_AXIS).length ();
274
275       int line_count = SCM_VECTOR_LENGTH ((SCM) scores_[i]);
276       for (int j = 0; j < line_count; j++)
277         {
278           SCM s = scm_vector_ref ((SCM) scores_[i], scm_int2num (j));
279           height_ += unsmob_paper_line (s)->dim ()[Y_AXIS];
280         }
281     }
282
283   Paper_def *paper = papers_[0];
284   SCM scopes = get_scopes (0);
285
286   SCM make_tagline = scm_primitive_eval (ly_symbol2scm ("make-tagline"));
287   tagline_ = scm_call_2 (make_tagline, paper->smobbed_copy (), scopes);
288   Real tag_height = 0;
289   if (Stencil *s = unsmob_stencil (tagline_))
290     tag_height = s->extent (Y_AXIS).length ();
291   height_ += tag_height;
292
293   SCM make_copyright = scm_primitive_eval (ly_symbol2scm ("make-copyright"));
294   copyright_ = scm_call_2 (make_copyright, paper->smobbed_copy (), scopes);
295   Real copy_height = 0;
296   if (Stencil *s = unsmob_stencil (copyright_))
297     copy_height = s->extent (Y_AXIS).length ();
298   height_ += copy_height;
299 }
300
301 /* Ideas:
302    - real page breaking algorithm (Gourlay?)
303      Hmmughr, Gourlay uses Grobs, columns etc -- looks like it needs serious
304      refactoring before it can be applied to Page breaking...
305    - override: # pages, or pageBreakLines= #'(3 3 4), ?  */
306 Link_array<Page>*
307 Paper_book::get_pages ()
308 {
309   init ();
310   Page::page_count_ = 0;
311   Paper_def *paper = papers_[0];
312   Page *page = new Page (paper, 1);
313
314   Real text_height = page->text_height ();
315   Real page_frac = height_ / text_height;
316   int page_count = (int) ceil (page_frac);
317   if (unsmob_stencil (copyright_))
318     page->copyright_ = copyright_;
319   if (unsmob_stencil (tagline_) && page_count == 1)
320     page->tagline_ = tagline_;
321
322   /* Attempt to fill pages better using FUDGE kludge.  */
323   Real r = page_frac - (int) page_frac;
324   Real cramp_fudge = page_count > 1 ? (r / (page_count - 1)) * text_height : 0;
325   Real expand_fudge = - ((1 - r) / page_count) * text_height;
326   
327   Link_array<Page>* pages = 0;
328   if ((page_count > 1 && (r / (page_count - 1)) < MAX_CRAMP))
329     {
330       /* Just a little more space (<MAX_CRAMP) per page is needed,
331          cramp onto one page less.  */
332       pages = fill_pages (page, page_count - 1, cramp_fudge);
333       if (pages->size () != page_count - 1)
334         {
335           /* Cramping failed.  */
336           page = pages->get (0);
337           delete pages;
338           pages = 0;
339         }
340     }      
341   if (!pages && ((1 - r) / page_count) < 1 - MIN_COVERAGE)
342     {
343       /* There is space left, but not so much that paged would have too
344          little blackness (< MIN_COVERAGE), distribute evenly.  */
345       pages = fill_pages (page, page_count, expand_fudge);
346       bool badly_covered = false;
347       if (pages->size () == page_count)
348         for (int i = 0; i < page_count; i++)
349           {
350             Page *p = (*pages)[i];
351             Real coverage = p ->height_ / p->text_height ();
352             if (coverage < MIN_COVERAGE)
353               {
354                 badly_covered = true;
355                 break;
356               }
357           }
358       if (pages->size () != page_count || badly_covered)
359         {
360           /* expanding failed.  */
361           page = pages->get (0);
362           delete pages;
363           pages = 0;
364         }
365     }
366   
367   if (!pages)
368     /* Fudging failed; just fill pages.  */
369     pages = fill_pages (page, page_count, 0);
370   return pages;
371 }
372
373 /* Simplistic page breaking:
374    add lines until HEIGHT > PAGE.TEXT_HEIGHT_ + FUDGE  */
375 Link_array<Page>*
376 Paper_book::fill_pages (Page *page, int page_count, Real fudge)
377 {
378   int page_number = 1;
379   int score_count = scores_.size ();
380   Paper_def *paper = papers_[0];
381   Link_array<Page> *pages = new Link_array<Page>;
382   page->lines_ = SCM_EOL;
383   page->height_ = 0;
384   page->line_count_ = 0;
385   Real text_height = page->text_height ();
386   for (int i = 0; i < score_count; i++)
387     {
388       Real h = 0;
389       Stencil *title = get_title (i);
390       if (title)
391         h = title->extent (Y_AXIS).length ();
392
393       int line_count = SCM_VECTOR_LENGTH ((SCM) scores_[i]);
394       for (int j = 0; j < line_count; j++)
395         {
396           SCM line = scm_vector_ref ((SCM) scores_[i], scm_int2num (j));
397           h += unsmob_paper_line (line)->dim ()[Y_AXIS];
398           Real fill = (page->height_ - text_height) / text_height;
399           // Real fill_h = (page->height_ + h - text_height) / text_height;
400           Real fudged_fill = (page->height_ - (text_height + fudge))
401             / (text_height + fudge);
402           Real fudged_fill_h = ((page->height_ + h) - (text_height + fudge))
403             / (text_height + fudge);
404           if (fill > -MAX_CRAMP
405               || (fudged_fill > -MAX_CRAMP
406                   && (fudge < 0
407                       || !(fudged_fill_h > 0
408                            && abs (fudged_fill_h) < 4 * abs (fudged_fill)))))
409             {
410               pages->push (page);
411               page = new Page (paper, ++page_number);
412               if (unsmob_stencil (tagline_) && page_number == page_count)
413                 page->tagline_ = tagline_;
414               text_height = page->text_height ();
415             }
416           if (j == 0 && title)
417             page->lines_ = ly_snoc (stencil2line (title, true), page->lines_);
418           page->lines_ = ly_snoc (line, page->lines_);
419           page->line_count_++;
420           page->height_ += h;
421           h = 0;
422         }
423     }
424
425   pages->push (page);
426   return pages;
427 }
428
429 void
430 Paper_book::classic_output (String outname)
431 {
432   int count = scores_.size ();
433   Paper_outputter *out = papers_.top ()->get_paper_outputter (outname);
434   out->output_header (papers_.top (), get_scopes (count - 1), 0);
435
436   int line_count = SCM_VECTOR_LENGTH ((SCM) scores_.top ());
437   for (int i = 0; i < line_count; i++)
438     out->output_line (scm_vector_ref ((SCM) scores_.top (), scm_int2num (i)),
439                       0, i == line_count - 1);
440   
441   out->output_scheme (scm_list_1 (ly_symbol2scm ("end-output")));
442   progress_indication ("\n");
443 }
444
445
446 #include "ly-smobs.icc"
447
448 IMPLEMENT_DEFAULT_EQUAL_P (Paper_book);
449 IMPLEMENT_SMOBS (Paper_book)
450 IMPLEMENT_TYPE_P (Paper_book, "ly:paper-book?")
451
452 SCM
453 Paper_book::mark_smob (SCM smob)
454 {
455   Paper_book *pb = (Paper_book*) SCM_CELL_WORD_1 (smob);
456   for (int i = 0; i < pb->headers_.size (); i++)
457     scm_gc_mark (pb->headers_[i]);
458   for (int i = 0; i < pb->global_headers_.size (); i++)
459     scm_gc_mark (pb->global_headers_[i]);
460   for (int i = 0; i < pb->papers_.size (); i++)
461     scm_gc_mark (pb->papers_[i]->self_scm ());
462   for (int i = 0; i < pb->scores_.size (); i++)
463     scm_gc_mark (pb->scores_[i]);
464
465   scm_gc_mark (pb->copyright_);
466
467   
468   return pb->tagline_;
469 }
470
471 int
472 Paper_book::print_smob (SCM smob, SCM port, scm_print_state*)
473 {
474   Paper_book *b = (Paper_book*) ly_cdr (smob);
475      
476   scm_puts ("#<", port);
477   scm_puts (classname (b), port);
478   scm_puts (" ", port);
479   //scm_puts (b->, port);
480   scm_puts (">", port);
481   return 1;
482 }