]> git.donarmstrong.com Git - lilypond.git/blob - lily/axis-group-interface.cc
Run `make grand-replace'.
[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--2008 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 "lookup.hh"
18 #include "paper-column.hh"
19 #include "paper-score.hh"
20 #include "separation-item.hh"
21 #include "skyline-pair.hh"
22 #include "stencil.hh"
23 #include "system.hh"
24 #include "warn.hh"
25
26 void
27 Axis_group_interface::add_element (Grob *me, Grob *e)
28 {
29   SCM axes = me->get_property ("axes");
30   if (!scm_is_pair (axes))
31     programming_error ("axes should be nonempty");
32
33   for (SCM ax = axes; scm_is_pair (ax); ax = scm_cdr (ax))
34     {
35       Axis a = (Axis) scm_to_int (scm_car (ax));
36
37       if (!e->get_parent (a))
38         e->set_parent (me, a);
39
40       e->set_object ((a == X_AXIS)
41                      ? ly_symbol2scm ("axis-group-parent-X")
42                      : ly_symbol2scm ("axis-group-parent-Y"),
43                      me->self_scm ());
44     }
45
46   /* must be ordered, because Align_interface also uses
47      Axis_group_interface  */
48   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), e);
49 }
50
51 bool
52 Axis_group_interface::has_axis (Grob *me, Axis a)
53 {
54   SCM axes = me->get_property ("axes");
55
56   return (SCM_BOOL_F != scm_memq (scm_from_int (a), axes));
57 }
58
59 Interval
60 Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
61                                              Grob *common, Axis a)
62 {
63   Interval r;
64   for (vsize i = 0; i < elts.size (); i++)
65     {
66       Grob *se = elts[i];
67       if (!to_boolean (se->get_property ("cross-staff")))
68         {
69           Interval dims = se->extent (common, a);
70           if (!dims.is_empty ())
71             r.unite (dims);
72         }
73     }
74   return r;
75 }
76
77
78 /*
79   FIXME: pure extent handling has a lot of ad-hoc caching.
80   This should be done with grob property callbacks.
81
82   --hwn
83 */
84
85 Interval
86 Axis_group_interface::cached_pure_height (Grob *me, 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
92   SCM extents = me->get_property ("adjacent-pure-heights");
93
94   if (!scm_is_vector (extents))
95     return Interval (0, 0);
96
97   Interval ext;
98   for (vsize i = 0; i + 1 < breaks.size (); i++)
99     {
100       int r = Paper_column::get_rank (cols[breaks[i]]);
101       if (r >= end)
102         break;
103
104       if (r >= start)
105         ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
106     }
107
108   return ext;
109 }
110
111 MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
112 SCM
113 Axis_group_interface::adjacent_pure_heights (SCM smob)
114 {
115   Grob *me = unsmob_grob (smob);
116
117   Grob *common = calc_pure_elts_and_common (me);
118   extract_grob_set (me, "pure-relevant-items", items);
119   extract_grob_set (me, "pure-relevant-spanners", spanners);
120
121   Paper_score *ps = get_root_system (me)->paper_score ();
122   vector<vsize> breaks = ps->get_break_indices ();
123   vector<Grob*> cols = ps->get_columns ();
124
125   SCM ret = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
126   vsize it_index = 0;
127   for (vsize i = 0; i + 1 < breaks.size (); i++)
128     {
129       int start = Paper_column::get_rank (cols[breaks[i]]);
130       int end = Paper_column::get_rank (cols[breaks[i+1]]);
131       Interval iv;
132
133       for (vsize j = it_index; j < items.size (); j++)
134         {
135           Item *it = dynamic_cast<Item*> (items[j]);
136           int rank = it->get_column ()->get_rank ();
137
138           if (rank <= end && it->pure_is_visible (start, end)
139               && !to_boolean (it->get_property ("cross-staff")))
140             {
141               Interval dims = items[j]->pure_height (common, start, end);
142               if (!dims.is_empty ())
143                 iv.unite (dims);
144             }
145
146           if (rank < end)
147             it_index++;
148           else if (rank > end)
149             break;
150         }
151
152       for (vsize j = 0; j < spanners.size (); j++)
153         {
154           Interval_t<int> rank_span = spanners[j]->spanned_rank_interval ();
155           if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
156               && !to_boolean (spanners[j]->get_property ("cross-staff")))
157             {
158               Interval dims = spanners[j]->pure_height (common, start, end);
159               if (!dims.is_empty ())
160                 iv.unite (dims);
161             }
162         }
163
164       scm_vector_set_x (ret, scm_from_int (i), ly_interval2scm (iv));
165     }
166   return ret;
167 }
168
169 Interval
170 Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
171 {
172   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
173      (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
174      Unfortunately, it isn't always true, particularly if there is a
175      VerticalAlignment somewhere in the descendants.
176
177      Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
178      count, the only VerticalAlignment comes from Score. This makes it
179      reasonably safe to assume that if our parent is a VerticalAlignment,
180      we can assume additivity and cache things nicely. */
181   Grob *p = me->get_parent (Y_AXIS);
182   if (p && Align_interface::has_interface (p))
183     return Axis_group_interface::cached_pure_height (me, start, end);
184
185   Grob *common = calc_pure_elts_and_common (me);
186   extract_grob_set (me, "pure-relevant-items", items);
187   extract_grob_set (me, "pure-relevant-spanners", spanners);
188
189   Interval r;
190
191   for (vsize i = 0; i < items.size (); i++)
192     {
193       Item *it = dynamic_cast<Item*> (items[i]);
194       int rank = it->get_column ()->get_rank ();
195
196       if (rank > end)
197         break;
198       else if (rank >= start && it->pure_is_visible (start, end)
199                && !to_boolean (it->get_property ("cross-staff")))
200         {
201           Interval dims = it->pure_height (common, start, end);
202           if (!dims.is_empty ())
203             r.unite (dims);
204         }
205     }
206
207   for (vsize i = 0; i < spanners.size (); i++)
208     {
209       Interval_t<int> rank_span = spanners[i]->spanned_rank_interval ();
210       if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
211           && !to_boolean (spanners[i]->get_property ("cross-staff")))
212         {
213           Interval dims = spanners[i]->pure_height (common, start, end);
214           if (!dims.is_empty ())
215             r.unite (dims);
216         }
217     }
218   return r;
219 }
220
221 MAKE_SCHEME_CALLBACK (Axis_group_interface, width, 1);
222 SCM
223 Axis_group_interface::width (SCM smob)
224 {
225   Grob *me = unsmob_grob (smob);
226   return generic_group_extent (me, X_AXIS);
227 }
228
229 MAKE_SCHEME_CALLBACK (Axis_group_interface, height, 1);
230 SCM
231 Axis_group_interface::height (SCM smob)
232 {
233   Grob *me = unsmob_grob (smob);
234   return generic_group_extent (me, Y_AXIS);
235 }
236
237 MAKE_SCHEME_CALLBACK (Axis_group_interface, pure_height, 3);
238 SCM
239 Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
240 {
241   int start = robust_scm2int (start_scm, 0);
242   int end = robust_scm2int (end_scm, INT_MAX);
243   Grob *me = unsmob_grob (smob);
244
245   /* Maybe we are in the second pass of a two-pass spacing run. In that
246      case, the Y-extent of a system is already given to us */
247   System *system = dynamic_cast<System*> (me);
248   if (system)
249     {
250       SCM line_break_details = system->column (start)->get_property ("line-break-system-details");
251       SCM system_y_extent = scm_assq (ly_symbol2scm ("system-Y-extent"), line_break_details);
252       if (scm_is_pair (system_y_extent))
253         return scm_cdr (system_y_extent);
254     }
255
256   return ly_interval2scm (pure_group_height (me, start, end));
257 }
258
259 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
260 SCM
261 Axis_group_interface::calc_skylines (SCM smob)
262 {
263   Grob *me = unsmob_grob (smob);
264   extract_grob_set (me, "elements", elts);
265   Skyline_pair skylines = skyline_spacing (me, elts);
266
267   return skylines.smobbed_copy ();
268 }
269
270 /* whereas calc_skylines calculates skylines for axis-groups with a lot of
271    visible children, combine_skylines is designed for axis-groups whose only
272    children are other axis-groups (ie. VerticalAlignment). Rather than
273    calculating all the skylines from scratch, we just merge the skylines
274    of the children.
275 */
276 MAKE_SCHEME_CALLBACK (Axis_group_interface, combine_skylines, 1);
277 SCM
278 Axis_group_interface::combine_skylines (SCM smob)
279 {
280   Grob *me = unsmob_grob (smob);
281   extract_grob_set (me, "elements", elements);
282   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
283   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
284
285   if (y_common != me)
286     programming_error ("combining skylines that don't belong to me");
287
288   Skyline_pair ret;
289   for (vsize i = 0; i < elements.size (); i++)
290     {
291       SCM skyline_scm = elements[i]->get_property ("vertical-skylines");
292       if (Skyline_pair::unsmob (skyline_scm))
293         {
294           Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
295           Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
296           other.raise (offset);
297           other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
298           ret.merge (other);
299         }
300     }
301   return ret.smobbed_copy ();
302 }
303   
304 SCM
305 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
306 {
307   /* trigger the callback to do skyline-spacing on the children */
308   if (a == Y_AXIS)
309     (void) me->get_property ("vertical-skylines");
310
311   extract_grob_set (me, "elements", elts);
312   Grob *common = common_refpoint_of_array (elts, me, a);
313
314   Real my_coord = me->relative_coordinate (common, a);
315   Interval r (relative_group_extent (elts, common, a));
316
317   return ly_interval2scm (r - my_coord);
318 }
319
320 /* This is like generic_group_extent, but it only counts the grobs that
321    are children of some other axis-group. This is uncached; if it becomes
322    commonly used, it may be necessary to cache it somehow. */
323 Interval
324 Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
325 {
326   extract_grob_set (me, "elements", elts);
327   vector<Grob*> new_elts;
328
329   for (vsize i = 0; i < elts.size (); i++)
330     if (elts[i]->common_refpoint (staff, parent_a) == staff)
331       new_elts.push_back (elts[i]);
332
333   return relative_group_extent (new_elts, refp, ext_a);
334 }
335
336
337 Grob *
338 Axis_group_interface::calc_pure_elts_and_common (Grob *me)
339 {
340   if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
341     return c;
342   
343   extract_grob_set (me, "elements", elts);
344
345   vector<Grob*> relevant_items;
346   vector<Grob*> relevant_spanners;
347   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
348
349   for (vsize i = 0; i < elts.size (); i++)
350     {
351       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
352         {
353           if (dynamic_cast<Item*> (elts[i]))
354             relevant_items.push_back (elts[i]);
355           else if (dynamic_cast<Spanner*> (elts[i]))
356             relevant_spanners.push_back (elts[i]);
357         }
358             
359
360       Item *it = dynamic_cast<Item*> (elts[i]);
361       Direction d = LEFT;
362       if (it)
363         do
364           {
365             Item *piece = it->find_prebroken_piece (d);
366             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
367               relevant_items.push_back (piece);
368           }
369         while (flip (&d) != LEFT);
370     }
371   vector_sort (relevant_items, Item::less);
372
373   Grob *common = common_refpoint_of_array (relevant_items, me, Y_AXIS);
374   common = common_refpoint_of_array (relevant_spanners, common, Y_AXIS);
375
376   me->set_object ("pure-Y-common", common->self_scm ());
377   
378   SCM items_scm = Grob_array::make_array ();
379   SCM spanners_scm = Grob_array::make_array ();
380
381   unsmob_grob_array (items_scm)->set_array (relevant_items);
382   unsmob_grob_array (spanners_scm)->set_array (relevant_spanners);
383   me->set_object ("pure-relevant-items", items_scm);
384   me->set_object ("pure-relevant-spanners", spanners_scm);
385
386   return common;
387 }
388
389 SCM
390 Axis_group_interface::calc_common (Grob *me, Axis axis)
391 {
392   extract_grob_set (me, "elements", elts);
393   Grob *common = common_refpoint_of_array (elts, me, axis);
394   if (!common)
395     {
396       me->programming_error ("No common parent found in calc_common axis.");
397       return SCM_EOL;
398     }
399   
400   return common->self_scm ();
401 }
402
403
404 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
405 SCM
406 Axis_group_interface::calc_x_common (SCM grob)
407 {
408   return calc_common (unsmob_grob (grob), X_AXIS);
409 }
410
411 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
412 SCM
413 Axis_group_interface::calc_y_common (SCM grob)
414 {
415   return calc_common (unsmob_grob (grob), Y_AXIS);
416 }
417
418 Interval
419 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
420 {
421   Grob *common = calc_pure_elts_and_common (me);
422         
423   Real my_coord = me->relative_coordinate (common, Y_AXIS);
424   Interval r (relative_pure_height (me, start, end));
425
426   return r - my_coord;
427 }
428
429 void
430 Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
431 {
432   found->push_back (me);
433
434   if (!has_interface (me))
435     return;
436
437   extract_grob_set (me, "elements", elements);
438   for (vsize i = 0; i < elements.size (); i++)
439     {
440       Grob *e = elements[i];
441       Axis_group_interface::get_children (e, found);
442     }
443 }
444
445 bool
446 staff_priority_less (Grob * const &g1, Grob * const &g2)
447 {
448   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
449   Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
450
451   if (priority_1 < priority_2)
452     return true;
453   else if (priority_1 > priority_2)
454     return false;
455
456   /* if neither grob has an outside-staff priority, the ordering will have no
457      effect -- we just need to choose a consistent ordering. We do this to
458      avoid the side-effect of calculating extents. */
459   if (isinf (priority_1))
460     return g1 < g2;
461
462   /* if there is no preference in staff priority, choose the left-most one */
463   Grob *common = g1->common_refpoint (g2, X_AXIS);
464   Real start_1 = g1->extent (common, X_AXIS)[LEFT];
465   Real start_2 = g2->extent (common, X_AXIS)[LEFT];
466   return start_1 < start_2;
467 }
468
469 static void
470 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
471 {
472   /* if a child has skylines, use them instead of the extent box */
473   if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
474     {
475       Skyline_pair s = *pair;
476       s.shift (me->relative_coordinate (x_common, X_AXIS));
477       s.raise (me->relative_coordinate (y_common, Y_AXIS));
478       skylines->merge (s);
479     }
480   else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
481     {
482       for (vsize i = 0; i < elements->size (); i++)
483         add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
484     }
485   else if (!scm_is_number (me->get_property ("outside-staff-priority"))
486            && !to_boolean (me->get_property ("cross-staff")))
487     {
488       boxes->push_back (Box (me->extent (x_common, X_AXIS),
489                              me->extent (y_common, Y_AXIS)));
490     }
491 }
492
493 /* We want to avoid situations like this:
494            still more text
495       more text
496    text
497    -------------------
498    staff
499    -------------------
500
501    The point is that "still more text" should be positioned under
502    "more text".  In order to achieve this, we place the grobs in several
503    passes.  We keep track of the right-most horizontal position that has been
504    affected by the current pass so far (actually we keep track of 2
505    positions, one for above the staff, one for below).
506
507    In each pass, we loop through the unplaced grobs from left to right.
508    If the grob doesn't overlap the right-most affected position, we place it
509    (and then update the right-most affected position to point to the right
510    edge of the just-placed grob).  Otherwise, we skip it until the next pass.
511 */
512 static void
513 add_grobs_of_one_priority (Skyline_pair *const skylines,
514                            vector<Grob*> elements,
515                            Grob *x_common,
516                            Grob *y_common)
517 {
518   vector<Box> boxes;
519   Drul_array<Real> last_affected_position;
520
521   reverse (elements);
522   while (!elements.empty ())
523     {
524       last_affected_position[UP] = -infinity_f;
525       last_affected_position[DOWN] = -infinity_f;
526       /* do one pass */
527       for (vsize i = elements.size (); i--;)
528         {
529           Direction dir = get_grob_direction (elements[i]);
530           if (dir == CENTER)
531             {
532               warning (_ ("an outside-staff object should have a direction, defaulting to up"));
533               dir = UP;
534             }
535
536           Box b (elements[i]->extent (x_common, X_AXIS),
537                  elements[i]->extent (y_common, Y_AXIS));
538           SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
539           Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
540
541           if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
542             continue;
543
544           if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
545             {
546               boxes.clear ();
547               boxes.push_back (b);
548               Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
549               Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
550               Real dist = (*skylines)[dir].distance (other) + padding;
551
552               if (dist > 0)
553                 {
554                   b.translate (Offset (0, dir*dist));
555                   elements[i]->translate_axis (dir*dist, Y_AXIS);
556                 }
557               (*skylines)[dir].insert (b, 0, X_AXIS);
558               elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
559               last_affected_position[dir] = b[X_AXIS][RIGHT];
560             }
561
562           /*
563             Ugh: quadratic. --hwn
564            */
565           elements.erase (elements.begin () + i);
566         }
567     }
568 }
569
570 // TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
571 // For example, cross-staff beams cannot be formatted until the distance between
572 // staves is known and therefore any grobs that depend on the beam cannot be placed
573 // until the skylines are known. On the other hand, the distance between staves should
574 // really depend on position of the cross-staff grobs that lie between them.
575 // Currently, we just leave cross-staff grobs out of the
576 // skyline altogether, but this could mean that staves are placed so close together
577 // that there is no room for the cross-staff grob. It also means, of course, that
578 // we don't get the benefits of skyline placement for cross-staff grobs.
579 Skyline_pair
580 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
581 {
582   /* For grobs with an outside-staff-priority, the sorting function might
583      call extent and cause suicide. This breaks the contract that is required
584      for the STL sort function. To avoid this, we make sure that any suicides
585      are triggered beforehand.
586   */
587   for (vsize i = 0; i < elements.size (); i++)
588     if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
589       elements[i]->extent (elements[i], X_AXIS);
590
591   vector_sort (elements, staff_priority_less);
592   Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
593   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
594
595   assert (y_common == me);
596
597   vsize i = 0;
598   vector<Box> boxes;
599
600   Skyline_pair skylines;
601   for (i = 0; i < elements.size ()
602          && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
603     if (!to_boolean (elements[i]->get_property ("cross-staff")))
604       add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
605
606   SCM padding_scm = me->get_property ("skyline-horizontal-padding");
607   Real padding = robust_scm2double (padding_scm, 0.1);
608   skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
609   for (; i < elements.size (); i++)
610     {
611       if (to_boolean (elements[i]->get_property ("cross-staff")))
612         continue;
613
614       SCM priority = elements[i]->get_property ("outside-staff-priority");
615       vector<Grob*> current_elts;
616       current_elts.push_back (elements[i]);
617       while (i + 1 < elements.size () 
618              && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
619         current_elts.push_back (elements[++i]);
620
621       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
622     }
623   skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
624   return skylines;
625 }
626
627 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
628 SCM
629 Axis_group_interface::calc_max_stretch (SCM smob)
630 {
631   Grob *me = unsmob_grob (smob);
632   Real ret = 0;
633   extract_grob_set (me, "elements", elts);
634
635   for (vsize i = 0; i < elts.size (); i++)
636     if (Axis_group_interface::has_interface (elts[i]))
637       ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
638
639   return scm_from_double (ret);
640 }
641
642 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
643 SCM
644 Axis_group_interface::print (SCM smob)
645 {
646   if (!debug_skylines)
647     return SCM_BOOL_F;
648
649   Grob *me = unsmob_grob (smob);
650   Stencil ret;
651   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
652     {
653       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS))
654                        .in_color (255, 0, 255));
655       ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS))
656                        .in_color (0, 255, 255));
657     }
658   return ret.smobbed_copy ();
659 }
660
661 ADD_INTERFACE (Axis_group_interface,
662                "An object that groups other layout objects.",
663
664                /* properties */
665                "X-common "
666                "Y-common "
667                "adjacent-pure-heights "
668                "axes "
669                "elements "
670                "keep-fixed-while-stretching "
671                "max-stretch "
672                "no-alignment "
673                "pure-Y-common "
674                "pure-relevant-items "
675                "pure-relevant-spanners "
676                "vertical-skylines "
677                );