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