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