]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
[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 /* This is like generic_group_extent, but it only counts the grobs that
327    are children of some other axis-group. This is uncached; if it becomes
328    commonly used, it may be necessary to cache it somehow. */
329 Interval
330 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
331 {
332   extract_grob_set (me, "elements", elts);
333   vector<Grob*> new_elts;
334
335   for (vsize i = 0; i < elts.size (); i++)
336     if (elts[i]->common_refpoint (staff, parent_a) == staff)
337       new_elts.push_back (elts[i]);
338
339   return relative_group_extent (new_elts, refp, ext_a);
340 }
341
342
343 Grob *
344 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
345 {
346   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
347     return c;
348   
349   extract_grob_set (me, "elements", elts);
350
351   vector<Grob*> relevant_items;
352   vector<Grob*> relevant_spanners;
353   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
354
355   for (vsize i = 0; i < elts.size (); i++)
356     {
357       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
358         {
359           if (dynamic_cast<Item*> (elts[i]))
360             relevant_items.push_back (elts[i]);
361           else if (dynamic_cast<Spanner*> (elts[i]))
362             relevant_spanners.push_back (elts[i]);
363         }
364             
365
366       Item *it = dynamic_cast<Item*> (elts[i]);
367       Direction d = LEFT;
368       if (it)
369         do
370           {
371             Item *piece = it->find_prebroken_piece (d);
372             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
373               relevant_items.push_back (piece);
374           }
375         while (flip (&d) != LEFT);
376     }
377   vector_sort (relevant_items, Item::less);
378
379   Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
380   common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
381
382   me->set_object ("pure-Y-common", common->self_scm ());
383   
384   SCM items_scm = Grob_array::make_array ();
385   SCM spanners_scm = Grob_array::make_array ();
386
387   unsmob_grob_array (items_scm)->set_array (relevant_items);
388   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
389   me->set_object ("pure-relevant-items", items_scm);
390   me->set_object ("pure-relevant-spanners", spanners_scm);
391
392   return common;
393 }
394
395 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
396 SCM
397 Axis_group_interface::calc_x_common (SCM grob)
398 {
399   Grob *me = unsmob_grob (grob);
400
401   extract_grob_set (me, "elements", elts);
402   Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
403   return common->self_scm ();
404 }
405
406 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
407 SCM
408 Axis_group_interface::calc_y_common (SCM grob)
409 {
410   Grob *me = unsmob_grob (grob);
411
412   extract_grob_set (me, "elements", elts);
413   Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
414   return common->self_scm ();
415 }
416
417 Interval
418 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
419 {
420   Grob *common = calc_pure_elts_and_common (me);
421         
422   Real my_coord = me->relative_coordinate (common, Y_AXIS);
423   Interval r (relative_pure_height (me, start, end));
424
425   return r - my_coord;
426 }
427
428 void
429 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
430 {
431   found->push_back (me);
432
433   if (!has_interface (me))
434     return;
435
436   extract_grob_set (me, "elements", elements);
437   for (vsize i = 0; i < elements.size (); i++)
438     {
439       Grob *e = elements[i];
440       Axis_group_interface::get_children (e, found);
441     }
442 }
443
444 bool
445 staff_priority_less (Grob * const &g1, Grob * const &g2)
446 {
447   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
448   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
449
450   if (priority_1 < priority_2)
451     return true;
452   else if (priority_1 > priority_2)
453     return false;
454
455   /* if neither grob has an outside-staff priority, the ordering will have no
456      effect -- we just need to choose a consistent ordering. We do this to
457      avoid the side-effect of calculating extents. */
458   if (isinf (priority_1))
459     return g1 < g2;
460
461   /* if there is no preference in staff priority, choose the left-most one */
462   Grob *common = g1->common_refpoint (g2, X_AXIS);
463   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
464   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
465   return start_1 < start_2;
466 }
467
468 static void
469 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
470 {
471   /* if a child has skylines, use them instead of the extent box */
472   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
473     {
474       Skyline_pair s = *pair;
475       s.shift (me->relative_coordinate (x_common, X_AXIS));
476       s.raise (me->relative_coordinate (y_common, Y_AXIS));
477       skylines->merge (s);
478     }
479   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
480     {
481       for (vsize i = 0; i < elements->size (); i++)
482         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
483     }
484   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
485            && !to_boolean (me->get_property ("cross-staff")))
486     {
487       boxes->push_back (Box (me->extent (x_common, X_AXIS),
488                              me->extent (y_common, Y_AXIS)));
489     }
490 }
491
492 /* We want to avoid situations like this:
493            still more text
494       more text
495    text
496    -------------------
497    staff
498    -------------------
499
500    The point is that "still more text" should be positioned under
501    "more text".  In order to achieve this, we place the grobs in several
502    passes.  We keep track of the right-most horizontal position that has been
503    affected by the current pass so far (actually we keep track of 2
504    positions, one for above the staff, one for below).
505
506    In each pass, we loop through the unplaced grobs from left to right.
507    If the grob doesn't overlap the right-most affected position, we place it
508    (and then update the right-most affected position to point to the right
509    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
510 */
511 static void
512 add_grobs_of_one_priority (Skyline_pair *const skylines,
513                            vector<Grob*> elements,
514                            Grob *x_common,
515                            Grob *y_common)
516 {
517   vector<Box> boxes;
518   Drul_array<Real> last_affected_position;
519
520   reverse (elements);
521   while (!elements.empty ())
522     {
523       last_affected_position[UP] = -infinity_f;
524       last_affected_position[DOWN] = -infinity_f;
525       /* do one pass */
526       for (vsize i = elements.size (); i--;)
527         {
528           Direction dir = get_grob_direction (elements[i]);
529           if (dir == CENTER)
530             {
531               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
532               dir = UP;
533             }
534
535           Box b (elements[i]->extent (x_common, X_AXIS),
536                  elements[i]->extent (y_common, Y_AXIS));
537           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
538           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
539
540           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
541             continue;
542
543           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
544             {
545               boxes.clear ();
546               boxes.push_back (b);
547               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
548               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
549               Real dist = (*skylines)[dir].distance (other) + padding;
550
551               if (dist > 0)
552                 {
553                   b.translate (Offset (0, dir*dist));
554                   elements[i]->translate_axis (dir*dist, Y_AXIS);
555                 }
556               (*skylines)[dir].insert (b, 0, X_AXIS);
557               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
558               last_affected_position[dir] = b[X_AXIS][RIGHT];
559             }
560
561           /*
562             Ugh: quadratic. --hwn
563            */
564           elements.erase (elements.begin () + i);
565         }
566     }
567 }
568
569 Skyline_pair
570 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
571 {
572   vector_sort (elements, staff_priority_less);
573   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
574   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
575
576   assert (y_common == me);
577
578   vsize i = 0;
579   vector<Box> boxes;
580
581   Skyline_pair skylines;
582   for (i = 0; i < elements.size ()
583          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
584     add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
585
586   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
587   Real padding = robust_scm2double (padding_scm, 0.1);
588   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
589   for (; i < elements.size (); i++)
590     {
591       SCM priority = elements[i]->get_property ("outside-staff-priority");
592       vector<Grob*> current_elts;
593       current_elts.push_back (elements[i]);
594       while (i + 1 < elements.size () 
595              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
596         current_elts.push_back (elements[++i]);
597
598       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
599     }
600   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
601   return skylines;
602 }
603
604 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
605 SCM
606 Axis_group_interface::calc_max_stretch (SCM smob)
607 {
608   Grob *me = unsmob_grob (smob);
609   Real ret = 0;
610   extract_grob_set (me, "elements", elts);
611
612   for (vsize i = 0; i < elts.size (); i++)
613     if (Axis_group_interface::has_interface (elts[i]))
614       ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
615
616   return scm_from_double (ret);
617 }
618
619 extern bool debug_skylines;
620 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
621 SCM
622 Axis_group_interface::print (SCM smob)
623 {
624   if (!debug_skylines)
625     return SCM_BOOL_F;
626
627   Grob *me = unsmob_grob (smob);
628   Stencil ret;
629   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
630     {
631       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
632       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
633     }
634   return ret.smobbed_copy ();
635 }
636
637 ADD_INTERFACE (Axis_group_interface,
638
639                "An object that groups other layout objects.",
640
641                /* properties */
642                "X-common "
643                "Y-common "
644                "adjacent-pure-heights "
645                "axes "
646                "elements "
647                "keep-fixed-while-stretching "
648                "max-stretch "
649                "no-alignment "
650                "pure-Y-common "
651                "pure-relevant-items "
652                "pure-relevant-spanners "
653                "vertical-skylines "
654                );