2 align-interface.cc -- implement Align_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "align-interface.hh"
10 #include "axis-group-interface.hh"
11 #include "grob-array.hh"
12 #include "hara-kiri-group-spanner.hh"
13 #include "international.hh"
15 #include "paper-column.hh"
16 #include "pointer-group-interface.hh"
18 #include "skyline-pair.hh"
23 TODO: for vertical spacing, should also include a rod & spring
24 scheme of sorts into this: the alignment should default to a certain
25 distance between element refpoints, unless bbox force a bigger
29 MAKE_SCHEME_CALLBACK (Align_interface
, calc_positioning_done
, 1);
31 Align_interface::calc_positioning_done (SCM smob
)
33 Grob
*me
= unsmob_grob (smob
);
35 me
->set_property ("positioning-done", SCM_BOOL_T
);
37 SCM axis
= scm_car (me
->get_property ("axes"));
38 Axis ax
= Axis (scm_to_int (axis
));
40 Align_interface::align_elements_to_extents (me
, ax
);
46 TODO: This belongs to the old two-pass spacing. Delete me.
48 MAKE_SCHEME_CALLBACK (Align_interface
, stretch_after_break
, 1)
50 Align_interface::stretch_after_break (SCM grob
)
52 Grob
*me
= unsmob_grob (grob
);
54 Spanner
*me_spanner
= dynamic_cast<Spanner
*> (me
);
55 extract_grob_set (me
, "elements", elems
);
57 if (me_spanner
&& elems
.size ())
59 Grob
*common
= common_refpoint_of_array (elems
, me
, Y_AXIS
);
61 /* force position callbacks */
62 for (vsize i
= 0; i
< elems
.size (); i
++)
63 elems
[i
]->relative_coordinate (common
, Y_AXIS
);
65 SCM details
= me_spanner
->get_bound (LEFT
)->get_property ("line-break-system-details");
66 SCM extra_space_handle
= scm_assoc (ly_symbol2scm ("fixed-alignment-extra-space"), details
);
68 Real extra_space
= robust_scm2double (scm_is_pair (extra_space_handle
)
69 ? scm_cdr (extra_space_handle
)
73 Direction stacking_dir
= robust_scm2dir (me
->get_property ("stacking-dir"),
75 Real delta
= extra_space
/ elems
.size () * stacking_dir
;
76 for (vsize i
= 0; i
< elems
.size (); i
++)
77 elems
[i
]->translate_axis (i
* delta
, Y_AXIS
);
80 return SCM_UNSPECIFIED
;
83 /* for each grob, find its upper and lower skylines. If the grob has
84 an empty extent, delete it from the list instead. If the extent is
85 non-empty but there is no skyline available (or pure is true), just
86 create a flat skyline from the bounding box */
88 get_skylines (Grob
*me
,
89 vector
<Grob
*> *const elements
,
91 bool pure
, int start
, int end
,
92 vector
<Skyline_pair
> *const ret
)
94 Grob
*other_common
= common_refpoint_of_array (*elements
, me
, other_axis (a
));
96 for (vsize i
= elements
->size (); i
--;)
98 Grob
*g
= (*elements
)[i
];
99 Skyline_pair skylines
;
103 Skyline_pair
*skys
= Skyline_pair::unsmob (g
->get_property (a
== Y_AXIS
104 ? "vertical-skylines"
105 : "horizontal-skylines"));
109 /* this is perhaps an abuse of minimum-?-extent: maybe we should create
110 another property? But it seems that the only (current) use of
111 minimum-Y-extent is to separate vertically-aligned elements */
112 SCM min_extent
= g
->get_property (a
== X_AXIS
113 ? ly_symbol2scm ("minimum-X-extent")
114 : ly_symbol2scm ("minimum-Y-extent"));
116 if (is_number_pair (min_extent
))
119 Interval other_extent
= g
->extent (other_common
, other_axis (a
));
120 b
[a
] = ly_scm2interval (min_extent
);
121 b
[other_axis (a
)] = other_extent
;
122 if (!other_extent
.is_empty ())
123 skylines
.insert (b
, 0, other_axis (a
));
126 /* This skyline was calculated relative to the grob g. In order to compare it to
127 skylines belonging to other grobs, we need to shift it so that it is relative
128 to the common reference. */
129 Real offset
= g
->relative_coordinate (other_common
, other_axis (a
));
130 skylines
.shift (offset
);
134 assert (a
== Y_AXIS
);
135 Interval extent
= g
->pure_height (g
, start
, end
);
136 if (!extent
.is_empty ())
140 b
[other_axis (a
)] = Interval (-infinity_f
, infinity_f
);
141 skylines
.insert (b
, 0, other_axis (a
));
145 if (skylines
.is_empty ())
146 elements
->erase (elements
->begin () + i
);
148 ret
->push_back (skylines
);
154 Align_interface::get_extents_aligned_translates (Grob
*me
,
155 vector
<Grob
*> const &all_grobs
,
157 bool pure
, int start
, int end
)
159 Spanner
*me_spanner
= dynamic_cast<Spanner
*> (me
);
162 SCM line_break_details
= SCM_EOL
;
163 if (a
== Y_AXIS
&& me_spanner
)
166 line_break_details
= get_root_system (me
)->column (start
)->get_property ("line-break-system-details");
168 line_break_details
= me_spanner
->get_bound (LEFT
)->get_property ("line-break-system-details");
170 if (!me
->get_system () && !pure
)
171 me
->programming_error ("vertical alignment called before line-breaking");
174 Direction stacking_dir
= robust_scm2dir (me
->get_property ("stacking-dir"),
177 vector
<Grob
*> elems (all_grobs
); // writable copy
178 vector
<Skyline_pair
> skylines
;
180 get_skylines (me
, &elems
, a
, pure
, start
, end
, &skylines
);
183 /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
184 SCM extra_space_handle
= scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details
);
185 Real extra_space
= robust_scm2double (scm_is_pair (extra_space_handle
)
186 ? scm_cdr (extra_space_handle
)
190 Real padding
= robust_scm2double (me
->get_property ("padding"), 0.0);
191 vector
<Real
> translates
;
192 Skyline
down_skyline (stacking_dir
);
193 for (vsize j
= 0; j
< elems
.size (); j
++)
197 dy
= skylines
[j
][-stacking_dir
].max_height ();
200 down_skyline
.merge (skylines
[j
-1][stacking_dir
]);
201 dy
= down_skyline
.distance (skylines
[j
][-stacking_dir
]);
204 if (isinf (dy
)) /* if the skyline is empty, maybe max_height is infinity_f */
207 dy
= max (0.0, dy
+ padding
+ extra_space
/ elems
.size ());
208 down_skyline
.raise (-stacking_dir
* dy
);
209 where
+= stacking_dir
* dy
;
210 translates
.push_back (where
);
213 SCM offsets_handle
= scm_assq (ly_symbol2scm ("alignment-offsets"),
215 if (scm_is_pair (offsets_handle
))
219 for (SCM s
= scm_cdr (offsets_handle
);
220 scm_is_pair (s
) && i
< translates
.size (); s
= scm_cdr (s
), i
++)
222 if (scm_is_number (scm_car (s
)))
223 translates
[i
] = scm_to_double (scm_car (s
));
227 vector
<Real
> all_translates
;
229 if (!translates
.empty ())
231 Real w
= translates
[0];
232 for (vsize i
= 0, j
= 0; j
< all_grobs
.size (); j
++)
234 if (i
< elems
.size () && all_grobs
[j
] == elems
[i
])
236 all_translates
.push_back (w
);
239 return all_translates
;
243 Align_interface::align_elements_to_extents (Grob
*me
, Axis a
)
245 extract_grob_set (me
, "elements", all_grobs
);
247 vector
<Real
> translates
= get_extents_aligned_translates (me
, all_grobs
, a
, false, 0, 0);
248 if (translates
.size ())
249 for (vsize j
= 0; j
< all_grobs
.size (); j
++)
250 all_grobs
[j
]->translate_axis (translates
[j
], a
);
253 /* After we have already determined the y-offsets of our children, we may still
254 want to stretch them a little. */
256 Align_interface::stretch (Grob
*me
, Real amount
, Axis a
)
258 extract_grob_set (me
, "elements", elts
);
259 Real non_empty_elts
= stretchable_children_count (me
);
261 Direction dir
= robust_scm2dir (me
->get_property ("stacking-dir"), DOWN
);
262 for (vsize i
= 1; i
< elts
.size (); i
++)
264 if (!elts
[i
]->extent (me
, a
).is_empty ()
265 && !to_boolean (elts
[i
]->get_property ("keep-fixed-while-stretching")))
266 offset
+= amount
/ non_empty_elts
;
267 elts
[i
]->translate_axis (dir
* offset
, a
);
269 me
->flush_extent_cache (Y_AXIS
);
273 Align_interface::get_pure_child_y_translation (Grob
*me
, Grob
*ch
, int start
, int end
)
275 extract_grob_set (me
, "elements", all_grobs
);
276 SCM dy_scm
= me
->get_property ("forced-distance");
278 if (scm_is_number (dy_scm
))
280 Real dy
= scm_to_double (dy_scm
) * robust_scm2dir (me
->get_property ("stacking-dir"), DOWN
);
282 for (vsize i
= 0; i
< all_grobs
.size (); i
++)
284 if (all_grobs
[i
] == ch
)
286 if (!Hara_kiri_group_spanner::has_interface (all_grobs
[i
])
287 || !Hara_kiri_group_spanner::request_suicide (all_grobs
[i
], start
, end
))
293 vector
<Real
> translates
= get_extents_aligned_translates (me
, all_grobs
, Y_AXIS
, true, start
, end
);
295 if (translates
.size ())
297 for (vsize i
= 0; i
< all_grobs
.size (); i
++)
298 if (all_grobs
[i
] == ch
)
299 return translates
[i
];
305 programming_error (_ ("tried to get a translation for something that is no child of mine"));
310 Align_interface::axis (Grob
*me
)
312 return Axis (scm_to_int (scm_car (me
->get_property ("axes"))));
316 Align_interface::add_element (Grob
*me
, Grob
*element
)
318 Axis a
= Align_interface::axis (me
);
319 SCM sym
= axis_offset_symbol (a
);
320 SCM proc
= axis_parent_positioning (a
);
322 element
->set_property (sym
, proc
);
323 Axis_group_interface::add_element (me
, element
);
327 Align_interface::set_ordered (Grob
*me
)
329 SCM ga_scm
= me
->get_object ("elements");
330 Grob_array
*ga
= unsmob_grob_array (ga_scm
);
333 ga_scm
= Grob_array::make_array ();
334 ga
= unsmob_grob_array (ga_scm
);
335 me
->set_object ("elements", ga_scm
);
338 ga
->set_ordered (true);
342 Align_interface::stretchable_children_count (Grob
const *me
)
344 extract_grob_set (me
, "elements", elts
);
347 /* start at 1: we will never move the first child while stretching */
348 for (vsize i
= 1; i
< elts
.size (); i
++)
349 if (!to_boolean (elts
[i
]->get_property ("keep-fixed-while-stretching"))
350 && !elts
[i
]->extent (elts
[i
], Y_AXIS
).is_empty ())
356 MAKE_SCHEME_CALLBACK (Align_interface
, calc_max_stretch
, 1)
358 Align_interface::calc_max_stretch (SCM smob
)
360 Grob
*me
= unsmob_grob (smob
);
361 Spanner
*spanner_me
= dynamic_cast<Spanner
*> (me
);
364 if (spanner_me
&& stretchable_children_count (me
) > 0)
366 Paper_column
*left
= dynamic_cast<Paper_column
*> (spanner_me
->get_bound (LEFT
));
367 Real height
= me
->extent (me
, Y_AXIS
).length ();
368 SCM line_break_details
= left
->get_property ("line-break-system-details");
369 SCM fixed_offsets
= scm_assq (ly_symbol2scm ("alignment-offsets"),
372 /* if there are fixed offsets, we refuse to stretch */
373 if (fixed_offsets
!= SCM_BOOL_F
)
376 ret
= height
* height
/ 80.0; /* why this, exactly? -- jneem */
378 return scm_from_double (ret
);
381 ADD_INTERFACE (Align_interface
,
382 "Order grobs from top to bottom, left to right, right to left"
383 " or bottom to top. For vertical alignments of staves, the"
384 " @code{break-system-details} of the left"
385 " @rinternals{NonMusicalPaperColumn} may be set to tune"
386 " vertical spacing. Set @code{alignment-extra-space} to add"
387 " extra space for staves. Set"
388 " @code{fixed-alignment-extra-space} to force staves in"
389 " @code{PianoStaff}s further apart.",