Hi Kieren, hi Karim,

Am 09.08.24 um 16:40 schrieb Kieren MacMillan:
Just trying to find out how to manage the thickness of the edges of 
tupletbrackets and if this is possible (cf. screenshot). In the documentation 
we can adjust the thickness of the whole bracket but not just the edges. Any 
idea?
I would imagine that either TupletBracket.edge-text or TupletBracket.stencil 
could be tweaked… Unfortunately I’ve spent 15 minutes and can’t find the magic 
incantation — hopefully someone else can!

Well, trying to teach how to fish :-):

In the Internals Reference https://lilypond.org/doc/v2.23/Documentation/internals/tupletbracket we see that the TupletBracket stencil is ly:tuplet-bracket::print. Moving to the source, git grep "tuplet-bracket::print" on the command line yields:

Documentation/ly-examples/stockhausen-klavierstueckII.ly: (ly:tuplet-bracket::print grob))) lily/tuplet-bracket.cc:MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, "ly:tuplet-bracket::print", 1);
scm/define-grobs.scm:        (stencil . ,ly:tuplet-bracket::print)
scm/define-grobs.scm:        (stencil . ,ly:tuplet-bracket::print)

The second line shows that the procedure is in the C++ part of LilyPond (alas...); the third and fourth lines show _two_ grobs using this procedure as stencil. (Reading in scm/define-grobs.scm reveals that the other one besides TupletBracket is LigatureBracket).

Now in lily/tuplet-bracket.cc we find:

MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, "ly:tuplet-bracket::print", 1);
SCM
Tuplet_bracket::print (SCM smob)
{
  Spanner *me = LY_ASSERT_SMOB (Spanner, smob, 1);
  Stencil mol;

  bool tuplet_slur = scm_is_true (get_property (me, "tuplet-slur"));

  bool bracket_visibility = bracket_basic_visibility (me);
  SCM bracket_vis_prop = get_property (me, "bracket-visibility");

  /*
    Don't print a tuplet bracket and number if
    no X or Y positions were calculated.
  */
  SCM scm_x_span = get_property (me, "X-positions");
  SCM scm_positions = get_property (me, "positions");
  if (!scm_is_pair (scm_x_span) || !scm_is_pair (scm_positions))
    {
      me->suicide ();
      return SCM_EOL;
    }

  Interval x_span = from_scm (scm_x_span, Interval (0.0, 0.0));
  Interval positions = from_scm (scm_positions, Interval (0.0, 0.0));

  Drul_array<Offset> points;
  for (const auto d : {LEFT, RIGHT})
    points[d] = Offset (x_span[d], positions[d]);

  Grob *number_grob = unsmob<Grob> (get_object (me, "tuplet-number"));

  /*
    Don't print the bracket when it would be smaller than the number.
    ...Unless the user has coded bracket-visibility = #t, that is.
  */

  Real gap = 0.;
  if (bracket_visibility && number_grob)
    {
      Interval ext = number_grob->extent (number_grob, X_AXIS);
      if (!ext.is_empty ())
        {
          gap = ext.length () + 1.0;

          if (!from_scm<bool> (bracket_vis_prop) && gap > x_span.length ())
            bracket_visibility = false;
        }
    }

  if (bracket_visibility)
    {
      Drul_array<Real> zero (0, 0);

      Drul_array<Real> shorten
        = from_scm (get_property (me, "shorten-pair"), zero);

      Real ss = Staff_symbol_referencer::staff_space (me);
      scale_drul (&shorten, ss);

      Stencil brack;

      if (tuplet_slur)
        {
          brack = make_tuplet_slur (me, points[LEFT], points[RIGHT], shorten);
          mol.add_stencil (brack);
        }
      else
        {
*          Drul_array<Stencil> edge_stencils;
*
          Drul_array<Real> height
            = from_scm (get_property (me, "edge-height"), zero);
          Drul_array<Real> flare
            = from_scm (get_property (me, "bracket-flare"), zero);
          Direction dir = get_grob_direction (me);

          scale_drul (&height, -ss * dir);
          scale_drul (&flare, ss);

          const auto connect_to_other = from_scm (
            get_property (me, "connect-to-neighbor"), Drul_array<bool> ());

*          for (const auto d : {LEFT, RIGHT})
*            {
*              if (connect_to_other[d])
*                {
*                  height[d] = 0.0;
                  flare[d] = 0.0;
                  shorten[d] = 0.0;
*
*                  SCM edge_text = get_property (me, "edge-text");
*
                  if (scm_is_pair (edge_text))
                    {
*                      SCM text = index_get_cell (edge_text, d);
*                      if (Text_interface::is_markup (text))
                        {
*                          auto &es = edge_stencils[d];
                          es = Text_interface::grob_interpret_markup (me, text); *                          es.translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
                        }
                    }
                }
            }

          Stencil brack;
          if (tuplet_slur)
            brack = make_tuplet_slur (me, points[LEFT], points[RIGHT], shorten);
          else
*brack = Bracket::make_bracket (
              me, Y_AXIS, points[RIGHT] - points[LEFT], height,
              /*
                                             0.1 = more space at right due to italics                                              TODO: use italic correction of font.
                                           */
              Interval (-0.5, 0.5) * gap + 0.1, flare, shorten);*

          for (const auto d : {LEFT, RIGHT})
            {
              if (!edge_stencils[d].is_empty ())
*brack.add_stencil (edge_stencils[d])*;
            }

          mol.add_stencil (brack);
          mol.translate (points[LEFT]);
        }
    }
  return mol.smobbed_copy ();
}

