]> git.donarmstrong.com Git - lilypond.git/blob - lily/page-layout-problem.cc
8b0be3a5f704205e3e1001832be1f09118624596
[lilypond.git] / lily / page-layout-problem.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2009--2012 Joe Neeman <joeneeman@gmail.com>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "page-layout-problem.hh"
21
22 #include "align-interface.hh"
23 #include "axis-group-interface.hh"
24 #include "hara-kiri-group-spanner.hh"
25 #include "international.hh"
26 #include "item.hh"
27 #include "output-def.hh"
28 #include "paper-book.hh"
29 #include "paper-column.hh"
30 #include "paper-score.hh"
31 #include "pointer-group-interface.hh"
32 #include "prob.hh"
33 #include "skyline-pair.hh"
34 #include "system.hh"
35 #include "text-interface.hh"
36
37 /*
38  Returns the number of footntoes associated with a given line.
39 */
40
41 vector<Grob *>
42 Page_layout_problem::get_footnote_grobs (SCM lines)
43 {
44   vector<Grob *> footnotes;
45   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
46     {
47       if (Grob *g = unsmob_grob (scm_car (s)))
48         {
49           System *sys = dynamic_cast<System *> (g);
50           if (!sys)
51             {
52               programming_error ("got a grob for footnotes that wasn't a System");
53               continue;
54             }
55           extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
56           footnotes.insert (footnotes.end (), footnote_grobs.begin (), footnote_grobs.end ());
57         }
58       else if (Prob *p = unsmob_prob (scm_car (s)))
59         {
60           SCM stencils = p->get_property ("footnotes");
61           if (stencils == SCM_EOL)
62             continue;
63           for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
64             footnotes.push_back (0);
65         }
66     }
67
68   return footnotes;
69 }
70
71 vsize
72 Page_layout_problem::get_footnote_count (SCM lines)
73 {
74   vector<Grob *> notes = get_footnote_grobs (lines);
75   return notes.size ();
76 }
77
78 SCM
79 Page_layout_problem::get_footnotes_from_lines (SCM lines)
80 {
81   if (!scm_is_pair (lines))
82     return SCM_EOL;
83
84   bool footnotes_added;
85   if (Grob *g = unsmob_grob (scm_car (lines)))
86     footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
87   else if (Prob *p = unsmob_prob (scm_car (lines)))
88     footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
89   else
90     {
91       programming_error ("Systems on a page must be a prob or grob.");
92       return SCM_EOL;
93     }
94   if (!footnotes_added)
95     {
96       programming_error ("Footnotes must be added to lines before they are retrieved.");
97       return SCM_EOL;
98     }
99
100   SCM out = SCM_EOL;
101   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
102     {
103       if (Grob *g = unsmob_grob (scm_car (s)))
104         out = scm_cons (g->get_property ("footnote-stencil"), out);
105       else if (Prob *p = unsmob_prob (scm_car (s)))
106         out = scm_cons (p->get_property ("footnote-stencil"), out);
107       else
108         programming_error ("Systems on a page must be a prob or grob.");
109     }
110
111   return scm_reverse_x (out, SCM_EOL);
112 }
113
114 /*
115    Adds a footnote stencil to each system.  This stencil may
116    itself be comprised of several footnotes.
117
118    This is a long function, but it seems better to keep it intact rather than
119    splitting it into parts.
120 */
121
122 void
123 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
124 {
125   /*
126     first, we have to see how many footnotes are on this page.
127     we need to do this first so that we can line them up
128   */
129
130   Output_def *paper = pb->paper_;
131
132   if (!paper)
133     {
134       programming_error ("Cannot get footnotes because there is no valid paper block.");
135       return;
136     }
137
138   SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
139   if (!scm_is_pair (number_footnote_table))
140     number_footnote_table = SCM_EOL;
141   SCM numbering_function = paper->c_variable ("footnote-numbering-function");
142   SCM layout = paper->self_scm ();
143   SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
144                           paper->self_scm ());
145   Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
146   Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
147
148   vector<Grob *> fn_grobs = get_footnote_grobs (lines);
149   vsize fn_count = fn_grobs.size ();
150
151   // now, make the footnote stencils with the numbering function
152   SCM numbers = SCM_EOL;
153   SCM in_text_numbers = SCM_EOL;
154   /*
155     TODO: This recalculates numbering every time this function is called, including once
156     after the balloon prints are called.  Although it is not a huge computational drain,
157     it'd be more elegant to turn this calculation off when it is no longer needed.
158
159     In a separate commit, it'd be nice to streamline the way that page layout property
160     is handled so that the process of building `config's in page-breaking does result
161     in duplicated work, either by making this process less complicated or (preferably)
162     by passing its results downstream.
163   */
164
165   // find the maximum X_AXIS length
166   Real max_length = -infinity_f;
167
168   for (vsize i = 0; i < fn_count; i++)
169     {
170       if (fn_grobs[i])
171         {
172           SCM assertion_function = fn_grobs[i]->get_property ("numbering-assertion-function");
173           if (ly_is_procedure (assertion_function))
174             (void) scm_call_1 (assertion_function, scm_from_int (counter));
175         }
176       SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
177       SCM stencil = Text_interface::interpret_markup (layout, props, markup);
178       Stencil *st = unsmob_stencil (stencil);
179       if (!st)
180         {
181           programming_error ("Your numbering function needs to return a stencil.");
182           markup = SCM_EOL;
183           stencil = Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL).smobbed_copy ();
184           st = unsmob_stencil (stencil);
185         }
186       in_text_numbers = scm_cons (markup, in_text_numbers);
187       numbers = scm_cons (stencil, numbers);
188
189       if (!st->extent (X_AXIS).is_empty ())
190         max_length = max (max_length, st->extent (X_AXIS)[RIGHT]);
191
192       counter++;
193     }
194
195   in_text_numbers = scm_reverse_x (in_text_numbers, SCM_EOL);
196   numbers = scm_reverse_x (numbers, SCM_EOL);
197
198   /*
199     translate each stencil such that it attains the correct maximum length and bundle the
200     footnotes into a scheme object.
201   */
202
203   for (SCM p = numbers; scm_is_pair (p); p = scm_cdr (p))
204     {
205       Stencil *st = unsmob_stencil (scm_car (p));
206       if (!st->extent (X_AXIS).is_empty ())
207         st->translate_axis ((max_length - st->extent (X_AXIS)[RIGHT]),
208                             X_AXIS);
209     }
210
211   // build the footnotes
212
213   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
214     {
215       // Take care of musical systems.
216       if (Grob *g = unsmob_grob (scm_car (s)))
217         {
218           System *sys = dynamic_cast<System *> (g);
219           if (!sys)
220             {
221               programming_error ("got a grob for footnotes that wasn't a System");
222               continue;
223             }
224           Stencil mol;
225           Stencil in_note_mol;
226           extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
227           for (vsize i = 0; i < footnote_grobs.size (); i++)
228             {
229               Grob *footnote = footnote_grobs[i];
230               SCM footnote_markup = footnote->get_property ("footnote-text");
231               if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
232                 if (orig->is_broken ())
233                   footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
234
235               SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
236                                       paper->self_scm ());
237
238               SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
239                                                                    props, footnote_markup);
240
241               Stencil footnote_stencil = *unsmob_stencil (footnote_stl);
242               bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
243               if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
244                 {
245                   if (orig->is_broken ())
246                     for (vsize i = 0; i < orig->broken_intos_.size (); i++)
247                       do_numbering = do_numbering
248                                      || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
249                 }
250               if (do_numbering)
251                 {
252                   SCM annotation_scm = scm_car (in_text_numbers);
253                   footnote->set_property ("text", annotation_scm);
254                   if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
255                     {
256                       orig->set_property ("text", annotation_scm);
257                       if (orig->is_broken ())
258                         for (vsize i = 0; i < orig->broken_intos_.size (); i++)
259                           orig->broken_intos_[i]->set_property ("text", annotation_scm);
260                     }
261
262                   Stencil annotation = *unsmob_stencil (scm_car (numbers));
263                   annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
264                                               + number_raise
265                                               - annotation.extent (Y_AXIS)[UP]),
266                                              Y_AXIS);
267                   footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
268                   numbers = scm_cdr (numbers);
269                   in_text_numbers = scm_cdr (in_text_numbers);
270                 }
271               if (!footnote_stencil.is_empty ())
272                 {
273                   if (to_boolean (footnote->get_property ("footnote")))
274                     mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
275                   else
276                     in_note_mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
277                 }
278             }
279           sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
280           sys->set_property ("footnote-stencil", mol.smobbed_copy ());
281         }
282       // Take care of top-level markups
283       else if (Prob *p = unsmob_prob (scm_car (s)))
284         {
285           SCM stencils = p->get_property ("footnotes");
286           Stencil mol;
287
288           for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
289             {
290               Stencil footnote_stencil = *unsmob_stencil (scm_caddar (st));
291               bool do_numbering = to_boolean (scm_cadar (st));
292               SCM in_text_stencil = Stencil ().smobbed_copy ();
293               if (do_numbering)
294                 {
295                   Stencil annotation = *unsmob_stencil (scm_car (numbers));
296                   SCM in_text_annotation = scm_car (in_text_numbers);
297                   in_text_stencil = Text_interface::interpret_markup (layout,
298                                                                       props,
299                                                                       in_text_annotation);
300                   if (!unsmob_stencil (in_text_stencil))
301                     in_text_stencil = SCM_EOL;
302                   annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
303                                               + number_raise
304                                               - annotation.extent (Y_AXIS)[UP]),
305                                              Y_AXIS);
306                   footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
307                   numbers = scm_cdr (numbers);
308                   in_text_numbers = scm_cdr (in_text_numbers);
309                 }
310               number_footnote_table = scm_cons (scm_cons (scm_caar (st),
311                                                           in_text_stencil),
312                                                 number_footnote_table);
313               if (!footnote_stencil.is_empty ())
314                 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
315             }
316           p->set_property ("footnote-stencil", mol.smobbed_copy ());
317         }
318     }
319
320   // note that this line of code doesn't do anything if numbering isn't turned on
321   pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
322 }
323
324 Stencil
325 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
326 {
327   SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
328                           paper->self_scm ());
329
330   SCM markup = paper->c_variable ("footnote-separator-markup");
331
332   if (!Text_interface::is_markup (markup))
333     return Stencil ();
334
335   SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
336                                                            props, markup);
337
338   Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
339
340   return footnote_separator ? *footnote_separator : Stencil ();
341 }
342
343 Stencil
344 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil foot, Paper_book *pb)
345 {
346
347   bool footnotes_found = false;
348   Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
349   Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
350
351   footnotes = scm_reverse (footnotes);
352
353   for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
354     {
355       Stencil *stencil = unsmob_stencil (scm_car (s));
356
357       if (!stencil)
358         continue;
359
360       if (!stencil->is_empty ())
361         {
362           foot.add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
363           footnotes_found = true;
364         }
365     }
366
367   if (footnotes_found)
368     {
369       Stencil separator = get_footnote_separator_stencil (pb->paper_);
370       if (!separator.is_empty ())
371         foot.add_at_edge (Y_AXIS, UP, separator, footnote_padding);
372     }
373
374   return foot;
375 }
376
377 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
378   : bottom_skyline_ (DOWN)
379 {
380   Prob *page = unsmob_prob (page_scm);
381   bottom_loose_baseline_ = 0;
382   header_height_ = 0;
383   footer_height_ = 0;
384   header_padding_ = 0;
385   footer_padding_ = 0;
386   page_height_ = 100;
387   force_ = 0;
388
389   if (page)
390     {
391       Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
392       Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
393
394       Stencil foot_stencil = foot ? *foot : Stencil ();
395
396       if (pb && pb->paper_)
397         {
398           SCM footnotes = get_footnotes_from_lines (systems);
399           foot_stencil = add_footnotes_to_footer (footnotes, foot_stencil, pb);
400         }
401       else
402         warning (_ ("A page layout problem has been initiated that cannot accommodate footnotes."));
403
404       header_height_ = head ? head->extent (Y_AXIS).length () : 0;
405       footer_height_ = foot_stencil.extent (Y_AXIS).length ();
406       page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
407     }
408
409   // Initially, bottom_skyline_ represents the top of the page. Make
410   // it solid, so that the top of the first system will be forced
411   // below the top of the printable area.
412   bottom_skyline_.set_minimum_height (-header_height_);
413
414   SCM system_system_spacing = SCM_EOL;
415   SCM score_system_spacing = SCM_EOL;
416   SCM markup_system_spacing = SCM_EOL;
417   SCM score_markup_spacing = SCM_EOL;
418   SCM markup_markup_spacing = SCM_EOL;
419
420   // top_system_spacing controls the spring from the top of the printable
421   // area to the first staff. It allows the user to control the offset of
422   // the first staff (as opposed to the top of the first system) from the
423   // top of the page. Similarly for last_bottom_spacing.
424   SCM top_system_spacing = SCM_EOL;
425   SCM last_bottom_spacing = SCM_EOL;
426   if (pb && pb->paper_)
427     {
428       Output_def *paper = pb->paper_;
429       system_system_spacing = paper->c_variable ("system-system-spacing");
430       score_system_spacing = paper->c_variable ("score-system-spacing");
431       markup_system_spacing = paper->c_variable ("markup-system-spacing");
432       score_markup_spacing = paper->c_variable ("score-markup-spacing");
433       markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
434       last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
435       top_system_spacing = paper->c_variable ("top-system-spacing");
436       if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
437         top_system_spacing = paper->c_variable ("top-markup-spacing");
438
439       // Note: the page height here does _not_ reserve space for headers and
440       // footers. This is because we want to anchor the top-system-spacing
441       // spring at the _top_ of the header.
442       page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
443                       + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
444
445       read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
446       read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
447       in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
448       in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
449     }
450   bool last_system_was_title = false;
451
452   for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
453     {
454       bool first = (s == systems);
455
456       if (Grob *g = unsmob_grob (scm_car (s)))
457         {
458           System *sys = dynamic_cast<System *> (g);
459           if (!sys)
460             {
461               programming_error ("got a grob for vertical spacing that wasn't a System");
462               continue;
463             }
464
465           SCM spec = system_system_spacing;
466           if (first)
467             spec = top_system_spacing;
468           else if (last_system_was_title)
469             spec = markup_system_spacing;
470           else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
471             spec = score_system_spacing;
472
473           Spring spring (0, 0);
474           Real padding = 0.0;
475           Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
476           alter_spring_from_spacing_spec (spec, &spring);
477           read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
478
479           append_system (sys, spring, indent, padding);
480           last_system_was_title = false;
481         }
482       else if (Prob *p = unsmob_prob (scm_car (s)))
483         {
484           SCM spec = first ? top_system_spacing
485                      : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
486           Spring spring (0, 0);
487           Real padding = 0.0;
488           alter_spring_from_spacing_spec (spec, &spring);
489           read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
490
491           append_prob (p, spring, padding);
492           last_system_was_title = true;
493         }
494       else
495         programming_error ("got a system that was neither a Grob nor a Prob");
496     }
497
498   Spring last_spring (0, 0);
499   Real last_padding = 0;
500   alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
501   read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
502   last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
503   springs_.push_back (last_spring);
504
505   if (elements_.size ())
506     {
507       Real bottom_padding = 0;
508
509       // TODO: junk bottom-space now that we have last-bottom-spacing?
510       // bottom-space has the flexibility that one can do it per-system.
511       // NOTE: bottom-space is misnamed since it is not stretchable space.
512       if (Prob *p = elements_.back ().prob)
513         bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
514       else if (elements_.back ().staves.size ())
515         {
516           SCM details = get_details (elements_.back ());
517           bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
518                                                             details,
519                                                             SCM_BOOL_F),
520                                               0.0);
521         }
522       page_height_ -= bottom_padding;
523     }
524 }
525
526 void
527 Page_layout_problem::set_header_height (Real height)
528 {
529   header_height_ = height;
530 }
531
532 void
533 Page_layout_problem::set_footer_height (Real height)
534 {
535   footer_height_ = height;
536 }
537
538 void
539 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
540 {
541   Grob *align = unsmob_grob (sys->get_object ("vertical-alignment"));
542   if (!align)
543     return;
544
545   align->set_property ("positioning-done", SCM_BOOL_T);
546
547   extract_grob_set (align, "elements", all_elts);
548   vector<Grob *> elts = filter_dead_elements (all_elts);
549   vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
550   vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
551
552   Skyline up_skyline (UP);
553   Skyline down_skyline (DOWN);
554   build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
555   up_skyline.shift (indent);
556   down_skyline.shift (indent);
557   Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
558
559   if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
560     {
561       sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
562       sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
563       Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
564       sky->set_minimum_height (sky->max_height ()
565                                + in_note_direction_
566                                * (in_note_padding_
567                                   + in_note_stencil->extent (Y_AXIS).length ()));
568     }
569
570   /*
571     We need to call distance with skyline-horizontal-padding because
572     the system skyline-horizontal-padding is not added during the creation
573     of an individual staff.  So we add the padding for the distance check
574     at the time of adding in the system.
575   */
576   Real minimum_distance = up_skyline.distance (bottom_skyline_,
577                                                robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
578                                                    0))
579                           + padding;
580
581   Spring spring_copy = spring;
582   spring_copy.ensure_min_distance (minimum_distance);
583   springs_.push_back (spring_copy);
584
585   if (elts.size () && !is_spaceable (elts[0]))
586     {
587       // store the minimum distance, considering relative indents,
588       // for a loose line
589       Skyline first_skyline (UP);
590       Skyline_pair *sky = Skyline_pair::unsmob (elts[0]->get_property ("vertical-skylines"));
591       if (sky)
592         first_skyline.merge ((*sky)[UP]);
593       first_skyline.shift (indent);
594       minimum_distance = first_skyline.distance (bottom_skyline_) - bottom_loose_baseline_;
595     }
596   bottom_skyline_ = down_skyline;
597   elements_.push_back (Element (elts, minimum_offsets, minimum_distance, padding));
598
599   // Add the springs for the VerticalAxisGroups in this system.
600
601   // If the user has specified the offsets of the individual staves, fix the
602   // springs at the given distances. Otherwise, use stretchable springs.
603   SCM details = get_details (elements_.back ());
604   SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
605   vsize last_spaceable_staff = 0;
606   bool found_spaceable_staff = false;
607   for (vsize i = 0; i < elts.size (); ++i)
608     {
609       if (is_spaceable (elts[i]))
610         {
611           if (!found_spaceable_staff)
612             {
613               // Ensure space for any loose lines above this system
614               if (i > 0)
615                 springs_.back ().ensure_min_distance (bottom_loose_baseline_
616                                                       - minimum_offsets_with_min_dist[i]
617                                                       + padding);
618               found_spaceable_staff = true;
619               last_spaceable_staff = i;
620               // We don't add a spring for the first staff, since
621               // we are only adding springs _between_ staves here.
622               continue;
623             }
624
625           Spring spring (0.5, 0.0);
626           SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
627           alter_spring_from_spacing_spec (spec, &spring);
628
629           springs_.push_back (spring);
630           Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
631           springs_.back ().ensure_min_distance (min_distance);
632
633           if (scm_is_pair (manual_dists))
634             {
635               if (scm_is_number (scm_car (manual_dists)))
636                 {
637                   Real dy = scm_to_double (scm_car (manual_dists));
638
639                   springs_.back ().set_distance (dy);
640                   springs_.back ().set_min_distance (dy);
641                   springs_.back ().set_inverse_stretch_strength (0);
642                 }
643               manual_dists = scm_cdr (manual_dists);
644             }
645           last_spaceable_staff = i;
646         }
647     }
648
649   bottom_loose_baseline_ = found_spaceable_staff
650                            ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
651                                - minimum_offsets_with_min_dist.back ())
652                            : 0;
653
654   // Corner case: there was only one staff, and it wasn't spaceable.
655   if (!found_spaceable_staff && elts.size ())
656     mark_as_spaceable (elts[0]);
657 }
658
659 void
660 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
661 {
662   Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
663   Real minimum_distance = 0;
664   bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
665
666   if (sky)
667     {
668       minimum_distance = max ((*sky)[UP].distance (bottom_skyline_),
669                               bottom_loose_baseline_);
670       bottom_skyline_ = (*sky)[DOWN];
671     }
672   else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
673     {
674       Interval iv = sten->extent (Y_AXIS);
675       minimum_distance = iv[UP] - bottom_skyline_.max_height ();
676
677       bottom_skyline_.clear ();
678       bottom_skyline_.set_minimum_height (iv[DOWN]);
679     }
680   bottom_loose_baseline_ = 0.0;
681
682   Spring spring_copy = spring;
683   if (tight_spacing)
684     {
685       spring_copy.set_min_distance (minimum_distance);
686       spring_copy.set_inverse_stretch_strength (0.0);
687       spring_copy.set_distance (0.0);
688     }
689   else
690     spring_copy.ensure_min_distance (minimum_distance + padding);
691
692   springs_.push_back (spring_copy);
693   elements_.push_back (Element (prob, padding));
694 }
695
696 /**
697    For ragged-last pages, we usually want to stretch the page so that it
698    is not much more compressed than the previous page.  Here, if ragged is
699    true and you pass a value of fixed_force that !isinf, then I will try
700    to space this page using the given force.  If it does not fit, I will
701    resort to just filling the page (non-raggedly).
702 */
703 void
704 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
705 {
706   Simple_spacer spacer;
707
708   for (vsize i = 0; i < springs_.size (); ++i)
709     spacer.add_spring (springs_[i]);
710
711   if (ragged && !isinf (fixed_force))
712     {
713       // We need to tell the spacer it isn't ragged.  Otherwise, it will
714       // refuse to stretch.
715       spacer.solve (page_height_, false);
716
717       if (spacer.configuration_length (fixed_force) <= page_height_)
718         spacer.set_force (fixed_force);
719     }
720   else
721     spacer.solve (page_height_, ragged);
722
723   solution_ = spacer.spring_positions ();
724   force_ = spacer.force ();
725
726   if (!spacer.fits ())
727     {
728       Real overflow = spacer.configuration_length (spacer.force ())
729                       - page_height_;
730       if (ragged && overflow < 1e-6)
731         warning (_ ("ragged-bottom was specified, but page must be compressed"));
732       else
733         {
734           warning (_f ("compressing over-full page by %.1f staff-spaces",
735                        overflow));
736           force_ = -infinity_f;
737           vsize space_count = solution_.size ();
738           Real spacing_increment = overflow / (space_count - 2);
739           for (vsize i = 2; i < space_count; i++)
740             solution_[i] -= (i - 1) * spacing_increment;
741         }
742     }
743 }
744
745 Real
746 Page_layout_problem::force () const
747 {
748   return force_;
749 }
750
751 // The solution_ vector stores the position of every live VerticalAxisGroup
752 // and every title. From that information,
753 // 1) within each system, stretch the staves so they land at the right position
754 // 2) find the offset of each system (relative to the printable area of the page).
755 // TODO: this function is getting too long, maybe split it up?
756 SCM
757 Page_layout_problem::find_system_offsets ()
758 {
759   SCM system_offsets = SCM_EOL;
760   SCM *tail = &system_offsets;
761
762   // spring_idx 0 is the top of the page. Interesting values start from 1.
763   vsize spring_idx = 1;
764   vector<Grob *> loose_lines;
765   vector<Real> loose_line_min_distances;
766   Grob *last_spaceable_line = 0;
767   Real last_spaceable_line_translation = 0;
768   Interval last_title_extent;
769   for (vsize i = 0; i < elements_.size (); ++i)
770     {
771       if (elements_[i].prob)
772         {
773           *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
774           tail = SCM_CDRLOC (*tail);
775           Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
776
777           // Lay out any non-spaceable lines between this line and
778           // the last one.
779           if (loose_lines.size ())
780             {
781               Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
782               Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
783                                    + elements_[i].padding);
784
785               loose_line_min_distances.push_back (min_distance);
786               loose_lines.push_back (0);
787
788               distribute_loose_lines (loose_lines, loose_line_min_distances,
789                                       last_spaceable_line_translation, -solution_[spring_idx]);
790               loose_lines.clear ();
791               loose_line_min_distances.clear ();
792             }
793
794           last_spaceable_line = 0;
795           last_spaceable_line_translation = -solution_[spring_idx];
796           last_title_extent = prob_extent;
797           spring_idx++;
798         }
799       else
800         {
801           // Getting this signs right here is a little tricky. The configuration
802           // we return has zero at the top of the page and positive numbers further
803           // down, as does the solution_ vector.  Within a staff, however, positive
804           // numbers are up.
805           // TODO: perhaps change the way the page 'configuration variable works so
806           // that it is consistent with the usual up/down sign conventions in
807           // Lilypond. Then this would be less confusing.
808
809           // These two positions are relative to the page (with positive numbers being
810           // down).
811           Real first_staff_position = solution_[spring_idx];
812           Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
813           Real system_position = first_staff_position + first_staff_min_translation;
814
815           // Position the staves within this system.
816           vector<Real> const &min_offsets = elements_[i].min_offsets;
817           bool found_spaceable_staff = false;
818           for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
819             {
820               Grob *staff = elements_[i].staves[staff_idx];
821               staff->set_property ("system-Y-offset", scm_from_double (-system_position));
822
823               if (is_spaceable (staff))
824                 {
825                   // this is relative to the system: negative numbers are down.
826                   staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
827
828                   // Lay out any non-spaceable lines between this line and
829                   // the last one.
830                   if (loose_lines.size ())
831                     {
832                       if (staff_idx)
833                         loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
834                       else
835                         {
836                           // A null line to break any staff-affinity from the previous system
837                           loose_line_min_distances.push_back (0.0);
838                           loose_lines.push_back (0);
839                           loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
840                         }
841                       loose_lines.push_back (staff);
842
843                       distribute_loose_lines (loose_lines, loose_line_min_distances,
844                                               last_spaceable_line_translation, -solution_[spring_idx]);
845                       loose_lines.clear ();
846                       loose_line_min_distances.clear ();
847                     }
848                   last_spaceable_line = staff;
849                   last_spaceable_line_translation = -solution_[spring_idx];
850                   found_spaceable_staff = true;
851                   spring_idx++;
852                 }
853               else // ! is_spaceable
854                 {
855                   if (staff->extent (staff, Y_AXIS).is_empty ())
856                     continue;
857
858                   if (loose_lines.empty ())
859                     loose_lines.push_back (last_spaceable_line);
860
861                   if (staff_idx)
862                     // NOTE: the way we do distances between loose lines (and other lines too, actually)
863                     // is not the most accurate way possible: we only insert rods between adjacent
864                     // lines.  To be more accurate, we could insert rods between non-adjacent lines
865                     // using a scheme similar to the one in set_column_rods.
866                     loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
867                   else
868                     {
869                       // this is the first line in a system
870                       Real min_dist = 0;
871                       if (loose_lines.back ())
872                         {
873                           // distance to the final line in the preceding system,
874                           // including 'system-system-spacing 'padding
875                           min_dist = elements_[i].min_distance + elements_[i].padding;
876                           // A null line to break any staff-affinity for the previous system
877                           loose_line_min_distances.push_back (0.0);
878                           loose_lines.push_back (0);
879                         }
880                       else if (!last_title_extent.is_empty ())
881                         // distance to the preceding title,
882                         //  including 'markup-system-wg 'padding
883                         min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
884                                     + elements_[i].padding);
885                       else // distance to the top margin
886                         min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
887
888                       loose_line_min_distances.push_back (min_dist);
889                     }
890                   loose_lines.push_back (staff);
891                 }
892             }
893
894           // Corner case: even if a system has no live staves, it still takes up
895           // one spring (a system with one live staff also takes up one spring),
896           // which we need to increment past.
897           if (!found_spaceable_staff)
898             spring_idx++;
899
900           *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
901           tail = SCM_CDRLOC (*tail);
902         }
903     }
904
905   if (loose_lines.size ())
906     {
907       Grob *last = loose_lines.back ();
908       Interval last_ext = last->extent (last, Y_AXIS);
909       loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
910       loose_lines.push_back (0);
911
912       distribute_loose_lines (loose_lines, loose_line_min_distances,
913                               last_spaceable_line_translation, -page_height_);
914
915     }
916
917   assert (spring_idx == solution_.size () - 1);
918   return system_offsets;
919 }
920
921 // Given two lines that are already spaced (the first and last
922 // elements of loose_lines), distribute some unspaced lines between
923 // them.
924 // first_translation and last_translation are relative to the page.
925 void
926 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
927                                              vector<Real> const &min_distances,
928                                              Real first_translation, Real last_translation)
929 {
930   Simple_spacer spacer;
931   for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
932     {
933       SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
934       Spring spring (1.0, 0.0);
935       alter_spring_from_spacing_spec (spec, &spring);
936       spring.ensure_min_distance (min_distances[i]);
937       spacer.add_spring (spring);
938     }
939
940   // Remember: offsets are decreasing, since we're going from UP to DOWN!
941   spacer.solve (first_translation - last_translation, false);
942
943   vector<Real> solution = spacer.spring_positions ();
944   for (vsize i = 1; i + 1 < solution.size (); ++i)
945     if (loose_lines[i])
946       {
947         Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
948         loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
949       }
950 }
951
952 SCM
953 Page_layout_problem::fixed_force_solution (Real force)
954 {
955   solve_rod_spring_problem (true, force);
956   return find_system_offsets ();
957 }
958
959 SCM
960 Page_layout_problem::solution (bool ragged)
961 {
962   solve_rod_spring_problem (ragged, -infinity_f);
963   return find_system_offsets ();
964 }
965
966 // Build upper and lower skylines for a system. We don't yet know the positions
967 // of the staves within the system, so we make the skyline as conservative as
968 // possible. That is, for the upper skyline, we pretend that all of the staves
969 // in the system are packed together close to the top system; for the lower
970 // skyline, we pretend that all of the staves are packed together close to
971 // the bottom system.
972 //
973 // The upper skyline is relative to the top staff; the lower skyline is relative to
974 // the bottom staff.
975 void
976 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
977                                            vector<Real> const &minimum_translations,
978                                            Skyline *up,
979                                            Skyline *down)
980 {
981   if (minimum_translations.empty ())
982     return;
983
984   assert (staves.size () == minimum_translations.size ());
985   Real first_translation = minimum_translations[0];
986   Real last_spaceable_dy = 0;
987   Real first_spaceable_dy = 0;
988   bool found_spaceable_staff = false;
989
990   for (vsize i = 0; i < staves.size (); ++i)
991     {
992       Real dy = minimum_translations[i] - first_translation;
993       Grob *g = staves[i];
994       Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
995       if (sky)
996         {
997           up->raise (-dy);
998           up->merge ((*sky)[UP]);
999           up->raise (dy);
1000
1001           down->raise (-dy);
1002           down->merge ((*sky)[DOWN]);
1003           down->raise (dy);
1004         }
1005       if (is_spaceable (staves[i]))
1006         {
1007           if (!found_spaceable_staff)
1008             {
1009               found_spaceable_staff = true;
1010               first_spaceable_dy = dy;
1011             }
1012           last_spaceable_dy = dy;
1013         }
1014     }
1015
1016   // Leave the up skyline at a position relative
1017   // to the top spaceable staff.
1018   up->raise (-first_spaceable_dy);
1019
1020   // Leave the down skyline at a position
1021   // relative to the bottom spaceable staff.
1022   down->raise (-last_spaceable_dy);
1023 }
1024
1025 Interval
1026 Page_layout_problem::prob_extent (Prob *p)
1027 {
1028   Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
1029   return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1030 }
1031
1032 Interval
1033 Page_layout_problem::first_staff_extent (Element const &e)
1034 {
1035   if (e.prob)
1036     return prob_extent (e.prob);
1037   else if (e.staves.size ())
1038     return e.staves[0]->extent (e.staves[0], Y_AXIS);
1039
1040   return Interval (0, 0);
1041 }
1042
1043 Interval
1044 Page_layout_problem::last_staff_extent (Element const &e)
1045 {
1046   if (e.prob)
1047     return prob_extent (e.prob);
1048   else if (e.staves.size ())
1049     return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1050
1051   return Interval (0, 0);
1052 }
1053
1054 SCM
1055 Page_layout_problem::get_details (Element const &elt)
1056 {
1057   if (elt.staves.empty ())
1058     return SCM_EOL;
1059
1060   return get_details (elt.staves.back ()->get_system ());
1061 }
1062
1063 SCM
1064 Page_layout_problem::get_details (Grob *g)
1065 {
1066   Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1067   return left_bound->get_property ("line-break-system-details");
1068 }
1069
1070 bool
1071 Page_layout_problem::is_spaceable (Grob *g)
1072 {
1073   return !scm_is_number (g->get_property ("staff-affinity"));
1074 }
1075
1076 void
1077 Page_layout_problem::mark_as_spaceable (Grob *g)
1078 {
1079   g->set_property ("staff-affinity", SCM_BOOL_F);
1080 }
1081
1082 bool
1083 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1084 {
1085   SCM pair = scm_sloppy_assq (sym, spec);
1086   if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1087     {
1088       *dest = scm_to_double (scm_cdr (pair));
1089       return true;
1090     }
1091   return false;
1092 }
1093
1094 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1095 // Otherwise, return -infinity_f.
1096 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1097 // its alignment.
1098 Real
1099 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1100 {
1101   Spanner *after_sp = dynamic_cast<Spanner *> (after);
1102   SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1103                      ? ly_symbol2scm ("spaceable-fixed-spacing")
1104                      : ly_symbol2scm ("loose-fixed-spacing");
1105   if (pure)
1106     {
1107       // The result of this function doesn't depend on "end," so we can reduce the
1108       // size of the cache by ignoring it.
1109       SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1110       if (scm_is_number (cached))
1111         return robust_scm2double (cached, 0.0);
1112     }
1113
1114   Real ret = -infinity_f;
1115
1116   // If we're pure, then paper-columns have not had their systems set,
1117   // and so elts[i]->get_system () is unreliable.
1118   System *sys = pure ? Grob::get_system (before) : before->get_system ();
1119   Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1120
1121   if (is_spaceable (before) && is_spaceable (after) && left_bound)
1122     {
1123       SCM details = left_bound->get_property ("line-break-system-details");
1124       SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1125       if (scm_is_pair (manual_dists))
1126         {
1127           SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1128           if (scm_is_number (forced))
1129             ret = max (ret, scm_to_double (forced));
1130         }
1131     }
1132
1133   // Cache the result.  As above, we ignore "end."
1134   if (pure)
1135     after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1136
1137   return ret;
1138 }
1139
1140 static SCM
1141 add_stretchability (SCM alist, Real stretch)
1142 {
1143   if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1144     return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1145
1146   return alist;
1147 }
1148
1149 // We want to put a large stretch between a non-spaceable line and its
1150 // non-affinity staff. We want to put an even larger stretch between
1151 // a non-spaceable line and the top/bottom of the page. That way,
1152 // a spacing-affinity UP line at the bottom of the page will still be
1153 // placed close to its staff.
1154 const double LARGE_STRETCH = 10e5;
1155 const double HUGE_STRETCH = 10e7;
1156
1157 // Returns the spacing spec connecting BEFORE to AFTER.
1158 SCM
1159 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1160 {
1161   // If there are no spacing wishes, return a very flexible spring.
1162   // This will occur, for example, if there are lyrics at the bottom of
1163   // the page, in which case we don't want the spring from the lyrics to
1164   // the bottom of the page to have much effect.
1165   if (!before || !after)
1166     return add_stretchability (SCM_EOL, HUGE_STRETCH);
1167
1168   if (is_spaceable (before))
1169     {
1170       if (is_spaceable (after))
1171         return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1172       else
1173         {
1174           Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1175           return (affinity == DOWN)
1176                  ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1177                                        LARGE_STRETCH)
1178                  : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1179         }
1180     }
1181   else
1182     {
1183       if (is_spaceable (after))
1184         {
1185           Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1186           return (affinity == UP)
1187                  ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1188                                        LARGE_STRETCH)
1189                  : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1190         }
1191       else
1192         {
1193           Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1194           Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1195           static bool warned = false;
1196           if (after_affinity > before_affinity
1197               && !warned && !pure)
1198             {
1199               warning (_ ("staff-affinities should only decrease"));
1200               warned = true;
1201             }
1202           if (before_affinity != UP)
1203             return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1204           else if (after_affinity != DOWN)
1205             return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1206           return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1207                                      LARGE_STRETCH);
1208         }
1209     }
1210
1211   assert (0);
1212   return SCM_BOOL_F;
1213 }
1214
1215 void
1216 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1217 {
1218   Real space;
1219   Real stretch;
1220   Real min_dist;
1221   if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1222     spring->set_distance (space);
1223   if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1224     spring->set_min_distance (min_dist);
1225   spring->set_default_strength ();
1226
1227   if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1228     spring->set_inverse_stretch_strength (stretch);
1229 }
1230
1231 vector<Grob *>
1232 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1233 {
1234   vector<Grob *> output;
1235   for (vsize i = 0; i < input.size (); ++i)
1236     {
1237       if (Hara_kiri_group_spanner::has_interface (input[i]))
1238         Hara_kiri_group_spanner::consider_suicide (input[i]);
1239
1240       if (input[i]->is_live ())
1241         output.push_back (input[i]);
1242     }
1243
1244   return output;
1245 }