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