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