]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
use the new spacing-interface stuff in staff-spacing
[lilypond.git] / lily / axis-group-interface.cc
1 /*
2   axis-group-interface.cc -- implement Axis_group_interface
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2000--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "axis-group-interface.hh"
10
11 #include "align-interface.hh"
12 #include "directional-element-interface.hh"
13 #include "pointer-group-interface.hh"
14 #include "grob-array.hh"
15 #include "hara-kiri-group-spanner.hh"
16 #include "international.hh"
17 #include "lookup.hh"
18 #include "paper-column.hh"
19 #include "paper-score.hh"
20 #include "separation-item.hh"
21 #include "stencil.hh"
22 #include "system.hh"
23 #include "warn.hh"
24
25 void
26 Axis_group_interface::add_element (Grob *me, Grob *e)
27 {
28   SCM axes = me->get_property ("axes");
29   if (!scm_is_pair (axes))
30     programming_error ("axes should be nonempty");
31
32   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
33     {
34       Axis a = (Axis) scm_to_int (scm_car (ax));
35
36       if (!e->get_parent (a))
37         e->set_parent (me, a);
38
39       e->set_object ((a == X_AXIS)
40                      ? ly_symbol2scm ("axis-group-parent-X")
41                      : ly_symbol2scm ("axis-group-parent-Y"),
42                      me->self_scm ());
43     }
44
45   /* must be ordered, because Align_interface also uses
46      Axis_group_interface  */
47   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
48 }
49
50 bool
51 Axis_group_interface::has_axis (Grob *me, Axis a)
52 {
53   SCM axes = me->get_property ("axes");
54
55   return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
56 }
57
58 Interval
59 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
60                                              Grob *common, Axis a)
61 {
62   Interval r;
63   for (vsize i = 0; i < elts.size (); i++)
64     {
65       Grob *se = elts[i];
66       if (!to_boolean (se->get_property ("cross-staff")))
67         {
68           Interval dims = se->extent (common, a);
69           if (!dims.is_empty ())
70             r.unite (dims);
71         }
72     }
73   return r;
74 }
75
76
77 /*
78   FIXME: pure extent handling has a lot of ad-hoc caching.
79   This should be done with grob property callbacks.
80
81   --hwn
82 */
83
84 Interval
85 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
86 {
87   Paper_score *ps = get_root_system (me)->paper_score ();
88   vector<vsize> breaks = ps->get_break_indices ();
89   vector<Grob*> cols = ps->get_columns ();
90
91   SCM extents = me->get_property ("adjacent-pure-heights");
92
93   Interval ext;
94   for (vsize i = 0; i + 1 < breaks.size (); i++)
95     {
96       int r = Paper_column::get_rank (cols[breaks[i]]);
97       if (r > end)
98         break;
99
100       if (r >= start)
101         ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
102     }
103
104   return ext;
105 }
106
107 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
108 SCM
109 Axis_group_interface::adjacent_pure_heights (SCM smob)
110 {
111   Grob *me = unsmob_grob (smob);
112
113   Grob *common = calc_pure_elts_and_common (me);
114   extract_grob_set (me, "pure-relevant-items", items);
115   extract_grob_set (me, "pure-relevant-spanners", spanners);
116
117   Paper_score *ps = get_root_system (me)->paper_score ();
118   vector<vsize> breaks = ps->get_break_indices ();
119   vector<Grob*> cols = ps->get_columns ();
120
121   SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
122   vsize it_index = 0;
123   for (vsize i = 0; i + 1 < breaks.size (); i++)
124     {
125       int start = Paper_column::get_rank (cols[breaks[i]]);
126       int end = Paper_column::get_rank (cols[breaks[i+1]]);
127       Interval iv;
128
129       for (vsize j = it_index; j < items.size (); j++)
130         {
131           Item *it = dynamic_cast<Item*> (items[j]);
132           int rank = it->get_column ()->get_rank ();
133
134           if (rank <= end && it->pure_is_visible (start, end))
135             {
136               Interval dims = items[j]->pure_height (common, start, end);
137               if (!dims.is_empty ())
138                 iv.unite (dims);
139             }
140
141           if (rank < end)
142             it_index++;
143           else if (rank > end)
144             break;
145         }
146
147       for (vsize j = 0; j < spanners.size (); j++)
148         {
149           Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
150           if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
151             {
152               Interval dims = spanners[j]->pure_height (common, start, end);
153               if (!dims.is_empty ())
154                 iv.unite (dims);
155             }
156         }
157
158       scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
159     }
160   return ret;
161 }
162
163 Interval
164 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
165 {
166   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
167      (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
168      Unfortunately, it isn't always true, particularly if there is a
169      VerticalAlignment somewhere in the descendants.
170
171      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
172      count, the only VerticalAlignment comes from Score. This makes it
173      reasonably safe to assume that if our parent is a VerticalAlignment,
174      we can assume additivity and cache things nicely. */
175   Grob *p = me->get_parent (Y_AXIS);
176   if (p && Align_interface::has_interface (p))
177     return Axis_group_interface::cached_pure_height (me, start, end);
178
179   Grob *common = calc_pure_elts_and_common (me);
180   extract_grob_set (me, "pure-relevant-items", items);
181   extract_grob_set (me, "pure-relevant-spanners", spanners);
182
183   Interval r;
184
185   for (vsize i = 0; i < items.size (); i++)
186     {
187       Item *it = dynamic_cast<Item*> (items[i]);
188       int rank = it->get_column ()->get_rank ();
189
190       if (rank > end)
191         break;
192       else if (rank >= start && it->pure_is_visible (start, end))
193         {
194           Interval dims = it->pure_height (common, start, end);
195           if (!dims.is_empty ())
196             r.unite (dims);
197         }
198     }
199
200   for (vsize i = 0; i < spanners.size (); i++)
201     {
202       Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
203       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start)
204         {
205           Interval dims = spanners[i]->pure_height (common, start, end);
206           if (!dims.is_empty ())
207             r.unite (dims);
208         }
209     }
210   return r;
211 }
212
213 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
214 SCM
215 Axis_group_interface::width (SCM smob)
216 {
217   Grob *me = unsmob_grob (smob);
218   return generic_group_extent (me, X_AXIS);
219 }
220
221 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
222 SCM
223 Axis_group_interface::height (SCM smob)
224 {
225   Grob *me = unsmob_grob (smob);
226   return generic_group_extent (me, Y_AXIS);
227 }
228
229 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
230 SCM
231 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
232 {
233   int start = robust_scm2int (start_scm, 0);
234   int end = robust_scm2int (end_scm, INT_MAX);
235   Grob *me = unsmob_grob (smob);
236
237   /* Maybe we are in the second pass of a two-pass spacing run. In that
238      case, the Y-extent of a system is already given to us */
239   System *system = dynamic_cast<System*> (me);
240   if (system)
241     {
242       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
243       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
244       if (scm_is_pair (system_y_extent))
245         return scm_cdr (system_y_extent);
246     }
247
248   return ly_interval2scm (pure_group_height (me, start, end));
249 }
250
251 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
252 SCM
253 Axis_group_interface::calc_skylines (SCM smob)
254 {
255   Grob *me = unsmob_grob (smob);
256   extract_grob_set (me, "elements", elts);
257   Skyline_pair skylines = skyline_spacing (me, elts);
258
259   /* add a minimum-Y-extent-sized box to the skyline */
260   SCM min_y_extent = me->get_property ("minimum-Y-extent");
261   if (is_number_pair (min_y_extent))
262     {
263       Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
264       skylines.insert (b, 0, X_AXIS);
265     }
266   return skylines.smobbed_copy ();
267 }
268
269 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
270    visible children, combine_skylines is designed for axis-groups whose only
271    children are other axis-groups (ie. VerticalAlignment). Rather than
272    calculating all the skylines from scratch, we just merge the skylines
273    of the children.
274 */
275 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
276 SCM
277 Axis_group_interface::combine_skylines (SCM smob)
278 {
279   Grob *me = unsmob_grob (smob);
280   extract_grob_set (me, "elements", elements);
281   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
282
283   assert (y_common == me);
284
285   Skyline_pair ret;
286   for (vsize i = 0; i < elements.size (); i++)
287     {
288       SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
289       if (Skyline_pair::unsmob (skyline_scm))
290         {
291           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
292           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
293           other.raise (offset);
294           ret.merge (other);
295         }
296     }
297   return ret.smobbed_copy ();
298 }
299   
300 SCM
301 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
302 {
303   /* trigger the callback to do skyline-spacing on the children */
304   if (a == Y_AXIS)
305     (void) me->get_property ("vertical-skylines");
306
307   extract_grob_set (me, "elements", elts);
308   Grob *common = common_refpoint_of_array (elts, me, a);
309
310   Real my_coord = me->relative_coordinate (common, a);
311   Interval r (relative_group_extent (elts, common, a));
312
313   return ly_interval2scm (r - my_coord);
314 }
315
316
317 Grob *
318 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
319 {
320   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
321     return c;
322   
323   extract_grob_set (me, "elements", elts);
324
325   vector<Grob*> relevant_items;
326   vector<Grob*> relevant_spanners;
327   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
328
329   for (vsize i = 0; i < elts.size (); i++)
330     {
331       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
332         {
333           if (dynamic_cast<Item*> (elts[i]))
334             relevant_items.push_back (elts[i]);
335           else if (dynamic_cast<Spanner*> (elts[i]))
336             relevant_spanners.push_back (elts[i]);
337         }
338             
339
340       Item *it = dynamic_cast<Item*> (elts[i]);
341       Direction d = LEFT;
342       if (it)
343         do
344           {
345             Item *piece = it->find_prebroken_piece (d);
346             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
347               relevant_items.push_back (piece);
348           }
349         while (flip (&d) != LEFT);
350     }
351   vector_sort (relevant_items, Item::less);
352
353   Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
354   common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
355
356   me->set_object ("pure-Y-common", common->self_scm ());
357   
358   SCM items_scm = Grob_array::make_array ();
359   SCM spanners_scm = Grob_array::make_array ();
360
361   unsmob_grob_array (items_scm)->set_array (relevant_items);
362   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
363   me->set_object ("pure-relevant-items", items_scm);
364   me->set_object ("pure-relevant-spanners", spanners_scm);
365
366   return common;
367 }
368
369 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
370 SCM
371 Axis_group_interface::calc_x_common (SCM grob)
372 {
373   Grob *me = unsmob_grob (grob);
374
375   extract_grob_set (me, "elements", elts);
376   Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
377   return common->self_scm ();
378 }
379
380 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
381 SCM
382 Axis_group_interface::calc_y_common (SCM grob)
383 {
384   Grob *me = unsmob_grob (grob);
385
386   extract_grob_set (me, "elements", elts);
387   Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
388   return common->self_scm ();
389 }
390
391 Interval
392 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
393 {
394   Grob *common = calc_pure_elts_and_common (me);
395         
396   Real my_coord = me->relative_coordinate (common, Y_AXIS);
397   Interval r (relative_pure_height (me, start, end));
398
399   return r - my_coord;
400 }
401
402 void
403 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
404 {
405   found->push_back (me);
406
407   if (!has_interface (me))
408     return;
409
410   extract_grob_set (me, "elements", elements);
411   for (vsize i = 0; i < elements.size (); i++)
412     {
413       Grob *e = elements[i];
414       Axis_group_interface::get_children (e, found);
415     }
416 }
417
418 bool
419 staff_priority_less (Grob * const &g1, Grob * const &g2)
420 {
421   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
422   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
423
424   if (priority_1 < priority_2)
425     return true;
426   else if (priority_1 > priority_2)
427     return false;
428
429   /* if neither grob has an outside-staff priority, the ordering will have no
430      effect -- we just need to choose a consistent ordering. We do this to
431      avoid the side-effect of calculating extents. */
432   if (isinf (priority_1))
433     return g1 < g2;
434
435   /* if there is no preference in staff priority, choose the left-most one */
436   Grob *common = g1->common_refpoint (g2, X_AXIS);
437   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
438   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
439   return start_1 < start_2;
440 }
441
442 static void
443 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
444 {
445   /* if a child has skylines, use them instead of the extent box */
446   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
447     {
448       Skyline_pair s = *pair;
449       s.shift (me->relative_coordinate (x_common, X_AXIS));
450       s.raise (me->relative_coordinate (y_common, Y_AXIS));
451       skylines->merge (s);
452     }
453   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
454     {
455       for (vsize i = 0; i < elements->size (); i++)
456         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
457     }
458   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
459            && !to_boolean (me->get_property ("cross-staff")))
460     {
461       boxes->push_back (Box (me->extent (x_common, X_AXIS),
462                              me->extent (y_common, Y_AXIS)));
463     }
464 }
465
466 /* We want to avoid situations like this:
467            still more text
468       more text
469    text
470    -------------------
471    staff
472    -------------------
473
474    The point is that "still more text" should be positioned under
475    "more text".  In order to achieve this, we place the grobs in several
476    passes.  We keep track of the right-most horizontal position that has been
477    affected by the current pass so far (actually we keep track of 2
478    positions, one for above the staff, one for below).
479
480    In each pass, we loop through the unplaced grobs from left to right.
481    If the grob overlaps the right-most affected position, we place it
482    (and then update the right-most affected position to point to the right
483    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
484 */
485 static void
486 add_grobs_of_one_priority (Skyline_pair *const skylines,
487                            vector<Grob*> elements,
488                            Grob *x_common,
489                            Grob *y_common)
490 {
491   vector<Box> boxes;
492   Drul_array<Real> last_affected_position;
493
494   reverse (elements);
495   while (!elements.empty ())
496     {
497       last_affected_position[UP] = -infinity_f;
498       last_affected_position[DOWN] = -infinity_f;
499       /* do one pass */
500       for (vsize i = elements.size (); i--;)
501         {
502           Direction dir = get_grob_direction (elements[i]);
503           if (dir == CENTER)
504             {
505               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
506               dir = UP;
507             }
508
509           Box b (elements[i]->extent (x_common, X_AXIS),
510                  elements[i]->extent (y_common, Y_AXIS));
511           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
512           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
513
514           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
515             continue;
516
517           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
518             {
519               boxes.clear ();
520               boxes.push_back (b);
521               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
522               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
523               Real dist = (*skylines)[dir].distance (other) + padding;
524
525               if (dist > 0)
526                 {
527                   b.translate (Offset (0, dir*dist));
528                   elements[i]->translate_axis (dir*dist, Y_AXIS);
529                 }
530               (*skylines)[dir].insert (b, 0, X_AXIS);
531               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
532               last_affected_position[dir] = b[X_AXIS][RIGHT];
533             }
534
535           /*
536             Ugh: quadratic. --hwn
537            */
538           elements.erase (elements.begin () + i);
539         }
540     }
541 }
542
543 Skyline_pair
544 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
545 {
546   vector_sort (elements, staff_priority_less);
547   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
548   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
549
550   assert (y_common == me);
551
552   vsize i = 0;
553   vector<Box> boxes;
554
555   Skyline_pair skylines;
556   for (i = 0; i < elements.size ()
557          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
558     add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
559
560   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
561   Real padding = robust_scm2double (padding_scm, 0.1);
562   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
563   for (; i < elements.size (); i++)
564     {
565       SCM priority = elements[i]->get_property ("outside-staff-priority");
566       vector<Grob*> current_elts;
567       current_elts.push_back (elements[i]);
568       while (i + 1 < elements.size () 
569              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
570         current_elts.push_back (elements[++i]);
571
572       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
573     }
574   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
575   return skylines;
576 }
577
578 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
579 SCM
580 Axis_group_interface::calc_max_stretch (SCM smob)
581 {
582   Grob *me = unsmob_grob (smob);
583   Real ret = 0;
584   extract_grob_set (me, "elements", elts);
585
586   for (vsize i = 0; i < elts.size (); i++)
587     if (Axis_group_interface::has_interface (elts[i]))
588       ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
589
590   return scm_from_double (ret);
591 }
592
593 extern bool debug_skylines;
594 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
595 SCM
596 Axis_group_interface::print (SCM smob)
597 {
598   if (!debug_skylines)
599     return SCM_BOOL_F;
600
601   Grob *me = unsmob_grob (smob);
602   Stencil ret;
603   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
604     {
605       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
606       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
607     }
608   return ret.smobbed_copy ();
609 }
610
611 ADD_INTERFACE (Axis_group_interface,
612
613                "An object that groups other layout objects.",
614
615                /* properties */
616                "X-common "
617                "Y-common "
618                "adjacent-pure-heights "
619                "axes "
620                "elements "
621                "keep-fixed-while-stretching "
622                "max-stretch "
623                "pure-Y-common "
624                "pure-relevant-items "
625                "pure-relevant-spanners "
626                "vertical-skylines "
627                );