From: Mike Solomon Date: Sun, 6 Mar 2011 15:56:59 +0000 (-0500) Subject: This patch is meant to be a TEST ONLY of footnotes at the bottom of X-Git-Tag: release/2.13.54-1~24 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=9cc7f76cb53f6f22844619efff966d13d4790327;p=lilypond.git This patch is meant to be a TEST ONLY of footnotes at the bottom of the page and, as a result, it does NOT print any annotations, which will hopefully be added in the not-too-distant future. The basic way this patch works is by adding a field in System and Line_details that allows for footnotes on any given system to be taken into account during LilyPond's vertical spacing routines. For grobs that only ever print once on one system, this is easy. For spanners, this becomes trickier if the spanner breaks over multiple lines. Currently, the parameter spanner-placement, which slides between -1 and 1, controls the broken spanner to which the footnote applies. This is done so that LEFT will guarantee placement on the first spanner and RIGHT will guarantee placement on the second spanner. For items that have break-visibility set, an is_visible function in the Balloon_interface checks to see if a given item is actually on a line, only printing the footnote if both the annotation and the object being annotated have break-visibility set to #t for that particular place. In markups that are not part of a score, the mechanism is entirely different. A stencil, called `footnote', is passed to ly:make-stencil. This stencil is never printed in the actual markup but is fished out of the stencil when the markup's Prob is created and added as a footnote. To study this code, the best place to start would be page-layout-problem.cc. Any function with the world `footnote' in its name will show you the trace of many other functions in all these other files through which footnotes move to their final destination of the bottom of the page. --- diff --git a/input/regression/footnote-break-visibility.ly b/input/regression/footnote-break-visibility.ly new file mode 100644 index 0000000000..e45d8012af --- /dev/null +++ b/input/regression/footnote-break-visibility.ly @@ -0,0 +1,25 @@ +\version "2.13.54" +\header { + texidoc = "With grobs that have break visibility, footnotes will +automatically print to the first line of the break. This behavior +can be overrided." +} + +#(set-default-paper-size "a6") + +\book { + +\new Staff \with { \consists "Footnote_engraver" } +{ + \relative c' { + c1 + \footnoteGrob #'TimeSignature #'(0 . 2) "foo" "bar" + \time 3/4 + \break \pageBreak + c2. + \once \override Staff . FootnoteItem #'break-visibility = ##(#f #f #t) + \footnoteGrob #'TimeSignature #'(0 . 2) "foo" "bar" + \time 4/4 + \break \pageBreak + c1 \bar "|." +}}} diff --git a/input/regression/footnote-spanner.ly b/input/regression/footnote-spanner.ly new file mode 100644 index 0000000000..eecbd97ca2 --- /dev/null +++ b/input/regression/footnote-spanner.ly @@ -0,0 +1,61 @@ +\version "2.13.54" +\header { + texidoc = "Footnotes are annotated at the correct place, and the +annotation goes to the correct page." +} + +#(set-default-paper-size "a6") + +\paper { ragged-last-bottom = ##f } + +\book { + +\relative c'' { +\once \override FootnoteSpanner #'spanner-placement = #-0.7 +\footnoteGrob #'Hairpin + #'(0.5 . 0.5) + \markup { \tiny "1." } + \markup { 1. \justify { Goes to the second broken spanner. } } +b4\< c d a +b c d a +b c d a +b c d a +b c d a +b c d a \break \pageBreak +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a\! + +\once \override FootnoteSpanner #'spanner-placement = #1.0 +\footnoteGrob #'Hairpin + #'(0.5 . 0.5) + \markup { \tiny "2." } + \markup { 2. \justify { Goes to the last broken spanner. } } +b4\< c d a +b c d a +b c d a +b c d a +b c d a +b c d a \break \pageBreak +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d a +b c d\! +}} diff --git a/input/regression/footnote.ly b/input/regression/footnote.ly new file mode 100644 index 0000000000..607d36a1bd --- /dev/null +++ b/input/regression/footnote.ly @@ -0,0 +1,33 @@ +\version "2.13.54" +\header { + texidoc = "Lilypond does footnotes." +} + +#(set-default-paper-size "a6") +\book { + +\markup { + a \footnote \concat { b \super 1 } "1. c" + \footnote \concat { d \super 2 } "2. e" + \footnote \line { f \super 3 } "3. g" +} + +\markup { h i } + +\relative c' { +\footnoteGrob #'NoteHead #'(1 . -1) \markup { \tiny 4 } \markup { 4. j } +a b c d } + +\pageBreak + +\markup { k \footnote \concat { l \super 5 } \line { 5. m } } + +\relative c' { a1 } + +\relative c' { + d4 e + < f a-\footnote #'(1 . -1) \markup { \tiny 6 } \markup { 6. n } c > + \footnoteGrob #'Beam #'(1 . 1) \markup { \tiny 7 } \markup { 7. o } + \footnoteGrob #'Hairpin #'(1 . 1) \markup { \tiny 8 } \markup { 8. p } + a8\< [ b c d\f ] r2. | +}} diff --git a/lily/constrained-breaking.cc b/lily/constrained-breaking.cc index fdcd645866..f6f84b8090 100644 --- a/lily/constrained-breaking.cc +++ b/lily/constrained-breaking.cc @@ -519,6 +519,8 @@ Constrained_breaking::fill_line_details (Line_details *const out, vsize start, v out->title_space_ = system_markup_space_; out->inverse_hooke_ = out->full_height () + system_system_space_; + out->footnotes_ = sys->get_footnotes_in_range (start_rank, end_rank); + out->refpoint_extent_ = sys->pure_refpoint_extent (start_rank, end_rank); if (out->refpoint_extent_.is_empty ()) out->refpoint_extent_ = Interval (0, 0); @@ -550,6 +552,11 @@ Line_details::Line_details (Prob *pb, Output_def *paper) Page_layout_problem::read_spacing_spec (spec, &min_distance_, ly_symbol2scm ("minimum-distance")); Page_layout_problem::read_spacing_spec (title_spec, &title_min_distance_, ly_symbol2scm ("minimum-distance")); + SCM footnotes = pb->get_property ("footnotes"); + if (scm_is_pair (footnotes)) + for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s)) + footnotes_.push_back (unsmob_stencil (scm_car (s))); + last_column_ = 0; force_ = 0; Interval stencil_extent = unsmob_stencil (pb->get_property ("stencil"))->extent (Y_AXIS); diff --git a/lily/dynamic-align-engraver.cc b/lily/dynamic-align-engraver.cc index 43498089d0..f62c45f0dc 100644 --- a/lily/dynamic-align-engraver.cc +++ b/lily/dynamic-align-engraver.cc @@ -37,6 +37,7 @@ class Dynamic_align_engraver : public Engraver DECLARE_TRANSLATOR_LISTENER (break_span); DECLARE_ACKNOWLEDGER (note_column); DECLARE_ACKNOWLEDGER (dynamic); + DECLARE_ACKNOWLEDGER (footnote_spanner); DECLARE_END_ACKNOWLEDGER (dynamic); protected: @@ -63,6 +64,7 @@ Dynamic_align_engraver::Dynamic_align_engraver () ADD_ACKNOWLEDGER (Dynamic_align_engraver, dynamic); ADD_ACKNOWLEDGER (Dynamic_align_engraver, note_column); +ADD_ACKNOWLEDGER (Dynamic_align_engraver, footnote_spanner); ADD_END_ACKNOWLEDGER (Dynamic_align_engraver, dynamic); void @@ -80,6 +82,15 @@ Dynamic_align_engraver::acknowledge_end_dynamic (Grob_info info) ended_.push_back (info.spanner ()); } +void +Dynamic_align_engraver::acknowledge_footnote_spanner (Grob_info info) +{ + Grob *parent = info.grob ()->get_parent (Y_AXIS); + if (line_ && parent + && parent->internal_has_interface (ly_symbol2scm ("dynamic-interface"))) + Axis_group_interface::add_element (line_, info.grob ()); +} + void Dynamic_align_engraver::acknowledge_note_column (Grob_info info) { diff --git a/lily/footnote-engraver.cc b/lily/footnote-engraver.cc new file mode 100644 index 0000000000..7539bf9503 --- /dev/null +++ b/lily/footnote-engraver.cc @@ -0,0 +1,136 @@ +/* + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2011 Mike Solomon + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . +*/ + +#include "engraver.hh" + +#include "stream-event.hh" +#include "item.hh" +#include "pointer-group-interface.hh" +#include "spanner.hh" + +#include "translator.icc" + +class Footnote_engraver : public Engraver +{ + TRANSLATOR_DECLARATIONS (Footnote_engraver); + + DECLARE_TRANSLATOR_LISTENER (footnote); + DECLARE_ACKNOWLEDGER (grob); + DECLARE_END_ACKNOWLEDGER (grob); + vector events_; + vector > annotated_spanners_; + + void stop_translation_timestep (); + + void footnotify (Grob *, Stream_event *); +}; + +IMPLEMENT_TRANSLATOR_LISTENER (Footnote_engraver, footnote); +void +Footnote_engraver::listen_footnote (Stream_event *ev) +{ + events_.push_back (ev); +} + +void +Footnote_engraver::stop_translation_timestep () +{ + events_.clear (); +} + +Footnote_engraver::Footnote_engraver () +{ +} + +void +Footnote_engraver::footnotify (Grob *g, Stream_event *event) +{ + Spanner *s = dynamic_cast(g); + + if (s) + { + Spanner *b = make_spanner ("FootnoteSpanner", event->self_scm ()); + b->set_parent (s, Y_AXIS); + b->set_parent (s, X_AXIS); + Grob *bound = unsmob_grob (get_property ("currentMusicalColumn")); + b->set_bound (LEFT, bound); + annotated_spanners_.push_back (Drul_array (s,b)); + } + else + { + Grob *b = make_item ("FootnoteItem", event->self_scm ()); + b->set_parent (g, Y_AXIS); + b->set_parent (g, X_AXIS); + } +} + +void +Footnote_engraver::acknowledge_grob (Grob_info info) +{ + Stream_event *cause = info.event_cause (); + + SCM arts = cause ? cause->get_property ("articulations") : SCM_EOL; + for (SCM s = arts; scm_is_pair (s); s = scm_cdr (s)) + { + Stream_event *e = unsmob_stream_event (scm_car (s)); + if (e->in_event_class ("footnote-event")) + footnotify (info.grob (), e); + } + + for (vsize i = 0; i < events_.size (); i++) + { + if (info.grob ()->name () == ly_symbol2string (events_[i]->get_property ("symbol"))) + footnotify (info.grob (), events_[i]); + } +} + +void +Footnote_engraver::acknowledge_end_grob (Grob_info info) +{ + Spanner *s = dynamic_cast(info.grob ()); + + if (s) + for (vsize i = 0; i < annotated_spanners_.size (); i++) + { + if (annotated_spanners_[i][LEFT] == s) + { + Grob *bound = unsmob_grob (get_property ("currentMusicalColumn")); + annotated_spanners_[i][RIGHT]->set_bound (RIGHT, bound); + break; + } + } +} + +ADD_ACKNOWLEDGER (Footnote_engraver, grob); +ADD_END_ACKNOWLEDGER (Footnote_engraver, grob); + +ADD_TRANSLATOR (Footnote_engraver, + /* doc */ + "Create footnote texts.", + + /* create */ + "FootnoteItem " + "FootnoteSpanner ", + + /*read*/ + "currentMusicalColumn ", + + /*write*/ + "" + ); diff --git a/lily/include/constrained-breaking.hh b/lily/include/constrained-breaking.hh index fc1b44e41f..d0e631d901 100644 --- a/lily/include/constrained-breaking.hh +++ b/lily/include/constrained-breaking.hh @@ -44,6 +44,9 @@ struct Line_details { Grob *last_column_; Real force_; Line_shape shape_; + vector footnotes_; /* The footnotes at the bottom of the + page, where each stencil represents + a different footnote. */ Interval refpoint_extent_; /* The refpoints of the first and last spaceable staff in this line. min-distance should be measured from the bottom diff --git a/lily/include/page-breaking.hh b/lily/include/page-breaking.hh index b34b6c4c84..fa68c61752 100644 --- a/lily/include/page-breaking.hh +++ b/lily/include/page-breaking.hh @@ -124,6 +124,8 @@ public: Real page_height (int page_number, bool last) const; Real paper_height () const; vsize system_count () const; + Real footnote_separator_stencil_height () const; + Real footnote_padding () const; Real line_count_penalty (int line_count) const; int line_count_status (int line_count) const; bool too_many_lines (int line_count) const; @@ -145,6 +147,7 @@ protected: void break_into_pieces (vsize start, vsize end, Line_division const &div); SCM systems (); + SCM footnotes (); void set_current_breakpoints (vsize start, vsize end, @@ -184,6 +187,8 @@ private: int max_systems_per_page_; int min_systems_per_page_; vsize system_count_; + Real footnote_separator_stencil_height_; + Real footnote_padding_; int orphan_penalty_; vector current_configurations_; diff --git a/lily/include/page-layout-problem.hh b/lily/include/page-layout-problem.hh index 941ccef2a7..fb1c7c373d 100644 --- a/lily/include/page-layout-problem.hh +++ b/lily/include/page-layout-problem.hh @@ -34,8 +34,11 @@ public: static bool read_spacing_spec (SCM spec, Real* dest, SCM sym); static bool is_spaceable (Grob *g); static SCM get_details (Grob *g); + static SCM get_footnotes_from_lines (SCM lines, Real padding); + static Stencil* get_footnote_separator_stencil (Output_def *paper); static SCM get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end); static Real get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end); + static void add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb); protected: void append_system (System*, Spring const&, Real indent, Real padding); diff --git a/lily/include/page-spacing.hh b/lily/include/page-spacing.hh index be261735aa..206bcbf426 100644 --- a/lily/include/page-spacing.hh +++ b/lily/include/page-spacing.hh @@ -102,6 +102,7 @@ struct Page_spacing Real rod_height_; Real spring_len_; Real inverse_spring_k_; + bool has_footnotes_; Line_details last_line_; Line_details first_line_; @@ -111,11 +112,13 @@ struct Page_spacing { page_height_ = page_height; breaker_ = breaker; + has_footnotes_ = false; clear (); } void calc_force (); void resize (Real new_height); + Real account_for_footnotes (Line_details const &line); void append_system (const Line_details &line); void prepend_system (const Line_details &line); void clear (); diff --git a/lily/include/paper-system.hh b/lily/include/paper-system.hh index 96053d8d03..1aaa530b8a 100644 --- a/lily/include/paper-system.hh +++ b/lily/include/paper-system.hh @@ -30,5 +30,6 @@ */ Prob *make_paper_system (SCM immutable_init); void paper_system_set_stencil (Prob *prob, Stencil s); +SCM get_footnotes (SCM expr); #endif /* PAPER_SYSTEM_HH */ diff --git a/lily/include/system.hh b/lily/include/system.hh index 882f209f96..940f6a5fb8 100644 --- a/lily/include/system.hh +++ b/lily/include/system.hh @@ -36,7 +36,9 @@ class System : public Spanner void init_elements (); friend class Paper_score; // ugh. Paper_score *pscore_; // ugh. - + bool checked_footnotes_; + vector footnote_grobs_; // TODO: make this a grob array + public: Paper_score *paper_score () const; Grob *get_vertical_alignment (); @@ -44,11 +46,16 @@ public: Grob *get_pure_bound (Direction dir, int start, int end); Grob *get_maybe_pure_bound (Direction dir, bool pure, int start, int end); int get_rank () const; + vector get_footnotes_in_range (vsize st, vsize end); + void get_footnote_grobs_in_range (vector &out, vsize st, vsize end); + Stencil make_footnote_stencil (Real padding); void do_break_substitution_and_fixup_refpoints (); void post_processing (); + void populate_footnote_grob_vector (); SCM get_paper_system (); SCM get_paper_systems (); SCM get_broken_system_grobs (); + SCM get_broken_footnote_stencils (); DECLARE_SCHEME_CALLBACK (calc_pure_relevant_grobs, (SCM)); DECLARE_SCHEME_CALLBACK (height, (SCM)); diff --git a/lily/page-breaking.cc b/lily/page-breaking.cc index 4e386b8183..80702f59ba 100644 --- a/lily/page-breaking.cc +++ b/lily/page-breaking.cc @@ -131,12 +131,14 @@ #include "international.hh" #include "item.hh" +#include "line-interface.hh" #include "output-def.hh" #include "page-layout-problem.hh" #include "page-spacing.hh" #include "paper-book.hh" #include "paper-score.hh" #include "paper-system.hh" +#include "text-interface.hh" #include "system.hh" #include "warn.hh" @@ -178,7 +180,12 @@ compress_lines (const vector &orig) // compressed.title_ is true if and only if the first of its // compressed lines was a title. - compressed.title_ = old.title_; + compressed.title_ = old.title_; + + // adds footnotes of one line to the footnotes of another + compressed.footnotes_.insert (compressed.footnotes_.begin (), + old.footnotes_.begin (), old.footnotes_.end ()); + ret.back () = compressed; } else @@ -243,6 +250,20 @@ Page_breaking::Page_breaking (Paper_book *pb, Break_predicate is_break, Prob_bre min_systems_per_page_ = max (0, robust_scm2int (pb->paper_->c_variable ("min-systems-per-page"), 0)); orphan_penalty_ = robust_scm2int (pb->paper_->c_variable ("orphan-penalty"), 100000); + Stencil *footnote_separator = Page_layout_problem::get_footnote_separator_stencil (pb->paper_); + + if (footnote_separator) + { + Interval separator_extent = footnote_separator->extent (Y_AXIS); + Real separator_span = separator_extent.length (); + + footnote_separator_stencil_height_ = separator_span; + } + else + footnote_separator_stencil_height_ = 0.0; + + footnote_padding_ = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0); + if (systems_per_page_ && (max_systems_per_page_ || min_systems_per_page_)) { warning (_f ("ignoring min-systems-per-page and max-systems-per-page because systems-per-page was set")); @@ -302,6 +323,18 @@ Page_breaking::system_count () const return system_count_; } +Real +Page_breaking::footnote_separator_stencil_height () const +{ + return footnote_separator_stencil_height_; +} + +Real +Page_breaking::footnote_padding () const +{ + return footnote_padding_; +} + bool Page_breaking::too_many_lines (int line_count) const { @@ -514,6 +547,12 @@ Page_breaking::draw_page (SCM systems, SCM configuration, int page_num, bool las Prob *p = unsmob_prob (page); p->set_property ("lines", paper_systems); p->set_property ("configuration", configuration); + + Stencil *foot = unsmob_stencil (p->get_property ("foot-stencil")); + SCM footnotes = Page_layout_problem::get_footnotes_from_lines (systems, footnote_padding ()); + Page_layout_problem::add_footnotes_to_footer (footnotes, foot, unsmob_paper_book (p->get_property ("paper-book"))); + + p->set_property ("foot-stencil", foot->smobbed_copy ()); scm_apply_1 (page_stencil, page, SCM_EOL); return page; @@ -559,6 +598,7 @@ Page_breaking::make_pages (vector lines_per_page, SCM systems) { SCM lines = scm_caar (s); SCM config = scm_cdar (s); + bool bookpart_last_page = (s == systems_and_configs); SCM page = draw_page (lines, config, page_num, bookpart_last_page); diff --git a/lily/page-layout-problem.cc b/lily/page-layout-problem.cc index aaf86815dc..c9512fcca8 100644 --- a/lily/page-layout-problem.cc +++ b/lily/page-layout-problem.cc @@ -32,6 +32,96 @@ #include "prob.hh" #include "skyline-pair.hh" #include "system.hh" +#include "text-interface.hh" + +/* + Returns a stencil for the footnote of each system. This stencil may + itself be comprised of several footnotes. +*/ + +SCM +Page_layout_problem::get_footnotes_from_lines (SCM lines, Real padding) +{ + SCM footnotes = SCM_EOL; + // ugh...code dup from the Page_layout_problem constructor + for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s)) + { + if (Grob *g = unsmob_grob (scm_car (s))) + { + System *sys = dynamic_cast (g); + if (!sys) + { + programming_error ("got a grob for footnotes that wasn't a System"); + continue; + } + footnotes = scm_cons (sys->make_footnote_stencil (padding).smobbed_copy (), footnotes); + } + else if (Prob *p = unsmob_prob (scm_car (s))) + { + SCM stencils = p->get_property ("footnotes"); + if (stencils == SCM_EOL) + continue; + Stencil footnote_stencil; + + for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st)) + footnote_stencil.add_at_edge (Y_AXIS, DOWN, *unsmob_stencil (scm_car (st)), padding); + footnotes = scm_cons (footnote_stencil.smobbed_copy (), footnotes); + } + } + + if (!scm_is_pair (footnotes)) + return SCM_EOL; + + return scm_reverse (footnotes); +} + +Stencil* +Page_layout_problem::get_footnote_separator_stencil (Output_def *paper) +{ + SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"), + paper->self_scm ()); + + SCM markup = paper->c_variable ("footnote-separator-markup"); + + if (!Text_interface::is_markup (markup)) + return NULL; + + SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (), + props, markup); + + Stencil *footnote_separator = unsmob_stencil (footnote_stencil); + + return footnote_separator; +} + +void +Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb) +{ + bool footnotes_found = false; + Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0); + + footnotes = scm_reverse (footnotes); + for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s)) + { + Stencil *stencil = unsmob_stencil (scm_car (s)); + + if (!stencil) + continue; + + if (!stencil->is_empty ()) + { + foot->add_at_edge (Y_AXIS, UP, *stencil, footnote_padding); + footnotes_found = true; + } + } + + if (footnotes_found) + { + Stencil *separator = get_footnote_separator_stencil (pb->paper_); + if (separator) + foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding); + } +} Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems) : bottom_skyline_ (DOWN) @@ -47,7 +137,13 @@ Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM syst { Stencil *head = unsmob_stencil (page->get_property ("head-stencil")); Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil")); - + + Real footnote_padding = 0.0; + if (pb && pb->paper_) + footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0); + SCM footnotes = get_footnotes_from_lines (systems, footnote_padding); + add_footnotes_to_footer (footnotes, foot, pb); + header_height_ = head ? head->extent (Y_AXIS).length () : 0; footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0; page_height_ = robust_scm2double (page->get_property ("paper-height"), 100); diff --git a/lily/page-spacing.cc b/lily/page-spacing.cc index 17ba73d923..13c6a0c02a 100644 --- a/lily/page-spacing.cc +++ b/lily/page-spacing.cc @@ -19,6 +19,7 @@ #include "page-spacing.hh" +#include "international.hh" #include "matrix.hh" #include "page-breaking.hh" #include "warn.hh" @@ -59,6 +60,7 @@ Page_spacing::append_system (const Line_details &line) first_line_ = line; } + rod_height_ += account_for_footnotes (line); inverse_spring_k_ += line.inverse_hooke_; last_line_ = line; @@ -66,6 +68,25 @@ Page_spacing::append_system (const Line_details &line) calc_force (); } +Real +Page_spacing::account_for_footnotes (Line_details const &line) +{ + Real footnote_height = 0.0; + for (vsize i = 0; i < line.footnotes_.size (); i++) + { + footnote_height += (has_footnotes_ + ? 0.0 + : (breaker_->footnote_separator_stencil_height () + + breaker_->footnote_padding ())); + + has_footnotes_ = true; + Interval extent = line.footnotes_[i]->extent (Y_AXIS); + footnote_height += extent[UP] - extent[DOWN]; + footnote_height += breaker_->footnote_padding (); + } + return footnote_height; +} + void Page_spacing::prepend_system (const Line_details &line) { @@ -77,7 +98,7 @@ Page_spacing::prepend_system (const Line_details &line) rod_height_ -= first_line_.full_height (); rod_height_ += first_line_.tallness_; rod_height_ += line.full_height(); - + rod_height_ += account_for_footnotes (line); inverse_spring_k_ += line.inverse_hooke_; first_line_ = line; @@ -90,6 +111,7 @@ Page_spacing::clear () { force_ = rod_height_ = spring_len_ = 0; inverse_spring_k_ = 0; + has_footnotes_ = false; } diff --git a/lily/paper-book.cc b/lily/paper-book.cc index 4678ee3895..7a13120c64 100644 --- a/lily/paper-book.cc +++ b/lily/paper-book.cc @@ -532,6 +532,9 @@ Paper_book::get_system_specs () list == texts? SCM_BOOL_T : SCM_BOOL_F); paper_system_set_stencil (ps, *unsmob_stencil (t)); + + SCM footnotes = get_footnotes (unsmob_stencil (t)->expr ()); + ps->set_property ("footnotes", footnotes); ps->set_property ("is-title", SCM_BOOL_T); if (list != texts) /* For each markup other than the first, place it as closely as diff --git a/lily/paper-system.cc b/lily/paper-system.cc index aef20f10c0..711d919446 100644 --- a/lily/paper-system.cc +++ b/lily/paper-system.cc @@ -27,6 +27,64 @@ make_paper_system (SCM immutable_init) return prob; } +/* + TODO + it might be interesting to split off the footnotes as well, ie. + + get_footnotes(SCM expr, SCM* footnotes, SCM* cleaned) + + by doing it this way and overwriting the old expr in the caller, + you can make sure nobody tries to handle footnotes differently + downstream. +*/ +SCM +get_footnotes (SCM expr) +{ + if (!scm_is_pair (expr)) + return SCM_EOL; + + SCM head = scm_car (expr); + + if (head == ly_symbol2scm ("delay-stencil-evaluation")) + { + // we likely need to do something here...just don't know what... + return SCM_EOL; + } + + if (head == ly_symbol2scm ("combine-stencil")) + { + SCM out = SCM_EOL; + SCM *tail = &out; + + for (SCM x = scm_cdr (expr); scm_is_pair (x); x = scm_cdr (x)) + { + SCM footnote = get_footnotes (scm_car (x)); + if (scm_is_pair (footnote)) + { + for (SCM y = footnote; scm_is_pair (y); y = scm_cdr (y)) + { + *tail = scm_cons (scm_car (y), SCM_EOL); + tail = SCM_CDRLOC (*tail); + } + } + else if (SCM_EOL != footnote) + { + *tail = scm_cons (footnote, SCM_EOL); + tail = SCM_CDRLOC (*tail); + } + } + return out; + } + if (head == ly_symbol2scm ("translate-stencil")) + return get_footnotes (scm_caddr (expr)); + + if (head == ly_symbol2scm ("footnote")) + return scm_cadr (expr); + + return SCM_EOL; +} + + void paper_system_set_stencil (Prob *prob, Stencil s) { diff --git a/lily/stencil-interpret.cc b/lily/stencil-interpret.cc index 19c241ea84..4f8c6bbc8b 100644 --- a/lily/stencil-interpret.cc +++ b/lily/stencil-interpret.cc @@ -37,6 +37,8 @@ interpret_stencil_expression (SCM expr, interpret_stencil_expression (scm_force (scm_cadr (expr)), func, func_arg, o); return; } + if (head == ly_symbol2scm ("footnote")) + return; if (head == ly_symbol2scm ("translate-stencil")) { o += ly_scm2offset (scm_cadr (expr)); diff --git a/lily/system.cc b/lily/system.cc index 19279d7cd0..8b1e6d4a22 100644 --- a/lily/system.cc +++ b/lily/system.cc @@ -22,6 +22,7 @@ #include "align-interface.hh" #include "all-font-metrics.hh" #include "axis-group-interface.hh" +#include "break-align-interface.hh" #include "grob-array.hh" #include "hara-kiri-group-spanner.hh" #include "international.hh" @@ -35,6 +36,7 @@ #include "pointer-group-interface.hh" #include "skyline-pair.hh" #include "staff-symbol-referencer.hh" +#include "text-interface.hh" #include "warn.hh" System::System (System const &src) @@ -43,6 +45,7 @@ System::System (System const &src) all_elements_ = 0; pscore_ = 0; rank_ = 0; + checked_footnotes_ = false; init_elements (); } @@ -51,6 +54,7 @@ System::System (SCM s) { all_elements_ = 0; rank_ = 0; + checked_footnotes_ = false; init_elements (); } @@ -162,7 +166,7 @@ System::do_break_substitution_and_fixup_refpoints () Grob *g = all_elts[j]; g->fixup_refpoint (); } - + count += all_elts.size (); } @@ -226,6 +230,116 @@ System::get_paper_systems () return lines; } +void +System::populate_footnote_grob_vector () +{ + extract_grob_set (this, "all-elements", all_elts); + for (vsize i = 0; i < all_elts.size (); i++) + if (all_elts[i]->internal_has_interface (ly_symbol2scm ("footnote-interface"))) + footnote_grobs_.push_back (all_elts[i]); + + sort (footnote_grobs_.begin (), footnote_grobs_.end (), Grob::less); + checked_footnotes_ = true; +} + +void +System::get_footnote_grobs_in_range (vector &out, vsize start, vsize end) +{ + if (!checked_footnotes_) + populate_footnote_grob_vector (); + + for (vsize i = 0; i < footnote_grobs_.size (); i++) + { + int pos = footnote_grobs_[i]->spanned_rank_interval ()[LEFT]; + bool end_of_line_visible = true; + if (Spanner *s = dynamic_cast(footnote_grobs_[i])) + { + Real spanner_placement = robust_scm2double (s->get_property ("spanner-placement"), -1.0); + if (spanner_placement < -1.0) + spanner_placement = -1.0; + if (spanner_placement > 1.0) + spanner_placement = 1.0; + + spanner_placement = (spanner_placement + 1.0) / 2.0; + int rpos = s->spanned_rank_interval ()[RIGHT]; + pos = (int)((rpos - pos) * spanner_placement + pos + 0.5); + } + + if (Item *item = dynamic_cast(footnote_grobs_[i])) + { + if (!Item::break_visible (item)) + continue; + // safeguard to bring down the column rank so that end of line footnotes show up on the correct line + end_of_line_visible = (LEFT == item->break_status_dir ()); + } + + if (pos < (int)start) + continue; + if (pos > (int)end) + break; + if (pos == (int)end && !end_of_line_visible) + continue; + if (!footnote_grobs_[i]->is_live ()) + continue; + + out.push_back (footnote_grobs_[i]); + } +} + +vector +System::get_footnotes_in_range (vsize start, vsize end) +{ + vector footnote_grobs; + get_footnote_grobs_in_range (footnote_grobs, start, end); + vector out; + + for (vsize i = 0; i < footnote_grobs.size (); i++) + { + SCM footnote_markup = footnote_grobs[i]->get_property ("footnote-text"); + + if (!Text_interface::is_markup (footnote_markup)) + continue; + + SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"), + pscore_->layout ()->self_scm ()); + + SCM footnote_stl = Text_interface::interpret_markup (pscore_->layout ()->self_scm (), + props, footnote_markup); + + Stencil *footnote_stencil = unsmob_stencil (footnote_stl); + out.push_back (footnote_stencil); + } + + return out; +} + +Stencil +System::make_footnote_stencil (Real padding) +{ + Stencil mol; + + for (vsize i = 0; i < footnote_grobs_.size (); i++) + { + SCM footnote_markup = footnote_grobs_[i]->get_property ("footnote-text"); + if (Spanner *orig = dynamic_cast(footnote_grobs_[i])) + if (orig->is_broken ()) + footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text"); + + if (!Text_interface::is_markup (footnote_markup)) + continue; + + SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"), + pscore_->layout ()->self_scm ()); + + SCM footnote_stl = Text_interface::interpret_markup (pscore_->layout ()->self_scm (), + props, footnote_markup); + + mol.add_at_edge (Y_AXIS, DOWN, *unsmob_stencil (footnote_stl), padding); + } + + return mol; +} + void System::break_into_pieces (vector const &breaking) { @@ -242,6 +356,8 @@ System::break_into_pieces (vector const &breaking) Interval iv (pure_height (this, st, end)); system->set_property ("pure-Y-extent", ly_interval2scm (iv)); + get_footnote_grobs_in_range (system->footnote_grobs_, st, end); + system->set_bound (LEFT, c[0]); system->set_bound (RIGHT, c.back ()); SCM system_labels = SCM_EOL; diff --git a/ly/engraver-init.ly b/ly/engraver-init.ly index 91bc12ccbd..b71704866d 100644 --- a/ly/engraver-init.ly +++ b/ly/engraver-init.ly @@ -229,6 +229,7 @@ multiple voices on the same staff." \consists "Dots_engraver" \consists "Rest_engraver" \consists "Tweak_engraver" + \consists "Footnote_engraver" %% switch on to make stem directions interpolate for the %% center line. diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly index ac62bffa9a..2ebbd8f1bc 100644 --- a/ly/music-functions-init.ly +++ b/ly/music-functions-init.ly @@ -342,7 +342,28 @@ featherDurations= argument)) - +footnoteGrob = +#(define-music-function (parser location grob-name offset text footnote) + (symbol? number-pair? markup? markup?) + (_i "Attach @var{text} to @var{grob-name} at offset @var{offset}, + with @var{text} referring to @var{footnote} (use like @code{\\once})") + (make-music 'FootnoteEvent + 'symbol grob-name + 'X-offset (car offset) + 'Y-offset (cdr offset) + 'text text + 'footnote-text footnote)) + +footnote = +#(define-music-function (parser location offset text footnote) + (number-pair? markup? markup?) + (_i "Attach @var{text} at @var{offset} with @var{text} referring + to @var{footnote} (use like @code{\\tweak})") + (make-music 'FootnoteEvent + 'X-offset (car offset) + 'Y-offset (cdr offset) + 'text text + 'footnote-text footnote)) grace = #(def-grace-function startGraceMusic stopGraceMusic diff --git a/ly/paper-defaults-init.ly b/ly/paper-defaults-init.ly index 69fecc16b6..9f88def048 100644 --- a/ly/paper-defaults-init.ly +++ b/ly/paper-defaults-init.ly @@ -56,7 +56,6 @@ ragged-bottom = ##f ragged-last-bottom = ##t % best for shorter scores - %% %% Flexible vertical spacing %% @@ -104,6 +103,13 @@ page-breaking = #ly:optimal-breaking + %% + %% Footnotes + %% + footnote-separator-markup = \markup { \draw-hline } + footnote-padding = 0.5\mm + + %% %% Page numbering %% @@ -111,7 +117,6 @@ print-first-page-number = ##f print-page-number = ##t - %% %% Headers, footers, and titles %% diff --git a/scm/define-event-classes.scm b/scm/define-event-classes.scm index 022ae640e1..c632e430a9 100644 --- a/scm/define-event-classes.scm +++ b/scm/define-event-classes.scm @@ -25,7 +25,7 @@ (RemoveContext ChangeParent Override Revert UnsetProperty SetProperty music-event OldMusicEvent CreateContext Prepare OneTimeStep Finish)) - (music-event . (annotate-output-event + (music-event . (annotate-output-event footnote-event arpeggio-event breathing-event extender-event span-event rhythmic-event dynamic-event break-event label-event percent-event key-change-event string-number-event stroke-finger-event tie-event diff --git a/scm/define-grob-interfaces.scm b/scm/define-grob-interfaces.scm index 23e5f4a0a9..223cdafcf6 100644 --- a/scm/define-grob-interfaces.scm +++ b/scm/define-grob-interfaces.scm @@ -81,6 +81,16 @@ note)." "A fingering instruction." '()) +(ly:add-interface + 'footnote-interface + "Make a footnote." + '(footnote-text)) + +(ly:add-interface + 'footnote-spanner-interface + "Make a footnote spanner." + '(footnote-text spanner-placement)) + (ly:add-interface 'fret-diagram-interface "A fret diagram" diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index b47a7bfc39..9a6ba22d8b 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -293,6 +293,7 @@ include @code{upright}, @code{italic}, @code{caps}.") @code{-1} is smaller, @code{+1} is bigger. Each step of@tie{}1 is approximately 12% larger; 6@tie{}steps are exactly a factor@tie{}2 larger. Fractional values are allowed.") + (footnote-text ,markup? "A footnote for the grob.") (force-hshift ,number? "This specifies a manual shift for notes in collisions. The unit is the note head width of the first voice note. This is used by @rinternals{note-collision-interface}.") @@ -1029,6 +1030,16 @@ grobs.") (spacing-wishes ,ly:grob-array? "An array of note spacing or staff spacing objects.") (span-start ,boolean? "Is the note head at the start of a spanner?") + (spanner-placement ,number? "The place of an annotation on a spanner. +Note that this number must be between -1 and 1, with -1 representing the +beginning of the spanner and 1 representing the end. The annotation will +still be placed at the left or right extremity of the spanner, but this +number ensures that when line breaking happens, the annotation is assigned +to the correct broken piece and the footnote is put on the correct page. +An important caveat is that this number applies to column ranks, not staff +space. For example, 0 will place the annotation at the middle column of +its parent's span, which may be to the right or left of the physical middle +of the spanner.") (staff-grouper ,ly:grob? "The staff grouper we belong to.") (staff-symbol ,ly:grob? "The staff symbol grob that we are in.") (stem ,ly:grob? "A pointer to a @code{Stem} object.") diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index 7b2f1c1e87..ac75852215 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -863,6 +863,37 @@ text-interface text-script-interface)))))) + (FootnoteItem + . ( + (break-visibility . ,inherit-y-parent-visibility) + (footnote-text . ,(grob::calc-property-by-copy 'footnote-text)) + (stencil . #f) + (text . ,(grob::calc-property-by-copy 'text)) + (Y-extent . 0.0) + (X-offset . ,(grob::calc-property-by-copy 'X-offset)) + (Y-offset . ,(grob::calc-property-by-copy 'Y-offset)) + (meta . ((class . Item) + (interfaces . (balloon-interface + footnote-interface + font-interface + text-interface)))))) + + (FootnoteSpanner + . ( + (footnote-text . ,(grob::calc-property-by-copy 'footnote-text)) + (spanner-placement . -1.0) + (stencil . #f) + (text . ,(grob::calc-property-by-copy 'text)) + (Y-extent . 0.0) + (X-offset . ,(grob::calc-property-by-copy 'X-offset)) + (Y-offset . ,(grob::calc-property-by-copy 'Y-offset)) + (meta . ((class . Spanner) + (interfaces . (balloon-interface + footnote-interface + footnote-spanner-interface + font-interface + text-interface)))))) + (FretBoard . ( (after-line-breaking . ,ly:chord-name::after-line-breaking) diff --git a/scm/define-markup-commands.scm b/scm/define-markup-commands.scm index 3e7c69429c..9a576439fb 100644 --- a/scm/define-markup-commands.scm +++ b/scm/define-markup-commands.scm @@ -139,6 +139,22 @@ A simple line. (y (cdr dest))) (make-line-stencil th 0 0 x y))) +(define-markup-command (draw-hline layout props) + () + #:category graphic + #:properties ((draw-line-markup) + (line-width)) + " +@cindex drawing a line across a page + +Draws a line across a page. +@lilypond[verbatim,quote] +\\markup { + \\draw-hline +} +@end lilypond" + (interpret-markup layout props (make-draw-line-markup (cons line-width 0)))) + (define-markup-command (draw-circle layout props radius thickness filled) (number? number? boolean?) #:category graphic @@ -1812,6 +1828,20 @@ returns an empty markup. (list markup?)) (interpret-markup layout props (list anonymous-with-signature arg)))) +(define-markup-command (footnote layout props mkup note) + (markup? markup?) + #:category other + "Have footnote @var{note} act as an annotation to the markup @var{mkup}." + (ly:stencil-combine-at-edge + (interpret-markup layout props mkup) + X + RIGHT + (ly:make-stencil + `(footnote ,(interpret-markup layout props note)) + '(0 . 0) + '(0 . 0)) + 0.0)) + (define-markup-command (override layout props new-prop arg) (pair? markup?) #:category other diff --git a/scm/define-music-properties.scm b/scm/define-music-properties.scm index b18a10f3ad..78a2dec6e1 100644 --- a/scm/define-music-properties.scm +++ b/scm/define-music-properties.scm @@ -82,6 +82,7 @@ a sequential iterator. Takes a single music parameter.") "If true, a parsing error was found in this expression.") (figure ,integer? "A bass figure.") + (footnote-text ,markup? "Text to appear in a footnote.") (force-accidental ,boolean? "If set, a cautionary accidental should always be printed on this note.") (forced-type ,symbol? "Override for the part-combiner.") diff --git a/scm/define-music-types.scm b/scm/define-music-types.scm index cdf5c0a6a1..d57cba8118 100644 --- a/scm/define-music-types.scm +++ b/scm/define-music-types.scm @@ -209,6 +209,11 @@ An alternative syntax is @var{note}@code{\\decr} @dots{} (types . (general-music fingering-event event)) )) + (FootnoteEvent + . ((description . "Footnote a grob.") + (types . (general-music event footnote-event)) + )) + (GlissandoEvent . ((description . "Start a glissando on this note.") (types . (general-music glissando-event event)) diff --git a/scm/define-stencil-commands.scm b/scm/define-stencil-commands.scm index eb7f85ddc1..4191a85532 100644 --- a/scm/define-stencil-commands.scm +++ b/scm/define-stencil-commands.scm @@ -67,6 +67,7 @@ are used internally in @file{lily/stencil-interpret.cc}." '(color combine-stencil delay-stencil-evaluation + footnote rotate-stencil scale-stencil translate-stencil diff --git a/scm/output-lib.scm b/scm/output-lib.scm index f00273add6..27c69fdfbe 100644 --- a/scm/output-lib.scm +++ b/scm/output-lib.scm @@ -244,6 +244,9 @@ and duration-log @var{log}." (define-public (inherit-x-parent-visibility grob) (let ((parent (ly:grob-parent grob X))) (ly:grob-property parent 'break-visibility all-invisible))) +(define-public (inherit-y-parent-visibility grob) + (let ((parent (ly:grob-parent grob X))) + (ly:grob-property parent 'break-visibility))) (define-public spanbar-begin-of-line-invisible #(#t #f #f))