]> git.donarmstrong.com Git - lilypond.git/commitdiff
This patch is meant to be a TEST ONLY of footnotes at the bottom of
authorMike Solomon <mike@apollinemike.com>
Sun, 6 Mar 2011 15:56:59 +0000 (10:56 -0500)
committerMike Solomon <mike@apollinemike.com>
Sun, 6 Mar 2011 15:56:59 +0000 (10:56 -0500)
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.

31 files changed:
input/regression/footnote-break-visibility.ly [new file with mode: 0644]
input/regression/footnote-spanner.ly [new file with mode: 0644]
input/regression/footnote.ly [new file with mode: 0644]
lily/constrained-breaking.cc
lily/dynamic-align-engraver.cc
lily/footnote-engraver.cc [new file with mode: 0644]
lily/include/constrained-breaking.hh
lily/include/page-breaking.hh
lily/include/page-layout-problem.hh
lily/include/page-spacing.hh
lily/include/paper-system.hh
lily/include/system.hh
lily/page-breaking.cc
lily/page-layout-problem.cc
lily/page-spacing.cc
lily/paper-book.cc
lily/paper-system.cc
lily/stencil-interpret.cc
lily/system.cc
ly/engraver-init.ly
ly/music-functions-init.ly
ly/paper-defaults-init.ly
scm/define-event-classes.scm
scm/define-grob-interfaces.scm
scm/define-grob-properties.scm
scm/define-grobs.scm
scm/define-markup-commands.scm
scm/define-music-properties.scm
scm/define-music-types.scm
scm/define-stencil-commands.scm
scm/output-lib.scm

diff --git a/input/regression/footnote-break-visibility.ly b/input/regression/footnote-break-visibility.ly
new file mode 100644 (file)
index 0000000..e45d801
--- /dev/null
@@ -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 (file)
index 0000000..eecbd97
--- /dev/null
@@ -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 (file)
index 0000000..607d36a
--- /dev/null
@@ -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. |
+}}
index fdcd64586600f5c3c50cfc66c2f169f390393e54..f6f84b80907714907d218525deb8c868a95f397e 100644 (file)
@@ -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);
index 43498089d003fd96d1d066bcdc5af61825f219a9..f62c45f0dc6d3adec09cccf6e700fa1722806a24 100644 (file)
@@ -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 (file)
index 0000000..7539bf9
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+  This file is part of LilyPond, the GNU music typesetter.
+
+  Copyright (C) 2011 Mike Solomon <mike@apollinemike.com>
+
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+#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<Stream_event *> events_;
+  vector<Drul_array<Spanner *> > 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<Spanner *>(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<Spanner *> (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<Spanner *>(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*/
+              ""
+              );
index fc1b44e41f586f2f830edf47cf0e107007736ee8..d0e631d901447f3f26cfd6cb5d5fb49dbba291b4 100644 (file)
@@ -44,6 +44,9 @@ struct Line_details {
   Grob *last_column_;
   Real force_;
   Line_shape shape_;
+  vector<Stencil *> 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
index b34b6c4c84a14e481a5a225cdd519634f632af62..fa68c6175207068f4543498ec5262ca1fc352089 100644 (file)
@@ -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<Line_division> current_configurations_;
index 941ccef2a793dd59fa4d05f8ff1f4d8cbb6a5366..fb1c7c373d4a1ad72252f8ebd2c4b8294aeeb7d0 100644 (file)
@@ -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);
index be261735aa2f61ae608220bb280178ca9c76dfb1..206bcbf426bb4193ab7c0bdf03c3665c01711229 100644 (file)
@@ -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 ();
index 96053d8d03de635d3c6f07d9a36ca624f73344e8..1aaa530b8a916b37288f3ed7efe43ac3bd0c8636 100644 (file)
@@ -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 */
index 882f209f9680b052afa354d72a99d5be11c312ef..940f6a5fb876e1b2ca76cc58280f714b570f9757 100644 (file)
@@ -36,7 +36,9 @@ class System : public Spanner
   void init_elements ();
   friend class Paper_score;    // ugh.
   Paper_score *pscore_;        // ugh.
-  
+  bool checked_footnotes_;
+  vector<Grob *> 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<Stencil *> get_footnotes_in_range (vsize st, vsize end);
+  void get_footnote_grobs_in_range (vector<Grob *> &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));
index 4e386b81832186cf48f7014f0b879b025cb616db..80702f59ba2e3517aa8afd8fd1f3acb9d790a9af 100644 (file)
 
 #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<Line_details> &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<vsize> 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);
 
index aaf86815dc2ea55540423c9f91cb3808974786d6..c9512fcca83a9d6908ae8237a048f47985a1fc98 100644 (file)
 #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<System *> (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);
index 17ba73d9239846e49c4e1d8e7fe23345e30e6efe..13c6a0c02a98f45cd0d9208e4a13f2e61fe315c5 100644 (file)
@@ -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;
 }
 
 
