]> git.donarmstrong.com Git - lilypond.git/blob - lily/break-align-interface.cc
* flower
[lilypond.git] / lily / break-align-interface.cc
1 /*
2   break-align-interface.cc -- implement Break_align_interface
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include <math.h>
10
11 #include "break-align-interface.hh"
12 #include "libc-extension.hh"    // isinf
13
14 #include "self-alignment-interface.hh"
15 #include "side-position-interface.hh"
16 #include "axis-group-interface.hh"
17 #include "warn.hh"
18 #include "dimensions.hh"
19 #include "output-def.hh"
20 #include "paper-column.hh"
21 #include "align-interface.hh"
22
23 MAKE_SCHEME_CALLBACK (Break_align_interface, alignment_callback, 2);
24 SCM
25 Break_align_interface::alignment_callback (SCM element_smob, SCM axis)
26 {
27   Grob *me = unsmob_grob (element_smob);
28   Axis a = (Axis) scm_to_int (axis);
29
30   assert (a == X_AXIS);
31   Grob *par = me->get_parent (a);
32   if (par && !to_boolean (par->get_property ("positioning-done")))
33     {
34       par->set_property ("positioning-done", SCM_BOOL_T);
35       Break_align_interface::do_alignment (par);
36     }
37
38   return scm_make_real (0);
39 }
40
41 MAKE_SCHEME_CALLBACK (Break_align_interface, self_align_callback, 2);
42 SCM
43 Break_align_interface::self_align_callback (SCM element_smob, SCM axis)
44 {
45   Grob *me = unsmob_grob (element_smob);
46   Axis a = (Axis) scm_to_int (axis);
47   assert (a == X_AXIS);
48
49   Item *item = dynamic_cast<Item *> (me);
50   Direction bsd = item->break_status_dir ();
51   if (bsd == LEFT)
52     {
53       me->set_property ("self-alignment-X", scm_int2num (RIGHT));
54     }
55
56   /*
57     Force break alignment itself to be done first, in the case
58   */
59   return Self_alignment_interface::aligned_on_self (element_smob, axis);
60 }
61
62 /*
63   This is tricky: we cannot modify 'elements, since callers are
64   iterating the same list. Reordering the list in-place, or resetting
65   'elements will skip elements in the loops of callers.
66
67   So we return the correct order as an array.
68 */
69 Link_array<Grob>
70 Break_align_interface::ordered_elements (Grob *grob)
71 {
72   Item *me = dynamic_cast<Item *> (grob);
73   SCM elts = me->get_property ("elements");
74   SCM order_vec = me->get_property ("break-align-orders");
75   if (!scm_is_vector (order_vec)
76       || scm_c_vector_length (order_vec) < 3)
77     return extract_grob_array (me, ly_symbol2scm ("elements"));
78   SCM order = scm_vector_ref (order_vec,
79                               scm_int2num (me->break_status_dir () + 1));
80
81   /*
82     Copy in order specified in BREAK-ALIGN-ORDER.
83   */
84   Link_array<Grob> new_elts;
85   for (; scm_is_pair (order); order = scm_cdr (order))
86     {
87       SCM sym = scm_car (order);
88
89       for (SCM s = elts; scm_is_pair (s); s = scm_cdr (s))
90         {
91           Grob *g = unsmob_grob (scm_car (s));
92           if (g && sym == g->get_property ("break-align-symbol"))
93             {
94               new_elts.push (g);
95               elts = scm_delq (g->self_scm (), elts);
96             }
97         }
98     }
99   return new_elts;
100 }
101
102 void
103 Break_align_interface::add_element (Grob *me, Grob *toadd)
104 {
105   Axis_group_interface::add_element (me, toadd);
106 }
107
108 void
109 Break_align_interface::do_alignment (Grob *grob)
110 {
111   Item *me = dynamic_cast<Item *> (grob);
112
113   Link_array<Grob> elems = ordered_elements (me);
114   Array<Interval> extents;
115
116   int last_nonempty = -1;
117   for (int i = 0; i < elems.size (); i++)
118     {
119       Interval y = elems[i]->extent (elems[i], X_AXIS);
120       extents.push (y);
121       if (!y.is_empty ())
122         last_nonempty = i;
123     }
124
125   int idx = 0;
126   while (idx < extents.size () && extents[idx].is_empty ())
127     idx++;
128
129   Array<Real> offsets;
130   offsets.set_size (elems.size ());
131   for (int i = 0; i < offsets.size ();i++)
132     offsets[i] = 0.0;
133
134   Real extra_right_space = 0.0;
135   int edge_idx = -1;
136   while (idx < elems.size ())
137     {
138       int next_idx = idx + 1;
139       while (next_idx < elems.size ()
140              && extents[next_idx].is_empty ())
141         next_idx++;
142
143       Grob *l = elems[idx];
144       Grob *r = 0;
145
146       if (next_idx < elems.size ())
147         r = elems[next_idx];
148
149       SCM alist = SCM_EOL;
150
151       /*
152         Find the first grob with a space-alist entry.
153       */
154       for (SCM s = l->get_property ("elements");
155            scm_is_pair (s); s = scm_cdr (s))
156         {
157           Grob *elt = unsmob_grob (scm_car (s));
158
159           if (edge_idx < 0
160               && elt->get_property ("break-align-symbol")
161               == ly_symbol2scm ("left-edge"))
162             edge_idx = idx;
163
164           SCM l = elt->get_property ("space-alist");
165           if (scm_is_pair (l))
166             {
167               alist = l;
168               break;
169             }
170         }
171
172       SCM rsym = r ? SCM_EOL : ly_symbol2scm ("right-edge");
173
174       /*
175         We used to use #'cause to find out the symbol and the spacing
176         table, but that gets icky when that grob is suicided for some
177         reason.
178       */
179       for (SCM s = r ? r->get_property ("elements") : SCM_EOL;
180            !scm_is_symbol (rsym) && scm_is_pair (s); s = scm_cdr (s))
181         {
182           Grob *elt = unsmob_grob (scm_car (s));
183
184           rsym = elt->get_property ("break-align-symbol");
185         }
186
187       if (rsym == ly_symbol2scm ("left-edge"))
188         edge_idx = next_idx;
189
190       SCM entry = SCM_EOL;
191       if (scm_is_symbol (rsym))
192         entry = scm_assq (rsym, alist);
193
194       bool entry_found = scm_is_pair (entry);
195       if (!entry_found)
196         {
197           String sym_string;
198           if (scm_is_symbol (rsym))
199             sym_string = ly_symbol2string (rsym);
200
201           String orig_string;
202           if (unsmob_grob (l->get_property ("cause")))
203             orig_string = unsmob_grob (l->get_property ("cause"))->name ();
204
205           programming_error (_f ("No spacing entry from %s to `%s'",
206                                  orig_string.to_str0 (),
207                                  sym_string.to_str0 ()));
208         }
209
210       Real distance = 1.0;
211       SCM type = ly_symbol2scm ("extra-space");
212
213       if (entry_found)
214         {
215           entry = scm_cdr (entry);
216
217           distance = scm_to_double (scm_cdr (entry));
218           type = scm_car (entry);
219         }
220
221       if (r)
222         {
223           if (type == ly_symbol2scm ("extra-space"))
224             offsets[next_idx] = extents[idx][RIGHT] + distance
225               - extents[next_idx][LEFT];
226           /* should probably junk minimum-space */
227           else if (type == ly_symbol2scm ("minimum-space"))
228             offsets[next_idx] = extents[idx][RIGHT] >? distance;
229         }
230       else
231         {
232           extra_right_space = distance;
233         }
234
235       idx = next_idx;
236     }
237
238   Real here = 0.0;
239   Interval total_extent;
240
241   Real alignment_off = 0.0;
242   for (int i = 0; i < offsets.size (); i++)
243     {
244       here += offsets[i];
245       if (i == edge_idx)
246         alignment_off = -here;
247       total_extent.unite (extents[i] + here);
248     }
249
250   if (me->break_status_dir () == LEFT)
251     {
252       alignment_off = -total_extent[RIGHT] - extra_right_space;
253     }
254   else if (edge_idx < 0)
255     alignment_off = -total_extent[LEFT];
256
257   here = alignment_off;
258   for (int i = 0; i < offsets.size (); i++)
259     {
260       here += offsets[i];
261       elems[i]->translate_axis (here, X_AXIS);
262     }
263 }
264
265 ADD_INTERFACE (Break_aligned_interface, "break-aligned-interface",
266                "Items that are aligned in prefatory matter.\n"
267                "\n"
268                "The spacing of these items is controlled by the @code{space-alist}\n"
269                "property. It contains a list @code{break-align-symbol}s with a specification\n"
270                "of the associated space. The space specification can be "
271                "@table @code\n"
272                "@item (minimum-space . @var{spc}))\n"
273                "  Pad space until the distance is @var{spc}\n"
274                "@item (fixed-space . @var{spc})\n"
275                "  Set a fixed space\n"
276                "@item (semi-fixed-space . @var{spc})\n"
277                "  Set a space. Half of it is fixed and half is stretchable. \n"
278                "(does not work at start of line. fixme)\n"
279                "@item (extra-space . @var{spc})\n"
280                "  Add @var{spc} amount of space.\n"
281                "@end table\n"
282                "\n"
283                "Special keys for the alist are @code{first-note} and @code{next-note}, signifying\n"
284                "the first note on a line, and the next note halfway a line.\n"
285                "\n"
286                "Rules for this spacing are much more complicated than this. \n"
287                "See [Wanske] page 126 -- 134, [Ross] pg 143 -- 147\n",
288                "break-align-symbol space-alist");
289
290 ADD_INTERFACE (Break_align_interface, "break-alignment-interface",
291                "The object that performs break aligment. See @ref{break-aligned-interface}.",
292                "positioning-done break-align-orders");
293