]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Add padding between Lyrics and the following staff.
[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--2009 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 "grob-array.hh"
14 #include "hara-kiri-group-spanner.hh"
15 #include "international.hh"
16 #include "lookup.hh"
17 #include "paper-column.hh"
18 #include "paper-score.hh"
19 #include "pointer-group-interface.hh"
20 #include "separation-item.hh"
21 #include "skyline-pair.hh"
22 #include "staff-grouper-interface.hh"
23 #include "stencil.hh"
24 #include "system.hh"
25 #include "warn.hh"
26
27 void
28 Axis_group_interface::add_element (Grob *me, Grob *e)
29 {
30   SCM axes = me->get_property ("axes");
31   if (!scm_is_pair (axes))
32     programming_error ("axes should be nonempty");
33
34   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
35     {
36       Axis a = (Axis) scm_to_int (scm_car (ax));
37
38       if (!e->get_parent (a))
39         e->set_parent (me, a);
40
41       e->set_object ((a == X_AXIS)
42                      ? ly_symbol2scm ("axis-group-parent-X")
43                      : ly_symbol2scm ("axis-group-parent-Y"),
44                      me->self_scm ());
45     }
46
47   /* must be ordered, because Align_interface also uses
48      Axis_group_interface  */
49   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
50 }
51
52 bool
53 Axis_group_interface::has_axis (Grob *me, Axis a)
54 {
55   SCM axes = me->get_property ("axes");
56
57   return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
58 }
59
60 Interval
61 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
62                                              Grob *common, Axis a)
63 {
64   Interval r;
65   for (vsize i = 0; i < elts.size (); i++)
66     {
67       Grob *se = elts[i];
68       if (!to_boolean (se->get_property ("cross-staff")))
69         {
70           Interval dims = se->extent (common, a);
71           if (!dims.is_empty ())
72             r.unite (dims);
73         }
74     }
75   return r;
76 }
77
78 Interval
79 Axis_group_interface::cached_pure_height (Grob *me, int start, int end)
80 {
81   Interval iv = begin_of_line_pure_height (me, start);
82   iv.unite (rest_of_line_pure_height (me, start, end));
83
84   return iv;
85 }
86
87 Interval
88 Axis_group_interface::rest_of_line_pure_height (Grob *me, int start, int end)
89 {
90   SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
91
92   if (!scm_is_pair (adjacent_pure_heights)
93       || !scm_is_vector (scm_cdr (adjacent_pure_heights)))
94     return Interval (0, 0);
95
96   return combine_pure_heights (me, scm_cdr (adjacent_pure_heights), start, end);
97 }
98
99 Interval
100 Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
101 {
102   SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
103
104   if (!scm_is_pair (adjacent_pure_heights)
105       || !scm_is_vector (scm_car (adjacent_pure_heights)))
106     return Interval (0, 0);
107
108   return combine_pure_heights (me, scm_car (adjacent_pure_heights), start, start+1);
109 }
110
111 Interval
112 Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
113 {
114   Paper_score *ps = get_root_system (me)->paper_score ();
115   vector<vsize> breaks = ps->get_break_indices ();
116   vector<Grob*> cols = ps->get_columns ();
117
118   Interval ext;
119   for (vsize i = 0; i + 1 < breaks.size (); i++)
120     {
121       int r = Paper_column::get_rank (cols[breaks[i]]);
122       if (r >= end)
123         break;
124
125       if (r >= start)
126         ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
127     }
128
129   return ext;
130 }
131
132 // adjacent-pure-heights is a pair of vectors, each of which has one element
133 // for every measure in the score. The first vector stores, for each measure,
134 // the combined height of the elements that are present only when the bar
135 // is at the beginning of a line. The second vector stores, for each measure,
136 // the combined height of the elements that are present only when the bar
137 // is not at the beginning of a line.
138
139 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
140 SCM
141 Axis_group_interface::adjacent_pure_heights (SCM smob)
142 {
143   Grob *me = unsmob_grob (smob);
144
145   Grob *common = calc_pure_elts_and_common (me);
146   extract_grob_set (me, "pure-relevant-items", items);
147   extract_grob_set (me, "pure-relevant-spanners", spanners);
148
149   Paper_score *ps = get_root_system (me)->paper_score ();
150   vector<vsize> breaks = ps->get_break_indices ();
151   vector<Grob*> cols = ps->get_columns ();
152
153   SCM begin_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
154   SCM mid_line_heights = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
155
156   vsize it_index = 0;
157   for (vsize i = 0; i + 1 < breaks.size (); i++)
158     {
159       int start = Paper_column::get_rank (cols[breaks[i]]);
160       int end = Paper_column::get_rank (cols[breaks[i+1]]);
161       Interval begin_line_iv;
162       Interval mid_line_iv;
163
164       for (vsize j = it_index; j < items.size (); j++)
165         {
166           Item *it = dynamic_cast<Item*> (items[j]);
167           int rank = it->get_column ()->get_rank ();
168
169           if (rank <= end && it->pure_is_visible (start, end)
170               && !to_boolean (it->get_property ("cross-staff")))
171             {
172               Interval dims = items[j]->pure_height (common, start, end);
173               Interval &target_iv = start == it->get_column ()->get_rank () ? begin_line_iv : mid_line_iv;
174
175               if (!dims.is_empty ())
176                 target_iv.unite (dims);
177             }
178
179           if (rank < end)
180             it_index++;
181           else if (rank > end)
182             break;
183         }
184
185       for (vsize j = 0; j < spanners.size (); j++)
186         {
187           Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
188           if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
189               && !to_boolean (spanners[j]->get_property ("cross-staff")))
190             {
191               Interval dims = spanners[j]->pure_height (common, start, end);
192
193               if (!dims.is_empty ())
194                 mid_line_iv.unite (dims);
195             }
196         }
197
198       scm_vector_set_x (begin_line_heights, scm_from_int (i), ly_interval2scm (begin_line_iv));
199       scm_vector_set_x (mid_line_heights, scm_from_int (i), ly_interval2scm (mid_line_iv));
200     }
201   return scm_cons (begin_line_heights, mid_line_heights);
202 }
203
204 Interval
205 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
206 {
207   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
208      (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
209      Unfortunately, it isn't always true, particularly if there is a
210      VerticalAlignment somewhere in the descendants.
211
212      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
213      count, the only VerticalAlignment comes from Score. This makes it
214      reasonably safe to assume that if our parent is a VerticalAlignment,
215      we can assume additivity and cache things nicely. */
216   Grob *p = me->get_parent (Y_AXIS);
217   if (p && Align_interface::has_interface (p))
218     return Axis_group_interface::cached_pure_height (me, start, end);
219
220   Grob *common = calc_pure_elts_and_common (me);
221   extract_grob_set (me, "pure-relevant-items", items);
222   extract_grob_set (me, "pure-relevant-spanners", spanners);
223
224   Interval r;
225
226   for (vsize i = 0; i < items.size (); i++)
227     {
228       Item *it = dynamic_cast<Item*> (items[i]);
229       int rank = it->get_column ()->get_rank ();
230
231       if (rank > end)
232         break;
233       else if (rank >= start && it->pure_is_visible (start, end)
234                && !to_boolean (it->get_property ("cross-staff")))
235         {
236           Interval dims = it->pure_height (common, start, end);
237           if (!dims.is_empty ())
238             r.unite (dims);
239         }
240     }
241
242   for (vsize i = 0; i < spanners.size (); i++)
243     {
244       Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
245       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
246           && !to_boolean (spanners[i]->get_property ("cross-staff")))
247         {
248           Interval dims = spanners[i]->pure_height (common, start, end);
249           if (!dims.is_empty ())
250             r.unite (dims);
251         }
252     }
253   return r;
254 }
255
256 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
257 SCM
258 Axis_group_interface::width (SCM smob)
259 {
260   Grob *me = unsmob_grob (smob);
261   return generic_group_extent (me, X_AXIS);
262 }
263
264 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
265 SCM
266 Axis_group_interface::height (SCM smob)
267 {
268   Grob *me = unsmob_grob (smob);
269   return generic_group_extent (me, Y_AXIS);
270 }
271
272 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
273 SCM
274 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
275 {
276   int start = robust_scm2int (start_scm, 0);
277   int end = robust_scm2int (end_scm, INT_MAX);
278   Grob *me = unsmob_grob (smob);
279
280   /* Maybe we are in the second pass of a two-pass spacing run. In that
281      case, the Y-extent of a system is already given to us */
282   System *system = dynamic_cast<System*> (me);
283   if (system)
284     {
285       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
286       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
287       if (scm_is_pair (system_y_extent))
288         return scm_cdr (system_y_extent);
289     }
290
291   return ly_interval2scm (pure_group_height (me, start, end));
292 }
293
294 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
295 SCM
296 Axis_group_interface::calc_skylines (SCM smob)
297 {
298   Grob *me = unsmob_grob (smob);
299   extract_grob_set (me, "elements", elts);
300   Skyline_pair skylines = skyline_spacing (me, elts);
301
302   return skylines.smobbed_copy ();
303 }
304
305 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
306    visible children, combine_skylines is designed for axis-groups whose only
307    children are other axis-groups (ie. VerticalAlignment). Rather than
308    calculating all the skylines from scratch, we just merge the skylines
309    of the children.
310 */
311 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
312 SCM
313 Axis_group_interface::combine_skylines (SCM smob)
314 {
315   Grob *me = unsmob_grob (smob);
316   extract_grob_set (me, "elements", elements);
317   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
318   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
319
320   if (y_common != me)
321     programming_error ("combining skylines that don't belong to me");
322
323   Skyline_pair ret;
324   for (vsize i = 0; i < elements.size (); i++)
325     {
326       SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
327       if (Skyline_pair::unsmob (skyline_scm))
328         {
329           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
330           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
331           other.raise (offset);
332           other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
333           ret.merge (other);
334         }
335     }
336   return ret.smobbed_copy ();
337 }
338   
339 SCM
340 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
341 {
342   /* trigger the callback to do skyline-spacing on the children */
343   if (a == Y_AXIS)
344     (void) me->get_property ("vertical-skylines");
345
346   extract_grob_set (me, "elements", elts);
347   Grob *common = common_refpoint_of_array (elts, me, a);
348
349   Real my_coord = me->relative_coordinate (common, a);
350   Interval r (relative_group_extent (elts, common, a));
351
352   return ly_interval2scm (r - my_coord);
353 }
354
355 /* This is like generic_group_extent, but it only counts the grobs that
356    are children of some other axis-group. This is uncached; if it becomes
357    commonly used, it may be necessary to cache it somehow. */
358 Interval
359 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
360 {
361   extract_grob_set (me, "elements", elts);
362   vector<Grob*> new_elts;
363
364   for (vsize i = 0; i < elts.size (); i++)
365     if (elts[i]->common_refpoint (staff, parent_a) == staff)
366       new_elts.push_back (elts[i]);
367
368   return relative_group_extent (new_elts, refp, ext_a);
369 }
370
371
372 Grob *
373 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
374 {
375   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
376     return c;
377   
378   extract_grob_set (me, "elements", elts);
379
380   vector<Grob*> relevant_items;
381   vector<Grob*> relevant_spanners;
382   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
383
384   for (vsize i = 0; i < elts.size (); i++)
385     {
386       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
387         {
388           if (dynamic_cast<Item*> (elts[i]))
389             relevant_items.push_back (elts[i]);
390           else if (dynamic_cast<Spanner*> (elts[i]))
391             relevant_spanners.push_back (elts[i]);
392         }
393             
394
395       Item *it = dynamic_cast<Item*> (elts[i]);
396       Direction d = LEFT;
397       if (it)
398         do
399           {
400             Item *piece = it->find_prebroken_piece (d);
401             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
402               relevant_items.push_back (piece);
403           }
404         while (flip (&d) != LEFT);
405     }
406   vector_sort (relevant_items, Item::less);
407
408   Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
409   common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
410
411   me->set_object ("pure-Y-common", common->self_scm ());
412   
413   SCM items_scm = Grob_array::make_array ();
414   SCM spanners_scm = Grob_array::make_array ();
415
416   unsmob_grob_array (items_scm)->set_array (relevant_items);
417   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
418   me->set_object ("pure-relevant-items", items_scm);
419   me->set_object ("pure-relevant-spanners", spanners_scm);
420
421   return common;
422 }
423
424 SCM
425 Axis_group_interface::calc_common (Grob *me, Axis axis)
426 {
427   extract_grob_set (me, "elements", elts);
428   Grob *common = common_refpoint_of_array (elts, me, axis);
429   if (!common)
430     {
431       me->programming_error ("No common parent found in calc_common axis.");
432       return SCM_EOL;
433     }
434   
435   return common->self_scm ();
436 }
437
438
439 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
440 SCM
441 Axis_group_interface::calc_x_common (SCM grob)
442 {
443   return calc_common (unsmob_grob (grob), X_AXIS);
444 }
445
446 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
447 SCM
448 Axis_group_interface::calc_y_common (SCM grob)
449 {
450   return calc_common (unsmob_grob (grob), Y_AXIS);
451 }
452
453 Interval
454 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
455 {
456   Grob *common = calc_pure_elts_and_common (me);
457         
458   Real my_coord = me->relative_coordinate (common, Y_AXIS);
459   Interval r (relative_pure_height (me, start, end));
460
461   return r - my_coord;
462 }
463
464 void
465 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
466 {
467   found->push_back (me);
468
469   if (!has_interface (me))
470     return;
471
472   extract_grob_set (me, "elements", elements);
473   for (vsize i = 0; i < elements.size (); i++)
474     {
475       Grob *e = elements[i];
476       Axis_group_interface::get_children (e, found);
477     }
478 }
479
480 bool
481 staff_priority_less (Grob * const &g1, Grob * const &g2)
482 {
483   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
484   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
485
486   if (priority_1 < priority_2)
487     return true;
488   else if (priority_1 > priority_2)
489     return false;
490
491   /* if neither grob has an outside-staff priority, the ordering will have no
492      effect -- we just need to choose a consistent ordering. We do this to
493      avoid the side-effect of calculating extents. */
494   if (isinf (priority_1))
495     return g1 < g2;
496
497   /* if there is no preference in staff priority, choose the left-most one */
498   Grob *common = g1->common_refpoint (g2, X_AXIS);
499   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
500   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
501   return start_1 < start_2;
502 }
503
504 static void
505 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
506 {
507   /* if a child has skylines, use them instead of the extent box */
508   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
509     {
510       Skyline_pair s = *pair;
511       s.shift (me->relative_coordinate (x_common, X_AXIS));
512       s.raise (me->relative_coordinate (y_common, Y_AXIS));
513       skylines->merge (s);
514     }
515   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
516     {
517       for (vsize i = 0; i < elements->size (); i++)
518         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
519     }
520   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
521            && !to_boolean (me->get_property ("cross-staff")))
522     {
523       boxes->push_back (Box (me->extent (x_common, X_AXIS),
524                              me->extent (y_common, Y_AXIS)));
525     }
526 }
527
528 /* We want to avoid situations like this:
529            still more text
530       more text
531    text
532    -------------------
533    staff
534    -------------------
535
536    The point is that "still more text" should be positioned under
537    "more text".  In order to achieve this, we place the grobs in several
538    passes.  We keep track of the right-most horizontal position that has been
539    affected by the current pass so far (actually we keep track of 2
540    positions, one for above the staff, one for below).
541
542    In each pass, we loop through the unplaced grobs from left to right.
543    If the grob doesn't overlap the right-most affected position, we place it
544    (and then update the right-most affected position to point to the right
545    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
546 */
547 static void
548 add_grobs_of_one_priority (Skyline_pair *const skylines,
549                            vector<Grob*> elements,
550                            Grob *x_common,
551                            Grob *y_common)
552 {
553   vector<Box> boxes;
554   Drul_array<Real> last_affected_position;
555
556   reverse (elements);
557   while (!elements.empty ())
558     {
559       last_affected_position[UP] = -infinity_f;
560       last_affected_position[DOWN] = -infinity_f;
561       /* do one pass */
562       for (vsize i = elements.size (); i--;)
563         {
564           Direction dir = get_grob_direction (elements[i]);
565           if (dir == CENTER)
566             {
567               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
568               dir = UP;
569             }
570
571           Box b (elements[i]->extent (x_common, X_AXIS),
572                  elements[i]->extent (y_common, Y_AXIS));
573           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
574           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
575
576           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
577             continue;
578
579           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
580             {
581               boxes.clear ();
582               boxes.push_back (b);
583               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
584               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
585               Real dist = (*skylines)[dir].distance (other) + padding;
586
587               if (dist > 0)
588                 {
589                   b.translate (Offset (0, dir*dist));
590                   elements[i]->translate_axis (dir*dist, Y_AXIS);
591                 }
592               skylines->insert (b, 0, X_AXIS);
593               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
594               last_affected_position[dir] = b[X_AXIS][RIGHT];
595             }
596
597           /*
598             Ugh: quadratic. --hwn
599            */
600           elements.erase (elements.begin () + i);
601         }
602     }
603 }
604
605 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
606 // For example, cross-staff beams cannot be formatted until the distance between
607 // staves is known and therefore any grobs that depend on the beam cannot be placed
608 // until the skylines are known. On the other hand, the distance between staves should
609 // really depend on position of the cross-staff grobs that lie between them.
610 // Currently, we just leave cross-staff grobs out of the
611 // skyline altogether, but this could mean that staves are placed so close together
612 // that there is no room for the cross-staff grob. It also means, of course, that
613 // we don't get the benefits of skyline placement for cross-staff grobs.
614 Skyline_pair
615 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
616 {
617   /* For grobs with an outside-staff-priority, the sorting function might
618      call extent and cause suicide. This breaks the contract that is required
619      for the STL sort function. To avoid this, we make sure that any suicides
620      are triggered beforehand.
621   */
622   for (vsize i = 0; i < elements.size (); i++)
623     if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
624       elements[i]->extent (elements[i], X_AXIS);
625
626   vector_sort (elements, staff_priority_less);
627   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
628   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
629
630   assert (y_common == me);
631
632   vsize i = 0;
633   vector<Box> boxes;
634
635   Skyline_pair skylines;
636   for (i = 0; i < elements.size ()
637          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
638     if (!to_boolean (elements[i]->get_property ("cross-staff")))
639       add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
640
641   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
642   Real padding = robust_scm2double (padding_scm, 0.1);
643   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
644   for (; i < elements.size (); i++)
645     {
646       if (to_boolean (elements[i]->get_property ("cross-staff")))
647         continue;
648
649       SCM priority = elements[i]->get_property ("outside-staff-priority");
650       vector<Grob*> current_elts;
651       current_elts.push_back (elements[i]);
652       while (i + 1 < elements.size () 
653              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
654         {
655           if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
656             current_elts.push_back (elements[i+1]);
657           ++i;
658         }
659
660       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
661     }
662   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
663   return skylines;
664 }
665
666 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
667 SCM
668 Axis_group_interface::print (SCM smob)
669 {
670   if (!debug_skylines)
671     return SCM_BOOL_F;
672
673   Grob *me = unsmob_grob (smob);
674   Stencil ret;
675   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
676     {
677       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
678                        .in_color (255, 0, 255));
679       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
680                        .in_color (0, 255, 255));
681     }
682   return ret.smobbed_copy ();
683 }
684
685 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
686 SCM
687 Axis_group_interface::calc_next_staff_spacing (SCM smob)
688 {
689   Grob *me = unsmob_grob (smob);
690   Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
691
692   if (grouper)
693     {
694       Grob *last_in_group = Staff_grouper_interface::get_last_grob (grouper);
695       if (me == last_in_group)
696         return grouper->get_property ("after-last-staff-spacing");
697       else
698         return grouper->get_property ("between-staff-spacing");
699     }
700   return me->get_property ("default-next-staff-spacing");
701 }
702
703 Real
704 Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
705 {
706   SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
707
708   Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
709   Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
710   if (s1 && s2)
711     return (*s1)[DOWN].distance ((*s2)[UP]);
712   return 0;
713 }
714
715 ADD_INTERFACE (Axis_group_interface,
716                "An object that groups other layout objects.",
717
718                // TODO: some of these properties are specific to
719                // VerticalAxisGroup. We should split off a
720                // vertical-axis-group-interface.
721                /* properties */
722                "X-common "
723                "Y-common "
724                "adjacent-pure-heights "
725                "axes "
726                "default-next-staff-spacing "
727                "elements "
728                "inter-loose-line-spacing "
729                "inter-staff-spacing "
730                "keep-fixed-while-stretching "
731                "max-stretch "
732                "non-affinity-spacing "
733                "next-staff-spacing "
734                "no-alignment "
735                "pure-Y-common "
736                "pure-relevant-items "
737                "pure-relevant-spanners "
738                "staff-affinity "
739                "staff-grouper "
740                "system-Y-offset "
741                "vertical-skylines "
742                );