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