]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Merge branch 'master' of ssh://kainhofer@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   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 SCM
389 Axis_group_interface::calc_common (Grob *me, Axis axis)
390 {
391   extract_grob_set (me, "elements", elts);
392   Grob *common = common_refpoint_of_array (elts, me, axis);
393   if (!common)
394     {
395       me->programming_error ("No common parent found in calc_common axis.");
396       return SCM_EOL;
397     }
398   
399   return common->self_scm ();
400 }
401
402
403 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
404 SCM
405 Axis_group_interface::calc_x_common (SCM grob)
406 {
407   return calc_common (unsmob_grob (grob), X_AXIS);
408 }
409
410 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
411 SCM
412 Axis_group_interface::calc_y_common (SCM grob)
413 {
414   return calc_common (unsmob_grob (grob), Y_AXIS);
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                "An object that groups other layout objects.",
639
640                /* properties */
641                "X-common "
642                "Y-common "
643                "adjacent-pure-heights "
644                "axes "
645                "elements "
646                "keep-fixed-while-stretching "
647                "max-stretch "
648                "no-alignment "
649                "pure-Y-common "
650                "pure-relevant-items "
651                "pure-relevant-spanners "
652                "vertical-skylines "
653                );