1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3 
4   Copyright (C) 1996--2020 Han-Wen Nienhuys <hanwen@xs4all.nl>
5   Jan Nieuwenhuizen <janneke@gnu.org>
6 
7   LilyPond is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11 
12   LilyPond is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16 
17   You should have received a copy of the GNU General Public License
18   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "config.hh"
22 
23 #include "slur-scoring.hh"
24 
25 #include "axis-group-interface.hh"
26 #include "accidental-interface.hh"
27 #include "beam.hh"
28 #include "clef.hh"
29 #include "directional-element-interface.hh"
30 #include "dots.hh"
31 #include "libc-extension.hh"
32 #include "international.hh"
33 #include "main.hh"
34 #include "misc.hh"
35 #include "note-column.hh"
36 #include "note-head.hh"
37 #include "output-def.hh"
38 #include "paper-column.hh"
39 #include "pitch.hh"
40 #include "pointer-group-interface.hh"
41 #include "slur-configuration.hh"
42 #include "slur.hh"
43 #include "spanner.hh"
44 #include "staff-symbol-referencer.hh"
45 #include "staff-symbol.hh"
46 #include "stem.hh"
47 #include "warn.hh"
48 
49 #include <cmath>
50 #include <memory>
51 #include <queue>
52 #include <string>
53 #include <vector>
54 
55 using std::string;
56 using std::unique_ptr;
57 using std::vector;
58 
59 /*
60   TODO:
61 
62   - curve around flag for y coordinate
63 
64   - short-cut: try a smaller region first.
65 
66   - handle non-visible stems better.
67 
68   - try to prune number of scoring criteria
69 
70   - take encompass-objects more into account when determining
71   slur shape
72 
73   - calculate encompass scoring directly after determining slur shape.
74 
75   - optimize.
76 */
77 class Slur_score_state;
78 
Slur_score_state()79 Slur_score_state::Slur_score_state ()
80 {
81   musical_dy_ = 0.0;
82   valid_ = false;
83   edge_has_beams_ = false;
84   has_same_beam_ = false;
85   is_broken_ = false;
86   dir_ = CENTER;
87   slur_ = 0;
88   common_[X_AXIS] = 0;
89   common_[Y_AXIS] = 0;
90 }
91 
~Slur_score_state()92 Slur_score_state::~Slur_score_state ()
93 {
94 }
95 
96 /*
97   If a slur is broken across a line break, the direction
98   of the post-break slur must be the same as the pre-break
99   slur.
100 */
101 Direction
slur_direction() const102 Slur_score_state::slur_direction () const
103 {
104   Grob *left_neighbor = slur_->broken_neighbor (LEFT);
105 
106   if (left_neighbor && left_neighbor->is_live ())
107     return get_grob_direction (left_neighbor);
108 
109   Direction dir = get_grob_direction (slur_);
110 
111   if (Grob *right_neighbor = slur_->broken_neighbor (RIGHT))
112     set_grob_direction (right_neighbor, dir);
113 
114   return dir;
115 }
116 
117 Encompass_info
get_encompass_info(Grob * notecol) const118 Slur_score_state::get_encompass_info (Grob *notecol) const
119 {
120   Grob *stem = unsmob<Grob> (get_object (notecol, "stem"));
121   Encompass_info ei;
122 
123   if (!stem)
124     {
125       programming_error ("no stem for note column");
126       ei.x_ = notecol->relative_coordinate (common_[X_AXIS], X_AXIS);
127       ei.head_ = ei.stem_ = notecol->extent (common_[Y_AXIS],
128                                              Y_AXIS)[dir_];
129       return ei;
130     }
131   Direction stem_dir = get_grob_direction (stem);
132 
133   if (Grob *head = Note_column::first_head (notecol))
134     {
135       Interval head_ext = head->extent (common_[X_AXIS], X_AXIS);
136       // FIXME: Is there a better option than setting to 0?
137       if (head_ext.is_empty ())
138         ei.x_ = 0;
139       else
140         ei.x_ = head_ext.center ();
141     }
142   else
143     ei.x_ = notecol->extent (common_[X_AXIS], X_AXIS).center ();
144 
145   Grob *h = Stem::extremal_heads (stem)[Direction (dir_)];
146   if (!h)
147     {
148       ei.head_ = ei.stem_ = notecol->extent (common_[Y_AXIS], Y_AXIS)[dir_];
149       return ei;
150     }
151 
152   ei.head_ = h->extent (common_[Y_AXIS], Y_AXIS)[dir_];
153 
154   if ((stem_dir == dir_)
155       && !stem->extent (stem, Y_AXIS).is_empty ())
156     {
157       ei.stem_ = stem->extent (common_[Y_AXIS], Y_AXIS)[dir_];
158       if (Grob *b = Stem::get_beam (stem))
159         ei.stem_ += stem_dir * 0.5 * Beam::get_beam_thickness (b);
160 
161       Interval x = stem->extent (common_[X_AXIS], X_AXIS);
162       ei.x_ = x.is_empty ()
163               ? stem->relative_coordinate (common_[X_AXIS], X_AXIS)
164               : x.center ();
165     }
166   else
167     ei.stem_ = ei.head_;
168 
169   return ei;
170 }
171 
172 Drul_array<Bound_info>
get_bound_info() const173 Slur_score_state::get_bound_info () const
174 {
175   Drul_array<Bound_info> extremes;
176 
177   Direction dir = dir_;
178 
179   for (LEFT_and_RIGHT (d))
180     {
181       extremes[d].bound_ = slur_->get_bound (d);
182       if (has_interface<Note_column> (extremes[d].bound_))
183         {
184           extremes[d].note_column_ = extremes[d].bound_;
185           extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_);
186           extremes[d].flag_ = Note_column::get_flag (extremes[d].note_column_);
187 
188           if (extremes[d].stem_)
189             {
190               extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_);
191 
192               for (int a = X_AXIS; a < NO_AXES; a++)
193                 {
194                   Axis ax = Axis (a);
195                   Interval s = extremes[d].stem_->extent (common_[ax], ax);
196                   if (extremes[d].flag_)
197                     s.unite (extremes[d].flag_->extent (common_[ax], ax));
198                   if (s.is_empty ())
199                     {
200                       /*
201                         do not issue warning. This happens for rests and
202                         whole notes.
203                       */
204                       s = Interval (0, 0)
205                           + extremes[d].stem_->relative_coordinate (common_[ax], ax);
206                     }
207                   extremes[d].stem_extent_[ax] = s;
208                 }
209 
210               extremes[d].slur_head_
211                 = Stem::extremal_heads (extremes[d].stem_)[dir];
212               if (!extremes[d].slur_head_
213                   && Note_column::has_rests (extremes[d].bound_))
214                 extremes[d].slur_head_ = Note_column::get_rest (extremes[d].bound_);
215               extremes[d].staff_ = Staff_symbol_referencer
216                                    ::get_staff_symbol (extremes[d].stem_);
217               extremes[d].staff_space_ = Staff_symbol_referencer
218                                          ::staff_space (extremes[d].stem_);
219             }
220 
221         }
222       else if (has_interface<Note_head> (extremes[d].bound_))
223         {
224           extremes[d].slur_head_ = extremes[d].bound_;
225         }
226       if (extremes[d].slur_head_)
227         extremes[d].slur_head_x_extent_
228           = extremes[d].slur_head_->extent (common_[X_AXIS], X_AXIS);
229     }
230 
231   return extremes;
232 }
233 
234 void
fill(Grob * me)235 Slur_score_state::fill (Grob *me)
236 {
237   slur_ = dynamic_cast<Spanner *> (me);
238   note_columns_
239     = internal_extract_grob_array (me, ly_symbol2scm ("note-columns"));
240 
241   if (note_columns_.empty ())
242     {
243       me->suicide ();
244       return;
245     }
246 
247   Slur::replace_breakable_encompass_objects (me);
248   staff_space_ = Staff_symbol_referencer::staff_space (me);
249   line_thickness_ = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
250   thickness_ = from_scm<double> (get_property (me, "thickness"), 1.0) * line_thickness_;
251 
252   dir_ = slur_direction ();
253   parameters_.fill (me);
254 
255   extract_grob_set (me, "note-columns", columns);
256   extract_grob_set (me, "encompass-objects", extra_objects);
257 
258   Spanner *sp = dynamic_cast<Spanner *> (me);
259 
260   for (int i = X_AXIS; i < NO_AXES; i++)
261     {
262       Axis a = (Axis)i;
263       common_[a] = common_refpoint_of_array (columns, me, a);
264       common_[a] = common_refpoint_of_array (extra_objects, common_[a], a);
265 
266       for (LEFT_and_RIGHT (d))
267         {
268           /*
269             If bound is not in note-columns, we don't want to know about
270             its Y-position
271           */
272           if (a != Y_AXIS)
273             common_[a] = common_[a]->common_refpoint (sp->get_bound (d), a);
274         }
275     }
276 
277   extremes_ = get_bound_info ();
278   is_broken_ = (!(extremes_[LEFT].note_column_ || extremes_[LEFT].slur_head_)
279                 || !(extremes_[RIGHT].note_column_ || extremes_[RIGHT].slur_head_));
280 
281   has_same_beam_
282     = (extremes_[LEFT].stem_ && extremes_[RIGHT].stem_
283        && Stem::get_beam (extremes_[LEFT].stem_) == Stem::get_beam (extremes_[RIGHT].stem_));
284 
285   base_attachments_ = get_base_attachments ();
286 
287   Drul_array<Real> end_ys
288     = get_y_attachment_range ();
289 
290   extra_encompass_infos_ = get_extra_encompass_infos ();
291 
292   Interval additional_ys (0.0, 0.0);
293 
294   for (vsize i = 0; i < extra_encompass_infos_.size (); i++)
295     {
296       if (extra_encompass_infos_[i].extents_[X_AXIS].is_empty ())
297         continue;
298 
299       Real y_place = linear_interpolate (extra_encompass_infos_[i].extents_[X_AXIS].center (),
300                                          base_attachments_[RIGHT][X_AXIS],
301                                          base_attachments_[LEFT][X_AXIS],
302                                          end_ys[RIGHT],
303                                          end_ys[LEFT]);
304       Real encompass_place = extra_encompass_infos_[i].extents_[Y_AXIS][dir_];
305       if (scm_is_eq (extra_encompass_infos_[i].type_,
306                      ly_symbol2scm ("inside"))
307           && minmax (dir_, encompass_place, y_place) == encompass_place
308           && (!extra_encompass_infos_[i].grob_->internal_has_interface (ly_symbol2scm ("key-signature-interface"))
309               && !has_interface<Clef> (extra_encompass_infos_[i].grob_)
310               && !extra_encompass_infos_[i].grob_->internal_has_interface (ly_symbol2scm ("time-signature-interface"))))
311         {
312           for (LEFT_and_RIGHT (d))
313             additional_ys[d] = minmax (dir_,
314                                        additional_ys[d],
315                                        (dir_
316                                         * (parameters_.encompass_object_range_overshoot_
317                                            + (y_place - encompass_place)
318                                            * (normalize (extra_encompass_infos_[i].extents_[X_AXIS].center (),
319                                                          base_attachments_[RIGHT][X_AXIS],
320                                                          base_attachments_[LEFT][X_AXIS])
321                                               + (dir_ == LEFT ? 0 : -1)))));
322         }
323     }
324 
325   for (LEFT_and_RIGHT (d))
326     end_ys[d] += additional_ys[d];
327 
328   configurations_ = enumerate_attachments (end_ys);
329   for (vsize i = 0; i < note_columns_.size (); i++)
330     encompass_infos_.push_back (get_encompass_info (note_columns_[i]));
331 
332   valid_ = true;
333 
334   musical_dy_ = 0.0;
335   for (LEFT_and_RIGHT (d))
336     {
337       if (!is_broken_
338           && extremes_[d].slur_head_)
339         musical_dy_ += d
340                        * extremes_[d].slur_head_->relative_coordinate (common_[Y_AXIS], Y_AXIS);
341     }
342 
343   edge_has_beams_
344     = (extremes_[LEFT].stem_ && Stem::get_beam (extremes_[LEFT].stem_))
345       || (extremes_[RIGHT].stem_ && Stem::get_beam (extremes_[RIGHT].stem_));
346 
347   if (is_broken_)
348     musical_dy_ = 0.0;
349 }
350 
351 MAKE_SCHEME_CALLBACK (Slur, calc_control_points, 1)
352 SCM
calc_control_points(SCM smob)353 Slur::calc_control_points (SCM smob)
354 {
355   Spanner *me = unsmob<Spanner> (smob);
356 
357   Slur_score_state state;
358   state.fill (me);
359 
360   if (!state.valid_)
361     return SCM_EOL;
362   if (state.configurations_.empty ())
363     {
364       me->warning (_ ("no viable slur configuration found"));
365       return SCM_EOL;
366     }
367 
368   state.generate_curves ();
369 
370   SCM end_ys = get_property (me, "positions");
371   SCM inspect_quants = get_property (me, "inspect-quants");
372   bool debug_slurs = from_scm<bool> (me->layout ()
373                                      ->lookup_variable (ly_symbol2scm ("debug-slur-scoring")));
374 
375   if (is_number_pair (inspect_quants))
376     {
377       debug_slurs = true;
378       end_ys = inspect_quants;
379     }
380 
381   Slur_configuration *best = NULL;
382   if (is_number_pair (end_ys))
383     best = state.get_forced_configuration (from_scm<Interval> (end_ys));
384   else
385     best = state.get_best_curve ();
386 
387 #if DEBUG_SLUR_SCORING
388   if (debug_slurs)
389     {
390       string total = best->card ();
391       total += to_string (" TOTAL=%.2f idx=%zu", best->score (), best->index_);
392       set_property (me, "annotation", ly_string2scm (total));
393     }
394 #endif
395 
396   SCM controls = SCM_EOL;
397   for (int i = 4; i--;)
398     {
399       Offset o = best->curve_.control_[i]
400                  - Offset (me->relative_coordinate (state.common_[X_AXIS], X_AXIS),
401                            me->relative_coordinate (state.common_[Y_AXIS], Y_AXIS));
402       controls = scm_cons (to_scm (o), controls);
403     }
404 
405   return controls;
406 }
407 
408 Slur_configuration *
get_forced_configuration(Interval ys) const409 Slur_score_state::get_forced_configuration (Interval ys) const
410 {
411   Slur_configuration *best = NULL;
412   Real mindist = 1e6;
413   for (vsize i = 0; i < configurations_.size (); i++)
414     {
415       Real d = fabs (configurations_[i]->attachment_[LEFT][Y_AXIS] - ys[LEFT])
416                + fabs (configurations_[i]->attachment_[RIGHT][Y_AXIS] - ys[RIGHT]);
417       if (d < mindist)
418         {
419           best = configurations_[i].get ();
420           mindist = d;
421         }
422     }
423 
424   while (!best->done ())
425     best->run_next_scorer (*this);
426 
427   if (mindist > 1e5)
428     programming_error ("cannot find quant");
429 
430   return best;
431 }
432 
433 Slur_configuration *
get_best_curve() const434 Slur_score_state::get_best_curve () const
435 {
436   std::priority_queue < Slur_configuration *, std::vector<Slur_configuration *>,
437       Slur_configuration_less > queue;
438   for (vsize i = 0; i < configurations_.size (); i++)
439     queue.push (configurations_[i].get ());
440 
441   Slur_configuration *best = NULL;
442   while (true)
443     {
444       best = queue.top ();
445       if (best->done ())
446         break;
447 
448       queue.pop ();
449       best->run_next_scorer (*this);
450       queue.push (best);
451     }
452 
453   return best;
454 }
455 
456 Interval
breakable_bound_extent(Direction d) const457 Slur_score_state::breakable_bound_extent (Direction d) const
458 {
459   Grob *paper_col = slur_->get_bound (d)->get_column ();
460   Interval ret;
461   ret.set_empty ();
462 
463   extract_grob_set (slur_, "encompass-objects", extra_encompasses);
464 
465   for (vsize i = 0; i < extra_encompasses.size (); i++)
466     {
467       Item *item = dynamic_cast<Item *> (extra_encompasses[i]);
468       if (item && paper_col == item->get_column ())
469         ret.unite (robust_relative_extent (item, common_[X_AXIS], X_AXIS));
470     }
471 
472   return ret;
473 }
474 
475 /*
476   TODO: should analyse encompasses to determine sensible region, and
477   should limit slopes available.
478 */
479 
480 Drul_array<Real>
get_y_attachment_range() const481 Slur_score_state::get_y_attachment_range () const
482 {
483   Drul_array<Real> end_ys;
484   for (LEFT_and_RIGHT (d))
485     {
486       if (extremes_[d].note_column_)
487         {
488           Interval nc_extent = extremes_[d].note_column_
489                                ->extent (common_[Y_AXIS], Y_AXIS);
490           if (nc_extent.is_empty ())
491             slur_->warning (_ ("slur trying to encompass an empty note column."));
492           else
493             end_ys[d] = dir_
494                         * std::max (std::max (dir_ * (base_attachments_[d][Y_AXIS]
495                                                       + parameters_.region_size_ * dir_),
496                                               dir_ * (dir_ + nc_extent[dir_])),
497                                     dir_ * base_attachments_[-d][Y_AXIS]);
498         }
499       else if (extremes_[d].slur_head_)
500         {
501           // allow only minimal movement
502           end_ys[d] = base_attachments_[d][Y_AXIS] + 0.3 * dir_;
503         }
504       else
505         end_ys[d] = base_attachments_[d][Y_AXIS] + parameters_.region_size_ * dir_;
506     }
507 
508   return end_ys;
509 }
510 
511 bool
spanner_less(Spanner * s1,Spanner * s2)512 spanner_less (Spanner *s1, Spanner *s2)
513 {
514   Slice b1, b2;
515   for (LEFT_and_RIGHT (d))
516     {
517       b1[d] = s1->get_bound (d)->get_column ()->get_rank ();
518       b2[d] = s2->get_bound (d)->get_column ()->get_rank ();
519     }
520 
521   return b2[LEFT] <= b1[LEFT] && b2[RIGHT] >= b1[RIGHT]
522          && (b2[LEFT] != b1[LEFT] || b2[RIGHT] != b1[RIGHT]);
523 }
524 
525 Drul_array<Offset>
get_base_attachments() const526 Slur_score_state::get_base_attachments () const
527 {
528   Drul_array<Offset> base_attachment;
529   for (LEFT_and_RIGHT (d))
530     {
531       Grob *stem = extremes_[d].stem_;
532       Grob *head = extremes_[d].slur_head_;
533 
534       Real x = 0.0;
535       Real y = 0.0;
536       if (extremes_[d].note_column_)
537         {
538 
539           /*
540             fixme: X coord should also be set in this case.
541           */
542           if (stem
543               && !Stem::is_invisible (stem)
544               && extremes_[d].stem_dir_ == dir_
545               && Stem::get_beaming (stem, -d)
546               && Stem::get_beam (stem)
547               && (!spanner_less (slur_, Stem::get_beam (stem))
548                   || has_same_beam_))
549             y = extremes_[d].stem_extent_[Y_AXIS][dir_];
550           else if (head)
551             y = head->extent (common_[Y_AXIS], Y_AXIS)[dir_];
552           y += dir_ * 0.5 * staff_space_;
553 
554           y = move_away_from_staffline (y, head);
555 
556           Grob *fh = Note_column::first_head (extremes_[d].note_column_);
557           x
558             = (fh ? fh->extent (common_[X_AXIS], X_AXIS)
559                : extremes_[d].bound_->extent (common_[X_AXIS], X_AXIS))
560               .linear_combination (CENTER);
561           if (!std::isfinite (x))
562             x = extremes_[d].note_column_->extent (common_[X_AXIS], X_AXIS)
563                 .linear_combination (CENTER);
564           if (!std::isfinite (y))
565             y = extremes_[d].note_column_->extent (common_[Y_AXIS], Y_AXIS)
566                 .linear_combination (CENTER);
567         }
568       else if (head)
569         {
570           y = head->extent (common_[Y_AXIS], Y_AXIS)
571               .linear_combination (0.5 * dir_);
572 
573           // Don't "move_away_from_staffline" because that makes it
574           // harder to recognize the specific attachment point
575           x = head->extent (common_[X_AXIS], X_AXIS)[-d];
576         }
577 
578       base_attachment[d] = Offset (x, y);
579     }
580 
581   for (LEFT_and_RIGHT (d))
582     {
583       if (!extremes_[d].note_column_ && !extremes_[d].slur_head_)
584         {
585           Real x = 0;
586           Real y = 0;
587 
588           Interval ext = breakable_bound_extent (d);
589           if (ext.is_empty ())
590             ext = Axis_group_interface::
591                   generic_bound_extent (extremes_[d].bound_,
592                                         common_[X_AXIS], X_AXIS);
593           x = ext[-d];
594 
595           Grob *col = (d == LEFT) ? note_columns_[0] : note_columns_.back ();
596 
597           if (extremes_[-d].bound_ != col)
598             {
599               y = robust_relative_extent (col, common_[Y_AXIS], Y_AXIS)[dir_];
600               y += dir_ * 0.5 * staff_space_;
601 
602               if (get_grob_direction (col) == dir_
603                   && Note_column::get_stem (col)
604                   && !Stem::is_invisible (Note_column::get_stem (col)))
605                 y -= dir_ * 1.5 * staff_space_;
606             }
607           else
608             y = base_attachment[-d][Y_AXIS];
609 
610           y = move_away_from_staffline (y, col);
611 
612           base_attachment[d] = Offset (x, y);
613         }
614     }
615 
616   for (LEFT_and_RIGHT (d))
617     {
618       for (int a = X_AXIS; a < NO_AXES; a++)
619         {
620           Real &b = base_attachment[d][Axis (a)];
621 
622           if (!std::isfinite (b))
623             {
624               programming_error ("slur attachment is inf/nan");
625               b = 0.0;
626             }
627         }
628     }
629 
630   return base_attachment;
631 }
632 
633 Real
move_away_from_staffline(Real y,Grob * on_staff) const634 Slur_score_state::move_away_from_staffline (Real y,
635                                             Grob *on_staff) const
636 {
637   if (!on_staff)
638     return y;
639 
640   Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (on_staff);
641   if (!staff_symbol)
642     return y;
643 
644   Real pos
645     = (y - staff_symbol->relative_coordinate (common_[Y_AXIS],
646                                               Y_AXIS))
647       * 2.0 / staff_space_;
648 
649   if (fabs (pos - round_halfway_up (pos)) < 0.2
650       && Staff_symbol_referencer::on_staff_line (on_staff, (int) rint (pos)))
651     y += 1.5 * staff_space_ * dir_ / 10;
652 
653   return y;
654 }
655 
656 vector<Offset>
generate_avoid_offsets() const657 Slur_score_state::generate_avoid_offsets () const
658 {
659   vector<Offset> avoid;
660   vector<Grob *> encompasses = note_columns_;
661 
662   for (vsize i = 0; i < encompasses.size (); i++)
663     {
664       if (extremes_[LEFT].note_column_ == encompasses[i]
665           || extremes_[RIGHT].note_column_ == encompasses[i])
666         continue;
667 
668       Encompass_info inf (get_encompass_info (encompasses[i]));
669       Real y = dir_ * (std::max (dir_ * inf.head_, dir_ * inf.stem_));
670 
671       avoid.push_back (Offset (inf.x_, y + dir_ * parameters_.free_head_distance_));
672     }
673 
674   extract_grob_set (slur_, "encompass-objects", extra_encompasses);
675   for (vsize i = 0; i < extra_encompasses.size (); i++)
676     {
677       if (has_interface<Slur> (extra_encompasses[i]))
678         {
679           Grob *small_slur = extra_encompasses[i];
680           Bezier b = Slur::get_curve (small_slur);
681 
682           Offset z = b.curve_point (0.5);
683           z += Offset (small_slur->relative_coordinate (common_[X_AXIS], X_AXIS),
684                        small_slur->relative_coordinate (common_[Y_AXIS], Y_AXIS));
685 
686           z[Y_AXIS] += dir_ * parameters_.free_slur_distance_;
687           avoid.push_back (z);
688         }
689       else if (scm_is_eq (get_property (extra_encompasses[i], "avoid-slur"),
690                           ly_symbol2scm ("inside")))
691         {
692           Grob *g = extra_encompasses [i];
693           Interval xe = g->extent (common_[X_AXIS], X_AXIS);
694           Interval ye = g->extent (common_[Y_AXIS], Y_AXIS);
695 
696           if (!xe.is_empty ()
697               && !ye.is_empty ())
698             avoid.push_back (Offset (xe.center (), ye[dir_]));
699         }
700     }
701   return avoid;
702 }
703 
704 void
generate_curves() const705 Slur_score_state::generate_curves () const
706 {
707   Real r_0 = from_scm<double> (get_property (slur_, "ratio"), 0.33);
708   Real h_inf = staff_space_ * scm_to_double (get_property (slur_, "height-limit"));
709 
710   vector<Offset> avoid = generate_avoid_offsets ();
711   for (vsize i = 0; i < configurations_.size (); i++)
712     configurations_[i]->generate_curve (*this, r_0, h_inf, avoid);
713 }
714 
715 vector<unique_ptr<Slur_configuration>>
enumerate_attachments(Drul_array<Real> end_ys) const716                                     Slur_score_state::enumerate_attachments (Drul_array<Real> end_ys) const
717 {
718   vector<unique_ptr<Slur_configuration>> scores;
719 
720   Drul_array<Offset> os;
721   os[LEFT] = base_attachments_[LEFT];
722   Real minimum_length = staff_space_
723                         * from_scm<double> (get_property (slur_, "minimum-length"), 2.0);
724 
725   for (int i = 0; dir_ * os[LEFT][Y_AXIS] <= dir_ * end_ys[LEFT]; i++)
726     {
727       os[RIGHT] = base_attachments_[RIGHT];
728       for (int j = 0; dir_ * os[RIGHT][Y_AXIS] <= dir_ * end_ys[RIGHT]; j++)
729         {
730 
731           Drul_array<bool> attach_to_stem (false, false);
732           for (LEFT_and_RIGHT (d))
733             {
734               os[d][X_AXIS] = base_attachments_[d][X_AXIS];
735               if (extremes_[d].stem_
736                   && !Stem::is_invisible (extremes_[d].stem_)
737                   && extremes_[d].stem_dir_ == dir_)
738                 {
739                   Interval stem_y = extremes_[d].stem_extent_[Y_AXIS];
740                   stem_y.widen (0.25 * staff_space_);
741                   if (stem_y.contains (os[d][Y_AXIS]))
742                     {
743                       os[d][X_AXIS] = extremes_[d].stem_extent_[X_AXIS][-d]
744                                       - d * 0.3;
745                       attach_to_stem[d] = true;
746                     }
747                   else if (dir_ * extremes_[d].stem_extent_[Y_AXIS][dir_]
748                            < dir_ * os[d][Y_AXIS]
749                            && !extremes_[d].stem_extent_[X_AXIS].is_empty ())
750 
751                     os[d][X_AXIS] = extremes_[d].stem_extent_[X_AXIS].center ();
752                 }
753             }
754 
755           Offset dz;
756           dz = os[RIGHT] - os[LEFT];
757           if (dz[X_AXIS] < minimum_length
758               || fabs (dz[Y_AXIS] / dz[X_AXIS]) > parameters_.max_slope_)
759             {
760               for (LEFT_and_RIGHT (d))
761                 {
762                   if (extremes_[d].slur_head_
763                       && !extremes_[d].slur_head_x_extent_.is_empty ())
764                     {
765                       os[d][X_AXIS] = extremes_[d].slur_head_x_extent_.center ();
766                       attach_to_stem[d] = false;
767                     }
768                 }
769             }
770 
771           dz = (os[RIGHT] - os[LEFT]).direction ();
772           for (LEFT_and_RIGHT (d))
773             {
774               if (extremes_[d].slur_head_
775                   && !attach_to_stem[d])
776                 {
777                   /* Horizontally move tilted slurs a little.  Move
778                      more for bigger tilts.
779 
780                      TODO: parameter */
781                   os[d][X_AXIS]
782                   -= dir_ * extremes_[d].slur_head_x_extent_.length ()
783                      * dz[Y_AXIS] / 3;
784                 }
785             }
786 
787           scores.push_back (Slur_configuration::new_config (os, scores.size ()));
788 
789           os[RIGHT][Y_AXIS] += dir_ * staff_space_ / 2;
790         }
791 
792       os[LEFT][Y_AXIS] += dir_ * staff_space_ / 2;
793     }
794 
795   return scores;
796 }
797 
798 vector<Extra_collision_info>
get_extra_encompass_infos() const799 Slur_score_state::get_extra_encompass_infos () const
800 {
801   extract_grob_set (slur_, "encompass-objects", encompasses);
802   vector<Extra_collision_info> collision_infos;
803   for (vsize i = encompasses.size (); i--;)
804     {
805       if (has_interface<Slur> (encompasses[i]))
806         {
807           Spanner *small_slur = dynamic_cast<Spanner *> (encompasses[i]);
808           Bezier b = Slur::get_curve (small_slur);
809 
810           Offset relative (small_slur->relative_coordinate (common_[X_AXIS], X_AXIS),
811                            small_slur->relative_coordinate (common_[Y_AXIS], Y_AXIS));
812 
813           for (int k = 0; k < 3; k++)
814             {
815               Direction hdir = Direction (k - 1);
816 
817               /*
818                 Only take bound into account if small slur starts
819                 together with big slur.
820               */
821               if (hdir && small_slur->get_bound (hdir) != slur_->get_bound (hdir))
822                 continue;
823 
824               Offset z = b.curve_point (k / 2.0);
825               z += relative;
826 
827               Interval yext;
828               yext.set_full ();
829               yext[dir_] = z[Y_AXIS] + dir_ * thickness_ * 1.0;
830 
831               Interval xext (-1, 1);
832               xext = xext * (thickness_ * 2) + z[X_AXIS];
833               Extra_collision_info info (small_slur,
834                                          hdir,
835                                          xext,
836                                          yext,
837                                          parameters_.extra_object_collision_penalty_);
838               collision_infos.push_back (info);
839             }
840         }
841       else
842         {
843           Grob *g = encompasses [i];
844           Interval xe = g->extent (common_[X_AXIS], X_AXIS);
845           Interval ye = g->extent (common_[Y_AXIS], Y_AXIS);
846           if (has_interface<Dots> (g))
847             ye.widen (0.2);
848 
849           Real xp = 0.0;
850           Real penalty = parameters_.extra_object_collision_penalty_;
851           if (has_interface<Accidental_interface> (g))
852             {
853               penalty = parameters_.accidental_collision_;
854 
855               Rational alt = from_scm<Rational> (get_property (g, "alteration"));
856               SCM scm_style = get_property (g, "style");
857               if (!scm_is_symbol (scm_style)
858                   && !from_scm<bool> (get_property (g, "parenthesized"))
859                   && !from_scm<bool> (get_property (g, "restore-first")))
860                 {
861                   if (alt == FLAT_ALTERATION
862                       || alt == DOUBLE_FLAT_ALTERATION)
863                     xp = LEFT;
864                   else if (alt == SHARP_ALTERATION)
865                     xp = 0.5 * dir_;
866                   else if (alt == NATURAL_ALTERATION)
867                     xp = -dir_;
868                 }
869             }
870 
871           ye.widen (thickness_ * 0.5);
872           xe.widen (thickness_ * 1.0);
873           Extra_collision_info info (g, xp, xe, ye, penalty);
874           collision_infos.push_back (info);
875         }
876     }
877 
878   return collision_infos;
879 }
880 
Extra_collision_info(Grob * g,Real idx,Interval x,Interval y,Real p)881 Extra_collision_info::Extra_collision_info (Grob *g, Real idx, Interval x, Interval y, Real p)
882 {
883   idx_ = idx;
884   extents_[X_AXIS] = x;
885   extents_[Y_AXIS] = y;
886   penalty_ = p;
887   grob_ = g;
888   type_ = get_property (g, "avoid-slur");
889 }
890 
Extra_collision_info()891 Extra_collision_info::Extra_collision_info ()
892 {
893   idx_ = 0.0;
894   penalty_ = 0.;
895   grob_ = 0;
896   type_ = SCM_EOL;
897 }
898