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