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