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 "page-layout-problem.hh"
16 #include "paper-book.hh"
17 #include "paper-column.hh"
18 #include "pointer-group-interface.hh"
20 #include "skyline-pair.hh"
25 MAKE_SCHEME_CALLBACK (Align_interface
, align_to_minimum_distances
, 1);
27 Align_interface::align_to_minimum_distances (SCM smob
)
29 Grob
*me
= unsmob_grob (smob
);
31 me
->set_property ("positioning-done", SCM_BOOL_T
);
33 SCM axis
= scm_car (me
->get_property ("axes"));
34 Axis ax
= Axis (scm_to_int (axis
));
36 Align_interface::align_elements_to_minimum_distances (me
, ax
);
41 MAKE_SCHEME_CALLBACK (Align_interface
, align_to_ideal_distances
, 1);
43 Align_interface::align_to_ideal_distances (SCM smob
)
45 Grob
*me
= unsmob_grob (smob
);
47 me
->set_property ("positioning-done", SCM_BOOL_T
);
49 Align_interface::align_elements_to_ideal_distances (me
);
54 /* for each grob, find its upper and lower skylines. If the grob has
55 an empty extent, delete it from the list instead. If the extent is
56 non-empty but there is no skyline available (or pure is true), just
57 create a flat skyline from the bounding box */
58 // TODO(jneem): the pure and non-pure parts seem to share very little
59 // code. Split them into 2 functions, perhaps?
61 get_skylines (Grob
*me
,
62 vector
<Grob
*> *const elements
,
64 bool pure
, int start
, int end
,
65 vector
<Skyline_pair
> *const ret
)
67 Grob
*other_common
= common_refpoint_of_array (*elements
, me
, other_axis (a
));
69 for (vsize i
= elements
->size (); i
--;)
71 Grob
*g
= (*elements
)[i
];
72 Skyline_pair skylines
;
76 Skyline_pair
*skys
= Skyline_pair::unsmob (g
->get_property (a
== Y_AXIS
78 : "horizontal-skylines"));
82 /* This skyline was calculated relative to the grob g. In order to compare it to
83 skylines belonging to other grobs, we need to shift it so that it is relative
84 to the common reference. */
85 Real offset
= g
->relative_coordinate (other_common
, other_axis (a
));
86 skylines
.shift (offset
);
91 Interval extent
= g
->pure_height (g
, start
, end
);
93 // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
94 // It's quite common for a treble clef to be the highest element of one system
95 // and for a low note (or lyrics) to be the lowest note on another. The two will
96 // never collide, but the pure-height stuff only works with bounding boxes, so it
97 // doesn't know that. The result is a significant over-estimation of the pure-height,
98 // especially on systems with many staves. To correct for this, we build a skyline
99 // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
100 // while the bit we're about to do only contains the breakable grobs at the beginning
101 // of the system. This way, the tall treble clefs are only compared with the treble
102 // clefs of the other staff and they will be ignored if the staff above is, for example,
104 if (Axis_group_interface::has_interface (g
)
105 && !Hara_kiri_group_spanner::request_suicide (g
, start
, end
))
107 extent
= Axis_group_interface::rest_of_line_pure_height (g
, start
, end
);
108 Interval begin_of_line_extent
= Axis_group_interface::begin_of_line_pure_height (g
, start
);
109 if (!begin_of_line_extent
.is_empty ())
112 b
[a
] = begin_of_line_extent
;
113 b
[other_axis (a
)] = Interval (-infinity_f
, -1);
114 skylines
.insert (b
, 0, other_axis (a
));
118 if (!extent
.is_empty ())
122 b
[other_axis (a
)] = Interval (0, infinity_f
);
123 skylines
.insert (b
, 0, other_axis (a
));
127 if (skylines
.is_empty ())
128 elements
->erase (elements
->begin () + i
);
130 ret
->push_back (skylines
);
136 Align_interface::get_minimum_translations (Grob
*me
,
137 vector
<Grob
*> const &all_grobs
,
139 bool pure
, int start
, int end
)
141 if (!pure
&& a
== Y_AXIS
&& dynamic_cast<Spanner
*> (me
) && !me
->get_system ())
142 me
->programming_error ("vertical alignment called before line-breaking");
144 Direction stacking_dir
= robust_scm2dir (me
->get_property ("stacking-dir"),
146 vector
<Grob
*> elems (all_grobs
); // writable copy
147 vector
<Skyline_pair
> skylines
;
149 get_skylines (me
, &elems
, a
, pure
, start
, end
, &skylines
);
151 SCM forced_distances
= ly_assoc_get (ly_symbol2scm ("alignment-distances"),
152 Page_layout_problem::get_details (me
),
156 Real default_padding
= robust_scm2double (me
->get_property ("padding"), 0.0);
157 vector
<Real
> translates
;
158 Skyline
down_skyline (stacking_dir
);
159 Real last_spaceable_element_pos
= 0;
160 Grob
*last_spaceable_element
= 0;
161 for (vsize j
= 0; j
< elems
.size (); j
++)
164 Real padding
= default_padding
;
167 dy
= skylines
[j
][-stacking_dir
].max_height ();
170 down_skyline
.merge (skylines
[j
-1][stacking_dir
]);
171 dy
= down_skyline
.distance (skylines
[j
][-stacking_dir
]);
173 SCM spec
= Page_layout_problem::get_spacing_spec (elems
[j
-1], elems
[j
]);
174 Page_layout_problem::read_spacing_spec (spec
, &padding
, ly_symbol2scm ("padding"));
176 Real min_distance
= 0;
177 if (Page_layout_problem::read_spacing_spec (spec
, &min_distance
, ly_symbol2scm ("minimum-distance")))
178 dy
= max (dy
, min_distance
);
180 if (Page_layout_problem::is_spaceable (elems
[j
]) && last_spaceable_element
)
182 // Spaceable staves may have min-distance and padding
183 // constraints coming from the previous spaceable staff
184 // as well as from the previous staff.
185 spec
= Page_layout_problem::get_spacing_spec (last_spaceable_element
, elems
[j
]);
186 Real spaceable_padding
= 0;
187 Page_layout_problem::read_spacing_spec (spec
,
189 ly_symbol2scm ("padding"));
190 padding
= max (padding
, spaceable_padding
);
192 Real min_distance
= 0;
193 if (Page_layout_problem::read_spacing_spec (spec
,
195 ly_symbol2scm ("minimum-distance")))
196 dy
= max (dy
, min_distance
+ stacking_dir
*(last_spaceable_element_pos
- where
));
198 if (scm_is_pair (forced_distances
))
200 SCM forced_dist
= scm_car (forced_distances
);
201 forced_distances
= scm_cdr (forced_distances
);
203 if (scm_is_number (forced_dist
))
204 dy
= scm_to_double (forced_dist
) + stacking_dir
* (last_spaceable_element_pos
- where
);
209 if (isinf (dy
)) /* if the skyline is empty, maybe max_height is infinity_f */
212 dy
= max (0.0, dy
+ padding
);
213 down_skyline
.raise (-stacking_dir
* dy
);
214 where
+= stacking_dir
* dy
;
215 translates
.push_back (where
);
217 if (Page_layout_problem::is_spaceable (elems
[j
]))
219 last_spaceable_element
= elems
[j
];
220 last_spaceable_element_pos
= where
;
224 // So far, we've computed the translates for all the non-empty elements.
225 // Here, we set the translates for the empty elements: an empty element
226 // gets the same translation as the last non-empty element before it.
227 vector
<Real
> all_translates
;
228 if (!translates
.empty ())
230 Real w
= translates
[0];
231 for (vsize i
= 0, j
= 0; j
< all_grobs
.size (); j
++)
233 if (i
< elems
.size () && all_grobs
[j
] == elems
[i
])
235 all_translates
.push_back (w
);
238 return all_translates
;
242 Align_interface::align_elements_to_ideal_distances (Grob
*me
)
244 System
*sys
= me
->get_system ();
245 Page_layout_problem
layout (NULL
, SCM_EOL
, scm_list_1 (sys
->self_scm ()));
247 layout
.solution (true);
251 Align_interface::align_elements_to_minimum_distances (Grob
*me
, Axis a
)
253 extract_grob_set (me
, "elements", all_grobs
);
255 vector
<Real
> translates
= get_minimum_translations (me
, all_grobs
, a
, false, 0, 0);
256 if (translates
.size ())
257 for (vsize j
= 0; j
< all_grobs
.size (); j
++)
258 all_grobs
[j
]->translate_axis (translates
[j
], a
);
262 Align_interface::get_pure_child_y_translation (Grob
*me
, Grob
*ch
, int start
, int end
)
264 extract_grob_set (me
, "elements", all_grobs
);
265 SCM dy_scm
= me
->get_property ("forced-distance");
267 if (scm_is_number (dy_scm
))
269 Real dy
= scm_to_double (dy_scm
) * robust_scm2dir (me
->get_property ("stacking-dir"), DOWN
);
271 for (vsize i
= 0; i
< all_grobs
.size (); i
++)
273 if (all_grobs
[i
] == ch
)
275 if (!Hara_kiri_group_spanner::has_interface (all_grobs
[i
])
276 || !Hara_kiri_group_spanner::request_suicide (all_grobs
[i
], start
, end
))
282 vector
<Real
> translates
= get_minimum_translations (me
, all_grobs
, Y_AXIS
, true, start
, end
);
284 if (translates
.size ())
286 for (vsize i
= 0; i
< all_grobs
.size (); i
++)
287 if (all_grobs
[i
] == ch
)
288 return translates
[i
];
294 programming_error ("tried to get a translation for something that is no child of mine");
299 Align_interface::axis (Grob
*me
)
301 return Axis (scm_to_int (scm_car (me
->get_property ("axes"))));
305 Align_interface::add_element (Grob
*me
, Grob
*element
)
307 Axis a
= Align_interface::axis (me
);
308 SCM sym
= axis_offset_symbol (a
);
309 SCM proc
= axis_parent_positioning (a
);
311 element
->set_property (sym
, proc
);
312 Axis_group_interface::add_element (me
, element
);
316 Align_interface::set_ordered (Grob
*me
)
318 SCM ga_scm
= me
->get_object ("elements");
319 Grob_array
*ga
= unsmob_grob_array (ga_scm
);
322 ga_scm
= Grob_array::make_array ();
323 ga
= unsmob_grob_array (ga_scm
);
324 me
->set_object ("elements", ga_scm
);
327 ga
->set_ordered (true);
330 ADD_INTERFACE (Align_interface
,
331 "Order grobs from top to bottom, left to right, right to left"
332 " or bottom to top. For vertical alignments of staves, the"
333 " @code{break-system-details} of the left"
334 " @rinternals{NonMusicalPaperColumn} may be set to tune"
335 " vertical spacing. Set @code{alignment-extra-space} to add"
336 " extra space for staves. Set"
337 " @code{fixed-alignment-extra-space} to force staves in"
338 " @code{PianoStaff}s further apart.",