]> git.donarmstrong.com Git - lilypond.git/blob - lily/system.cc
* ly/declarations-init.ly (paper): Define page-breaking.
[lilypond.git] / lily / system.cc
1 /*
2   system.cc -- implement System
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include "axis-group-interface.hh"
10 #include "warn.hh"
11 #include "system.hh"
12 #include "main.hh"
13 #include "paper-column.hh"
14 #include "paper-def.hh"
15 #include "paper-outputter.hh"
16 #include "paper-score.hh"
17 #include "string.hh"
18 #include "warn.hh"
19 #include "stencil.hh"
20 #include "all-font-metrics.hh"
21 #include "spacing-interface.hh"
22 #include "staff-symbol-referencer.hh"
23 #include "paper-book.hh"
24 #include "paper-line.hh"
25
26 System::System (SCM s)
27   : Spanner (s)
28 {
29   rank_ = 0;
30 }
31
32 int
33 System::element_count () const
34 {
35   return scm_ilength (get_property ("all-elements"));
36 }
37
38 int
39 System::spanner_count () const
40 {
41   int k =0;
42   for (SCM s = get_property ("all-elements");
43        gh_pair_p (s); s = ly_cdr (s))
44     {
45       if (dynamic_cast<Spanner*> (unsmob_grob (gh_car (s))))
46         k++;
47     }
48
49   return k;
50 }
51   
52
53 int
54 scm_default_compare (const void * a, const void *b)
55 {
56   SCM pa = *(SCM *)a;
57   SCM pb = *(SCM *)b;
58
59   if (pa < pb) return -1 ;
60   else if (pa > pb) return 1;
61   else return 0;
62 }
63
64 /*
65   modify L in place: sort it 
66 */
67
68 SCM
69 uniquify_list (SCM l)
70 {
71   int len = scm_ilength (l);
72   SCM  * arr = new SCM[len];
73   int k = 0;
74   for (SCM s =l ; SCM_NNULLP (s); s = SCM_CDR (s))
75     arr[k++] = SCM_CAR (s);
76
77   assert (k == len);
78   qsort (arr, len, sizeof (SCM), &scm_default_compare);
79
80   k = 0;
81   SCM s =l;
82   for (int i = 0; i < len ; i++)
83     {
84       if (i && arr[i] == arr[i-1])
85         continue;
86
87       SCM_SETCAR (s, arr[i]);
88
89       if (i < len - 1)
90         s = SCM_CDR (s);
91     }
92
93   SCM_SETCDR (s, SCM_EOL);
94   delete[] arr;
95   
96   return l; 
97 }
98
99 void
100 System::typeset_grob (Grob * elem)
101 {
102   if (elem->pscore_)
103     programming_error ("Adding element twice.");
104   
105   elem->pscore_ = pscore_;
106   Pointer_group_interface::add_grob (this, ly_symbol2scm ("all-elements"), elem);
107   scm_gc_unprotect_object (elem->self_scm ());
108 }
109
110 // todo: use map.
111 static void
112 fixup_refpoints (SCM s)
113 {
114   for (; gh_pair_p (s); s = ly_cdr (s))
115     {
116       Grob::fixup_refpoint (ly_car (s));
117     }
118 }
119
120 SCM
121 System::get_lines ()
122 {
123   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
124     {
125       Grob *g = unsmob_grob (ly_car (s));
126       if (g->internal_has_interface (ly_symbol2scm ("only-prebreak-interface")))
127         {
128           /*
129             Kill no longer needed grobs. 
130            */
131           Item * it = dynamic_cast<Item*> (g);
132           if (it && Item::is_breakable (it))
133             {
134               it->find_prebroken_piece (LEFT)->suicide ();
135               it->find_prebroken_piece (RIGHT)->suicide ();
136             }
137           g->suicide ();
138         }
139       else if (g->live ())
140         g->do_break_processing ();
141     }
142
143   /*
144     fixups must be done in broken line_of_scores, because new elements
145     are put over there.  */
146   int count = 0;
147   for (int i=0; i < broken_intos_.size (); i++)
148     {
149       Grob *se = broken_intos_[i];
150       SCM all = se->get_property ("all-elements");
151       for (SCM s = all; gh_pair_p (s); s = ly_cdr (s))
152         fixup_refpoint (ly_car (s));
153       count += scm_ilength (all);
154     }
155   
156   /*
157     needed for doing items.
158    */
159   fixup_refpoints (get_property ("all-elements"));
160
161   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
162     unsmob_grob (ly_car (s))->handle_broken_dependencies ();
163   handle_broken_dependencies ();
164
165 #if 0  /* don't do this: strange side effects.  */
166   
167   /* Because the this->get_property (all-elements) contains items in 3
168      versions, handle_broken_dependencies () will leave duplicated
169      items in all-elements.  Strictly speaking this is harmless, but
170      it leads to duplicated symbols in the output.  uniquify_list ()
171      makes sure that no duplicates are in the list.  */
172   for (int i = 0; i < line_count; i++)
173     {
174       SCM all = broken_intos_[i]->get_property ("all-elements");
175       all = uniquify_list (all); 
176     }
177 #endif
178   
179   if (verbose_global_b)
180     progress_indication (_f ("Element count %d.",  count + element_count ()));
181
182   int line_count = broken_intos_.size ();
183   SCM lines = scm_c_make_vector (line_count, SCM_UNDEFINED);
184   
185    for (int i = 0; i < line_count; i++)
186     {
187       if (verbose_global_b)
188         progress_indication ("[");
189
190       System *system = dynamic_cast<System*> (broken_intos_[i]);
191       system->post_processing ();
192       scm_vector_set_x (lines, scm_int2num (i), system->get_line ());
193
194       if (verbose_global_b)
195         progress_indication (to_string (i) + "]");
196     }
197    return lines;
198 }
199
200
201
202
203 /*
204   Find the loose columns in POSNS, and drape them around the columns
205   specified in BETWEEN-COLS.  */
206 void
207 set_loose_columns (System* which, Column_x_positions const *posns)
208 {
209   for (int i = 0; i < posns->loose_cols_.size (); i++)
210     {
211       int divide_over = 1;
212       Item *loose = dynamic_cast<Item*> (posns->loose_cols_[i]);
213       Paper_column* col = dynamic_cast<Paper_column*> (loose);
214       
215       if (col->system_)
216         continue;
217       
218       Item * left = 0;
219       Item * right = 0;
220       do
221         {
222           SCM between = loose->get_property ("between-cols");
223           if (!gh_pair_p (between))
224             break;
225
226
227           Item * l=dynamic_cast<Item*> (unsmob_grob (ly_car (between)));
228           Item * r=dynamic_cast<Item*> (unsmob_grob (ly_cdr (between)));
229
230           if (!(l && r))
231             break ;
232           
233           if (!left && l)
234             {
235               left = l->get_column ();
236               if (!left->get_system ())
237                 left = left->find_prebroken_piece (RIGHT);
238             }
239
240           divide_over ++;
241           loose = right = r->get_column ();
242         }
243       while (1);
244
245       if (!right->get_system ())
246         right = right->find_prebroken_piece (LEFT);
247       
248       /*
249         We divide the remaining space of the column over the left and
250         right side. At the moment, we  
251         
252       */
253       Grob * common = right->common_refpoint (left, X_AXIS);
254       
255       Real rx = right->extent (common, X_AXIS)[LEFT];
256       Real lx = left->extent (common, X_AXIS)[RIGHT];
257       Real total_dx = rx - lx;
258       Interval cval =col->extent (col, X_AXIS);
259
260       /*
261         
262         We put it in the middle. This is not an ideal solution -- the
263         break alignment code inserts a fixed space before the clef
264         (about 1 SS), while the space following the clef is
265         flexible. In tight situations, the clef will almost be on top
266         of the following note. 
267         
268       */
269       Real dx = rx-lx - cval.length ();
270       if (total_dx < 2* cval.length ())
271         {
272           /*
273             todo: this is discontinuous. I'm too tired to
274             invent a sliding mechanism. Duh.
275
276             TODO.
277            */
278           dx *= 0.25;
279         }
280       else
281         dx *= 0.5;
282
283       col->system_ = which;
284       col->translate_axis (- col->relative_coordinate (common, X_AXIS), X_AXIS);
285       col->translate_axis (lx + dx - cval[LEFT], X_AXIS); 
286     }
287 }
288
289 // const?
290 void
291 System::break_into_pieces (Array<Column_x_positions> const &breaking)
292 {
293   for (int i=0; i < breaking.size (); i++)
294     {
295       System *system = dynamic_cast <System*> (clone ());
296       system->rank_ = i;
297
298       Link_array<Grob> c (breaking[i].cols_);
299       pscore_->typeset_line (system);
300       
301       system->set_bound (LEFT,c[0]);
302       system->set_bound (RIGHT,c.top ());
303       for (int j=0; j < c.size (); j++)
304         {
305           c[j]->translate_axis (breaking[i].config_[j],X_AXIS);
306           dynamic_cast<Paper_column*> (c[j])->system_ = system;
307         }
308       set_loose_columns (system, &breaking[i]);
309       broken_intos_.push (system);
310     }
311 }
312
313 void
314 System::add_column (Paper_column*p)
315 {
316   Grob *me = this;
317   SCM cs = me->get_property ("columns");
318   Grob * prev =  gh_pair_p (cs) ? unsmob_grob (ly_car (cs)) : 0;
319
320   p->rank_ = prev ? Paper_column::get_rank (prev) + 1 : 0; 
321
322   me->set_property ("columns",  gh_cons (p->self_scm (), cs));
323
324   Axis_group_interface::add_element (me, p);
325 }
326
327 void
328 System::pre_processing ()
329 {
330   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
331     unsmob_grob (ly_car (s))->discretionary_processing ();
332
333   if (verbose_global_b)
334     progress_indication (_f ("Grob count %d ",  element_count ()));
335
336   
337   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
338     unsmob_grob (ly_car (s))->handle_prebroken_dependencies ();
339   
340   fixup_refpoints (get_property ("all-elements"));
341   
342   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
343     {
344       Grob* sc = unsmob_grob (ly_car (s));
345       sc->calculate_dependencies (PRECALCED, PRECALCING, ly_symbol2scm ("before-line-breaking-callback"));
346     }
347   
348   progress_indication ("\n" + _ ("Calculating line breaks...") + " ");
349   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
350     {
351       Grob * e = unsmob_grob (ly_car (s));
352       SCM proc = e->get_property ("spacing-procedure");
353       if (gh_procedure_p (proc))
354         gh_call1 (proc, e->self_scm ());
355     }
356 }
357
358 void
359 System::post_processing ()
360 {
361   for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
362     {
363       Grob *g = unsmob_grob (ly_car (s));
364       g->calculate_dependencies (POSTCALCED, POSTCALCING,
365           ly_symbol2scm ("after-line-breaking-callback"));
366     }
367
368   Interval iv (extent (this, Y_AXIS));
369   if (iv.is_empty ())
370     programming_error ("System with zero extent.");
371   else
372     translate_axis (-iv[MAX], Y_AXIS);
373
374   /* Generate all stencils to trigger font loads.
375      This might seem inefficient, but Stencils are cached per grob
376      anyway. */
377   SCM all = get_property ("all-elements");
378   all = uniquify_list (all);
379
380   this->get_stencil ();
381   for (SCM s = all; gh_pair_p (s); s = ly_cdr (s))
382     {
383       Grob *g = unsmob_grob (ly_car (s));
384       g->get_stencil ();
385     }
386 }
387
388 SCM
389 System::get_line ()
390 {  
391   static int const LAYER_COUNT = 3;
392   SCM stencils = SCM_EOL;
393   if (Stencil *me = get_stencil ())
394     stencils = scm_cons (me->smobbed_copy (), stencils);
395
396   /* Output stencils in three layers: 0, 1, 2.  The default layer is
397      1.  */
398   for (int i = 0; i < LAYER_COUNT; i++)
399     for (SCM s = get_property ("all-elements"); gh_pair_p (s); s = ly_cdr (s))
400       {
401         Grob *g = unsmob_grob (ly_car (s));
402         Stencil *stil = g->get_stencil ();
403
404         /* Skip empty stencils and grobs that are not in this layer.  */
405         if (!stil
406             || robust_scm2int (g->get_property ("layer"), 1) != i)
407           continue;
408
409         Offset o (g->relative_coordinate (this, X_AXIS),
410                   g->relative_coordinate (this, Y_AXIS));
411
412         Offset extra = robust_scm2offset (g->get_property ("extra-offset"),
413                                           Offset (0, 0))
414           * Staff_symbol_referencer::staff_space (g);
415         /* FIXME: 0.5 */
416         stil->translate ((o + extra) * 0.5);
417         stencils = scm_cons (stil->smobbed_copy (), stencils);
418       }
419
420   if (output_format_global != PAGE_LAYOUT)
421     {
422       SCM lastcol = ly_car (get_property ("columns"));
423       Grob *g = unsmob_grob (lastcol);
424       
425       SCM between = ly_symbol2scm ("between-system-string");
426       SCM inter = g->internal_get_property (between);
427       if (gh_string_p (inter))
428         stencils = scm_cons (scm_cons (between, inter), stencils);
429     }
430
431   Interval x (extent (this, X_AXIS));
432   Interval y (extent (this, Y_AXIS));
433   Paper_line *pl = new Paper_line (Offset (x.length (), y.length ()),
434                                    stencils);
435   return pl->self_scm ();
436 }
437
438 Link_array<Item> 
439 System::broken_col_range (Item const*l, Item const*r) const
440 {
441   Link_array<Item> ret;
442
443   l = l->get_column ();
444   r = r->get_column ();
445   SCM s = get_property ("columns");
446
447   while (gh_pair_p (s) && ly_car (s) != r->self_scm ())
448     s = ly_cdr (s);
449
450   if (gh_pair_p (s))
451     s = ly_cdr (s);
452
453   while (gh_pair_p (s) && ly_car (s) != l->self_scm ())
454     {
455       Paper_column*c = dynamic_cast<Paper_column*> (unsmob_grob (ly_car (s)));
456       if (Item::is_breakable (c) && !c->system_)
457         ret.push (c);
458
459       s = ly_cdr (s);
460     }
461
462   ret.reverse ();
463   return ret;
464 }
465
466 /**
467    Return all columns, but filter out any unused columns , since they might
468    disrupt the spacing problem.
469  */
470 Link_array<Grob>
471 System::columns ()const
472 {
473   Link_array<Grob> acs
474     = Pointer_group_interface__extract_grobs (this, (Grob*) 0, "columns");
475   bool bfound = false;
476   for (int i= acs.size (); i -- ;)
477     {
478       bool brb = Item::is_breakable (acs[i]);
479       bfound = bfound || brb;
480
481       /*
482         the last column should be breakable. Weed out any columns that
483         seem empty. We need to retain breakable columns, in case
484         someone forced a breakpoint.
485       */
486       if (!bfound || !Paper_column::is_used (acs[i]))
487         acs.del (i);
488     }
489   return acs;
490 }
491
492 ADD_INTERFACE (System,"system-interface",
493                "This is the toplevel object: each object in a score "
494                "ultimately has a System object as its X and Y parent. ",
495                "between-system-string all-elements columns")