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