//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2002-2011 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

#include "lyrics.h"

#include "chord.h"
#include "score.h"
#include "sym.h"
#include "system.h"
#include "xml.h"

namespace Ms {

// some useful values:
static const qreal      HALF                                = 0.5;
static const qreal      TWICE                               = 2.0;

//---------------------------------------------------------
//   searchNextLyrics
//---------------------------------------------------------

static Lyrics* searchNextLyrics(Segment* s, int staffIdx, int verse)
      {
      Lyrics* l = 0;
      while ((s = s->next1(Segment::Type::ChordRest))) {
            int strack = staffIdx * VOICES;
            int etrack = strack + VOICES;
            // search through all tracks of current staff looking for a lyric in specified verse
            for (int track = strack; track < etrack; ++track) {
                  ChordRest* cr = static_cast<ChordRest*>(s->element(track));
                  if (cr && !cr->lyricsList().isEmpty()) {
                        // cr with lyrics found, but does it have a syllable in specified verse?
                        l = cr->lyricsList().value(verse);
                        if (l)
                              break;
                        }
                  }
            if (l)
                  break;
            }
      return l;
      }

//=========================================================
//   Lyrics
//=========================================================

Lyrics::Lyrics(Score* s)
   : Text(s)
      {
      setTextStyleType(TextStyleType::LYRIC1);
      _no         = 0;
      _ticks      = 0;
      _syllabic   = Syllabic::SINGLE;
      _separator  = nullptr;
      }

Lyrics::Lyrics(const Lyrics& l)
   : Text(l)
      {
      _no         = l._no;
      _ticks      = l._ticks;
      _syllabic   = l._syllabic;
#if 0
      // if we copy lines at all, they need to be parented to new lyric
      // but they will be regenerated upon layout anyhow
      for (const Line* line : l._separator) {
            Line* nline = new Line(*line);
            nline->setParent(this);
            _separator.append(nline);
            }
#endif
      _separator = nullptr;
      }

Lyrics::~Lyrics()
      {
      if (_separator != nullptr)
            remove(_separator);
      }

//---------------------------------------------------------
//   scanElements
//---------------------------------------------------------

void Lyrics::scanElements(void* data, void (*func)(void*, Element*), bool /*all*/)
      {
      func(data, this);
/* DO NOT ADD EITHER THE LYRICSLINE OR THE SEGMENTS: segments are added through the system each belongs to;
      LyricsLine is not needed, as it is internally manged.
      if (_separator != nullptr)
            _separator->scanElements(data, func, all); */
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void Lyrics::write(Xml& xml) const
      {
      if (!xml.canWrite(this))
            return;
      xml.stag("Lyrics");
      if (_no)
            xml.tag("no", _no);
      if (_syllabic != Syllabic::SINGLE) {
            static const char* sl[] = {
                  "single", "begin", "end", "middle"
                  };
            xml.tag("syllabic", sl[int(_syllabic)]);
            }
      writeProperty(xml, P_ID::LYRIC_TICKS);

      Text::writeProperties(xml);
      xml.etag();
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Lyrics::read(XmlReader& e)
      {
      int   iEndTick = 0;           // used for backward compatibility
      Text* _verseNumber = 0;

      while (e.readNextStartElement()) {
            const QStringRef& tag(e.name());
            if (tag == "no")
                  _no = e.readInt();
            else if (tag == "syllabic") {
                  QString val(e.readElementText());
                  if (val == "single")
                        _syllabic = Syllabic::SINGLE;
                  else if (val == "begin")
                        _syllabic = Syllabic::BEGIN;
                  else if (val == "end")
                        _syllabic = Syllabic::END;
                  else if (val == "middle")
                        _syllabic = Syllabic::MIDDLE;
                  else
                        qDebug("bad syllabic property");
                  }
            else if (tag == "endTick") {          // obsolete
                  // store <endTick> tag value until a <ticks> tag has been read
                  // which positions this lyrics element in the score
                  iEndTick = e.readInt();
                  }
            else if (tag == "ticks")
                  _ticks = e.readInt();
            else if (tag == "Number") {                           // obsolete
                  Text* _verseNumber = new Text(score());
                  _verseNumber->read(e);
                  _verseNumber->setParent(this);
                  }
            else if (!Text::readProperties(e))
                  e.unknown();
            }
      // if any endTick, make it relative to current tick
      if (iEndTick) {
            _ticks = iEndTick - e.tick();
            // qDebug("Lyrics::endTick: %d  ticks %d", iEndTick, _ticks);
            }
      if (_verseNumber) {
            // TODO: add text to main text
            }

      delete _verseNumber;
      }

//---------------------------------------------------------
//   add
//---------------------------------------------------------

void Lyrics::add(Element* el)
      {
      el->setParent(this);
      if (el->type() == Element::Type::LINE)
//            _separator.append((Line*)el);           // ignore! Internally managed
            ;
      else
            qDebug("Lyrics::add: unknown element %s", el->name());
      }

//---------------------------------------------------------
//   remove
//---------------------------------------------------------

void Lyrics::remove(Element* el)
      {
      if (el->type() == Element::Type::LYRICSLINE) {
            // only if separator still exists and is the right one
            if (_separator != nullptr && el == _separator) {
                  // Lyrics::remove() and LyricsLine::removeUnmanaged() call each other;
                  // be sure each finds a clean context
                  LyricsLine* separ = _separator;
                  _separator = nullptr;
                  separ->setParent(nullptr);
                  separ->removeUnmanaged();
                  delete separ;
                  }
            }
      else
            qDebug("Lyrics::remove: unknown element %s", el->name());
      }

//---------------------------------------------------------
//   draw
//---------------------------------------------------------

/* As the lyrics line is now drawn by the system it belongs to, standard Text drawing is enough.

void Lyrics::draw(QPainter* painter) const
      {
      Text::draw(painter);
      }
*/

//---------------------------------------------------------
//   isMelisma
//---------------------------------------------------------

bool Lyrics::isMelisma() const
      {
      // entered as melisma using underscore?
      if (_ticks > 0)
            return true;

      // hyphenated?
      // if so, it is a melisma only if there is no lyric in same verse on next CR
      if (_syllabic == Syllabic::BEGIN || _syllabic == Syllabic::MIDDLE) {
            // find next CR on same track and check for existence of lyric in same verse
            ChordRest* cr = chordRest();
            Segment* s = cr->segment()->next1();
            ChordRest* ncr = s ? s->nextChordRest(cr->track()) : nullptr;
            if (ncr && !ncr->lyrics(_no))
                  return true;
            }

      // default - not a melisma
      return false;
}

//---------------------------------------------------------
//   layout
//---------------------------------------------------------

void Lyrics::layout()
      {
      // setPos(_textStyle.offset(spatium()));
      layout1();
      QPointF rp(readPos());
      if (!rp.isNull()) {
            if (score()->mscVersion() <= 114) {
                  rp.ry() += lineSpacing() + 2;
                  rp.rx() += bbox().width() * .5;
                  }
            setUserOff(rp - ipos());
            setReadPos(QPointF());
            }
      }

//---------------------------------------------------------
//   layout1
//---------------------------------------------------------

#if defined (USE_FONT_DASH_METRIC)
      static QString    g_fontFamily      = QString();
      static qreal      g_fontSize        = -1;
      static qreal      g_cachedDashY;
      static qreal      g_cachedDashLength;
   #if defined(USE_FONT_DASH_TICKNESS)
      static qreal      g_cachedDashThickness;
   #endif
#endif

void Lyrics::layout1()
      {
      setPos(textStyle().offset(spatium()));
      Text::layout1();
      if (!parent()) // palette & clone trick
          return;

      ChordRest* cr = chordRest();
      const QList<Lyrics*>* ll = &(cr->lyricsList());

      qreal lh = lineSpacing() * score()->styleD(StyleIdx::lyricsLineHeight);
      int line = ll->indexOf(this);
      qreal y  = lh * line + point(score()->styleS(StyleIdx::lyricsDistance));
      qreal x  = 0.0;

      //
      // parse leading verse number and/or punctuation, so we can factor it into layout separately
      // TODO: provide a way to disable this
      //
      bool hasNumber = false; // _verseNumber;
      qreal centerAdjust = 0.0;
      qreal leftAdjust = 0.0;
      QString s = plainText(true);
      // find:
      // 1) string of numbers and non-word characters at start of syllable
      // 2) at least one other character (indicating start of actual lyric)
      // 3) string of non-word characters at end of syllable
      //QRegularExpression leadingPattern("(^[\\d\\W]+)([^\\d\\W]+)");
      QRegularExpression punctuationPattern("(^[\\d\\W]*)([^\\d\\W].*?)([\\d\\W]*$)", QRegularExpression::UseUnicodePropertiesOption);
      QRegularExpressionMatch punctuationMatch = punctuationPattern.match(s);
      if (punctuationMatch.hasMatch()) {
            // leading and trailing punctuation
            QString lp = punctuationMatch.captured(1);
            QString tp = punctuationMatch.captured(3);
            // actual lyric
            //QString actualLyric = punctuationMatch.captured(2);
            Text leading(*this);
            leading.setPlainText(lp);
            leading.layout1();
            Text trailing(*this);
            trailing.setPlainText(tp);
            trailing.layout1();
            leftAdjust = leading.width();
            centerAdjust = leading.width() - trailing.width();
            if (!lp.isEmpty() && lp[0].isDigit())
                  hasNumber = true;
            }

      Align ta = textStyle().align();
      if (ta & AlignmentFlags::HCENTER) {
            //
            // center under notehead, not origin
            // however, lyrics that are melismas or have verse numbers will be forced to left alignment
            // TODO: provide a way to disable the automatic left alignment
            //
#if 0
            // using this value below forces left-aligned lyrics to align with left edge of whole notes
            // but that means they won't align with left-aligned lyrics on quarter notes
            qreal maxWidth;
            if (cr->type() == Element::Type::CHORD)
                  maxWidth = static_cast<Chord*>(cr)->maxHeadWidth();
            else
                  maxWidth = cr->width();       // TODO: exclude ledger line for multivoice rest?
#endif
            qreal nominalWidth = symWidth(SymId::noteheadBlack);
            if (!isMelisma() && !hasNumber)     // center under notehead
                  x += nominalWidth * .5 - cr->x() - centerAdjust * 0.5;
            else                                // force left alignment
#if 1
                  x += width() * .5 - cr->x() - leftAdjust;
#else
                  x += (width() + nominalWidth - maxWidth) * .5 - cr->x() - leftAdjust;
#endif
            }
      else if (!(ta & AlignmentFlags::RIGHT)) {
            // even for left aligned syllables, ignore leading verse numbers and/or punctuation
            x -= leftAdjust;
            }

      rxpos() += x;
      rypos() += y;

      if (_ticks > 0 || _syllabic == Syllabic::BEGIN || _syllabic == Syllabic::MIDDLE) {
            if (_separator == nullptr) {
                  _separator = new LyricsLine(score());
                  _separator->setTick(cr->tick());
                  score()->addUnmanagedSpanner(_separator);
                  }
            _separator->setParent(this);
            _separator->setTick(cr->tick());
            _separator->setTrack(track());
            _separator->setTrack2(track());
#if defined(USE_FONT_DASH_METRIC)
            // if font parameters different from font cached values, compute new dash values from font metrics
            if (textStyle().family() != g_fontFamily && textStyle().size() != g_fontSize) {
                  QFontMetricsF     fm    = textStyle().fontMetrics(spatium());
                  QRectF            r     = fm.tightBoundingRect("\u2013");   // U+2013 EN DASH
                  g_cachedDashY           = _dashY          = r.y() + (r.height() * HALF);
                  g_cachedDashLength      = _dashLength     = r.width();
   #if defined(USE_FONT_DASH_TICKNESS)
                  g_cachedDashThickness   = _dashThickness  = r.height();
   #endif
                  g_fontFamily            = textStyle().family();
                  g_fontSize              = textStyle().size();
                  }
            // if same font, use cached values
            else {
                  _dashY                  = g_cachedDashY;
                  _dashLength             = g_cachedDashLength;
   #if defined(USE_FONT_DASH_TICKNESS)
                  _dashThickness          = g_cachedDashThickness;
   #endif
                  }
#endif
            }
      else
            if (_separator != nullptr) {
                  _separator->removeUnmanaged();
                  delete _separator;
                  _separator = nullptr;
                  }
      }

//---------------------------------------------------------
//   paste
//---------------------------------------------------------

void Lyrics::paste(MuseScoreView* scoreview)
      {
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
      QClipboard::Mode mode = QClipboard::Clipboard;
#else
      QClipboard::Mode mode = QClipboard::Selection;
#endif
      QString txt = QApplication::clipboard()->text(mode);
      QStringList sl = txt.split(QRegExp("\\s+"), QString::SkipEmptyParts);
      if (sl.isEmpty())
            return;

      QStringList hyph = sl[0].split("-");
      bool minus = false;
      bool underscore = false;
      if(hyph.length() > 1) {
            insertText(hyph[0]);
            hyph.removeFirst();
            sl[0] =  hyph.join("-");
            minus = true;
            }
      else if (sl.length() > 1 && sl[1] == "-") {
            insertText(sl[0]);
            sl.removeFirst();
            sl.removeFirst();
            minus = true;
            }
      else if (sl[0].startsWith("_")) {
            sl[0].remove(0, 1);
            if (sl[0].isEmpty())
                  sl.removeFirst();
            underscore = true;
            }
      else if (sl[0].contains("_")) {
            int p = sl[0].indexOf("_");
            insertText(sl[0].left(p));
            sl[0] = sl[0].mid(p + 1);
            if (sl[0].isEmpty())
                  sl.removeFirst();
            underscore = true;
            }
      else if (sl.length() > 1 && sl[1] == "_") {
            insertText(sl[0]);
            sl.removeFirst();
            sl.removeFirst();
            underscore = true;
            }
      else {
            insertText(sl[0]);
            sl.removeFirst();
            }

      layout();
      score()->setLayoutAll(true);
      score()->end();
      txt = sl.join(" ");

      QApplication::clipboard()->setText(txt, mode);
      if (minus)
            scoreview->lyricsMinus();
      else if (underscore)
            scoreview->lyricsUnderscore();
      else
            scoreview->lyricsTab(false, false, true);
      }

//---------------------------------------------------------
//   endTick
//---------------------------------------------------------

int Lyrics::endTick() const
      {
      return segment()->tick() + ticks();
      }

//---------------------------------------------------------
//   acceptDrop
//---------------------------------------------------------

bool Lyrics::acceptDrop(const DropData& data) const
      {
      return data.element->type() == Element::Type::TEXT || Text::acceptDrop(data);
      }

//---------------------------------------------------------
//   drop
//---------------------------------------------------------

Element* Lyrics::drop(const DropData& data)
      {
      Element::Type type = data.element->type();
      if (type == Element::Type::SYMBOL || type == Element::Type::FSYMBOL) {
            Text::drop(data);
            return 0;
            }
      Text* e = static_cast<Text*>(data.element);
      if (!(type == Element::Type::TEXT && e->textStyle().name() == "Lyrics Verse Number")) {
            delete e;
            return 0;
            }
      e->setParent(this);
      score()->undoAddElement(e);
      return e;
      }

//---------------------------------------------------------
//   setNo
//---------------------------------------------------------

void Lyrics::setNo(int n)
      {
      _no = n;
      // adjust beween LYRICS1 and LYRICS2 only; keep other styles as they are
      // (_no is 0-based, so odd _no means even line and viceversa)
      if (type() == Element::Type::LYRICS) {
            if( (_no & 1) && textStyleType() == TextStyleType::LYRIC1)
                  setTextStyleType(TextStyleType::LYRIC2);
            if( !(_no & 1) && textStyleType() == TextStyleType::LYRIC2)
                  setTextStyleType(TextStyleType::LYRIC1);
            }
      }

//---------------------------------------------------------
//   endEdit
//---------------------------------------------------------

void Lyrics::endEdit()
      {
      Text::endEdit();
      score()->setLayoutAll(true);
      }

//---------------------------------------------------------
//   removeFromScore
//---------------------------------------------------------

void Lyrics::removeFromScore()
      {
      if (_separator) {
            _separator->removeUnmanaged();
            delete _separator;
            _separator = nullptr;
            }
      }

//---------------------------------------------------------
//   getProperty
//---------------------------------------------------------

QVariant Lyrics::getProperty(P_ID propertyId) const
      {
      switch (propertyId) {
            case P_ID::SYLLABIC:
                  return int(_syllabic);
            case P_ID::LYRIC_TICKS:
                  return _ticks;
            default:
                  return Text::getProperty(propertyId);
            }
      }

//---------------------------------------------------------
//   setProperty
//---------------------------------------------------------

bool Lyrics::setProperty(P_ID propertyId, const QVariant& v)
      {
      switch (propertyId) {
            case P_ID::SYLLABIC:
                  _syllabic = Syllabic(v.toInt());
                  break;
            case P_ID::LYRIC_TICKS:
                  _ticks = v.toInt();
                  break;
            default:
                  if (!Text::setProperty(propertyId, v))
                        return false;
                  break;
            }
      score()->setLayoutAll(true);
      return true;
      }

//---------------------------------------------------------
//   propertyDefault
//---------------------------------------------------------

QVariant Lyrics::propertyDefault(P_ID id) const
      {
      switch (id) {
            case P_ID::SYLLABIC:
                  return int(Syllabic::SINGLE);
            case P_ID::LYRIC_TICKS:
                  return 0;
            default: return Text::propertyDefault(id);
            }
      }

//=========================================================
//   LyricsLine
//=========================================================

LyricsLine::LyricsLine(Score* s)
  : SLine(s)
      {
      setFlags(0);

      setGenerated(true);           // no need to save it, as it can be re-generated
      setDiagonal(false);
      setLineWidth(Spatium(Lyrics::LYRICS_DASH_DEFAULT_LINE_THICKNESS));
      setAnchor(Spanner::Anchor::SEGMENT);
      _nextLyrics = nullptr;
      }

LyricsLine::LyricsLine(const LyricsLine& g)
   : SLine(g)
      {
      _nextLyrics = nullptr;
      }

//---------------------------------------------------------
//   layout
//---------------------------------------------------------

void LyricsLine::layout()
      {
      bool tempMelismaTicks = (lyrics()->ticks() == Lyrics::TEMP_MELISMA_TICKS);
      if (lyrics()->ticks() > 0) {              // melisma
            setLineWidth(Spatium(Lyrics::MELISMA_DEFAULT_LINE_THICKNESS));
            // if lyrics has a temporary one-chord melisma, set to 0 ticks (just its own chord)
            if (tempMelismaTicks)
                  lyrics()->setTicks(0);
            // Lyrics::_ticks points to the beginning of the last spanned segment,
            // but the line shall include it:
            // include the duration of this last segment in the melisma duration
            Segment* lyricsSegment = lyrics()->segment();
            int lyricsStartTick = lyricsSegment->tick();
            int lyricsEndTick = lyrics()->endTick();
            int lyricsTrack = lyrics()->track();
            // find segment with tick >= endTick
            Segment* s = lyricsSegment;
            while (s && s->tick() < lyricsEndTick)
                  s = s->nextCR(lyricsTrack, true);
            if (!s) {
                  // user probably deleted measures at end of score, leaving this melisma too long
                  // set s to last segment and reset lyricsEndTick to trigger FIXUP code below
                  s = score()->lastSegment();
                  lyricsEndTick = -1;
                  }
            Element* se = s->element(lyricsTrack);
            // everything is OK if we have reached a chord at right tick on right track
            if (s->tick() == lyricsEndTick && se && se->type() == Element::Type::CHORD) {
                  // advance to next CR, or last segment if no next CR
                  s = s->nextCR(lyricsTrack, true);
                  if (!s)
                        s = score()->lastSegment();
                  }
            else {
                  // FIXUP - lyrics tick count not valid
                  // this happens if edits to score have removed the original end segment
                  // so let's fix it here
                  // s is already pointing to segment past endTick (or to last segment)
                  // we should shorten the lyrics tick count to make this work
                  Segment* ns = s;
                  Segment* ps = s->prev1(Segment::Type::ChordRest);
                  while (ps && ps != lyricsSegment) {
                        Element* pe = ps->element(lyricsTrack);
                        // we're looking for an actual chord on this track
                        if (pe && pe->type() == Element::Type::CHORD)
                              break;
                        s = ps;
                        ps = ps->prev1(Segment::Type::ChordRest);
                        }
                  if (!ps || ps == lyricsSegment) {
                        // no valid previous CR, so try to lengthen melisma instead
                        ps = ns;
                        s = ps->nextCR(lyricsTrack, true);
                        Element* e = s ? s->element(lyricsTrack) : nullptr;
                        // check to make sure we have a chord
                        if (!e || e->type() != Element::Type::CHORD) {
                              // nothing to do but set ticks to 0
                              // this will result in melisma being deleted later
                              lyrics()->undoChangeProperty(P_ID::LYRIC_TICKS, 0);
                              setTicks(0);
                              return;
                              }
                        }
                  lyrics()->undoChangeProperty(P_ID::LYRIC_TICKS, ps->tick() - lyricsStartTick);
                  }
            setTicks(s->tick() - lyricsStartTick);
            }
      else {                                    // dash(es)
#if defined(USE_FONT_DASH_TICKNESS)
            setLineWidth(Spatium(lyrics()->dashThickness() / spatium()));
#endif
            _nextLyrics = searchNextLyrics(lyrics()->segment(), staffIdx(), lyrics()->no());
            setTick2(_nextLyrics != nullptr ? _nextLyrics->segment()->tick() : tick());
      }
      if (ticks()) {                // only do layout if some time span
            // do layout with non-0 duration
            if (tempMelismaTicks)
                  lyrics()->setTicks(Lyrics::TEMP_MELISMA_TICKS);
            SLine::layout();
            // if temp melisma and there is a first line segment,
            // extend it to be after the lyrics syllable (otherwise
            // the melisma segment will be often covered by the syllable itself)
            if (tempMelismaTicks && segments.size() > 0)
                  segmentAt(0)->rxpos2() += lyrics()->width();
            }
      }

//---------------------------------------------------------
//   createLineSegment
//---------------------------------------------------------

LineSegment* LyricsLine::createLineSegment()
      {
      LyricsLineSegment* seg = new LyricsLineSegment(score());
      seg->setTrack(track());
      seg->setColor(color());
      return seg;
      }

//---------------------------------------------------------
//   removeUnmanaged
//    same as Spanner::removeUnmanaged(), but in addition, remove from hosting Lyrics
//---------------------------------------------------------

void LyricsLine::removeUnmanaged()
      {
      Spanner::removeUnmanaged();
      if (lyrics())
            lyrics()->remove(this);
      }

//---------------------------------------------------------
//   setProperty
//---------------------------------------------------------

bool LyricsLine::setProperty(P_ID propertyId, const QVariant& v)
      {
      switch(propertyId) {
            case P_ID::SPANNER_TICKS:
                  {
                  // if parent lyrics has a melisma, change its length too
                  if (parent() && parent()->type() == Element::Type::LYRICS
                              && static_cast<Lyrics*>(parent())->ticks() > 0) {
                        int newTicks   = static_cast<Lyrics*>(parent())->ticks() + v.toInt() - ticks();
                        score()->undoChangeProperty(parent(), P_ID::LYRIC_TICKS, newTicks);
                        }
                  setTicks(v.toInt());
                  }
                  break;
            default:
                  if (!SLine::setProperty(propertyId, v))
                        return false;
                  break;
            }
      score()->setLayoutAll(true);
      return true;
      }

//=========================================================
//   LyricsLineSegment
//=========================================================

LyricsLineSegment::LyricsLineSegment(Score* s)
      : LineSegment(s)
      {
      setFlags(ElementFlag::SEGMENT | ElementFlag::ON_STAFF);
      setGenerated(true);
      }

//---------------------------------------------------------
//   layout
//---------------------------------------------------------

void LyricsLineSegment::layout()
      {
      Lyrics*     lyr;
      System*     sys;
      bool        endOfSystem = false;
      bool        isEndMelisma      = lyricsLine()->lyrics()->ticks() > 0;
      qreal       sp                = spatium();

      if (lyricsLine()->ticks() <= 0) {   // if no span,
            _numOfDashes = 0;             // nothing to draw
            return;                       // and do nothing
            }

      // HORIZONTAL POSITION
      // A) if line precedes a syllable, advance line end to right before the next syllable text
      // if not a melisma and there is a next syllable;
      if (!isEndMelisma && lyricsLine()->nextLyrics() != nullptr
                  && (spannerSegmentType() == SpannerSegmentType::END
                        || spannerSegmentType() == SpannerSegmentType::SINGLE)) {
            lyr         = lyricsLine()->nextLyrics();
            sys         = lyr->segment()->system();
            endOfSystem = (sys != system());
            // if next lyrics is on a different sytem, this line segment is at the end of its system:
            // do not adjust for next lyrics position
            if (!endOfSystem) {
                  qreal lyrX        = lyr->bbox().x();
                  qreal lyrXp       = lyr->pagePos().x();
                  qreal sysXp       = sys->pagePos().x();
                  qreal offsetX     = lyrXp - sysXp + lyrX - pos().x() - pos2().x();
                  //                    syst.rel. X pos.   | as a delta from current end pos.
                  offsetX           -= Lyrics::LYRICS_DASH_DEFAULT_PAD * sp;    // add ending padding
                  rxpos2()          += offsetX;
                  }
            }
      // B) if line follows a syllable, advance line start to after the syllable text
      lyr   = lyricsLine()->lyrics();
      sys   = lyr->segment()->system();
      if (spannerSegmentType() == SpannerSegmentType::BEGIN || spannerSegmentType() == SpannerSegmentType::SINGLE) {
            qreal lyrX        = lyr->bbox().x();
            qreal lyrXp       = lyr->pagePos().x();
            qreal lyrW        = lyr->bbox().width();
            qreal sysXp       = sys->pagePos().x();
            qreal offsetX     = lyrXp - sysXp + lyrX + lyrW - pos().x();
            //               syst.rel. X pos. | lyr.advance | as a delta from current pos.
            // add initial padding
            offsetX           += (isEndMelisma ? Lyrics::MELISMA_DEFAULT_PAD : Lyrics::LYRICS_DASH_DEFAULT_PAD) * sp;
            rxpos()           += offsetX;
            rxpos2()          -= offsetX;
            }

      // VERTICAL POSITION: at the base line of the syllable text
      if (spannerSegmentType() != SpannerSegmentType::END)
            rypos() = lyr->y();
      else {
            // use Y position of *next* syllable if there is one on same system
            Lyrics* nextLyr = searchNextLyrics(lyr->segment(), lyr->staffIdx(), lyr->no());
            if (nextLyr && nextLyr->segment()->system() == system())
                  rypos() = nextLyr->y();
            else
                  rypos() = lyr->y();
            }

      // MELISMA vs. DASHES
      if (isEndMelisma) {                 // melisma
            _numOfDashes = 1;
            rypos()  -= lyricsLine()->lineWidth().val() * sp * HALF; // let the line 'sit on' the base line
            qreal offsetX = score()->styleD(StyleIdx::minNoteDistance) * sp;
            // if final segment, extend slightly after the chord, otherwise shorten it
            rxpos2() +=
                  (spannerSegmentType() == SpannerSegmentType::BEGIN ||
                        spannerSegmentType() == SpannerSegmentType::MIDDLE)
                  ? -offsetX : +offsetX;

            }
      else {                              // dash(es)
#if defined(USE_FONT_DASH_METRIC)
            rypos()     += lyr->dashY();
            _dashLength = lyr->dashLength();
#else
            rypos()     -= lyr->bbox().height() * Lyrics::LYRICS_DASH_Y_POS_RATIO;    // set conventional dash Y pos
            _dashLength = Lyrics::LYRICS_DASH_DEFAULT_LENGTH * sp;                    // and dash length
#endif
            qreal len         = pos2().x();
            qreal minDashLen  = Lyrics::LYRICS_DASH_MIN_LENGTH * sp;
            if (len < minDashLen) {                                           // if no room for a dash
                  if (endOfSystem) {                                          //   if at end of system
                        rxpos2()          = minDashLen;                       //     draw minimal dash
                        _numOfDashes      = 1;
                        _dashLength       = minDashLen;
                        }
                  else                                                        //   if within system
                        _numOfDashes = 0;                                     //     draw no dash
                  }
            else if (len < (Lyrics::LYRICS_DASH_DEFAULT_STEP * TWICE * sp)) { // if no room for two dashes
                  _numOfDashes = 1;                                           //    draw one dash
                  if (_dashLength > len)                                      // if no room for a full dash
                        _dashLength = len;                                    //    shorten it
                  }
            else
                  _numOfDashes = len / (Lyrics::LYRICS_DASH_DEFAULT_STEP * sp);// draw several dashes
            }

      // set bounding box
      QRectF r = QRectF(0.0, 0.0, pos2().x(), pos2().y()).normalized();
      qreal lw = spatium() * lyricsLine()->lineWidth().val() * HALF;
      setbbox(r.adjusted(-lw, -lw, lw, lw));
      adjustReadPos();
      }

//---------------------------------------------------------
//   draw
//---------------------------------------------------------

void LyricsLineSegment::draw(QPainter* painter) const
      {
      if (_numOfDashes < 1)               // nothing to draw
            return;
      qreal _spatium = spatium();

      QPen pen(lyricsLine()->curColor());
      pen.setWidthF(lyricsLine()->lineWidth().val() * _spatium);
      pen.setCapStyle(Qt::FlatCap);
      painter->setPen(pen);
      if (lyricsLine()->lyrics()->ticks() > 0)           // melisma
            painter->drawLine(QPointF(), pos2());
      else {                                          // dash(es)
            qreal step  = pos2().x() / (_numOfDashes+1);
            qreal x     = step - _dashLength * HALF;
            for (int i = 0; i < _numOfDashes; i++, x += step)
                  painter->drawLine(QPointF(x, 0.0), QPointF(x + _dashLength, 0.0));
            }
      }

}

