2 align-interface.cc -- implement Align_interface
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "align-interface.hh"
12 #include "axis-group-interface.hh"
13 #include "pointer-group-interface.hh"
14 #include "hara-kiri-group-spanner.hh"
15 #include "grob-array.hh"
16 #include "international.hh"
19 #include "paper-column.hh"
22 TODO: for vertical spacing, should also include a rod & spring
23 scheme of sorts into this: the alignment should default to a certain
24 distance between element refpoints, unless bbox force a bigger
28 MAKE_SCHEME_CALLBACK (Align_interface
, calc_positioning_done
, 1);
30 Align_interface::calc_positioning_done (SCM smob
)
32 Grob
*me
= unsmob_grob (smob
);
34 me
->set_property ("positioning-done", SCM_BOOL_T
);
36 SCM axis
= scm_car (me
->get_property ("axes"));
37 Axis ax
= Axis (scm_to_int (axis
));
39 Align_interface::align_elements_to_extents (me
, ax
);
45 TODO: This belongs to the old two-pass spacing. Delete me.
47 MAKE_SCHEME_CALLBACK (Align_interface
, stretch_after_break
, 1)
49 Align_interface::stretch_after_break (SCM grob
)
51 Grob
*me
= unsmob_grob (grob
);
53 Spanner
*me_spanner
= dynamic_cast<Spanner
*> (me
);
54 extract_grob_set (me
, "elements", elems
);
56 if (me_spanner
&& elems
.size ())
58 Grob
*common
= common_refpoint_of_array (elems
, me
, Y_AXIS
);
60 /* force position callbacks */
61 for (vsize i
= 0; i
< elems
.size (); i
++)
62 elems
[i
]->relative_coordinate (common
, Y_AXIS
);
64 SCM details
= me_spanner
->get_bound (LEFT
)->get_property ("line-break-system-details");
65 SCM extra_space_handle
= scm_assoc (ly_symbol2scm ("fixed-alignment-extra-space"), details
);
67 Real extra_space
= robust_scm2double (scm_is_pair (extra_space_handle
)
68 ? scm_cdr (extra_space_handle
)
72 Direction stacking_dir
= robust_scm2dir (me
->get_property ("stacking-dir"),
74 Real delta
= extra_space
/ elems
.size () * stacking_dir
;
75 for (vsize i
= 0; i
< elems
.size (); i
++)
76 elems
[i
]->translate_axis (i
* delta
, Y_AXIS
);
79 return SCM_UNSPECIFIED
;
82 /* for each grob, find its upper and lower skylines. If the grob has
83 an empty extent, delete it from the list instead. If the extent is
84 non-empty but there is no skyline available (or pure is true), just
85 create a flat skyline from the bounding box */
87 get_skylines (Grob
*me
,
88 vector
<Grob
*> *const elements
,
90 bool pure
, int start
, int end
,
91 vector
<Skyline_pair
> *const ret
)
93 /* each child's skyline was calculated according to the common refpoint of its
94 elements. Here we need all the skylines to be positioned with respect to
95 a single refpoint, so we need the common refpoint of the common refpoints
96 of the elements of the children */
97 vector
<Grob
*> child_refpoints
;
98 for (vsize i
= 0; i
< elements
->size (); i
++)
100 Grob
*elt
= (*elements
)[i
];
101 Grob
*child_common
= unsmob_grob ((a
== Y_AXIS
)
102 ? elt
->get_object ("X-common")
103 : elt
->get_object ("Y-common"));
107 extract_grob_set (elt
, "elements", child_elts
);
108 child_common
= common_refpoint_of_array (child_elts
, elt
, other_axis (a
));
111 child_refpoints
.push_back (child_common
);
114 Grob
*other_common
= common_refpoint_of_array (child_refpoints
, me
, other_axis (a
));
116 for (vsize i
= elements
->size (); i
--;)
118 Grob
*g
= (*elements
)[i
];
119 Skyline_pair skylines
;
121 /* each skyline is calculated relative to (potentially) a different other_axis
122 coordinate. In order to compare the skylines effectively, we need to shift them
123 to some absolute reference point */
126 Skyline_pair
*skys
= Skyline_pair::unsmob (g
->get_property (a
== Y_AXIS
127 ? "vertical-skylines"
128 : "horizontal-skylines"));
132 /* this is perhaps an abuse of minimum-?-extent: maybe we should create
133 another property? But it seems that the only (current) use of
134 minimum-Y-extent is to separate vertically-aligned elements */
135 SCM min_extent
= g
->get_property (a
== X_AXIS
136 ? ly_symbol2scm ("minimum-X-extent")
137 : ly_symbol2scm ("minimum-Y-extent"));
139 if (is_number_pair (min_extent
))
142 Interval other_extent
= g
->extent (other_common
, other_axis (a
));
143 b
[a
] = ly_scm2interval (min_extent
);
144 b
[other_axis (a
)] = other_extent
;
145 if (!other_extent
.is_empty ())
146 skylines
.insert (b
, 0, other_axis (a
));
149 Real offset
= child_refpoints
[i
]->relative_coordinate (other_common
, other_axis (a
));
150 skylines
.shift (offset
);
154 assert (a
== Y_AXIS
);
155 Interval extent
= g
->pure_height (g
, start
, end
);
156 if (!extent
.is_empty ())
160 b
[other_axis (a
)] = Interval (-infinity_f
, infinity_f
);
161 skylines
.insert (b
, 0, other_axis (a
));
165 if (skylines
.is_empty ())
166 elements
->erase (elements
->begin () + i
);
168 ret
->push_back (skylines
);
174 Align_interface::get_extents_aligned_translates (Grob
*me
,
175 vector
<Grob
*> const &all_grobs
,
177 bool pure
, int start
, int end
)
179 Spanner
*me_spanner
= dynamic_cast<Spanner
*> (me
);
182 SCM line_break_details
= SCM_EOL
;
183 if (a
== Y_AXIS
&& me_spanner
)
186 line_break_details
= get_root_system (me
)->column (start
)->get_property ("line-break-system-details");
188 line_break_details
= me_spanner
->get_bound (LEFT
)->get_property ("line-break-system-details");
190 if (!me
->get_system () && !pure
)
191 me
->programming_error ("vertical alignment called before line-breaking");
194 Direction stacking_dir
= robust_scm2dir (me
->get_property ("stacking-dir"),
197 vector
<Grob
*> elems (all_grobs
); // writable copy
198 vector
<Skyline_pair
> skylines
;
200 get_skylines (me
, &elems
, a
, pure
, start
, end
, &skylines
);
203 /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
204 SCM extra_space_handle
= scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details
);
205 Real extra_space
= robust_scm2double (scm_is_pair (extra_space_handle
)
206 ? scm_cdr (extra_space_handle
)
210 Real padding
= robust_scm2double (me
->get_property ("padding"), 0.0);
211 vector
<Real
> translates
;
212 Skyline
down_skyline (stacking_dir
);
213 for (vsize j
= 0; j
< elems
.size (); j
++)
217 dy
= skylines
[j
][-stacking_dir
].max_height ();
220 down_skyline
.merge (skylines
[j
-1][stacking_dir
]);
221 dy
= down_skyline
.distance (skylines
[j
][-stacking_dir
]);
224 if (isinf (dy
)) /* if the skyline is empty, maybe max_height is infinity_f */
227 dy
= max (0.0, dy
+ padding
+ extra_space
/ elems
.size ());
228 down_skyline
.raise (-stacking_dir
* dy
);
229 where
+= stacking_dir
* dy
;
230 translates
.push_back (where
);
233 SCM offsets_handle
= scm_assq (ly_symbol2scm ("alignment-offsets"),
235 if (scm_is_pair (offsets_handle
))
239 for (SCM s
= scm_cdr (offsets_handle
);
240 scm_is_pair (s
) && i
< translates
.size (); s
= scm_cdr (s
), i
++)
242 if (scm_is_number (scm_car (s
)))
243 translates
[i
] = scm_to_double (scm_car (s
));
247 vector
<Real
> all_translates
;
249 if (!translates
.empty ())
251 Real w
= translates
[0];
252 for (vsize i
= 0, j
= 0; j
< all_grobs
.size (); j
++)
254 if (i
< elems
.size () && all_grobs
[j
] == elems
[i
])
256 all_translates
.push_back (w
);
259 return all_translates
;
263 Align_interface::align_elements_to_extents (Grob
*me
, Axis a
)
265 extract_grob_set (me
, "elements", all_grobs
);
267 vector
<Real
> translates
= get_extents_aligned_translates (me
, all_grobs
, a
, false, 0, 0);
268 if (translates
.size ())
269 for (vsize j
= 0; j
< all_grobs
.size (); j
++)
270 all_grobs
[j
]->translate_axis (translates
[j
], a
);
273 /* After we have already determined the y-offsets of our children, we may still
274 want to stretch them a little. */
276 Align_interface::stretch (Grob
*me
, Real amount
, Axis a
)
278 extract_grob_set (me
, "elements", elts
);
279 Real non_empty_elts
= stretchable_children_count (me
);
281 Direction dir
= robust_scm2dir (me
->get_property ("stacking-dir"), DOWN
);
282 for (vsize i
= 1; i
< elts
.size (); i
++)
284 if (!elts
[i
]->extent (me
, a
).is_empty ()
285 && !to_boolean (elts
[i
]->get_property ("keep-fixed-while-stretching")))
286 offset
+= amount
/ non_empty_elts
;
287 elts
[i
]->translate_axis (dir
* offset
, a
);
289 me
->flush_extent_cache (Y_AXIS
);
293 Align_interface::get_pure_child_y_translation (Grob
*me
, Grob
*ch
, int start
, int end
)
295 extract_grob_set (me
, "elements", all_grobs
);
296 SCM dy_scm
= me
->get_property ("forced-distance");
298 if (scm_is_number (dy_scm
))
300 Real dy
= scm_to_double (dy_scm
) * robust_scm2dir (me
->get_property ("stacking-dir"), DOWN
);
302 for (vsize i
= 0; i
< all_grobs
.size (); i
++)
304 if (all_grobs
[i
] == ch
)
306 if (!Hara_kiri_group_spanner::has_interface (all_grobs
[i
])
307 || !Hara_kiri_group_spanner::request_suicide (all_grobs
[i
], start
, end
))
313 vector
<Real
> translates
= get_extents_aligned_translates (me
, all_grobs
, Y_AXIS
, true, start
, end
);
315 if (translates
.size ())
317 for (vsize i
= 0; i
< all_grobs
.size (); i
++)
318 if (all_grobs
[i
] == ch
)
319 return translates
[i
];
325 programming_error (_ ("tried to get a translation for something that is no child of mine"));
330 Align_interface::axis (Grob
*me
)
332 return Axis (scm_to_int (scm_car (me
->get_property ("axes"))));
336 Align_interface::add_element (Grob
*me
, Grob
*element
)
338 Axis a
= Align_interface::axis (me
);
339 SCM sym
= axis_offset_symbol (a
);
340 SCM proc
= axis_parent_positioning (a
);
342 element
->set_property (sym
, proc
);
343 Axis_group_interface::add_element (me
, element
);
347 Align_interface::set_ordered (Grob
*me
)
349 SCM ga_scm
= me
->get_object ("elements");
350 Grob_array
*ga
= unsmob_grob_array (ga_scm
);
353 ga_scm
= Grob_array::make_array ();
354 ga
= unsmob_grob_array (ga_scm
);
355 me
->set_object ("elements", ga_scm
);
358 ga
->set_ordered (true);
362 Align_interface::stretchable_children_count (Grob
const *me
)
364 extract_grob_set (me
, "elements", elts
);
367 /* start at 1: we will never move the first child while stretching */
368 for (vsize i
= 1; i
< elts
.size (); i
++)
369 if (!to_boolean (elts
[i
]->get_property ("keep-fixed-while-stretching"))
370 && !elts
[i
]->extent (elts
[i
], Y_AXIS
).is_empty ())
376 MAKE_SCHEME_CALLBACK (Align_interface
, calc_max_stretch
, 1)
378 Align_interface::calc_max_stretch (SCM smob
)
380 Grob
*me
= unsmob_grob (smob
);
381 Spanner
*spanner_me
= dynamic_cast<Spanner
*> (me
);
384 if (spanner_me
&& stretchable_children_count (me
) > 0)
386 Paper_column
*left
= dynamic_cast<Paper_column
*> (spanner_me
->get_bound (LEFT
));
387 Real height
= me
->extent (me
, Y_AXIS
).length ();
388 SCM line_break_details
= left
->get_property ("line-break-system-details");
389 SCM fixed_offsets
= scm_assq (ly_symbol2scm ("alignment-offsets"),
392 /* if there are fixed offsets, we refuse to stretch */
393 if (fixed_offsets
!= SCM_BOOL_F
)
396 ret
= height
* height
/ 80.0; /* why this, exactly? -- jneem */
398 return scm_from_double (ret
);
401 ADD_INTERFACE (Align_interface
,
403 "Order grobs from top to bottom, left to right, right to left or bottom "
405 "For vertical alignments of staves, the @code{break-system-details} of "
406 "the left @internalsref{NonMusicalPaperColumn} may be set to tune vertical spacing "
407 "Set @code{alignment-extra-space} to add extra space for staves. Set "
408 "@code{fixed-alignment-extra-space} to force staves in PianoStaves further apart."