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