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