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