I highlighted the relevant lines in bold face. These show that edge-text is only being used for TupletBrackets that connect to their neighbors (i.e. broken TupletBracket grobs), which can also be observed in the regression test input/regression/tuplet-broken.ly). I admit that this is not so clear from reading the description of edge-text in https://lilypond.org/doc/v2.23/Documentation/internals/tuplet_002dbracket_002dinterface ...

So in any case: The bracket itself gets drawn by Bracket::make_bracket, which has the following signature (found in lily/bracket.cc):

Stencil
Bracket::make_bracket (Grob *me, // for line properties.
                       Axis protrusion_axis, Offset dz, Drul_array<Real> height,
                       Interval gap, Drul_array<Real> flare,
                       Drul_array<Real> shorten)

So there's no parameter for thickness only for the side lines, and indeed line properties are taken from the grob itself for all three lines. Conclusion: There's no easy way to influence the way the bracket is drawn.

So we have to do something else. One brute-force possibility would be to just crack the finished stencil open and change the line width of the edge lines. If we look at the stencil expression of a TupletBracket, using

\version "2.24.0"

{
  \override TupletBracket.stencil =
  #(grob-transformer 'stencil
                     (lambda (grob stil)
                     (pretty-print (ly:stencil-expr stil))
                     stil))

  \tuplet 3/2 { c'4 d' c''' }
}

we find:

(translate-stencil
  (1.1742119999999971 . 3.954403577019947)
  (combine-stencil
    (draw-line
      0.16
      5.465341561078519
      2.323245692493028
      5.465341561078519
      1.6232456924930279)
    (draw-line
      0.16
      -0.1840604182213724
      -0.07824169249302755
      -0.1840604182213724
      -0.7782416924930275)
    (draw-line
      0.16
      5.465341561078519
      2.323245692493028
      3.632732022820297
      1.544227186842694)
    (draw-line
      0.16
      -0.1840604182213724
      -0.07824169249302755
      1.8326095382582217
      0.7790185056503338)))

This looks as if the left and right line are the first two lines in the stencil (those have constant x coordinates). This aligns with the definition of make_bracket that adds the corner stencils first.

So we might do:

\version "2.24.0"

{
  \override TupletBracket.stencil =
  #(grob-transformer 'stencil
                     (lambda (grob stil)
                       (let*
                        ((expr (ly:stencil-expr stil))
                         (xext (ly:stencil-extent stil X))
                         (yext (ly:stencil-extent stil Y))
                         (lines (cdaddr expr))
                         (line1 (first lines))
                         (line2 (second lines)))

                        (list-set! line1 1 0.5)
                        (list-set! line2 1 0.5)

                        (ly:make-stencil expr xext yext))))

  \tuplet 3/2 { c'4 d' c''' }
  \tuplet 3/2 { c'4 d' e' }
}

But note that this is a) an dirty hack since it assumes that the TupletBracket stencil always comes in the form we expect, b) not yet ideal since the rounded lines still have to be moved down. At least the second issue can be remedied:

\version "2.24.0"

{
  \override TupletBracket.stencil =
  #(grob-transformer 'stencil
                     (lambda (grob stil)
                       (let*
                        ((expr (ly:stencil-expr stil))
                         (xext (ly:stencil-extent stil X))
                         (yext (ly:stencil-extent stil Y))
                         (lines (cdaddr expr))
                         (line1 (first lines))
                         (thick (second line1))
                         (new-thick 0.6)  ;; adjust at will
                         (offset (/ (- new-thick thick) 2))
                         (line2 (second lines)))

                        (list-set! line1 1 new-thick)
                        (list-set! line2 1 new-thick)

                        (list-set! line1 3 (- (list-ref line1 3) offset))
                        (list-set! line2 3 (- (list-ref line2 3) offset))

                        (ly:make-stencil expr xext yext))))

  \tuplet 3/2 { c'4 d' c''' }
  \tuplet 3/2 { c'4 d' e' }
}

Left to do: We probably also should modify the extents of the resulting stencil to avoid collisions...

Lukas

Reply via email to