index 4678ee3895b88d6f8b11bd1e200d213a5dafac8e..7a13120c6464d0f426e8d63fad05cf1d8ef56d1a 100644 (file)
@@ -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
index aef20f10c04ea9a8d81e80d1f1ae263d9e88e8d1..711d919446d4f8d21a3205563d6dde914aceb8b2 100644 (file)
@@ -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)
 {
index 19c241ea849fa8f09f591e9e30ec9219746e2595..4f8c6bbc8be85712cf2bdea34b6fc25f78bd51a8 100644 (file)
@@ -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));
index 19279d7cd061e7b0c309f96c2a6f74b67f236e95..8b1e6d4a22f6f0beceaf7d15326b42da16a92b5e 100644 (file)
@@ -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<Grob *> &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<Spanner *>(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<Item *>(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<Stencil *>
+System::get_footnotes_in_range (vsize start, vsize end)
+{
+  vector<Grob *> footnote_grobs;
+  get_footnote_grobs_in_range (footnote_grobs, start, end);
+  vector<Stencil *> 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<Spanner *>(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<Column_x_positions> const &breaking)
 {
@@ -242,6 +356,8 @@ System::break_into_pieces (vector<Column_x_positions> 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;
index 91bc12ccbdd99d24d8c71ae3b475c970a0346381..b71704866d3f4c5e063763a3c814552599883094 100644 (file)
@@ -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.
index ac62bffa9ae67575da8bbce6e60ec12fd8d18595..2ebbd8f1bce3bd83b240ffa066b754d6cac37b82 100644 (file)
@@ -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
index 69fecc16b61756a24343d6eee6a0b2801c35b6fd..9f88def048a7cee394aa93cb396d00fe12b2ee37 100644 (file)
@@ -56,7 +56,6 @@
   ragged-bottom = ##f
   ragged-last-bottom = ##t  % best for shorter scores
 
-
   %%
   %% Flexible vertical spacing
   %%
   page-breaking = #ly:optimal-breaking
 
 
+  %%
+  %% Footnotes
+  %%
+  footnote-separator-markup = \markup { \draw-hline }
+  footnote-padding = 0.5\mm
+
+
   %%
   %% Page numbering
   %%
   print-first-page-number = ##f
   print-page-number = ##t
 
-
   %%
   %% Headers, footers, and titles
   %%
index 022ae640e1f208b82292ae682b88c72e274f502b..c632e430a95f37a1dd184e8a8cdef5e4906ce132 100644 (file)
@@ -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
index 23e5f4a0a9575a07c3fd34fd09050dc1fec74e2e..223cdafcf62f88a1a840401916883936b81bc793 100644 (file)
@@ -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"
index b47a7bfc39fa7e06e7ba5a8b684857d85b8da458..9a6ba22d8b44130b7e12648d1007dd06bdd2998d 100644 (file)
@@ -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.")
index 7b2f1c1e87b852c681110a0ecb940c91fb5face3..ac75852215a03d5cde2ab83815e16b2ec172c2d7 100644 (file)
                                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)
index 3e7c69429c8be25c07fd736449305201c1492301..9a576439fbd1386b35cf71c7eb85de52cf659b01 100644 (file)
@@ -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
index b18a10f3adf551cdd0d6fd4adb8ec020fa31b2c0..78a2dec6e1b71a89dd4624e9850180088e04b36b 100644 (file)
@@ -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.")
index cdf5c0a6a1e1e92b5a8ef2f301894e0a53a5f031..d57cba811816f44214c150b8e781ae403eac8f95 100644 (file)
@@ -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))
index eb7f85ddc1dc137a7538348888bcbe5f2af4627f..4191a855329da164e231811e84aaa6e6525cfba7 100644 (file)
@@ -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
index f00273add6e236028ade8dffbe917d4f7d8d21d2..27c69fdfbe82eba10f1d3dcc20fe8156863c0926 100644 (file)
@@ -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))