LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - slurtielayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 87.0 % 1966 1711
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2023 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "slurtielayout.h"
      23              : 
      24              : #include "iengravingconfiguration.h" // IWYU pragma: keep
      25              : #include "iengravingfont.h"
      26              : 
      27              : #include "dom/slur.h"
      28              : #include "dom/chord.h"
      29              : #include "dom/clef.h"
      30              : #include "dom/score.h"
      31              : #include "dom/system.h"
      32              : #include "dom/staff.h"
      33              : #include "dom/stafftype.h"
      34              : #include "dom/ledgerline.h"
      35              : #include "dom/note.h"
      36              : #include "dom/hook.h"
      37              : #include "dom/stem.h"
      38              : #include "dom/tremolotwochord.h"
      39              : #include "dom/tie.h"
      40              : #include "dom/engravingitem.h"
      41              : #include "dom/measure.h"
      42              : #include "dom/guitarbend.h"
      43              : #include "dom/laissezvib.h"
      44              : #include "dom/parenthesis.h"
      45              : #include "dom/partialtie.h"
      46              : 
      47              : #include "editing/editchord.h"
      48              : 
      49              : #include "tlayout.h"
      50              : #include "chordlayout.h"
      51              : #include "stemlayout.h"
      52              : #include "tremololayout.h"
      53              : 
      54              : #include "draw/types/transform.h"
      55              : #include "global/stringutils.h"
      56              : 
      57              : using namespace muse::draw;
      58              : using namespace mu::engraving;
      59              : using namespace mu::engraving::rendering::score;
      60              : 
      61          929 : SpannerSegment* SlurTieLayout::layoutSystem(Slur* item, System* system, LayoutContext& ctx)
      62              : {
      63          929 :     const double horizontalTieClearance = 0.35 * item->spatium();
      64          929 :     const double tieClearance = 0.65 * item->spatium();
      65          929 :     const double continuedSlurOffsetY = item->spatium() * .4;
      66          929 :     const double continuedSlurMaxDiff = 2.5 * item->spatium();
      67          929 :     Fraction stick = system->firstMeasure()->tick();
      68          929 :     Fraction etick = system->lastMeasure()->endTick();
      69              : 
      70          929 :     SlurSegment* slurSegment = toSlurSegment(TLayout::getNextLayoutSystemSegment(item, system, [item]() {
      71          471 :         return item->newSlurTieSegment();
      72              :     }));
      73              : 
      74              :     SpannerSegmentType sst;
      75          929 :     if (item->tick() >= stick) {
      76              :         //
      77              :         // this is the first call to layoutSystem,
      78              :         // processing the first line segment
      79              :         //
      80          911 :         if (item->track2() == muse::nidx) {
      81            8 :             item->setTrack2(item->track());
      82              :         }
      83          911 :         if (item->startCR() == 0 || item->startCR()->measure() == 0) {
      84            0 :             LOGD("Slur::layout(): track %zu-%zu  %p - %p tick %d-%d null start anchor",
      85            0 :                  item->track(), item->track2(), item->startCR(), item->endCR(), item->tick().ticks(), item->tick2().ticks());
      86            0 :             return slurSegment;
      87              :         }
      88         1000 :         if (item->ticks().isZero() && !(item->startCR() && item->startCR()->isGrace()) && !(item->endCR() && item->endCR()->isGrace())
      89         1000 :             && !item->isIncoming() && !item->isOutgoing() && !item->isTappingHalfSlur()) {
      90            0 :             LOGD("Slur::layout(): track %zu tick %d zero-length slur, skipping",
      91            0 :                  item->track(), item->tick().ticks());
      92            0 :             return slurSegment;
      93              :         }
      94          911 :         if (item->endCR() == 0) {         // sanity check
      95            0 :             item->setEndElement(item->startCR());
      96            0 :             item->setTick2(item->tick());
      97              :         }
      98          911 :         computeUp(item, ctx);
      99          911 :         sst = item->tick2() < etick ? SpannerSegmentType::SINGLE : SpannerSegmentType::BEGIN;
     100           18 :     } else if (item->tick() < stick && item->tick2() >= etick) {
     101            0 :         sst = SpannerSegmentType::MIDDLE;
     102              :     } else {
     103           18 :         sst = SpannerSegmentType::END;
     104              :     }
     105          929 :     slurSegment->setSpannerSegmentType(sst);
     106              : 
     107          929 :     SlurTiePos sPos;
     108          929 :     slurPos(item, &sPos, ctx);
     109          929 :     PointF p1, p2;
     110              :     // adjust for ties
     111          929 :     p1 = sPos.p1;
     112          929 :     p2 = sPos.p2;
     113          929 :     bool constrainLeftAnchor = false;
     114              : 
     115          929 :     const bool incomingPartialSlur = item->isIncoming();
     116          929 :     const bool outgoingPartialSlur = item->isOutgoing();
     117              : 
     118              :     // start anchor, either on the start chordrest or at the beginning of the system
     119          929 :     if ((sst == SpannerSegmentType::SINGLE || sst == SpannerSegmentType::BEGIN) && !incomingPartialSlur) {
     120          887 :         Chord* sc = item->startCR()->isChord() ? toChord(item->startCR()) : nullptr;
     121              : 
     122              :         // on chord
     123          887 :         if (sc) {
     124          887 :             Tie* tie = (item->up() ? sc->upNote() : sc->downNote())->tieFor();
     125          887 :             PointF endPoint = PointF();
     126          887 :             if (tie && (tie->isInside() || tie->up() != item->up() || tieSegmentShouldBeSkipped(tie))) {
     127              :                 // there is a tie that starts on this chordrest
     128            4 :                 tie = nullptr;
     129              :             }
     130          887 :             if (tie && !tie->segmentsEmpty()) {
     131            7 :                 endPoint = tie->segmentAt(0)->ups(Grip::START).pos();
     132              :             }
     133          887 :             bool adjustedVertically = false;
     134          887 :             if (tie) {
     135            7 :                 if (item->up() && tie->up()) {
     136            0 :                     if (endPoint.y() - p1.y() < tieClearance) {
     137            0 :                         p1.ry() = endPoint.y() - tieClearance;
     138            0 :                         adjustedVertically = true;
     139              :                     }
     140            7 :                 } else if (!item->up() && !tie->up()) {
     141            7 :                     if (p1.y() - endPoint.y() < tieClearance) {
     142            7 :                         p1.ry() = endPoint.y() + tieClearance;
     143            7 :                         adjustedVertically = true;
     144              :                     }
     145              :                 }
     146              :             }
     147          887 :             Tie* tieBack = sc->upNote()->tieBack();
     148          880 :             if (!adjustedVertically && tieBack && (!tieBack->isInside() || tieBack->isPartialTie())
     149         1767 :                 && tieBack->up() == item->up() && !tieSegmentShouldBeSkipped(tieBack)) {
     150              :                 // there is a tie that ends on this chordrest
     151            7 :                 tie = tieBack;
     152            7 :                 if (!tie->segmentsEmpty()) {
     153            7 :                     endPoint = tie->segmentAt(static_cast<int>(tie->nsegments()) - 1)->ups(Grip::END).pos();
     154            7 :                     if (std::abs(endPoint.y() - p1.y()) < tieClearance) {
     155            6 :                         p1.rx() += horizontalTieClearance;
     156              :                     }
     157              :                 }
     158              :             }
     159              :         }
     160          929 :     } else if (sst == SpannerSegmentType::END || sst == SpannerSegmentType::MIDDLE || incomingPartialSlur) {
     161              :         // beginning of system
     162           42 :         Measure* measure = item->startCR()->measure();
     163           42 :         ChordRest* firstCr = incomingPartialSlur ? measure->firstChordRest(item->track()) : system->firstChordRest(item->track2());
     164           42 :         const bool sameStaff = firstCr && firstCr->vStaffIdx() == slurSegment->vStaffIdx();
     165           42 :         double y = p1.y();
     166           42 :         if (firstCr && firstCr == item->endCR()) {
     167           21 :             constrainLeftAnchor = true;
     168              :         }
     169           42 :         if (firstCr && firstCr->isChord() && sameStaff) {
     170           41 :             Chord* chord = toChord(firstCr);
     171           41 :             Shape chordShape = chord->shape();
     172           82 :             chordShape.removeTypes({ ElementType::ACCIDENTAL });
     173           41 :             y = item->up() ? chordShape.top() : chordShape.bottom();
     174           41 :             bool isAboveStem = chord->stem() && chord->up() == item->up();
     175           41 :             if (!isAboveStem) {
     176           40 :                 y += continuedSlurOffsetY * (item->up() ? -1 : 1);
     177              :             }
     178           41 :         }
     179              : 
     180           42 :         double segmentX = incomingPartialSlur ? measure->firstNoteRestSegmentX(true) : system->firstNoteRestSegmentX(true);
     181           42 :         p1 = PointF(segmentX, y);
     182              : 
     183              :         // adjust for ties at the start of the system
     184           42 :         ChordRest* cr = incomingPartialSlur ? measure->firstChordRest(item->track()) : system->firstChordRest(item->track());
     185           42 :         const bool tieSameStaff = cr && cr->vStaffIdx() == item->staffIdx();
     186           42 :         if (cr && cr->isChord() && cr->tick() >= stick && cr->tick() <= etick && tieSameStaff) {
     187           41 :             Chord* c = toChord(cr);
     188           41 :             Tie* tie = nullptr;
     189           41 :             PointF endPoint;
     190           41 :             Tie* tieBack = c->upNote()->tieBack();
     191           24 :             if (tieBack && (tieBack->isPartialTie() || !tieBack->isInside()) && tieBack->up() == item->up()
     192           65 :                 && !tieSegmentShouldBeSkipped(tieBack)) {
     193              :                 // there is a tie that ends on this chordrest
     194           24 :                 if (!tieBack->segmentsEmpty()) { //Checks for spanner segment esxists
     195           24 :                     tie = tieBack;
     196           24 :                     endPoint = tie->backSegment()->ups(Grip::START).pos();
     197              :                 }
     198              :             }
     199           41 :             if (tie) {
     200           24 :                 if (tie->isPartialTie()) {
     201           24 :                     p1.rx() = endPoint.rx();
     202              :                 }
     203           24 :                 if (item->up() && tie->up()) {
     204            2 :                     if (endPoint.y() - p1.y() < tieClearance) {
     205            2 :                         p1.ry() = endPoint.y() - tieClearance;
     206              :                     }
     207           22 :                 } else if (!item->up() && !tie->up()) {
     208           22 :                     if (p1.y() - endPoint.y() < tieClearance) {
     209           21 :                         p1.ry() = endPoint.y() + tieClearance;
     210              :                     }
     211              :                 }
     212              :             }
     213              :         }
     214              :     }
     215              : 
     216              :     // end anchor
     217          929 :     if ((sst == SpannerSegmentType::SINGLE || sst == SpannerSegmentType::END) && !outgoingPartialSlur) {
     218          882 :         Chord* ec = item->endCR()->isChord() ? toChord(item->endCR()) : nullptr;
     219              : 
     220              :         // on chord
     221          882 :         if (ec) {
     222          869 :             Tie* tie = (item->up() ? ec->upNote() : ec->downNote())->tieBack();
     223          869 :             PointF endPoint;
     224          869 :             if (tie && (tie->isInside() || tie->up() != item->up() || tieSegmentShouldBeSkipped(tie))) {
     225            4 :                 tie = nullptr;
     226              :             }
     227          869 :             bool adjustedVertically = false;
     228          869 :             if (tie && !tie->segmentsEmpty()) {
     229           27 :                 endPoint = tie->segmentAt(0)->ups(Grip::END).pos();
     230           27 :                 if (item->up() && tie->up()) {
     231            0 :                     if (endPoint.y() - p2.y() < tieClearance) {
     232            0 :                         p2.ry() = endPoint.y() - tieClearance;
     233            0 :                         adjustedVertically = true;
     234              :                     }
     235           27 :                 } else if (!item->up() && !tie->up()) {
     236           27 :                     if (p2.y() - endPoint.y() < tieClearance) {
     237           27 :                         p2.ry() = endPoint.y() + tieClearance;
     238           27 :                         adjustedVertically = true;
     239              :                     }
     240              :                 }
     241              :             }
     242          869 :             Tie* tieFor = ec->upNote()->tieFor();
     243          842 :             if (!adjustedVertically && tieFor && (tieFor->isPartialTie() || !tieFor->isInside()) && tieFor->up() == item->up()
     244         1711 :                 && !tieSegmentShouldBeSkipped(tieFor)) {
     245              :                 // there is a tie that starts on this chordrest
     246            6 :                 if (!tieFor->segmentsEmpty() && std::abs(tieFor->frontSegment()->ups(Grip::START).pos().y() - p2.y()) < tieClearance) {
     247            6 :                     p2.rx() -= horizontalTieClearance;
     248              :                 }
     249              :             }
     250              :         }
     251          882 :     } else {
     252              :         // at end of system
     253           47 :         Measure* measure = item->endCR()->measure();
     254           47 :         ChordRest* lastCr = outgoingPartialSlur ? measure->lastChordRest(item->track()) : system->lastChordRest(item->track());
     255           47 :         double y = p1.y();
     256           47 :         const bool sameStaff = lastCr && lastCr->vStaffIdx() == slurSegment->vStaffIdx();
     257           47 :         if (lastCr && lastCr == item->startCR()) {
     258            2 :             y += 0.25 * item->spatium() * (item->up() ? -1 : 1);
     259           45 :         } else if (lastCr && lastCr->isChord() && sameStaff) {
     260           38 :             Chord* chord = toChord(lastCr);
     261           38 :             Shape chordShape = chord->shape();
     262           76 :             chordShape.removeTypes({ ElementType::ACCIDENTAL });
     263           38 :             y = item->up() ? chordShape.top() : chordShape.bottom();
     264           38 :             bool isAboveStem = chord->stem() && chord->up() == item->up();
     265           38 :             if (!isAboveStem) {
     266           38 :                 y += continuedSlurOffsetY * (item->up() ? -1 : 1);
     267              :             }
     268           38 :             double diff = item->up() ? y - p1.y() : p1.y() - y;
     269           38 :             if (diff > continuedSlurMaxDiff) {
     270            0 :                 y = p1.y() + (y > p1.y() ? continuedSlurMaxDiff : -continuedSlurMaxDiff);
     271              :             }
     272           38 :         }
     273              : 
     274           47 :         double endingX = endingXForOpenEndedSlurTie(outgoingPartialSlur ? measure : system->lastMeasure(), item->startCR()->vStaffIdx());
     275           47 :         p2 = PointF(endingX, y);
     276              : 
     277              :         // adjust for ties at the end of the system
     278           47 :         ChordRest* cr = outgoingPartialSlur ? measure->lastChordRest(item->track()) : system->lastChordRest(item->track());
     279           47 :         const bool tieSameStaff = cr && cr->vStaffIdx() == item->staffIdx();
     280              : 
     281           47 :         if (cr && cr->isChord() && cr->tick() >= stick && cr->tick() <= etick && tieSameStaff) {
     282           40 :             Chord* c = toChord(cr);
     283           40 :             Tie* tie = nullptr;
     284           40 :             PointF endPoint;
     285           40 :             Tie* tieFor = c->upNote()->tieFor();
     286           26 :             if (tieFor && (tieFor->isPartialTie() || !tieFor->isInside()) && tieFor->up() == item->up()
     287           66 :                 && !tieSegmentShouldBeSkipped(tieFor)) {
     288              :                 // there is a tie that starts on this chordrest
     289           26 :                 if (!tieFor->segmentsEmpty()) { //Checks is spanner segment exists
     290           26 :                     tie = tieFor;
     291           26 :                     endPoint = tie->segmentAt(0)->ups(Grip::END).pos();
     292              :                 }
     293              : 
     294           26 :                 if (outgoingPartialSlur && tie->type() == ElementType::TIE && tie->nsegments() == 1) {
     295              :                     // For partial slurs ending midway through a tie, get top of the tie shape at the slur's end X
     296            0 :                     const TieSegment* tieSeg = tie->frontSegment();
     297            0 :                     const Shape tieShape = tieSeg->shape().translate(tieSeg->pos());
     298            0 :                     if (item->up() && tie->up()) {
     299            0 :                         endPoint.ry() = p2.y() + tieShape.topDistance(p2);
     300            0 :                     } else if (!item->up() && !tie->up()) {
     301            0 :                         endPoint.ry() = p2.y() - tieShape.bottomDistance(p2);
     302              :                     }
     303            0 :                 }
     304              :             }
     305           40 :             if (tie) {
     306           26 :                 if (item->up() && tie->up()) {
     307            0 :                     if (endPoint.y() - p2.y() < tieClearance) {
     308            0 :                         p2.ry() = endPoint.y() - tieClearance;
     309              :                     }
     310           26 :                 } else if (!item->up() && !tie->up()) {
     311           26 :                     if (p2.y() - endPoint.y() < tieClearance) {
     312           26 :                         p2.ry() = endPoint.y() + tieClearance;
     313              :                     }
     314              :                 }
     315              :             }
     316              :         }
     317              :     }
     318          929 :     if (constrainLeftAnchor) {
     319           21 :         p1.ry() = p2.y() + (0.25 * item->spatium() * (item->up() ? -1 : 1));
     320              :     }
     321              : 
     322          929 :     if (item->isTappingHalfSlur()) {
     323           32 :         p1 = p2 - PointF(2.5 * item->spatium(), 0.0);
     324              :     }
     325              : 
     326          911 :     bool isHangingSlur = sst == SpannerSegmentType::BEGIN || sst == SpannerSegmentType::END || incomingPartialSlur
     327         1840 :                          || outgoingPartialSlur;
     328          929 :     if (isHangingSlur && ctx.conf().styleB(Sid::angleHangingSlursAwayFromStaff)) {
     329            0 :         adjustSlurFloatingEndPointAngles(slurSegment, p1, p2, incomingPartialSlur, outgoingPartialSlur);
     330              :     }
     331              : 
     332          929 :     layoutSegment(slurSegment,  p1, p2);
     333              : 
     334          929 :     return slurSegment;
     335              : }
     336              : 
     337            0 : void SlurTieLayout::adjustSlurFloatingEndPointAngles(SlurSegment* slurSeg, PointF& p1, PointF& p2, bool incomingPartial,
     338              :                                                      bool outgoingPartial)
     339              : {
     340            0 :     bool startIsHanging = slurSeg->spannerSegmentType() == SpannerSegmentType::END || incomingPartial;
     341            0 :     bool endIsHanging = slurSeg->spannerSegmentType() == SpannerSegmentType::BEGIN || outgoingPartial;
     342              : 
     343            0 :     IF_ASSERT_FAILED(startIsHanging != endIsHanging) {
     344            0 :         return;
     345              :     }
     346              : 
     347            0 :     bool up = slurSeg->slur()->up();
     348            0 :     const double heightDiff = 1.0 * slurSeg->spatium();
     349              : 
     350            0 :     if (startIsHanging) {
     351            0 :         double yCur = p1.y();
     352            0 :         p1.setY(up ? std::min(yCur, p2.y() - heightDiff) : std::max(yCur, p2.y() + heightDiff));
     353            0 :     } else if (endIsHanging) {
     354            0 :         double yCur = p2.y();
     355            0 :         p2.setY(up ? std::min(yCur, p1.y() - heightDiff) : std::max(yCur, p1.y() + heightDiff));
     356              :     }
     357              : }
     358              : 
     359          615 : double SlurTieLayout::endingXForOpenEndedSlurTie(const Measure* measure, staff_idx_t staffIdx)
     360              : {
     361          615 :     double endingX = measure->endingXForOpenEndedLines();
     362              : 
     363          615 :     const SegmentType segmentTypesToAvoid = SegmentType::TimeSigTypes | SegmentType::KeySigTypes | SegmentType::ClefTypes;
     364              : 
     365              :     // Find the first segment at the end of the measure which meets the criteria for the SlurTie to stop before it:
     366          615 :     const Segment* endSegment = measure->findSegmentR(segmentTypesToAvoid, measure->ticks());
     367          615 :     const EngravingItem* endItem = nullptr;
     368          617 :     while (endSegment) {
     369            2 :         if (endSegment->isType(segmentTypesToAvoid) && endSegment->enabled()) {
     370            0 :             endItem = endSegment->element(staff2track(staffIdx));
     371            0 :             if (endItem && endItem->visible() && endItem->addToSkyline()) {
     372            0 :                 break;
     373              :             }
     374              :         }
     375            2 :         endSegment = endSegment->next();
     376              :     }
     377              : 
     378          615 :     if (!endSegment || !endItem) {
     379          615 :         return endingX;
     380              :     }
     381              : 
     382            0 :     const double margin = endItem->style().styleAbsolute(Sid::lineEndToBarlineDistance);
     383            0 :     const double itemLeftX = measure->x() + endSegment->x() + endItem->ldata()->shape().translated(endItem->pos()).left();
     384              : 
     385            0 :     return std::min(endingX, itemLeftX - margin);
     386              : }
     387              : 
     388              : //---------------------------------------------------------
     389              : //   slurPos
     390              : //    calculate position of start- and endpoint of slur
     391              : //    relative to System() position
     392              : //---------------------------------------------------------
     393              : 
     394          929 : void SlurTieLayout::slurPos(Slur* item, SlurTiePos* sp, LayoutContext& ctx)
     395              : {
     396          929 :     item->stemFloated().reset();
     397          929 :     double _spatium = (item->staffType() ? item->staffType()->lineDistance().val() : 1.0) * item->spatium();
     398          929 :     const double stemSideInset = 0.5;
     399          929 :     const double stemOffsetX = 0.35;
     400          929 :     const double beamClearance = 0.35;
     401          929 :     const double beamAnchorInset = 0.15;
     402          929 :     const double straightStemXOffset = 0.5; // how far down a straight stem a slur attaches (percent)
     403          929 :     const double minOffset = 0.2;
     404              :     // hack alert!! -- fakeCutout
     405              :     // The fakeCutout const describes the slope of a line from the top of the stem to the full width of the hook.
     406              :     // this is necessary because hooks don't have SMuFL cutouts
     407              :     // Gonville and MuseJazz have really weirdly-shaped hooks compared to Leland and Bravura and Emmentaler,
     408              :     // so we need to adjust the slope of our hook-avoidance line. this will be unnecessary when hooks have
     409              :     // SMuFL anchors
     410          929 :     const std::string fontName = muse::strings::toLower(ctx.engravingFont()->name());
     411          929 :     bool bulkyHook = fontName == "gonville" || fontName == "musejazz";
     412          929 :     const double fakeCutoutSlope = bulkyHook ? 1.5 : 1.0;
     413              : 
     414          929 :     if (item->endCR() == 0) {
     415            0 :         sp->p1 = item->startCR()->pagePos();
     416            0 :         sp->p1.rx() += item->startCR()->width();
     417            0 :         sp->p2 = sp->p1;
     418            0 :         sp->p2.rx() += 5 * _spatium;
     419            0 :         sp->system1 = item->startCR()->measure()->system();
     420            0 :         sp->system2 = sp->system1;
     421            0 :         return;
     422              :     }
     423              : 
     424          929 :     bool useTablature = item->staff() && item->staff()->isTabStaff(item->endCR()->tick());
     425          929 :     bool staffHasStems = true;       // assume staff uses stems
     426          929 :     const StaffType* stt = 0;
     427          929 :     if (useTablature) {
     428           31 :         stt = item->staff()->staffType(item->tick());
     429           31 :         staffHasStems = stt->stemThrough();       // if tab with stems beside, stems do not count for slur pos
     430              :     }
     431              : 
     432              :     // start and end cr, chord, and note
     433          929 :     ChordRest* scr = item->startCR();
     434          929 :     ChordRest* ecr = item->endCR();
     435          929 :     Chord* sc = 0;
     436          929 :     Note* note1 = 0;
     437          929 :     if (scr->isChord()) {
     438          929 :         sc = toChord(scr);
     439          929 :         note1 = item->up() ? sc->upNote() : sc->downNote();
     440              :     }
     441          929 :     Chord* ec = 0;
     442          929 :     Note* note2 = 0;
     443          929 :     if (ecr->isChord()) {
     444          916 :         ec = toChord(ecr);
     445          916 :         note2 = item->up() ? ec->upNote() : ec->downNote();
     446              :     }
     447              : 
     448          929 :     sp->system1 = scr->measure()->system();
     449          929 :     sp->system2 = ecr->measure()->system();
     450              : 
     451          929 :     if (sp->system1 == 0) {
     452            0 :         LOGD("no system1");
     453            0 :         return;
     454              :     }
     455              : 
     456          929 :     sp->p1 = scr->pos() + scr->segment()->pos() + scr->measure()->pos();
     457          929 :     sp->p2 = ecr->pos() + ecr->segment()->pos() + ecr->measure()->pos();
     458          929 :     if (scr->isGrace()) {
     459           49 :         sp->p1 += scr->layoutParent()->pos();
     460              :     }
     461          929 :     if (ecr->isGrace()) {
     462           38 :         sp->p2 += ecr->layoutParent()->pos();
     463              :     }
     464              : 
     465              :     // adjust for cross-staff
     466          929 :     if (scr->vStaffIdx() != item->vStaffIdx() && sp->system1) {
     467            9 :         double diff = sp->system1->staff(scr->vStaffIdx())->y() - sp->system1->staff(item->vStaffIdx())->y();
     468            9 :         sp->p1.ry() += diff;
     469              :     }
     470          929 :     if (ecr->vStaffIdx() != item->vStaffIdx() && sp->system2) {
     471           21 :         double diff = sp->system2->staff(ecr->vStaffIdx())->y() - sp->system2->staff(item->vStaffIdx())->y();
     472           21 :         sp->p2.ry() += diff;
     473              :     }
     474              : 
     475              :     // account for centering or other adjustments (other than mirroring)
     476          929 :     if (note1 && !note1->ldata()->mirror.value()) {
     477          929 :         sp->p1.rx() += note1->x();
     478              :     }
     479          929 :     if (note2 && !note2->ldata()->mirror.value()) {
     480          916 :         sp->p2.rx() += note2->x();
     481              :     }
     482              : 
     483          929 :     PointF po = PointF();
     484              : 
     485          929 :     Stem* stem1 = sc && staffHasStems ? sc->stem() : 0;
     486          929 :     Stem* stem2 = ec && staffHasStems ? ec->stem() : 0;
     487              : 
     488              :     enum class SlurAnchor : char {
     489              :         NONE, STEM
     490              :     };
     491          929 :     SlurAnchor sa1 = SlurAnchor::NONE;
     492          929 :     SlurAnchor sa2 = SlurAnchor::NONE;
     493          929 :     if (staffHasStems) {
     494          899 :         if (sc && sc->hook() && sc->up() == item->up()) {
     495            6 :             sa1 = SlurAnchor::STEM;
     496              :         }
     497          899 :         if (scr->up() == ecr->up() && scr->up() == item->up()) {
     498          118 :             if (stem1 && !stemSideStartForBeam(item)) {
     499           91 :                 sa1 = SlurAnchor::STEM;
     500              :             }
     501          118 :             if (stem2 && !stemSideEndForBeam(item)) {
     502           91 :                 sa2 = SlurAnchor::STEM;
     503              :             }
     504          781 :         } else if (ecr->segment()->system() != scr->segment()->system()) {
     505              :             // in the case of continued slurs, we anchor to stem when necessary
     506           34 :             if (scr->up() == item->up() && stem1 && !scr->beam()) {
     507           12 :                 sa1 = SlurAnchor::STEM;
     508              :             }
     509           34 :             if (ecr->up() == item->up() && stem2 && !ecr->beam()) {
     510            0 :                 sa2 = SlurAnchor::STEM;
     511              :             }
     512              :         }
     513              :     }
     514              : 
     515          929 :     double __up = item->up() ? -1.0 : 1.0;
     516          929 :     double hw1 = note1 ? note1->tabHeadWidth(stt) : scr->width() * scr->mag();        // if stt == 0, tabHeadWidth()
     517          929 :     double hw2 = note2 ? note2->tabHeadWidth(stt) : ecr->width() * ecr->mag();        // defaults to headWidth()
     518          929 :     PointF pt;
     519          929 :     switch (sa1) {
     520          108 :     case SlurAnchor::STEM:                //sc can't be null
     521              :     {
     522              :         // place slur starting point at stem end point
     523          108 :         pt = StemLayout::stemPos(sc) - sc->pagePos() + sc->stem()->ldata()->line.p2();
     524          108 :         if (useTablature) {                           // in tabs, stems are centred on note:
     525            0 :             pt.rx() = hw1 * 0.5 + (note1 ? note1->bboxXShift() : 0.0);                      // skip half notehead to touch stem, anatoly-os: incorrect. half notehead width is not always the stem position
     526              :         }
     527              :         // clear the stem (x)
     528              :         // allow slight overlap (y)
     529              :         // don't allow overlap with hook if not disabling the autoplace checks against start/end segments in SlurSegment::layoutSegment()
     530          108 :         double yadj = -stemSideInset* sc->intrinsicMag();
     531          108 :         yadj *= _spatium * __up;
     532          108 :         double offset = std::max(stemOffsetX * sc->intrinsicMag(), minOffset);
     533          108 :         pt += PointF(offset * _spatium, yadj);
     534              :         // account for articulations
     535          108 :         fixArticulations(item, pt, sc, __up, true);
     536              :         // adjust for hook
     537          108 :         double fakeCutout = 0.0;
     538          108 :         if (!ctx.conf().styleB(Sid::useStraightNoteFlags)) {
     539          108 :             Hook* hook = sc->hook();
     540              :             // regular flags
     541              : 
     542          108 :             if (hook && hook->ldata()->bbox().translated(hook->pos()).contains(pt)) {
     543              :                 // TODO: in the utopian far future where all hooks have SMuFL cutouts, this fakeCutout business will no
     544              :                 // longer be used. for the time being fakeCutout describes a point on the line y=mx+b, out from the top of the stem
     545              :                 // where y = yadj, m = fakeCutoutSlope, and x = y/m + fakeCutout
     546            6 :                 fakeCutout = std::min(0.0, std::abs(yadj) - (hook->width() / fakeCutoutSlope));
     547            6 :                 pt.rx() = StemLayout::stemPosX(sc) - fakeCutout;
     548              :             }
     549              :         } else {
     550            0 :             Hook* hook = sc->hook();
     551              :             // straight flags
     552            0 :             if (hook && hook->ldata()->bbox().translated(hook->pos()).contains(pt)) {
     553            0 :                 double hookWidth = hook->width() * hook->mag();
     554            0 :                 pt.rx() = (hookWidth * straightStemXOffset) + (hook->pos().x() + sc->x());
     555            0 :                 if (item->up()) {
     556            0 :                     pt.ry() = sc->downNote()->pos().y() - stem1->height() - (beamClearance * _spatium * .7);
     557              :                 } else {
     558            0 :                     pt.ry() = sc->upNote()->pos().y() + stem1->height() + (beamClearance * _spatium * .7);
     559              :                 }
     560              :             }
     561              :         }
     562          108 :         sp->p1 += pt;
     563              :     }
     564          108 :     break;
     565          821 :     case SlurAnchor::NONE:
     566          821 :         break;
     567              :     }
     568          929 :     switch (sa2) {
     569           91 :     case SlurAnchor::STEM:                //ec can't be null
     570              :     {
     571           91 :         pt = StemLayout::stemPos(ec) - ec->pagePos() + ec->stem()->ldata()->line.p2();
     572           91 :         if (useTablature) {
     573            0 :             pt.rx() = hw2 * 0.5;
     574              :         }
     575              :         // don't allow overlap with beam
     576              :         double yadj;
     577           91 :         if (ec->beam() && ec->beam()->elements().front() != ec) {
     578            0 :             yadj = 0.75;
     579           91 :         } else if (ec->tremoloTwoChord() && ec->tremoloTwoChord()->chord2() == ec) {
     580            0 :             yadj = 0.75;
     581              :         } else {
     582           91 :             yadj = -stemSideInset;
     583              :         }
     584           91 :         yadj *= _spatium * __up;
     585           91 :         double offset = std::max(stemOffsetX * ec->intrinsicMag(), minOffset);
     586           91 :         pt += PointF(-offset * _spatium, yadj);
     587              :         // account for articulations
     588           91 :         fixArticulations(item, pt, ec, __up, true);
     589           91 :         sp->p2 += pt;
     590              :     }
     591           91 :     break;
     592          838 :     case SlurAnchor::NONE:
     593          838 :         break;
     594              :     }
     595              : 
     596              :     //
     597              :     // default position:
     598              :     //    horizontal: middle of notehead
     599              :     //    vertical:   _spatium * .4 above/below notehead
     600              :     //
     601              :     //------p1
     602              :     // Compute x0, y0 and stemPos
     603          929 :     if (sa1 == SlurAnchor::NONE || sa2 == SlurAnchor::NONE) {   // need stemPos if sa2 == SlurAnchor::NONE
     604          840 :         bool stemPos = false;       // p1 starts at chord stem side
     605              : 
     606              :         // default positions
     607          840 :         po.rx() = hw1 * .5 + (note1 ? note1->bboxXShift() : 0.0);
     608          840 :         if (note1) {
     609          840 :             po.ry() = note1->pos().y();
     610            0 :         } else if (item->up()) {
     611            0 :             po.ry() = scr->ldata()->bbox().top();
     612              :         } else {
     613            0 :             po.ry() = scr->ldata()->bbox().top() + scr->height();
     614              :         }
     615          840 :         double offset = useTablature ? 0.75 : 0.9;
     616          840 :         po.ry() += scr->intrinsicMag() * _spatium * offset * __up;
     617              : 
     618              :         // adjustments for stem and/or beam
     619          840 :         TremoloTwoChord* trem = sc ? sc->tremoloTwoChord() : nullptr;
     620          840 :         if (stem1 || trem) {     //sc not null
     621          766 :             Beam* beam1 = sc->beam();
     622          766 :             if (stem1 && beam1 && (beam1->elements().back() != sc) && (sc->up() == item->up())) {
     623           33 :                 TLayout::layoutBeam(beam1, ctx);
     624              :                 // start chord is beamed but not the last chord of beam group
     625              :                 // and slur direction is same as start chord (stem side)
     626              : 
     627              :                 // in these cases, layout start of slur to stem
     628           33 :                 double beamWidthSp = ctx.conf().styleS(Sid::beamWidth).val() * beam1->magS();
     629           33 :                 double offset2 = std::max(beamClearance * sc->intrinsicMag(), minOffset) * _spatium;
     630           33 :                 double sh = stem1->length() + (beamWidthSp / 2) + offset2;
     631           33 :                 if (item->up()) {
     632           26 :                     po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() - sh;
     633              :                 } else {
     634            7 :                     po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() + sh;
     635              :                 }
     636           33 :                 po.rx() = StemLayout::stemPosX(sc) + (beamAnchorInset * _spatium * sc->intrinsicMag()) + (stem1->lineWidthMag() / 2 * __up);
     637              : 
     638              :                 // account for articulations
     639           33 :                 fixArticulations(item, po, sc, __up, true);
     640              : 
     641              :                 // force end of slur to layout to stem as well,
     642              :                 // if start and end chords have same stem direction
     643           33 :                 stemPos = true;
     644          733 :             } else if (trem && trem->chord2() != sc && sc->up() == item->up()) {
     645            0 :                 TremoloLayout::layout(trem, ctx);
     646            0 :                 Note* note = item->up() ? sc->upNote() : sc->downNote();
     647            0 :                 double stemHeight = stem1 ? stem1->length() : defaultStemLengthStart(trem);
     648            0 :                 double offset2 = std::max(beamClearance * sc->intrinsicMag(), minOffset) * _spatium;
     649            0 :                 double sh = stemHeight + offset2;
     650              : 
     651            0 :                 if (item->up()) {
     652            0 :                     po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() - sh;
     653              :                 } else {
     654            0 :                     po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() + sh;
     655              :                 }
     656            0 :                 if (!stem1) {
     657            0 :                     po.rx() = note->noteheadCenterX();
     658              :                 } else {
     659            0 :                     po.rx() = StemLayout::stemPosX(sc) + (beamAnchorInset * _spatium * sc->intrinsicMag())
     660            0 :                               + (stem1->lineWidthMag() / 2. * __up);
     661              :                 }
     662            0 :                 fixArticulations(item, po, sc, __up, true);
     663              : 
     664            0 :                 stemPos = true;
     665              :             } else {
     666              :                 // start chord is not beamed or is last chord of beam group
     667              :                 // or slur direction is opposite that of start chord
     668              : 
     669              :                 // at this point slur is in default position relative to note on slur side
     670              :                 // but we may need to make further adjustments
     671              : 
     672              :                 // if stem and slur are both up
     673              :                 // we need to clear stem horizontally
     674          733 :                 double stemOffsetMag = stemOffsetX * sc->intrinsicMag();
     675          733 :                 if (sc->up() && item->up()) {
     676              :                     // stems in tab staves come from the middle of the head, which means it's much easier
     677              :                     // to just subtract an offset from the notehead center (which po already is)
     678           57 :                     if (useTablature) {
     679            0 :                         po.rx() += stemOffsetMag * _spatium;
     680              :                     } else {
     681           57 :                         po.rx() = hw1 + _spatium * stemOffsetMag;
     682              :                     }
     683              :                 }
     684              : 
     685              :                 //
     686              :                 // handle case: stem up   - stem down
     687              :                 //              stem down - stem up
     688              :                 //
     689          733 :                 if ((sc->up() != ecr->up()) && (sc->up() == item->up())) {
     690           59 :                     item->stemFloated().left = true;
     691              :                     // start and end chord have opposite direction
     692              :                     // and slur direction is same as start chord
     693              :                     // (so slur starts on stem side)
     694              : 
     695              :                     // float the start point along the stem to follow direction of movement
     696              :                     // see for example Gould p. 111
     697              : 
     698              :                     // get position of note on slur side for start & end chords
     699           59 :                     Note* n1  = sc->up() ? sc->upNote() : sc->downNote();
     700           59 :                     Note* n2  = 0;
     701           59 :                     if (ec) {
     702           59 :                         n2 = ec->up() ? ec->upNote() : ec->downNote();
     703              :                     }
     704              : 
     705              :                     // differential in note positions
     706           59 :                     double yd  = (n2 ? n2->pos().y() : ecr->pos().y()) - n1->pos().y();
     707           59 :                     yd *= .5;
     708              : 
     709              :                     // float along stem according to differential
     710           59 :                     double sh = stem1 ? stem1->height() : 0.0;
     711           59 :                     if (item->up() && yd < 0.0) {
     712           45 :                         po.ry() = std::max(po.y() + yd, sc->downNote()->pos().y() - sh - _spatium);
     713           14 :                     } else if (!item->up() && yd > 0.0) {
     714            0 :                         po.ry() = std::min(po.y() + yd, sc->upNote()->pos().y() + sh + _spatium);
     715              :                     }
     716              : 
     717              :                     // account for articulations
     718           59 :                     fixArticulations(item, po, sc, __up, true);
     719              : 
     720              :                     // we may wish to force end to align to stem as well,
     721              :                     // if it is in same direction
     722              :                     // (but it won't be, so this assignment should have no effect)
     723           59 :                     stemPos = true;
     724              :                 } else {
     725              :                     // avoid articulations
     726          674 :                     fixArticulations(item, po, sc, __up, sc->up() == item->up());
     727              :                 }
     728              :             }
     729          840 :         } else if (sc) {
     730              :             // avoid articulations
     731           74 :             fixArticulations(item, po, sc, __up, sc->up() == item->up());
     732              :         }
     733              : 
     734              :         // TODO: offset start position if there is another slur ending on this cr
     735              : 
     736          840 :         if (sa1 == SlurAnchor::NONE) {
     737          821 :             sp->p1 += po;
     738              :         }
     739              : 
     740              :         //------p2
     741          840 :         if (sa2 == SlurAnchor::NONE) {
     742              :             // default positions
     743          838 :             po.rx() = hw2 * .5 + (note2 ? note2->bboxXShift() : 0.0);
     744          838 :             if (note2) {
     745          825 :                 po.ry() = note2->pos().y();
     746           13 :             } else if (item->up()) {
     747            0 :                 po.ry() = item->endCR()->ldata()->bbox().top();
     748              :             } else {
     749           13 :                 po.ry() = item->endCR()->ldata()->bbox().top() + item->endCR()->height();
     750              :             }
     751          838 :             double offset2 = useTablature ? 0.75 : 0.9;
     752          838 :             po.ry() += ecr->intrinsicMag() * _spatium * offset2 * __up;
     753              : 
     754              :             // adjustments for stem and/or beam
     755          838 :             TremoloTwoChord* trem2 = ec ? ec->tremoloTwoChord() : nullptr;
     756          838 :             if (stem2 || trem2) {       //ec can't be null
     757          738 :                 Beam* beam2 = ec->beam();
     758           88 :                 if ((stemPos && (scr->up() == ec->up()))
     759          715 :                     || (beam2
     760          206 :                         && (!beam2->elements().empty())
     761          206 :                         && (beam2->elements().front() != ec)
     762          125 :                         && (ec->up() == item->up())
     763            4 :                         && sc && (sc->noteType() == NoteType::NORMAL)
     764              :                         )
     765         1476 :                     || (trem2 && ec->up() == item->up())
     766              :                     ) {
     767           27 :                     if (beam2) {
     768           27 :                         TLayout::layoutBeam(beam2, ctx);
     769              :                     }
     770           27 :                     if (trem2) {
     771            0 :                         TremoloLayout::layout(trem2, ctx);
     772              :                     }
     773              :                     // slur start was laid out to stem and start and end have same direction
     774              :                     // OR
     775              :                     // end chord is beamed but not the first chord of beam group
     776              :                     // and slur direction is same as end chord (stem side)
     777              :                     // and start chordrest is not a grace chord
     778              : 
     779              :                     // in these cases, layout end of slur to stem
     780           27 :                     double beamWidthSp = beam2 ? ctx.conf().styleS(Sid::beamWidth).val() : 0;
     781           27 :                     Note* note = item->up() ? ec->upNote() : ec->downNote();
     782           27 :                     double stemHeight = stem2 ? stem2->length() + (beamWidthSp / 2) : defaultStemLengthEnd(trem2);
     783           27 :                     double offset3 = std::max(beamClearance * ec->intrinsicMag(), minOffset) * _spatium;
     784           27 :                     double sh = stemHeight + offset3;
     785              : 
     786           27 :                     if (item->up()) {
     787           20 :                         po.ry() = StemLayout::stemPos(ec).y() - ec->pagePos().y() - sh;
     788              :                     } else {
     789            7 :                         po.ry() = StemLayout::stemPos(ec).y() - ec->pagePos().y() + sh;
     790              :                     }
     791           27 :                     if (!stem2) {
     792              :                         // tremolo whole notes
     793            0 :                         po.setX(note->noteheadCenterX());
     794              :                     } else {
     795           27 :                         po.setX(StemLayout::stemPosX(ec) + (stem2->lineWidthMag() / 2 * __up)
     796           27 :                                 - (beamAnchorInset * _spatium * ec->intrinsicMag()));
     797              :                     }
     798              : 
     799              :                     // account for articulations
     800           27 :                     fixArticulations(item, po, ec, __up, true);
     801              :                 } else {
     802              :                     // slur was not aligned to stem or start and end have different direction
     803              :                     // AND
     804              :                     // end chord is not beamed or is first chord of beam group
     805              :                     // or slur direction is opposite that of end chord
     806              : 
     807              :                     // if stem and slur are both down,
     808              :                     // we need to clear stem horizontally
     809          711 :                     double stemOffsetMag = stemOffsetX * ec->intrinsicMag();
     810          711 :                     if (!ec->up() && !item->up()) {
     811              :                         // stems in tab staves come from the middle of the head, which means it's much easier
     812              :                         // to just subtract an offset from the notehead center (which po already is)
     813           17 :                         if (useTablature) {
     814            0 :                             po.rx() -= stemOffsetMag * _spatium;
     815              :                         } else {
     816           17 :                             po.rx() = -_spatium * stemOffsetMag + note2->x();
     817              :                         }
     818          694 :                     } else if (useTablature && item->up() && ec->up()) {
     819              :                         // same as above
     820            0 :                         po.rx() -= _spatium * stemOffsetMag;
     821              :                     }
     822              : 
     823              :                     //
     824              :                     // handle case: stem up   - stem down
     825              :                     //              stem down - stem up
     826              :                     //
     827          711 :                     if ((scr->up() != ec->up()) && (ec->up() == item->up())) {
     828           46 :                         item->stemFloated().right = true;
     829              :                         // start and end chord have opposite direction
     830              :                         // and slur direction is same as end chord
     831              :                         // (so slur end on stem side)
     832              : 
     833              :                         // float the end point along the stem to follow direction of movement
     834              :                         // see for example Gould p. 111
     835              : 
     836           46 :                         Note* n1 = 0;
     837           46 :                         if (sc) {
     838           46 :                             n1 = sc->up() ? sc->upNote() : sc->downNote();
     839              :                         }
     840           46 :                         Note* n2 = ec->up() ? ec->upNote() : ec->downNote();
     841              : 
     842           46 :                         double yd = n2->pos().y() - (n1 ? n1->pos().y() : item->startCR()->pos().y());
     843           46 :                         yd *= .5;
     844              : 
     845           46 :                         double mh = stem2 ? stem2->height() : 0.0;
     846           46 :                         if (item->up() && yd > 0.0) {
     847           26 :                             po.ry() = std::max(po.y() - yd, ec->downNote()->pos().y() - mh - _spatium);
     848           20 :                         } else if (!item->up() && yd < 0.0) {
     849           15 :                             po.ry() = std::min(po.y() - yd, ec->upNote()->pos().y() + mh + _spatium);
     850              :                         }
     851              : 
     852              :                         // account for articulations
     853           46 :                         fixArticulations(item, po, ec, __up, true);
     854              :                     } else {
     855              :                         // avoid articulations
     856          665 :                         fixArticulations(item, po, ec, __up, ec->up() == item->up());
     857              :                     }
     858              :                 }
     859          838 :             } else if (ec) {
     860              :                 // avoid articulations
     861           87 :                 fixArticulations(item, po, ec, __up, ec->up() == item->up());
     862              :             }
     863              :             // TODO: offset start position if there is another slur ending on this cr
     864          838 :             sp->p2 += po;
     865              :         }
     866              :     }
     867              : 
     868          929 :     if (item->staffType()->isTabStaff()) {
     869           31 :         SlurTieLayout::avoidPreBendsOnTab(sc, ec, sp);
     870              :     }
     871              : 
     872          929 :     if (item->isTappingHalfSlur()) {
     873           32 :         adjustForTappingHalfSlurs(toTappingHalfSlur(item), sp, note2);
     874              :     }
     875          929 : }
     876              : 
     877           32 : void SlurTieLayout::adjustForTappingHalfSlurs(TappingHalfSlur* item, SlurTiePos* sp, Note* endNote)
     878              : {
     879           32 :     if (item->staffType()->isTabStaff()) {
     880           16 :         return;
     881              :     }
     882              : 
     883           16 :     int staffLines = endNote->staff()->lines(endNote->tick());
     884           16 :     bool noteIsInsideStaff = endNote->line() > 1 && endNote->line() < 2 * (staffLines - 1) - 1;
     885           16 :     bool noteIsOnSpace = endNote->line() % 2 != 0;
     886           16 :     if (noteIsInsideStaff && noteIsOnSpace) {
     887            0 :         sp->p2.ry() += (item->up() ? 0.2 : -0.2) * item->spatium();
     888              :     }
     889              : }
     890              : 
     891         1864 : void SlurTieLayout::fixArticulations(Slur* item, PointF& pt, Chord* c, double up, bool stemSide)
     892              : {
     893              :     //
     894              :     // handle special case of tenuto and staccato
     895              :     // yo = current offset of slur from chord position
     896              :     // return unchanged position, or position of outmost "close" articulation
     897              :     //
     898         1864 :     double slurTipToArticVertDist = c->spatium() * 0.5 * up;
     899         1864 :     double slurTipInwardAdjust = 0.1 * item->spatium();
     900         1864 :     bool start = item->startCR() && item->startCR() == c;
     901         1864 :     bool end = item->endCR() && item->endCR() == c;
     902         1964 :     for (Articulation* a : c->articulations()) {
     903          100 :         if (!a->layoutCloseToNote() || !a->addToSkyline()) {
     904           70 :             continue;
     905              :         }
     906              :         // skip if articulation on stem side but slur is not or vice versa
     907           30 :         if ((a->up() == c->up()) != stemSide) {
     908            0 :             continue;
     909              :         }
     910              :         // Correct x-position inwards
     911           30 :         Note* note = c->up() ? c->downNote() : c->upNote();
     912           30 :         pt.rx() = a->x() + 0.5 * a->width() - note->x();
     913           30 :         if (start) {
     914           13 :             pt.rx() += slurTipInwardAdjust;
     915           17 :         } else if (end) {
     916           17 :             pt.rx() -= slurTipInwardAdjust;
     917              :         }
     918              :         // Adjust y-position
     919           30 :         if (a->up()) {
     920           12 :             pt.ry() = std::min(pt.y(), a->y() + a->height() / 2 * up + slurTipToArticVertDist);
     921              :         } else {
     922           18 :             pt.ry() = std::max(pt.y(), a->y() + a->height() / 2 * up + slurTipToArticVertDist);
     923              :         }
     924              :     }
     925         1864 : }
     926              : 
     927         1900 : void SlurTieLayout::adjustEndPoints(SlurSegment* slurSeg)
     928              : {
     929         1900 :     double spatium = slurSeg->spatium();
     930         1900 :     double lw = (slurSeg->staffType() ? slurSeg->staffType()->lineDistance().val() : 1.0) * spatium;
     931         1900 :     const double staffLineMargin = 0.175 + (0.5 * slurSeg->style().styleS(Sid::staffLineWidth).val() * (spatium / lw));
     932         1900 :     PointF p1 = slurSeg->ups(Grip::START).p;
     933         1900 :     PointF p2 = slurSeg->ups(Grip::END).p;
     934              : 
     935         1900 :     double y1sp = p1.y() / lw;
     936         1900 :     double y2sp = p2.y() / lw;
     937              : 
     938              :     // point 1
     939         1900 :     const Staff* staff = slurSeg->staff();
     940         1900 :     int lines = staff ? staff->lines(slurSeg->tick()) : 0;
     941         1850 :     auto adjustPoint = [staffLineMargin](bool up, double ysp) {
     942         1850 :         double y1offset = ysp - floor(ysp);
     943         1850 :         double adjust = 0;
     944         1850 :         if (up) {
     945          964 :             if (y1offset < staffLineMargin) {
     946              :                 // endpoint too close to the line above
     947          174 :                 adjust = -(y1offset + staffLineMargin);
     948          790 :             } else if (y1offset > 1 - staffLineMargin) {
     949              :                 // endpoint too close to the line below
     950           81 :                 adjust = -(y1offset - (1 - staffLineMargin));
     951              :             }
     952              :         } else {
     953          886 :             if (y1offset < staffLineMargin) {
     954              :                 // endpoint too close to the line above
     955          239 :                 adjust = staffLineMargin - y1offset;
     956              :             }
     957          886 :             if (y1offset > 1 - staffLineMargin) {
     958              :                 // endpoint too close to the line below
     959          236 :                 adjust = (1 - y1offset) + staffLineMargin;
     960              :             }
     961              :         }
     962         1850 :         return adjust;
     963         1900 :     };
     964         1900 :     if (y1sp > -staffLineMargin && y1sp < (lines - 1) + staffLineMargin) {
     965          974 :         slurSeg->ups(Grip::START).p.ry() += adjustPoint(slurSeg->slur()->up(), y1sp) * lw;
     966              :     }
     967         1900 :     if (y2sp > -staffLineMargin && y2sp < (lines - 1) + staffLineMargin) {
     968          876 :         slurSeg->ups(Grip::END).p.ry() += adjustPoint(slurSeg->slur()->up(), y2sp) * lw;
     969              :     }
     970         1900 : }
     971              : 
     972          915 : void SlurTieLayout::avoidCollisions(SlurSegment* slurSeg, PointF& pp1, PointF& p2, PointF& p3, PointF& p4,
     973              :                                     Transform& toSystemCoordinates, double& slurAngle)
     974              : {
     975          924 :     TRACEFUNC;
     976          915 :     Slur* slur = slurSeg->slur();
     977          915 :     double spatium = slurSeg->spatium();
     978          915 :     double slurLength = std::abs(p2.x() / spatium);
     979          915 :     bool slurUp = slur->up();
     980          915 :     int upSign = slurUp ? -1 : 1;
     981              : 
     982          915 :     ChordRest* startCR = slur->startCR();
     983          915 :     ChordRest* endCR = slur->endCR();
     984              : 
     985          915 :     if (!startCR || !endCR) {
     986            0 :         return;
     987              :     }
     988              : 
     989          915 :     Shape segShapes = getSegmentShapes(slurSeg, startCR, endCR); // Shape of the music under this slur
     990          915 :     addMinClearanceToShapes(segShapes, spatium, slurUp, startCR, endCR);
     991              : 
     992          915 :     if (segShapes.empty()) {
     993           30 :         return;
     994              :     }
     995              : 
     996          885 :     const double arcClearance = -upSign* computeArcClearance(spatium, slurLength, slurAngle);  // Collision clearance at the center of the slur
     997              : 
     998              :     // balance: determines how much endpoint adjustment VS shape adjustment we will do.
     999              :     // 0 = end point is fixed, only the shape can be adjusted,
    1000              :     // 1 = shape is fixed, only end the point can be adjusted.
    1001          885 :     double leftBalance = 0.0;
    1002          885 :     double rightBalance = 0.0;
    1003          885 :     computeAdjustmentBalance(slurSeg, startCR, endCR, leftBalance, rightBalance);
    1004              : 
    1005              :     static constexpr unsigned MAX_ITER = 30;     // Max iterations allowed
    1006              : 
    1007          885 :     double step = computeAdjustmentStep(upSign, spatium, slurLength);
    1008              : 
    1009              :     // Divide slur in several rectangles to localize collisions
    1010          885 :     const unsigned npoints = 20;
    1011          885 :     std::vector<RectF> slurRects;
    1012          885 :     slurRects.reserve(npoints);
    1013              : 
    1014              :     // Define separate collision areas (left-mid-center)
    1015              :     struct SlurCollision
    1016              :     {
    1017              :         bool left = false;
    1018              :         bool mid = false;
    1019              :         bool right = false;
    1020              : 
    1021         2087 :         void reset()
    1022              :         {
    1023         2087 :             left = false;
    1024         2087 :             mid = false;
    1025         2087 :             right = false;
    1026         2087 :         }
    1027              :     };
    1028          885 :     SlurCollision collision;
    1029              : 
    1030              :     // CHECK FOR COLLISIONS
    1031          885 :     unsigned iter = 0;
    1032              :     do {
    1033         2087 :         collision.reset();
    1034              :         // Update tranform because pp1 may change
    1035         2087 :         toSystemCoordinates.reset();
    1036         2087 :         toSystemCoordinates.translate(pp1.x(), pp1.y());
    1037         2087 :         toSystemCoordinates.rotateRadians(slurAngle);
    1038              :         // Create rectangles
    1039         2087 :         slurRects.clear();
    1040         2087 :         CubicBezier clearanceBezier(PointF(0, 0), p3 + PointF(0.0, arcClearance), p4 + PointF(0.0, arcClearance), p2);
    1041        41740 :         for (unsigned i = 0; i < npoints - 1; i++) {
    1042        39653 :             PointF clearancePoint1 = clearanceBezier.pointAtPercent(double(i) / double(npoints));
    1043        39653 :             PointF clearancePoint2 = clearanceBezier.pointAtPercent(double(i + 1) / double(npoints));
    1044        39653 :             clearancePoint1 = toSystemCoordinates.map(clearancePoint1);
    1045        39653 :             clearancePoint2 = toSystemCoordinates.map(clearancePoint2);
    1046        39653 :             slurRects.push_back(RectF(clearancePoint1, clearancePoint2));
    1047              :         }
    1048              :         // Check collisions
    1049        41740 :         for (unsigned i=0; i < slurRects.size(); i++) {
    1050        39653 :             bool leftSection = i < slurRects.size() / 3;
    1051        39653 :             bool midSection = i >= slurRects.size() / 3 && i < 2 * slurRects.size() / 3;
    1052        39653 :             bool rightSection = i >= 2 * slurRects.size() / 3;
    1053        39653 :             if ((leftSection && collision.left)
    1054        38168 :                 || (midSection && collision.mid)
    1055        35998 :                 || (rightSection && collision.right)) {         // If a collision is already found in this section, no need to check again
    1056         7376 :                 continue;
    1057              :             }
    1058        46894 :             bool intersection = slur->up() ? !Shape(slurRects[i]).clearsVertically(segShapes)
    1059        32277 :                                 : !segShapes.clearsVertically(slurRects[i]);
    1060        32277 :             if (intersection) {
    1061         1961 :                 if (leftSection) {
    1062          556 :                     collision.left = true;
    1063              :                 }
    1064         1961 :                 if (midSection) {
    1065          594 :                     collision.mid = true;
    1066              :                 }
    1067         1961 :                 if (rightSection) {
    1068          811 :                     collision.right = true;
    1069              :                 }
    1070              :             }
    1071              :         }
    1072              : 
    1073              :         // In the even iterations, adjust the shape
    1074         2087 :         if (iter % 2 == 0) {
    1075              :             static constexpr double SHAPE_PREFER_CENTER_FACTOR = 2.0;
    1076         1454 :             if (collision.left) {
    1077          290 :                 double shapeLeftStep = (1 - leftBalance) * step / SHAPE_PREFER_CENTER_FACTOR;
    1078              :                 // Move left Bezier point up(/down) and outwards
    1079          290 :                 p3 += PointF(-abs(shapeLeftStep), shapeLeftStep);
    1080              :                 // and a bit also the right point to compensate asymmetry
    1081          290 :                 p4 += PointF(std::abs(shapeLeftStep), shapeLeftStep) / 2.0;
    1082              :             }
    1083         1454 :             if (collision.mid) {     // Move both Bezier points up(/down)
    1084          320 :                 double shapeLeftStep = (1 - leftBalance) * step;
    1085          320 :                 double shapeRightStep = (1 - rightBalance) * step;
    1086          320 :                 p3 += PointF(0.0, (shapeLeftStep + shapeRightStep) / 2);
    1087          320 :                 p4 += PointF(0.0, (shapeLeftStep + shapeRightStep) / 2);
    1088              :             }
    1089         1454 :             if (collision.right) {
    1090          426 :                 double shapeRightStep = (1 - rightBalance) * step / SHAPE_PREFER_CENTER_FACTOR;
    1091              :                 // Move right Bezier point up(/down) and outwards
    1092          426 :                 p4 += PointF(std::abs(shapeRightStep), shapeRightStep);
    1093              :                 // and a bit also the left point to compensate asymmetry
    1094          426 :                 p3 += PointF(-abs(shapeRightStep), shapeRightStep) / 2.0;
    1095              :             }
    1096          633 :         } else if (!slurSeg->isEndPointsEdited()) {
    1097              :             // In the odd iterations, adjust the end points
    1098              :             // Slurs steeper than 45° are gently compensated
    1099              :             static constexpr double STEEP_LIMIT = M_PI / 4;
    1100              :             // If the collision is in mid region, move more the endpoint which reduces the slur slant
    1101              :             static constexpr double SLANT_REDUCTION_ANGLE = 5 * M_PI / 180;
    1102              :             static constexpr double SLANT_REDUCTION_LENGTH = 8;
    1103          633 :             PointF p2SysCoords = toSystemCoordinates.map(p2);
    1104          633 :             double curSlant = (p2SysCoords.y() - pp1.y()) / (p2SysCoords.x() - pp1.x());
    1105          633 :             if (collision.left || collision.mid || slurAngle < -STEEP_LIMIT) {
    1106          406 :                 double endPointLeftStep = leftBalance * step;
    1107          140 :                 if (!collision.left && collision.mid && std::abs(curSlant) > SLANT_REDUCTION_ANGLE
    1108          546 :                     && slurLength > SLANT_REDUCTION_LENGTH) {
    1109           45 :                     double slantReductionRatio = std::abs(curSlant) / SLANT_REDUCTION_ANGLE;
    1110           45 :                     bool leftIsOuter = (slurUp && curSlant > 0) || (!slurUp && curSlant < 0);
    1111           45 :                     endPointLeftStep *= leftIsOuter ? (1 / slantReductionRatio) : slantReductionRatio;
    1112              :                 }
    1113              :                 // Lift the left end point, i.e. tilt the slur around p2
    1114          406 :                 double stepX = sin(slurAngle) * endPointLeftStep;
    1115          406 :                 double stepY = cos(slurAngle) * endPointLeftStep;
    1116          406 :                 PointF pp1delta = PointF(stepX, stepY);
    1117          406 :                 pp1 += PointF(0.0, endPointLeftStep);
    1118          406 :                 p3 += pp1delta * (p2.x() - p3.x()) / p2.x();
    1119          406 :                 p4 += pp1delta * (p2.x() - p4.x()) / p2.x();
    1120              :                 // All points are expressed with respect to pp1, so we need
    1121              :                 // to subtract pp1delta to avoid the whole slur moving up
    1122          406 :                 p2 -= pp1delta;
    1123          406 :                 p3 -= pp1delta;
    1124          406 :                 p4 -= pp1delta;
    1125              :             }
    1126          633 :             if (collision.right || collision.mid || slurAngle > STEEP_LIMIT) {
    1127          482 :                 double endPointRightStep = rightBalance * step;
    1128           97 :                 if (!collision.right && collision.mid && std::abs(curSlant) > SLANT_REDUCTION_ANGLE
    1129          579 :                     && slurLength > SLANT_REDUCTION_LENGTH) {
    1130           30 :                     double slantReductionRatio = std::abs(curSlant) / SLANT_REDUCTION_ANGLE;
    1131           30 :                     bool rightIsOuter = (slurUp && curSlant < 0) || (!slurUp && curSlant > 0);
    1132           30 :                     endPointRightStep *= rightIsOuter ? (1 / slantReductionRatio) : slantReductionRatio;
    1133              :                 }
    1134              :                 // Lift the right end point, i.e. tilt the slur around p1
    1135          482 :                 double stepX = sin(slurAngle) * endPointRightStep;
    1136          482 :                 double stepY = cos(slurAngle) * endPointRightStep;
    1137          482 :                 PointF p2delta = PointF(stepX, stepY);
    1138          482 :                 p2 += p2delta;
    1139          482 :                 p3 += p2delta * p3.x() / p2.x();
    1140          482 :                 p4 += p2delta * p4.x() / p2.x();
    1141              :             }
    1142              :         }
    1143              :         // Enforce non-ugliness rules
    1144              :         // 1) Slur cannot be taller than it is wide
    1145         2087 :         const double maxRelativeHeight = std::abs(p2.x());
    1146         2087 :         p3 = slur->up() ? PointF(p3.x(), std::max(p3.y(), -maxRelativeHeight)) : PointF(p3.x(), std::min(p3.y(), maxRelativeHeight));
    1147         2087 :         p4 = slur->up() ? PointF(p4.x(), std::max(p4.y(), -maxRelativeHeight)) : PointF(p4.x(), std::min(p4.y(), maxRelativeHeight));
    1148              :         // 2) Tangent rule: p3 and p4 cannot be further left than p1 nor further right than p2
    1149         2087 :         PointF p3SysCoord = toSystemCoordinates.map(p3);
    1150         2087 :         PointF p4SysCoord = toSystemCoordinates.map(p4);
    1151         2087 :         PointF p2SysCoord = toSystemCoordinates.map(p2);
    1152         2087 :         p3SysCoord = PointF(std::max(pp1.x(), p3SysCoord.x()), p3SysCoord.y());
    1153         2087 :         p3SysCoord = PointF(std::min(p2SysCoord.x(), p3SysCoord.x()), p3SysCoord.y());
    1154         2087 :         p4SysCoord = PointF(std::max(pp1.x(), p4SysCoord.x()), p4SysCoord.y());
    1155         2087 :         p4SysCoord = PointF(std::min(p2SysCoord.x(), p4SysCoord.x()), p4SysCoord.y());
    1156         2087 :         p3 = toSystemCoordinates.inverted().map(p3SysCoord);
    1157         2087 :         p4 = toSystemCoordinates.inverted().map(p4SysCoord);
    1158              : 
    1159         2087 :         ++iter;
    1160         2087 :     } while ((collision.left || collision.mid || collision.right) && iter < MAX_ITER);
    1161          945 : }
    1162              : 
    1163          915 : Shape SlurTieLayout::getSegmentShapes(SlurSegment* slurSeg, ChordRest* startCR, ChordRest* endCR)
    1164              : {
    1165          915 :     Shape segShapes;
    1166              : 
    1167              :     // Determine start and end segments for collision checks
    1168          915 :     Segment* startSeg = nullptr;
    1169          915 :     if (slurSeg->isSingleBeginType()) {
    1170          901 :         if (startCR->isChord() && toChord(startCR)->isGraceAfter()) {
    1171              :             // if this is a grace-note-after, the shape is stored the *appended* segment
    1172            4 :             Chord* parent = toChord(startCR->ownershipParent());
    1173            4 :             if (parent) {
    1174            4 :                 startSeg = parent->graceNotesAfter().appendedSegment();
    1175              :             }
    1176              :         } else {
    1177          897 :             startSeg = startCR->segment(); // first of the slur
    1178              :         }
    1179              :     } else {
    1180           14 :         startSeg = slurSeg->system()->firstMeasure()->findFirstR(SegmentType::ChordRest, Fraction(0, 0)); // first of the system
    1181              :     }
    1182              : 
    1183          915 :     Segment* endSeg = nullptr;
    1184          915 :     if (slurSeg->isSingleEndType()) {
    1185          899 :         if (endCR->isChord() && toChord(endCR)->isGraceAfter()) {
    1186              :             // if this is a grace-note-after, the shape is stored the *appended* segment
    1187           17 :             Chord* parent = toChord(endCR->ownershipParent());
    1188           17 :             if (parent) {
    1189           17 :                 endSeg = parent->graceNotesAfter().appendedSegment();
    1190              :             }
    1191              :         } else {
    1192          882 :             endSeg = endCR->segment(); // last of the slur
    1193              :         }
    1194              :     } else {
    1195           16 :         endSeg = slurSeg->system()->lastMeasure()->last(); // last of the system
    1196              :     }
    1197              : 
    1198          915 :     if (!startSeg || !endSeg) {
    1199            0 :         return segShapes;
    1200              :     }
    1201              : 
    1202         4412 :     for (Segment* seg = startSeg; seg && (seg->isBefore(endSeg) || seg == endSeg); seg = seg->next1enabled()) {
    1203         3497 :         if (seg->isType(SegmentType::BarLineTypes) || seg->isBreathType() || seg->hasTimeSigAboveStaves() || seg->isTimeTickType()) {
    1204          261 :             continue;
    1205              :         }
    1206         3236 :         segShapes.add(getSegmentShape(slurSeg, seg, startCR, endCR));
    1207              :     }
    1208              : 
    1209          915 :     return segShapes;
    1210            0 : }
    1211              : 
    1212         3236 : Shape SlurTieLayout::getSegmentShape(SlurSegment* slurSeg, Segment* seg, ChordRest* startCR, ChordRest* endCR)
    1213              : {
    1214         3236 :     Slur* slur = slurSeg->slur();
    1215         3236 :     staff_idx_t startStaffIdx = startCR->staffIdx();
    1216         3236 :     staff_idx_t endStaffIdx = endCR->staffIdx();
    1217         3236 :     Shape segShape = seg->staffShape(startStaffIdx).translated(seg->pos() + seg->measure()->pos());
    1218              : 
    1219         3236 :     bool crossStaffSlur = slur->isCrossStaff() && seg != startCR->segment();
    1220              : 
    1221              :     // If cross-staff, also add the shape of second staff
    1222         3236 :     if (crossStaffSlur) {
    1223          140 :         endStaffIdx = (endCR->staffIdx() != startStaffIdx) ? endCR->staffIdx() : endCR->vStaffIdx();
    1224          140 :         SysStaff* startStaff = slurSeg->system()->staves().at(startStaffIdx);
    1225          140 :         SysStaff* endStaff = slurSeg->system()->staves().at(endStaffIdx);
    1226          140 :         double dist = endStaff->y() - startStaff->y();
    1227          140 :         Shape secondStaffShape = seg->staffShape(endStaffIdx).translated(seg->pos() + seg->measure()->pos()); // translate horizontally
    1228          140 :         secondStaffShape.translate(PointF(0.0, dist)); // translate vertically
    1229          140 :         segShape.add(secondStaffShape);
    1230          140 :     }
    1231              : 
    1232        16408 :     for (track_idx_t track = staff2track(startStaffIdx); track < staff2track(endStaffIdx, VOICES); ++track) {
    1233        13172 :         EngravingItem* e = seg->element(track);
    1234        13172 :         if (!e || !e->isChordRest()) {
    1235         9845 :             continue;
    1236              :         }
    1237         3327 :         if (e->vStaffIdx() != slurSeg->vStaffIdx() && !crossStaffSlur) {
    1238           41 :             continue;
    1239              :         }
    1240              :         // Gets tie and 2 note tremolo shapes
    1241         3286 :         if (e->isChord()) {
    1242         3172 :             Chord* chord = toChord(e);
    1243         3172 :             if (chord->tremoloTwoChord()) {
    1244           40 :                 segShape.add(chord->tremoloTwoChord()->shape());
    1245              :             }
    1246         6562 :             for (Note* note : toChord(e)->notes()) {
    1247         3390 :                 Tie* tieFor = note->tieFor();
    1248         3390 :                 Tie* tieBack = note->tieBack();
    1249         3390 :                 if (tieFor && tieFor->up() == slur->up() && !tieFor->segmentsEmpty()) {
    1250           73 :                     TieSegment* tieSegment = tieFor->frontSegment();
    1251           73 :                     if (tieSegment->isSingleBeginType()) {
    1252           73 :                         segShape.add(tieSegment->shape());
    1253              :                     }
    1254              :                 }
    1255         3390 :                 if (tieBack && tieBack->up() == slur->up() && !tieBack->segmentsEmpty()) {
    1256           72 :                     TieSegment* tieSegment = tieBack->backSegment();
    1257           72 :                     if (tieSegment->isEndType()) {
    1258            2 :                         segShape.add(tieSegment->shape());
    1259              :                     }
    1260              :                 }
    1261              :             }
    1262              :         }
    1263              :     }
    1264              : 
    1265              :     // Remove items that the slur shouldn't try to avoid
    1266         3236 :     segShape.remove_if([&](ShapeElement& shapeEl) {
    1267        11299 :         if (!shapeEl.item() || !shapeEl.item()->ownershipParent() || !shapeEl.item()->visible()) {
    1268          104 :             return true;
    1269              :         }
    1270        11195 :         const EngravingItem* item = shapeEl.item();
    1271        11195 :         const EngravingObject* parent = item->ownershipParent();
    1272              :         // Don't remove arpeggio starting on a different voice and ending on the same voice as endCR when slur is on the outside
    1273        22390 :         if ((item->isArpeggio() || item->isChordBracket()) && (endCR->track() == toArpeggio(item)->endTrack())
    1274            0 :             && endCR->tick() == item->tick()
    1275        22390 :             && (!slur->up() && toArpeggio(item)->span() > 1)) {
    1276            0 :             return false;
    1277              :         }
    1278              : 
    1279              :         // Its own startCR or items belonging to it, lyrics, fingering, ledger lines, articulation on endCR
    1280        11195 :         if (item == startCR || parent == startCR || item->isTextBase() || item->isLedgerLine()
    1281        22390 :             || (item->isArticulationFamily() && parent == endCR) || item->isBend() || item->isTappingHalfSlurSegment()) {
    1282         3068 :             return true;
    1283              :         }
    1284              :         // Ornament accidentals on start or end chord
    1285         8127 :         if (item->isAccidental() && parent->isOrnament()) {
    1286            0 :             const EngravingObject* parentParent = parent->ownershipParent();
    1287            0 :             if (parentParent && (parentParent == startCR || parentParent == endCR)) {
    1288            0 :                 return true;
    1289              :             }
    1290              :         }
    1291              :         // Items that are on the start segment but in a different voice
    1292         8987 :         if ((item->tick() == startCR->tick() && item->track() != startCR->track())
    1293         8987 :             || (item->tick() == endCR->tick() && item->track() != endCR->track())) {
    1294          417 :             return true;
    1295              :         }
    1296              :         // Edge-case: multiple voices and slur is on the inside
    1297         7710 :         if (item->vStaffIdx() == startCR->staffIdx()
    1298        15132 :             && ((!slur->up() && item->track() > startCR->track()) // slur-down: ignore lower voices
    1299         7422 :                 || (slur->up() && item->track() < startCR->track()))) { // slur-up: ignore higher voices
    1300           22 :             return true;
    1301              :         }
    1302              :         // Remove arpeggios spanning more than 1 voice starting on endCR's voice when the slur is on the inside
    1303        15376 :         if ((item->isArpeggio() || item->isChordBracket())
    1304        15376 :             && (endCR->track() != item->track() || (!slur->up() && toArpeggio(item)->span() > 1))) {
    1305            0 :             return true;
    1306              :         }
    1307              :         // Ignore big time signatures
    1308         7688 :         if (item->isTimeSig() && toTimeSig(item)->timeSigPlacement() != TimeSigPlacement::NORMAL) {
    1309            0 :             return true;
    1310              :         }
    1311              :         // Ignore fermatas
    1312         7688 :         if (item->isFermata()) {
    1313           17 :             return true;
    1314              :         }
    1315              :         // Ignore fret diagrams
    1316         7671 :         if (item->isFretDiagram()) {
    1317            0 :             return true;
    1318              :         }
    1319              : 
    1320         7671 :         if (item->isNote() && toNote(item)->shouldHideFret()) {
    1321            0 :             return true;
    1322              :         }
    1323              : 
    1324         7671 :         return false;
    1325              :     });
    1326              : 
    1327         3236 :     return segShape;
    1328            0 : }
    1329              : 
    1330          915 : void SlurTieLayout::addMinClearanceToShapes(Shape& segShapes, double spatium, bool slurUp, const ChordRest* startCR, const ChordRest* endCR)
    1331              : {
    1332          915 :     const double noteClearance = 0.4 * spatium;
    1333          915 :     const double articulationClearance = 0.20 * spatium;
    1334          915 :     const double itemClearance = 0.1 * spatium;
    1335              : 
    1336         8586 :     for (ShapeElement& shapeEl : segShapes.elements()) {
    1337         7671 :         const EngravingItem* item = shapeEl.item();
    1338         7671 :         if (!item || item->parent() == startCR || item->parent() == endCR) {
    1339         1780 :             continue;
    1340              :         }
    1341              : 
    1342         5891 :         double clearance = 0.0;
    1343         5891 :         switch (item->type()) {
    1344         1541 :         case ElementType::NOTE:
    1345         1541 :             clearance = noteClearance;
    1346         1541 :             break;
    1347           74 :         case ElementType::ARTICULATION:
    1348              :         case ElementType::ORNAMENT:
    1349              :         case ElementType::TAPPING:
    1350           74 :             clearance = articulationClearance;
    1351           74 :             break;
    1352         4276 :         default:
    1353         4276 :             clearance = itemClearance;
    1354         4276 :             break;
    1355              :         }
    1356              : 
    1357         5891 :         slurUp ? shapeEl.adjust(0.0, -clearance, 0.0, 0.0) : shapeEl.adjust(0.0, 0.0, 0.0, clearance);
    1358              :     }
    1359          915 : }
    1360              : 
    1361          885 : double SlurTieLayout::computeArcClearance(double spatium, double slurLength, double slurAngle)
    1362              : {
    1363              :     static constexpr double CLEARANCE_TO_LENGTH_RATIO = 0.04;
    1364              :     static constexpr double MAX_CLEARANCE = 0.5;
    1365              :     static constexpr double MIN_CLEARANCE = 0.1;
    1366              : 
    1367          885 :     double clearance = CLEARANCE_TO_LENGTH_RATIO * slurLength * std::pow(cos(slurAngle), 2);
    1368          885 :     clearance = std::clamp(clearance, MIN_CLEARANCE, MAX_CLEARANCE);
    1369              : 
    1370          885 :     return clearance * spatium;
    1371              : }
    1372              : 
    1373          885 : void SlurTieLayout::computeAdjustmentBalance(SlurSegment* slurSeg, const ChordRest* startCR, const ChordRest* endCR,  double& leftBalance,
    1374              :                                              double& rightBalance)
    1375              : {
    1376          885 :     Slur* slur = slurSeg->slur();
    1377          885 :     if (slurSeg->isSingleBeginType() && !slur->stemFloated().left) {
    1378          820 :         if (slurSeg->isBeginType() || hasArticulationAbove(slurSeg, startCR) || slur->isCrossStaff()) {
    1379           30 :             leftBalance = 0.1;
    1380              :         } else {
    1381          790 :             leftBalance = 0.4;
    1382              :         }
    1383              :     } else {
    1384           65 :         leftBalance = 0.9;
    1385              :     }
    1386              : 
    1387          885 :     if (slurSeg->isSingleEndType() && !slur->stemFloated().right) {
    1388          825 :         if (slurSeg->isEndType() || hasArticulationAbove(slurSeg, endCR) || slur->isCrossStaff()) {
    1389           32 :             rightBalance = 0.1;
    1390              :         } else {
    1391          793 :             rightBalance = 0.4;
    1392              :         }
    1393              :     } else {
    1394           60 :         rightBalance = 0.9;
    1395              :     }
    1396          885 : }
    1397              : 
    1398         1619 : bool SlurTieLayout::hasArticulationAbove(SlurSegment* slurSeg, const ChordRest* chordRest)
    1399              : {
    1400         1619 :     IF_ASSERT_FAILED(chordRest) {
    1401            0 :         return false;
    1402              :     }
    1403              : 
    1404         1619 :     if (!chordRest->isChord()) {
    1405           12 :         return false;
    1406              :     }
    1407              : 
    1408         1607 :     const Chord* chord = toChord(chordRest);
    1409              : 
    1410         1607 :     bool slurUp = slurSeg->slur()->up();
    1411         1641 :     for (const Articulation* artic : chord->articulations()) {
    1412           36 :         if (!artic->layoutCloseToNote() && artic->up() == slurUp) {
    1413            2 :             return true;
    1414              :         }
    1415              :     }
    1416              : 
    1417         1605 :     if (Ornament* ornament = chord->findOrnament()) {
    1418            0 :         if (ornament->up() == slurUp) {
    1419            0 :             return true;
    1420              :         }
    1421              :     }
    1422              : 
    1423         1605 :     return false;
    1424              : }
    1425              : 
    1426          885 : double SlurTieLayout::computeAdjustmentStep(int upSign, double spatium, double slurLength)
    1427              : {
    1428          885 :     double step = upSign * 0.30 * spatium;
    1429              : 
    1430              :     static constexpr double LONG_SLUR_LIMIT = 16.0; // in spaces
    1431          885 :     if (slurLength > LONG_SLUR_LIMIT) {
    1432          216 :         step *= slurLength / LONG_SLUR_LIMIT;
    1433          216 :         step = std::min(step, 1.5 * spatium);
    1434              :     }
    1435              : 
    1436          885 :     return step;
    1437              : }
    1438              : 
    1439          232 : bool SlurTieLayout::stemSideForBeam(Slur* slur, bool start)
    1440              : {
    1441              :     // determines if the anchor point is exempted from the stem inset due to beams or tremolos.
    1442          232 :     if (!slur) {
    1443            0 :         return false;
    1444              :     }
    1445              : 
    1446          232 :     ChordRest* cr = start ? slur->startCR() : slur->endCR();
    1447          232 :     Chord* c = toChord(cr);
    1448          232 :     bool adjustForBeam = cr && cr->beam() && cr->up() == slur->up();
    1449          232 :     if (start) {
    1450          116 :         adjustForBeam = adjustForBeam && cr->beam()->elements().back() != cr;
    1451              :     } else {
    1452          116 :         adjustForBeam = adjustForBeam && cr->beam()->elements().front() != cr;
    1453              :     }
    1454          232 :     if (adjustForBeam) {
    1455           50 :         return true;
    1456              :     }
    1457              : 
    1458          182 :     bool adjustForTrem = false;
    1459          182 :     TremoloTwoChord* trem = c ? c->tremoloTwoChord() : nullptr;
    1460          182 :     adjustForTrem = trem && trem->up() == slur->up();
    1461          182 :     if (start) {
    1462           91 :         adjustForTrem = adjustForTrem && trem->chord2() != c;
    1463              :     } else {
    1464           91 :         adjustForTrem = adjustForTrem && trem->chord1() != c;
    1465              :     }
    1466          182 :     return adjustForTrem;
    1467              : }
    1468              : 
    1469          921 : bool SlurTieLayout::isOverBeams(Slur* slur)
    1470              : {
    1471              :     // returns true if all the chords spanned by the slur are beamed, and all beams are on the same side of the slur
    1472          921 :     const ChordRest* startCR = slur->startCR();
    1473          921 :     const ChordRest* endCR = slur->endCR();
    1474          921 :     if (!startCR || !endCR) {
    1475            0 :         return false;
    1476              :     }
    1477          921 :     if (startCR->track() != endCR->track()
    1478          921 :         || startCR->tick() >= endCR->tick()) {
    1479           81 :         return false;
    1480              :     }
    1481          840 :     size_t track = startCR->track();
    1482          840 :     Segment* seg = startCR->segment();
    1483         1028 :     while (seg && seg->tick() <= endCR->tick()) {
    1484         1014 :         if (!seg->isChordRestType()
    1485         1012 :             || !seg->elist().at(track)
    1486         2026 :             || !seg->elist().at(track)->isChordRest()) {
    1487            9 :             return false;
    1488              :         }
    1489         1005 :         ChordRest* cr = toChordRest(seg->elist().at(track));
    1490         1005 :         bool hasBeam = cr->beam() && cr->up() == slur->up();
    1491         1005 :         bool hasTrem = false;
    1492         1005 :         if (cr->isChord()) {
    1493         1005 :             Chord* c = toChord(cr);
    1494         1005 :             hasTrem = c->tremoloTwoChord() && c->up() == slur->up();
    1495              :         }
    1496         1005 :         if (!(hasBeam || hasTrem)) {
    1497          817 :             return false;
    1498              :         }
    1499          188 :         if ((!seg->next() || seg->next()->isEndBarLineType()) && seg->measure()->nextMeasure()) {
    1500           10 :             seg = seg->measure()->nextMeasure()->first();
    1501              :         } else {
    1502          178 :             seg = seg->next();
    1503              :         }
    1504              :     }
    1505           14 :     return true;
    1506              : }
    1507              : 
    1508           31 : void SlurTieLayout::avoidPreBendsOnTab(const Chord* sc, const Chord* ec, SlurTiePos* sp)
    1509              : {
    1510           31 :     GuitarBend* bendOnStart = nullptr;
    1511           31 :     GuitarBend* bendOnEnd = nullptr;
    1512           31 :     if (sc) {
    1513           64 :         for (Note* note : sc->notes()) {
    1514           33 :             GuitarBend* bf = note->bendFor();
    1515           33 :             GuitarBend* bb = note->bendBack();
    1516           33 :             if (bf && !bf->segmentsEmpty() && bf->bendType() == GuitarBendType::PRE_BEND && !bf->angledPreBend()) {
    1517            0 :                 bendOnStart = bf;
    1518           33 :             } else if (bb && !bb->segmentsEmpty() && bb->bendType() == GuitarBendType::PRE_BEND && !bb->angledPreBend()) {
    1519            0 :                 bendOnStart = bb;
    1520              :             }
    1521           33 :             if (bendOnStart) {
    1522            0 :                 break;
    1523              :             }
    1524              :         }
    1525              :     }
    1526           31 :     if (ec) {
    1527           64 :         for (Note* note : ec->notes()) {
    1528           33 :             GuitarBend* bf = note->bendFor();
    1529           33 :             GuitarBend* bb = note->bendBack();
    1530           33 :             if (bf && !bf->segmentsEmpty() && bf->bendType() == GuitarBendType::PRE_BEND && !bf->angledPreBend()) {
    1531            0 :                 bendOnEnd = bf;
    1532           33 :             } else if (bb && !bb->segmentsEmpty() && bb->bendType() == GuitarBendType::PRE_BEND && !bb->angledPreBend()) {
    1533            0 :                 bendOnEnd = bb;
    1534              :             }
    1535           33 :             if (bendOnEnd) {
    1536            0 :                 break;
    1537              :             }
    1538              :         }
    1539              :     }
    1540              : 
    1541           31 :     if (bendOnStart) {
    1542            0 :         sp->p1.rx() = std::max(sp->p1.x(), bendOnStart->frontSegment()->pos().x() + 0.33 * sc->spatium());
    1543              :     }
    1544           31 :     if (bendOnEnd) {
    1545            0 :         sp->p2.rx() = std::min(sp->p2.x(), bendOnEnd->frontSegment()->pos().x() - 0.33 * ec->spatium());
    1546              :     }
    1547           31 : }
    1548              : 
    1549         5378 : bool SlurTieLayout::tieSegmentShouldBeSkipped(const Tie* item)
    1550              : {
    1551         5378 :     Note* startNote = item->startNote();
    1552         5378 :     StaffType* st = item->staff()->staffType(startNote ? startNote->tick() : Fraction(0, 1));
    1553         5378 :     if (!st || !st->isTabStaff()) {
    1554         5325 :         return false;
    1555              :     }
    1556              : 
    1557           53 :     if (startNote && startNote->isContinuationOfBend()) {
    1558            4 :         return true;
    1559              :     }
    1560              : 
    1561           49 :     ShowTiedFret showTiedFret = item->style().value(Sid::tabShowTiedFret).value<ShowTiedFret>();
    1562              : 
    1563           49 :     return showTiedFret == ShowTiedFret::NONE;
    1564              : }
    1565              : 
    1566         4462 : TieSegment* SlurTieLayout::layoutTieFor(Tie* item, System* system)
    1567              : {
    1568         4462 :     item->setPos(0, 0);
    1569              : 
    1570         4462 :     if (!item->startNote()) {
    1571            0 :         LOGD("no start note");
    1572            0 :         return nullptr;
    1573              :     }
    1574              : 
    1575              :     // do not layout ties in tablature if not showing back-tied fret marks
    1576         4462 :     if (tieSegmentShouldBeSkipped(item)) {
    1577            4 :         if (!item->segmentsEmpty()) {
    1578            0 :             item->eraseSpannerSegments();
    1579              :         }
    1580              : 
    1581            4 :         return nullptr;
    1582              :     }
    1583              : 
    1584         4458 :     if (item->isPartialTie()) {
    1585          110 :         return layoutPartialTie(toPartialTie(item));
    1586              :     }
    1587              : 
    1588         4348 :     calculateDirection(item);
    1589         4348 :     calculateIsInside(item);
    1590              : 
    1591         4348 :     SlurTiePos sPos;
    1592         4348 :     sPos.p1 = computeDefaultStartOrEndPoint(item, Grip::START);
    1593              : 
    1594         4348 :     computeStartAndEndSystem(item, sPos);
    1595              : 
    1596         4348 :     int segmentCount = sPos.system1 == sPos.system2 ? 1 : 2;
    1597         4348 :     if (segmentCount == 2) {
    1598          568 :         sPos.p2 = PointF(endingXForOpenEndedSlurTie(system->lastMeasure(), item->startNote()->vStaffIdx()), sPos.p1.y());
    1599              :     } else {
    1600         3780 :         sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END);
    1601              :     }
    1602              : 
    1603         4348 :     correctForCrossStaff(item, sPos, sPos.system1 != sPos.system2 ? SpannerSegmentType::BEGIN : SpannerSegmentType::SINGLE);
    1604         4348 :     forceHorizontal(item, sPos);
    1605              : 
    1606         4348 :     item->fixupSegments(segmentCount);
    1607         4348 :     TieSegment* segment = item->segmentAt(0);
    1608         4348 :     segment->setTrack(item->track());
    1609         4348 :     segment->setSpannerSegmentType(sPos.system1 != sPos.system2 ? SpannerSegmentType::BEGIN : SpannerSegmentType::SINGLE);
    1610         4348 :     segment->moveToSystem(system);   // Needed to populate System.spannerSegments
    1611         4348 :     segment->resetAdjustmentOffset();
    1612         4348 :     segment->mutldata()->allJumpPointsInactive = item->allJumpPointsInactive();
    1613              : 
    1614         4348 :     const Chord* startChord = item->startNote()->chord();
    1615         4348 :     item->setTick(startChord->tick()); // Why is this here?? (M.S.)
    1616              : 
    1617         4348 :     if (segment->autoplace() && !segment->isEdited()) {
    1618         4348 :         adjustX(segment, sPos, Grip::START);
    1619         4348 :         if (segment->isSingleType()) {
    1620         3780 :             adjustX(segment, sPos, Grip::END);
    1621              :         }
    1622              :     }
    1623              : 
    1624         4348 :     adjustYforLedgerLines(segment, sPos);
    1625              : 
    1626         4348 :     segment->ups(Grip::START).p = sPos.p1;
    1627         4348 :     segment->ups(Grip::END).p = sPos.p2;
    1628              : 
    1629         4348 :     if (segment->autoplace() && !segment->isEdited()) {
    1630         4348 :         adjustY(segment);
    1631              :     } else {
    1632            0 :         computeBezier(segment);
    1633              :     }
    1634              : 
    1635         4348 :     addLineAttachPoints(segment); // add attach points to start and end note
    1636         4348 :     return segment;
    1637              : }
    1638              : 
    1639          819 : TieSegment* SlurTieLayout::layoutTieBack(Tie* item, System* system, LayoutContext& ctx)
    1640              : {
    1641          819 :     Chord* chord = item->endNote() ? item->endNote()->chord() : nullptr;
    1642              : 
    1643          819 :     if (item->staffType() && item->staffType()->isTabStaff()) {
    1644              :         // On TAB, the presence of this tie may require to add a parenthesis
    1645           10 :         ChordLayout::layout(chord, ctx);
    1646              :     }
    1647              :     // do not layout ties in tablature if not showing back-tied fret marks
    1648          819 :     if (tieSegmentShouldBeSkipped(item)) {
    1649            0 :         if (!item->segmentsEmpty()) {
    1650            0 :             item->eraseSpannerSegments();
    1651              :         }
    1652              : 
    1653            0 :         return nullptr;
    1654              :     }
    1655              : 
    1656          819 :     if (item->isPartialTie()) {
    1657          230 :         return layoutPartialTie(toPartialTie(item));
    1658              :     }
    1659              : 
    1660          589 :     SlurTiePos sPos;
    1661          589 :     computeStartAndEndSystem(item, sPos);
    1662          589 :     sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END);
    1663              : 
    1664          589 :     double x = system ? system->firstNoteRestSegmentX(true) : 0;
    1665          589 :     double y = sPos.p2.y();
    1666          589 :     sPos.p1 = PointF(x, y);
    1667              : 
    1668          589 :     item->fixupSegments(2);
    1669          589 :     TieSegment* segment = item->segmentAt(1);
    1670          589 :     segment->setTrack(item->track());
    1671          589 :     segment->moveToSystem(system);
    1672          589 :     segment->resetAdjustmentOffset();
    1673              : 
    1674          589 :     if (chord) {
    1675          589 :         segment->setStaffMove(static_cast<int>(chord->vStaffIdx() - segment->staffIdx()));
    1676              :     }
    1677              : 
    1678          589 :     adjustY(segment);
    1679          589 :     segment->setSpannerSegmentType(SpannerSegmentType::END);
    1680              : 
    1681          589 :     if (segment->autoplace() && !segment->isEdited()) {
    1682          589 :         adjustX(segment, sPos, Grip::END);
    1683              :     }
    1684              : 
    1685          589 :     adjustYforLedgerLines(segment, sPos);
    1686              : 
    1687          589 :     segment->ups(Grip::START).p = sPos.p1;
    1688          589 :     segment->ups(Grip::END).p = sPos.p2;
    1689              : 
    1690          589 :     if (segment->autoplace() && !segment->isEdited()) {
    1691          589 :         adjustY(segment);
    1692              :     } else {
    1693            0 :         computeBezier(segment);
    1694              :     }
    1695              : 
    1696          589 :     addLineAttachPoints(segment);
    1697          589 :     return segment;
    1698              : }
    1699              : 
    1700         5351 : void SlurTieLayout::computeStartAndEndSystem(Tie* item, SlurTiePos& slurTiePos)
    1701              : {
    1702         5351 :     Chord* startChord = item->startNote() ? item->startNote()->chord() : nullptr;
    1703         5351 :     Chord* endChord = item->endNote() ? item->endNote()->chord() : nullptr;
    1704              : 
    1705         5351 :     System* startSystem = startChord ? startChord->measure()->system() : nullptr;
    1706              : 
    1707         5351 :     System* endSystem = endChord ? endChord->measure()->system() : startSystem;
    1708              : 
    1709         5351 :     if (!startSystem && !endSystem) {
    1710            0 :         if (startChord) {
    1711            0 :             Measure* m = startChord->measure();
    1712            0 :             LOGD("No system: measure is %d has %d count %d", m->isMMRest(), m->hasMMRest(), m->mmRestCount());
    1713              :         } else {
    1714            0 :             LOGD("No start or end system for tie at ") << item->tick().toString();
    1715              :         }
    1716              :     }
    1717              : 
    1718         5351 :     slurTiePos.system1 = startSystem;
    1719         5351 :     slurTiePos.system2 = endSystem;
    1720         5351 : }
    1721              : 
    1722         9131 : PointF SlurTieLayout::computeDefaultStartOrEndPoint(const Tie* tie, Grip startOrEnd)
    1723              : {
    1724         9131 :     if (startOrEnd != Grip::START && startOrEnd != Grip::END) {
    1725            0 :         return PointF();
    1726              :     }
    1727              : 
    1728         9131 :     bool start = startOrEnd == Grip::START;
    1729              : 
    1730         9131 :     Note* note = start ? tie->startNote() : tie->endNote();
    1731         9131 :     Chord* chord = note ? note->chord() : nullptr;
    1732              : 
    1733         9131 :     if (!chord) {
    1734            0 :         return PointF();
    1735              :     }
    1736              : 
    1737         9131 :     PointF result = note->pos() + chord->pos() + chord->segment()->pos() + chord->measure()->pos();
    1738              : 
    1739         9131 :     const bool up = tie->up();
    1740         9131 :     const bool inside = tie->isInside();
    1741         9131 :     const bool noteIsHiddenFret = note->shouldHideFret();
    1742         9131 :     const int upSign = up ? -1 : 1;
    1743         9131 :     const int leftRightSign = start ? +1 : -1;
    1744         9131 :     const double noteWidth = note->width();
    1745         9131 :     const double noteHeight = note->height();
    1746         9131 :     const double spatium = tie->spatium();
    1747              : 
    1748         9131 :     double baseX = (inside && !noteIsHiddenFret) ? (start ? noteWidth : 0.0) : noteOpticalCenterForTie(note, up);
    1749         9131 :     double baseY = inside ? 0.0 : upSign * noteHeight / 2;
    1750              : 
    1751         9131 :     result += PointF(baseX, baseY);
    1752              : 
    1753         9131 :     double visualInsetSp = 0.0;
    1754         9131 :     if (inside || note->headGroup() == NoteHeadGroup::HEAD_SLASH || noteIsHiddenFret) {
    1755           24 :         visualInsetSp = 0.2;
    1756         9107 :     } else if (note->hasAnotherStraightAboveOrBelow(up)) {
    1757          386 :         visualInsetSp = 0.45;
    1758              :     } else {
    1759         8721 :         visualInsetSp = 0.1;
    1760              :     }
    1761              : 
    1762         9131 :     double visualInset = visualInsetSp * spatium * leftRightSign;
    1763         9131 :     const double yOffset = 0.20 * spatium * upSign; // TODO: style
    1764              : 
    1765         9131 :     result += PointF(visualInset, yOffset);
    1766              : 
    1767         9131 :     return result;
    1768              : }
    1769              : 
    1770         9131 : double SlurTieLayout::noteOpticalCenterForTie(const Note* note, bool up)
    1771              : {
    1772         9131 :     if (note->headGroup() == NoteHeadGroup::HEAD_SLASH) {
    1773           24 :         double singleSlashWidth = note->symBbox(SymId::noteheadSlashHorizontalEnds).width();
    1774           24 :         double noteWidth = note->headWidth();
    1775           24 :         double center = 0.20 * note->spatium() + 0.5 * (noteWidth - singleSlashWidth);
    1776           24 :         return up ? note->ldata()->bbox().right() - center : note->ldata()->bbox().left() + center;
    1777              :     }
    1778         9107 :     SymId symId = note->ldata()->cachedNoteheadSym.value();
    1779         9107 :     PointF cutOutLeft = note->symSmuflAnchor(symId, up ? SmuflAnchorId::cutOutNW : SmuflAnchorId::cutOutSW);
    1780         9107 :     PointF cutOutRight = note->symSmuflAnchor(symId, up ? SmuflAnchorId::cutOutNE : SmuflAnchorId::cutOutSE);
    1781              : 
    1782         9107 :     if (cutOutLeft.isNull() || cutOutRight.isNull()) {
    1783         5959 :         return 0.5 * note->width();
    1784              :     }
    1785              : 
    1786         3148 :     return 0.5 * (cutOutLeft.x() + cutOutRight.x());
    1787              : }
    1788              : 
    1789            0 : void SlurTieLayout::createSlurSegments(Slur* item, LayoutContext& ctx)
    1790              : {
    1791            0 :     const ChordRest* startCR = item->startCR();
    1792            0 :     const ChordRest* endCR = item->endCR();
    1793            0 :     assert(startCR && endCR);
    1794              : 
    1795            0 :     const System* startSys = startCR->measure()->system();
    1796            0 :     const System* endSys = endCR->measure()->system();
    1797              : 
    1798            0 :     const std::vector<System*>& systems = ctx.dom().systems();
    1799            0 :     system_idx_t startSysIdx = muse::indexOf(systems, startSys);
    1800            0 :     system_idx_t endSysIdx = muse::indexOf(systems, endSys);
    1801            0 :     if (startSysIdx == muse::nidx || endSysIdx == muse::nidx) {
    1802            0 :         return;
    1803              :     }
    1804              : 
    1805            0 :     int segmentsNeeded = 0;
    1806            0 :     for (system_idx_t i = startSysIdx; i <= endSysIdx; ++i) {
    1807            0 :         if (systems.at(i)->vbox()) {
    1808            0 :             continue;
    1809              :         }
    1810            0 :         ++segmentsNeeded;
    1811              :     }
    1812            0 :     int currentSegments = int(item->spannerSegments().size());
    1813              : 
    1814            0 :     if (currentSegments != segmentsNeeded) {
    1815            0 :         item->fixupSegments(segmentsNeeded);
    1816              :     }
    1817              : 
    1818            0 :     int segIdx = 0;
    1819            0 :     for (system_idx_t i = startSysIdx; i <= endSysIdx; ++i) {
    1820            0 :         System* system = systems.at(i);
    1821            0 :         if (system->vbox()) {
    1822            0 :             continue;
    1823              :         }
    1824            0 :         SlurSegment* lineSegm = item->segmentAt(segIdx++);
    1825            0 :         lineSegm->moveToSystem(system);
    1826            0 :         if (startSysIdx == endSysIdx) {
    1827            0 :             lineSegm->setSpannerSegmentType(SpannerSegmentType::SINGLE);
    1828            0 :         } else if (i == startSysIdx) {
    1829            0 :             lineSegm->setSpannerSegmentType(SpannerSegmentType::BEGIN);
    1830            0 :         } else if (i > 0 && i != endSysIdx) {
    1831            0 :             lineSegm->setSpannerSegmentType(SpannerSegmentType::MIDDLE);
    1832            0 :         } else if (i == endSysIdx) {
    1833            0 :             lineSegm->setSpannerSegmentType(SpannerSegmentType::END);
    1834              :         }
    1835              :     }
    1836              : }
    1837              : 
    1838          385 : void SlurTieLayout::adjustOverlappingSlurs(const std::list<SpannerSegment*>& spannerSegments)
    1839              : {
    1840          385 :     std::vector<SlurSegment*> segments;
    1841         1647 :     for (SpannerSegment* seg : spannerSegments) {
    1842         1262 :         if (seg->isSlurSegment()) {
    1843          871 :             segments.push_back(toSlurSegment(seg));
    1844              :         }
    1845              :     }
    1846          385 :     if (segments.size() <= 1) {
    1847          205 :         return;
    1848              :     }
    1849              : 
    1850              :     //how far vertically an endpoint should adjust to avoid other slur endpoints:
    1851          180 :     const double spatium = segments.front()->spatium();
    1852          180 :     const double slurCollisionVertOffset = 0.65 * spatium;
    1853          180 :     const double slurCollisionHorizOffset = 0.2 * spatium;
    1854          180 :     const double fuzzyHorizCompare = 0.25 * spatium;
    1855          179 :     auto compare = [fuzzyHorizCompare](double x1, double x2) { return std::abs(x1 - x2) < fuzzyHorizCompare; };
    1856          846 :     for (SlurSegment* slur1 : segments) {
    1857         4140 :         for (SlurSegment* slur2 : segments) {
    1858         3474 :             if (slur2 == slur1) {
    1859          666 :                 continue;
    1860              :             }
    1861         2808 :             if (slur1->slur()->endChord() == slur2->slur()->startChord()
    1862         2808 :                 && compare(slur1->ups(Grip::END).p.y(), slur2->ups(Grip::START).p.y())) {
    1863            8 :                 slur1->ups(Grip::END).p.rx() -= slurCollisionHorizOffset;
    1864            8 :                 slur2->ups(Grip::START).p.rx() += slurCollisionHorizOffset;
    1865            8 :                 SlurTieLayout::computeBezier(slur1);
    1866            8 :                 SlurTieLayout::computeBezier(slur2);
    1867            8 :                 continue;
    1868              :             }
    1869              : 
    1870         2800 :             SlurTieSegment* slurTie2 = toSlurTieSegment(slur2);
    1871              : 
    1872              :             // slurs don't collide with themselves or slurs on other staves
    1873         2800 :             if (slur1->vStaffIdx() != slurTie2->vStaffIdx()) {
    1874          922 :                 continue;
    1875              :             }
    1876              :             // slurs which don't overlap don't need to be checked
    1877         1878 :             if (slur1->ups(Grip::END).p.x() < slurTie2->ups(Grip::START).p.x()
    1878         1015 :                 || slurTie2->ups(Grip::END).p.x() < slur1->ups(Grip::START).p.x()
    1879         2893 :                 || slur1->slur()->up() != slurTie2->slurTie()->up()) {
    1880         1800 :                 continue;
    1881              :             }
    1882              :             // START POINT
    1883           78 :             if (compare(slur1->ups(Grip::START).p.x(), slurTie2->ups(Grip::START).p.x())) {
    1884           28 :                 if (slur1->ups(Grip::END).p.x() > slurTie2->ups(Grip::END).p.x() || slurTie2->isTieSegment()) {
    1885              :                     // slur1 is the "outside" slur
    1886            5 :                     slur1->ups(Grip::START).p.ry() += slurCollisionVertOffset * (slur1->slur()->up() ? -1 : 1);
    1887            5 :                     SlurTieLayout::computeBezier(slur1);
    1888              :                 }
    1889              :             }
    1890              :             // END POINT
    1891           78 :             if (compare(slur1->ups(Grip::END).p.x(), slurTie2->ups(Grip::END).p.x())) {
    1892              :                 // slurs have the same endpoint
    1893           24 :                 if (slur1->ups(Grip::START).p.x() < slurTie2->ups(Grip::START).p.x() || slurTie2->isTieSegment()) {
    1894              :                     // slur1 is the "outside" slur
    1895            3 :                     slur1->ups(Grip::END).p.ry() += slurCollisionVertOffset * (slur1->slur()->up() ? -1 : 1);
    1896            3 :                     SlurTieLayout::computeBezier(slur1);
    1897              :                 }
    1898              :             }
    1899              :         }
    1900              :     }
    1901          385 : }
    1902              : 
    1903           74 : LaissezVibSegment* SlurTieLayout::createLaissezVibSegment(LaissezVib* item)
    1904              : {
    1905           74 :     Chord* startChord = item->startNote()->chord();
    1906           74 :     item->setTick(startChord->tick());
    1907              : 
    1908           74 :     calculateDirection(item);
    1909           74 :     calculateIsInside(item);
    1910              : 
    1911           74 :     item->fixupSegments(1);
    1912           74 :     LaissezVibSegment* segment = item->segmentAt(0);
    1913           74 :     segment->setSpannerSegmentType(SpannerSegmentType::SINGLE);
    1914           74 :     segment->setTrack(item->track());
    1915           74 :     segment->moveToSystem(item->startNote()->chord()->segment()->measure()->system());
    1916           74 :     segment->resetAdjustmentOffset();
    1917              : 
    1918           74 :     return segment;
    1919              : }
    1920              : 
    1921           74 : void SlurTieLayout::calculateLaissezVibX(LaissezVibSegment* segment, SlurTiePos& sPos, bool smufl)
    1922              : {
    1923           74 :     LaissezVib* lv = segment->laissezVib();
    1924              : 
    1925           74 :     computeStartAndEndSystem(lv, sPos);
    1926           74 :     sPos.p1 = computeDefaultStartOrEndPoint(lv, Grip::START);
    1927              : 
    1928           74 :     correctForCrossStaff(lv, sPos, SpannerSegmentType::SINGLE);
    1929              : 
    1930           74 :     if (segment->autoplace() && !segment->isEdited()) {
    1931           74 :         adjustX(segment, sPos, Grip::START);
    1932              :     }
    1933              : 
    1934           74 :     if (smufl) {
    1935            0 :         LaissezVibSegment::LayoutData* ldata = segment->mutldata();
    1936            0 :         ldata->symbol = lv->symId();
    1937            0 :         ldata->setBbox(segment->symBbox(ldata->symbol));
    1938            0 :         ldata->setShape(Shape(ldata->bbox(), segment));
    1939            0 :         ldata->setMag(segment->laissezVib()->startNote()->mag());
    1940              :     }
    1941              : 
    1942           74 :     const double width = smufl ? segment->width() : lv->absoluteFromSpatium(lv->minLength());
    1943              : 
    1944           74 :     sPos.p2 = PointF(sPos.p1.x() + width, sPos.p1.y());
    1945           74 : }
    1946              : 
    1947           74 : void SlurTieLayout::calculateLaissezVibY(LaissezVibSegment* segment, SlurTiePos& sPos)
    1948              : {
    1949           74 :     LaissezVib* lv = segment->laissezVib();
    1950              : 
    1951           74 :     adjustYforLedgerLines(segment, sPos);
    1952              : 
    1953           74 :     Note* note = lv->startNote();
    1954           74 :     Chord* chord = note->chord();
    1955              : 
    1956           74 :     const NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(note);
    1957              : 
    1958           74 :     Parenthesis* paren = nullptr;
    1959           74 :     if (noteParenInfo) {
    1960            0 :         paren = noteParenInfo->leftParen();
    1961              :     }
    1962              : 
    1963           74 :     const bool avoidStem = chord->stem() && chord->stem()->visible() && chord->up() == lv->up();
    1964           74 :     if (paren && (!lv->isOuterTieOfChord(Grip::START) || avoidStem)) {
    1965            0 :         RectF parenBbox = paren->ldata()->bbox().translated(paren->systemPos());
    1966            0 :         double adj = sPos.p1.y() - (lv->up() ? parenBbox.top() : parenBbox.bottom());
    1967            0 :         const double PAREN_LV_PADDING = 0.1 * segment->spatium();
    1968            0 :         adj += PAREN_LV_PADDING * (lv->up() ? 1.0 : -1.0);
    1969            0 :         sPos.p1.ry() -= adj;
    1970            0 :         sPos.p2.ry() -= adj;
    1971              :     }
    1972              : 
    1973           74 :     segment->ups(Grip::START).p = sPos.p1;
    1974           74 :     segment->ups(Grip::END).p = sPos.p2;
    1975              : 
    1976           74 :     if (segment->autoplace() && !segment->isEdited()) {
    1977           74 :         adjustY(segment);
    1978              :     } else {
    1979            0 :         computeBezier(segment);
    1980              :     }
    1981           74 : }
    1982              : 
    1983          340 : PartialTieSegment* SlurTieLayout::createPartialTieSegment(PartialTie* item)
    1984              : {
    1985          340 :     Chord* chord = item->isOutgoing() ? item->startNote()->chord() : item->endNote()->chord();
    1986          340 :     item->setTick(chord->tick());
    1987              : 
    1988          340 :     calculateDirection(item);
    1989          340 :     calculateIsInside(item);
    1990              : 
    1991          340 :     item->fixupSegments(1);
    1992          340 :     PartialTieSegment* segment = item->segmentAt(0);
    1993          340 :     segment->setSpannerSegmentType(SpannerSegmentType::SINGLE);
    1994          340 :     segment->moveToSystem(chord->segment()->measure()->system());
    1995          340 :     segment->setTrack(item->track());
    1996          340 :     segment->resetAdjustmentOffset();
    1997          340 :     segment->mutldata()->allJumpPointsInactive = item->allJumpPointsInactive();
    1998              : 
    1999          340 :     return segment;
    2000              : }
    2001              : 
    2002          340 : PartialTieSegment* SlurTieLayout::layoutPartialTie(PartialTie* item)
    2003              : {
    2004          340 :     const bool outgoing = item->isOutgoing();
    2005          340 :     PartialTieSegment* segment = createPartialTieSegment(item);
    2006          340 :     SlurTiePos sPos;
    2007              : 
    2008          340 :     computeStartAndEndSystem(item, sPos);
    2009          340 :     if (outgoing) {
    2010          110 :         sPos.p1 = computeDefaultStartOrEndPoint(item, Grip::START);
    2011              :     } else {
    2012          230 :         sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END);
    2013              :     }
    2014              : 
    2015          340 :     if (segment->autoplace() && !segment->isEdited()) {
    2016          340 :         adjustX(segment, sPos, outgoing ? Grip::START : Grip::END);
    2017              :     }
    2018              : 
    2019          340 :     setPartialTieEndPos(item, sPos);
    2020              : 
    2021          340 :     correctForCrossStaff(item, sPos, SpannerSegmentType::SINGLE);
    2022              : 
    2023          340 :     adjustYforLedgerLines(segment, sPos);
    2024              : 
    2025          340 :     segment->ups(Grip::START).p = sPos.p1;
    2026          340 :     segment->ups(Grip::END).p = sPos.p2;
    2027              : 
    2028          340 :     if (segment->autoplace() && !segment->isEdited()) {
    2029          340 :         adjustY(segment);
    2030              :     } else {
    2031            0 :         computeBezier(segment);
    2032              :     }
    2033              : 
    2034          340 :     addLineAttachPoints(segment);
    2035              : 
    2036          340 :     return segment;
    2037              : }
    2038              : 
    2039          340 : void SlurTieLayout::setPartialTieEndPos(PartialTie* item, SlurTiePos& sPos)
    2040              : {
    2041          340 :     const bool outgoing = item->isOutgoing();
    2042              : 
    2043          340 :     const Chord* chord = item->parentNote()->chord();
    2044          340 :     const Segment* seg = chord->segment();
    2045          340 :     const Measure* measure = seg->measure();
    2046          340 :     const System* system = measure->system();
    2047              : 
    2048          340 :     if (seg->measure()->isFirstInSystem() && !outgoing) {
    2049           98 :         sPos.p1 = PointF((system ? system->firstNoteRestSegmentX(true) : 0), sPos.p2.y());
    2050           98 :         return;
    2051              :     }
    2052              : 
    2053          243 :     auto shouldSkipSegment = [](const Segment* adjSeg, staff_idx_t staff) {
    2054          482 :         bool inactiveOrInvisible = !adjSeg->isActive() || !adjSeg->enabled() || adjSeg->allElementsInvisible()
    2055          482 :                                    || !adjSeg->hasElements(staff);
    2056          243 :         bool isAboveStaff = adjSeg->isBreathType() || adjSeg->hasTimeSigAboveStaves();
    2057          243 :         return inactiveOrInvisible || isAboveStaff;
    2058              :     };
    2059              : 
    2060          242 :     const Segment* adjSeg = outgoing ? seg->next1() : seg->prev1();
    2061          246 :     while (adjSeg && shouldSkipSegment(adjSeg, item->vStaffIdx())) {
    2062            4 :         adjSeg = outgoing ? adjSeg->next1() : adjSeg->prev1();
    2063              :     }
    2064              : 
    2065          242 :     double widthToSegment = 0.0;
    2066          242 :     if (adjSeg) {
    2067          239 :         EngravingItem* element = adjSeg->element(staff2track(item->vStaffIdx()));
    2068          239 :         double elPos = 0.0;
    2069          239 :         double elementWidth = 0.0;
    2070          239 :         if (element) {
    2071          239 :             const Shape& elemShape = element->shape();
    2072          239 :             elPos = element->pos().x() + elemShape.bbox().x();
    2073          239 :             elementWidth = elemShape.bbox().width();
    2074          239 :         }
    2075              : 
    2076          239 :         const double elPosInSystemCoords = adjSeg->xPosInSystemCoords() + elPos;
    2077          239 :         widthToSegment = outgoing ? elPosInSystemCoords - sPos.p1.x() : sPos.p2.x() - (elPosInSystemCoords + elementWidth);
    2078          132 :         bool incomingFromBarline = !outgoing && element && element->isBarLine()
    2079          371 :                                    && toBarLine(element)->barLineType() != BarLineType::START_REPEAT;
    2080              :         widthToSegment
    2081          239 :             -= item->style().styleAbsolute(incomingFromBarline ? Sid::barlineToLineStartDistance : Sid::lineEndToBarlineDistance);
    2082              :     }
    2083              : 
    2084          242 :     if (outgoing) {
    2085          110 :         sPos.p2 = PointF(sPos.p1.x() + widthToSegment, sPos.p1.y());
    2086              :     } else {
    2087          132 :         sPos.p1 = PointF(sPos.p2.x() - widthToSegment, sPos.p2.y());
    2088              :     }
    2089              : }
    2090              : 
    2091       101714 : void SlurTieLayout::layoutLaissezVibChord(Chord* chord, LayoutContext& ctx)
    2092              : {
    2093              :     // Laissez vib ties should all end at the same rightmost point while honouring each tie's minimum length
    2094              :     // Ties drawn by MuseScore can start at differing points
    2095              :     // SMuFL symbols will also start at the same point because they are of fixed length
    2096       101714 :     double chordLvEndPoint = -DBL_MAX;
    2097       101714 :     std::map<LaissezVibSegment*, SlurTiePos> lvSegmentsWithPositions;
    2098       101714 :     const bool smuflLayout = ctx.conf().styleB(Sid::laissezVibUseSmuflSym);
    2099              : 
    2100       221854 :     for (const Note* note : chord->notes()) {
    2101       120140 :         LaissezVib* lv = note->laissezVib();
    2102       120140 :         if (!lv) {
    2103       120066 :             continue;
    2104              :         }
    2105           74 :         SlurTiePos sPos;
    2106           74 :         LaissezVibSegment* lvSeg = createLaissezVibSegment(lv);
    2107              : 
    2108           74 :         calculateLaissezVibX(lvSeg, sPos, smuflLayout);
    2109              : 
    2110           74 :         chordLvEndPoint = std::max(chordLvEndPoint, sPos.p2.x());
    2111              : 
    2112           74 :         lvSegmentsWithPositions.insert({ lvSeg, sPos });
    2113              :     }
    2114              : 
    2115       101788 :     for (auto& segWithPos : lvSegmentsWithPositions) {
    2116           74 :         LaissezVibSegment* lvSeg = segWithPos.first;
    2117           74 :         Note* note = lvSeg->laissezVib()->startNote();
    2118           74 :         SlurTiePos sPos = segWithPos.second;
    2119           74 :         const double xDiff = chordLvEndPoint - sPos.p2.x();
    2120           74 :         sPos.p2.setX(chordLvEndPoint);
    2121           74 :         if (smuflLayout) {
    2122            0 :             sPos.p1.setX(sPos.p1.x() + xDiff);
    2123              :         }
    2124              : 
    2125           74 :         calculateLaissezVibY(lvSeg, sPos);
    2126              : 
    2127           74 :         LaissezVibSegment::LayoutData* ldata = lvSeg->mutldata();
    2128           74 :         if (smuflLayout) {
    2129            0 :             ldata->setBbox(lvSeg->symBbox(ldata->symbol));
    2130            0 :             ldata->setShape(Shape(ldata->bbox(), lvSeg));
    2131            0 :             ldata->setPos(sPos.p1);
    2132              :         }
    2133              : 
    2134           74 :         double yOrigin = sPos.system1->staff(chord->staffIdx())->y();
    2135           74 :         double yMoved = sPos.system1->staff(chord->vStaffIdx())->y();
    2136           74 :         double yDiff = yMoved - yOrigin;
    2137              : 
    2138           74 :         const PointF chordPos = chord->pos() + chord->segment()->pos() + chord->measure()->pos() + PointF(0.0, yDiff);
    2139           74 :         const PointF notePos = chordPos + note->pos();
    2140           74 :         ldata->posRelativeToNote = sPos.p1 - notePos;
    2141              : 
    2142           74 :         TLayout::fillNoteShape(note, note->mutldata());
    2143              :     }
    2144       101714 : }
    2145              : 
    2146         4762 : void SlurTieLayout::correctForCrossStaff(Tie* tie, SlurTiePos& sPos, SpannerSegmentType type)
    2147              : {
    2148         4762 :     Chord* startChord = tie->startNote() ? tie->startNote()->chord() : nullptr;
    2149         4762 :     Chord* endChord = tie->endNote() ? tie->endNote()->chord() : nullptr;
    2150              : 
    2151         4762 :     if (!startChord) {
    2152           16 :         return;
    2153              :     }
    2154              : 
    2155         4746 :     bool startStaffDiff = startChord->vStaffIdx() != tie->staffIdx();
    2156         4746 :     bool endStaffDiff = endChord ? endChord->vStaffIdx() != tie->staffIdx() : false;
    2157         4746 :     double curY1 = sPos.p1.y();
    2158         4746 :     double curY2 = sPos.p2.y();
    2159              : 
    2160         4746 :     if (type == SpannerSegmentType::BEGIN) {
    2161              :         // Cross-system start tie
    2162          568 :         if (startStaffDiff && sPos.system1) {
    2163            0 :             double yOrigin = sPos.system1->staff(tie->staffIdx())->y();
    2164            0 :             double yMoved = sPos.system1->staff(startChord->vStaffIdx())->y();
    2165            0 :             double yDiff = yMoved - yOrigin;
    2166            0 :             sPos.p1.setY(curY1 + yDiff);
    2167            0 :             sPos.p2.setY(curY2 + yDiff);
    2168              :         }
    2169         4178 :     } else if (type == SpannerSegmentType::SINGLE) {
    2170              :         // Same system single tie
    2171         4178 :         if (startStaffDiff && sPos.system1) {
    2172            0 :             double yOrigin = sPos.system1->staff(tie->staffIdx())->y();
    2173            0 :             double yMoved = sPos.system1->staff(startChord->vStaffIdx())->y();
    2174            0 :             double yDiff = yMoved - yOrigin;
    2175            0 :             sPos.p1.setY(curY1 + yDiff);
    2176            0 :             if (tie->isLaissezVib()) {
    2177            0 :                 sPos.p2.setY(curY2 + yDiff);
    2178            0 :                 return;
    2179              :             }
    2180              :         }
    2181         4178 :         if (!endChord) {
    2182          184 :             return;
    2183              :         }
    2184         3994 :         if (endStaffDiff && sPos.system2) {
    2185            6 :             double yOrigin = sPos.system2->staff(tie->staffIdx())->y();
    2186            6 :             double yMoved = sPos.system2->staff(endChord->vStaffIdx())->y();
    2187            6 :             double yDiff = yMoved - yOrigin;
    2188            6 :             sPos.p2.setY(curY2 + yDiff);
    2189              :         }
    2190              :     }
    2191              : }
    2192              : 
    2193         4348 : void SlurTieLayout::forceHorizontal(Tie* tie, SlurTiePos& sPos)
    2194              : {
    2195         4348 :     Note* startNote = tie->startNote();
    2196         4348 :     Note* endNote = tie->endNote();
    2197              : 
    2198         4348 :     if (startNote && endNote
    2199         4348 :         && startNote->line() == endNote->line()
    2200         8696 :         && startNote->vStaffIdx() == endNote->vStaffIdx()) {
    2201         4012 :         double y1 = sPos.p1.y();
    2202         4012 :         double y2 = sPos.p2.y();
    2203         4012 :         double outerY = tie->up() ? std::min(y1, y2) : std::max(y1, y2);
    2204         4012 :         sPos.p1.setY(outerY);
    2205         4012 :         sPos.p2.setY(outerY);
    2206              :     }
    2207         4348 : }
    2208              : 
    2209         9131 : void SlurTieLayout::adjustX(TieSegment* tieSegment, SlurTiePos& sPos, Grip startOrEnd)
    2210              : {
    2211         9131 :     bool start = startOrEnd == Grip::START;
    2212              : 
    2213         9131 :     PointF& tiePoint = start ? sPos.p1 : sPos.p2;
    2214         9131 :     double resultingX = tiePoint.x();
    2215              : 
    2216         9131 :     Tie* tie = tieSegment->tie();
    2217         9131 :     Note* note = start ? tie->startNote() : tie->endNote();
    2218         9131 :     const Note* otherNote = start ? tie->endNote() : tie->startNote();
    2219         9131 :     if (!note || note->shouldHideFret()) {
    2220         7547 :         return;
    2221              :     }
    2222              : 
    2223         9131 :     Chord* chord = note->chord();
    2224         9131 :     const Chord* otherChord = otherNote ? otherNote->chord() : nullptr;
    2225         9131 :     const Segment* chordSeg = chord->segment();
    2226         9131 :     const double spatium = tieSegment->spatium();
    2227         9131 :     const double padding = 0.20 * spatium * (start ? 1 : -1); // TODO: style
    2228         9131 :     const bool isGrace = (chord && chord->isGrace())
    2229        18262 :                          || ((otherChord && otherChord->isGrace()) && chord->segment() == otherChord->segment());
    2230              : 
    2231         9131 :     PointF systemPos = chordSeg->pos() + chord->measure()->pos() + (isGrace ? chord->pos() : PointF());
    2232         9131 :     if (chord->vStaffIdx() != tieSegment->vStaffIdx()) {
    2233            6 :         System* system = tieSegment->system();
    2234            6 :         double yDiff = system->staff(chord->vStaffIdx())->y() - system->staff(tie->staffIdx())->y();
    2235            6 :         systemPos += PointF(0.0, yDiff);
    2236              :     }
    2237              : 
    2238         9131 :     const bool isOuterTieOfChord = tie->isOuterTieOfChord(startOrEnd);
    2239              : 
    2240         9131 :     TieDotsPlacement dotsPlacement = tie->style().styleV(Sid::tieDotsPlacement).value<TieDotsPlacement>();
    2241         4532 :     bool clearAllDots = start && dotsPlacement == TieDotsPlacement::AFTER_DOTS && !isOuterTieOfChord
    2242        13663 :                         && chord->dots() > 0 && !ChordLayout::chordHasDotsAllInvisible(chord);
    2243         9131 :     if (clearAllDots) {
    2244            0 :         double dotsOuterEdge = -DBL_MAX;
    2245            0 :         for (const Note* n : chord->notes()) {
    2246            0 :             for (const NoteDot* dot : n->dots()) {
    2247            0 :                 dotsOuterEdge = std::max(dotsOuterEdge, dot->x() + dot->width() + n->x());
    2248              :             }
    2249              :         }
    2250            0 :         dotsOuterEdge += systemPos.x();
    2251            0 :         resultingX = dotsOuterEdge + padding;
    2252            0 :         tieSegment->addAdjustmentOffset(PointF(resultingX - tiePoint.x(), 0.0), startOrEnd);
    2253            0 :         tiePoint.setX(resultingX);
    2254            0 :         return;
    2255              :     }
    2256              : 
    2257         9131 :     if (isOuterTieOfChord) {
    2258         8592 :         Tie* otherTie = start ? note->tieBack() : note->tieFor();
    2259         8592 :         bool avoidOtherTie = otherTie && otherTie->up() == tie->up() && !otherTie->isInside();
    2260         8592 :         if (avoidOtherTie) {
    2261         3154 :             resultingX += 0.1 * spatium * (start ? 1 : -1);
    2262              :         }
    2263              :     }
    2264              : 
    2265         9131 :     const bool avoidStem = chord->stem() && chord->stem()->visible() && chord->up() == tie->up();
    2266              : 
    2267         9131 :     if (isOuterTieOfChord && !avoidStem) {
    2268         7547 :         tieSegment->addAdjustmentOffset(PointF(resultingX - tiePoint.x(), 0.0), startOrEnd);
    2269         7547 :         tiePoint.setX(resultingX);
    2270         7547 :         return;
    2271              :     }
    2272              : 
    2273         1584 :     Shape shape = isGrace ? chord->shape().translate(systemPos) : chordSeg->staffShape(chord->vStaffIdx()).translated(systemPos);
    2274         1584 :     bool ignoreDot = start && (isOuterTieOfChord || dotsPlacement == TieDotsPlacement::BEFORE_DOTS);
    2275         1584 :     bool ignoreLvSeg = tieSegment->isLaissezVibSegment();
    2276         1584 :     bool ignoreArpeggio = note == chord->downNote() && !tie->up();
    2277         1584 :     bool ignoreParen = tieSegment->isLaissezVibSegment() && start;
    2278          771 :     auto ignoreAccidental = [&](const Accidental* acc) {
    2279          771 :         bool accIsInwardOfTie = tie->up() ? acc->line() >= note->line() : acc->line() <= note->line();
    2280          771 :         return !start && isOuterTieOfChord && accIsInwardOfTie;
    2281         1584 :     };
    2282              : 
    2283              :     static const std::set<ElementType> IGNORED_TYPES = {
    2284              :         ElementType::HOOK,
    2285              :         ElementType::STEM_SLASH,
    2286              :         ElementType::LEDGER_LINE,
    2287              :         ElementType::LYRICS,
    2288              :         ElementType::HARMONY,
    2289              :         ElementType::FRET_DIAGRAM
    2290         1600 :     };
    2291         1584 :     shape.remove_if([&](ShapeElement& s) {
    2292        22128 :         bool remove =  !s.item() || s.item() == note || muse::contains(IGNORED_TYPES, s.item()->type())
    2293         6550 :                       || (s.item()->isNoteDot() && ignoreDot)
    2294         6490 :                       || (s.item()->isAccidental() && ignoreAccidental(toAccidental(s.item())))
    2295         6470 :                       || (s.item()->isLaissezVibSegment() && ignoreLvSeg)
    2296         6270 :                       || ((s.item()->isArpeggio() || s.item()->isChordBracket()) && ignoreArpeggio)
    2297         6264 :                       || (s.item()->isParenthesis() && ignoreParen)
    2298        22128 :                       || !s.item()->addToSkyline();
    2299        11064 :         return remove;
    2300              :     });
    2301              : 
    2302         1584 :     const double arcSideMargin = 0.3 * spatium;
    2303         1584 :     const double pointsSideMargin = 0.15 * spatium;
    2304         1584 :     const double yBelow = tiePoint.y() - (tie->up() ? arcSideMargin : pointsSideMargin);
    2305         1584 :     const double yAbove = tiePoint.y() + (tie->up() ? pointsSideMargin : arcSideMargin);
    2306         1584 :     double pointToClear = start ? shape.rightMostEdgeAtHeight(yBelow, yAbove)
    2307          791 :                           : shape.leftMostEdgeAtHeight(yBelow, yAbove);
    2308              : 
    2309         1584 :     pointToClear += padding;
    2310              : 
    2311         1584 :     resultingX = start ? std::max(resultingX, pointToClear) : std::min(resultingX, pointToClear);
    2312              : 
    2313         1584 :     adjustXforLedgerLines(tieSegment, start, chord, note, systemPos, padding, resultingX);
    2314              : 
    2315         1584 :     tieSegment->addAdjustmentOffset(PointF(resultingX - tiePoint.x(), 0.0), startOrEnd);
    2316         1584 :     tiePoint.setX(resultingX);
    2317         1584 : }
    2318              : 
    2319         1584 : void SlurTieLayout::adjustXforLedgerLines(TieSegment* tieSegment, bool start, Chord* chord, Note* note,
    2320              :                                           const PointF& chordSystemPos, double padding, double& resultingX)
    2321              : {
    2322         1584 :     if (tieSegment->tie()->isInside() || chord->ledgerLines().empty()) {
    2323         1399 :         return;
    2324              :     }
    2325              : 
    2326          758 :     bool isOuterNote = note == chord->upNote() || note == chord->downNote();
    2327          758 :     if (isOuterNote) {
    2328          325 :         return;
    2329              :     }
    2330              : 
    2331          433 :     bool ledgersAbove = false;
    2332          433 :     bool ledgersBelow = false;
    2333         2016 :     for (LedgerLine* ledger : chord->ledgerLines()) {
    2334         1591 :         if (ledger->y() < 0.0) {
    2335          456 :             ledgersAbove = true;
    2336              :         } else {
    2337         1135 :             ledgersBelow = true;
    2338              :         }
    2339         1591 :         if (ledgersAbove && ledgersBelow) {
    2340            8 :             break;
    2341              :         }
    2342              :     }
    2343              : 
    2344          433 :     int noteLine = note->line();
    2345          433 :     bool isOddLine = noteLine % 2 != 0;
    2346          433 :     bool isAboveStaff = noteLine <= 0;
    2347          433 :     bool isBelowStaff = noteLine >= 2 * (note->staff()->lines(note->tick()) - 1);
    2348          433 :     bool isInsideStaff = !isAboveStaff && !isBelowStaff;
    2349          433 :     if (isOddLine || isInsideStaff || (isAboveStaff && !ledgersAbove) || (isBelowStaff && !ledgersBelow)) {
    2350          248 :         return;
    2351              :     }
    2352              : 
    2353          185 :     Shape noteShape = note->shape();
    2354          185 :     noteShape.remove_if([&](ShapeElement& s) {
    2355          237 :         return !s.item()->addToSkyline() || s.item()->isNoteDot() || (tieSegment->isLaissezVibSegment() && s.item()->isLaissezVibSegment());
    2356              :     });
    2357          185 :     noteShape.translate(note->pos() + chordSystemPos);
    2358          185 :     double xNoteEdge = (start ? noteShape.right() : noteShape.left()) + padding;
    2359              : 
    2360          185 :     resultingX = start ? std::max(resultingX, xNoteEdge) : std::min(resultingX, xNoteEdge);
    2361          185 : }
    2362              : 
    2363         5351 : void SlurTieLayout::adjustYforLedgerLines(TieSegment* tieSegment, SlurTiePos& sPos)
    2364              : {
    2365         5351 :     Tie* tie = tieSegment->tie();
    2366         5351 :     Note* note = tieSegment->isSingleBeginType() ? tie->startNote() : tie->endNote();
    2367         5351 :     if (!note) {
    2368         4443 :         return;
    2369              :     }
    2370              : 
    2371         5335 :     Chord* chord = note->chord();
    2372         5335 :     if (chord->ledgerLines().empty()) {
    2373         4427 :         return;
    2374              :     }
    2375          908 :     PointF chordSystemPos = chord->pos() + chord->segment()->pos() + chord->segment()->measure()->pos();
    2376          908 :     PointF& tiePoint = tieSegment->isSingleBeginType() ? sPos.p1 : sPos.p2;
    2377          908 :     double spatium = tie->spatium();
    2378          908 :     int upSign = tie->up() ? -1 : 1;
    2379          908 :     double margin = 0.4 * spatium;
    2380              : 
    2381         3119 :     for (LedgerLine* ledger : chord->ledgerLines()) {
    2382         2284 :         PointF ledgerPos = ledger->pos() + chordSystemPos;
    2383         2284 :         double yDiff = upSign * (ledgerPos.y() - tiePoint.y());
    2384         2284 :         bool collision = yDiff > 0 && yDiff < margin;
    2385         2284 :         if (collision) {
    2386           73 :             sPos.p1 += PointF(0.0, -upSign * (margin - yDiff));
    2387           73 :             sPos.p2 += PointF(0.0, -upSign * (margin - yDiff));
    2388           73 :             computeBezier(tieSegment);
    2389           73 :             break;
    2390              :         }
    2391              :     }
    2392              : }
    2393              : 
    2394         5940 : void SlurTieLayout::adjustY(TieSegment* tieSegment)
    2395              : {
    2396         5940 :     Staff* staff = tieSegment->score() ? tieSegment->score()->staff(tieSegment->vStaffIdx()) : nullptr;
    2397         5940 :     if (!staff) {
    2398         2775 :         return;
    2399              :     }
    2400              : 
    2401         5940 :     const double spatium = tieSegment->spatium();
    2402         5940 :     TieSegment::LayoutData* ldata = tieSegment->mutldata();
    2403              : 
    2404         5940 :     ldata->setPos(PointF());
    2405         5940 :     ldata->moveY(tieSegment->staffOffsetY());
    2406              : 
    2407         5940 :     Fraction tick = tieSegment->tick();
    2408              : 
    2409         5940 :     computeBezier(tieSegment);
    2410              : 
    2411         5940 :     bool up = tieSegment->tie()->up();
    2412         5940 :     int upSign = up ? -1 : 1;
    2413              : 
    2414         5940 :     const double staffLineDist = staff->lineDistance(tick) * spatium;
    2415         5940 :     const double staffLineThickness = tieSegment->style().styleAbsolute(Sid::staffLineWidth) * staff->staffMag(tick);
    2416              : 
    2417              :     // 1. Check for bad end point protrusion
    2418              : 
    2419         5940 :     const double endPointY = tieSegment->ups(Grip::START).p.y();
    2420         5940 :     const int closestLineToEndpoints = up ? floor(endPointY / staffLineDist) : ceil(endPointY / staffLineDist);
    2421         5940 :     const bool isEndInsideStaff = closestLineToEndpoints >= 0 && closestLineToEndpoints < staff->lines(tick);
    2422         5940 :     const bool isEndInsideLedgerLines = !isEndInsideStaff && !tieSegment->tie()->isOuterTieOfChord(Grip::START);
    2423              : 
    2424         5940 :     const double halfLineThicknessCorrection = 0.5 * staffLineThickness * upSign;
    2425         5940 :     const double protrusion = std::abs(endPointY - (closestLineToEndpoints * spatium - halfLineThicknessCorrection));
    2426         5940 :     const double badIntersectionLimit = 0.15 * spatium; // TODO: style
    2427              : 
    2428         5940 :     bool badIntersection = protrusion < badIntersectionLimit && (isEndInsideStaff || isEndInsideLedgerLines);
    2429         5940 :     if (badIntersection) {
    2430          384 :         double correctedY = closestLineToEndpoints * spatium + halfLineThicknessCorrection + badIntersectionLimit * upSign;
    2431          384 :         tieSegment->addAdjustmentOffset(PointF(0.0, correctedY - endPointY), Grip::START);
    2432          384 :         tieSegment->addAdjustmentOffset(PointF(0.0, correctedY - endPointY), Grip::END);
    2433          384 :         tieSegment->ups(Grip::START).p.setY(correctedY);
    2434          384 :         tieSegment->ups(Grip::END).p.setY(correctedY);
    2435          384 :         computeBezier(tieSegment);
    2436              :     }
    2437              : 
    2438              :     // 2. Check for bad arc protrusion
    2439              : 
    2440         5940 :     RectF tieSegmentBBox = tieSegment->ldata()->bbox();
    2441         5940 :     double tieLength = tieSegmentBBox.width();
    2442         5940 :     double tieHeight = tieSegmentBBox.height();
    2443         5940 :     double midThickness = tieSegment->ldata()->midThickness() * 2;
    2444         5940 :     double yOuterApogee = up ? tieSegmentBBox.top() : tieSegmentBBox.bottom();
    2445         5940 :     double yInnerApogee = yOuterApogee - midThickness * upSign;
    2446         5940 :     double yMidApogee = 0.5 * (yOuterApogee + yInnerApogee);
    2447              : 
    2448         5940 :     int closestLineToArc = round(yMidApogee / staffLineDist);
    2449         5940 :     bool isArcInsideStaff =  closestLineToArc >= 0 && closestLineToArc < staff->lines(tick);
    2450         5940 :     if (!isArcInsideStaff) {
    2451         1274 :         return;
    2452              :     }
    2453              : 
    2454         4666 :     double outwardMargin = -upSign * (yOuterApogee - (closestLineToArc * staffLineDist - halfLineThicknessCorrection));
    2455         4666 :     double inwardMargin = upSign * (yInnerApogee - (closestLineToArc * staffLineDist + halfLineThicknessCorrection));
    2456         4666 :     const double badArcIntersectionLimit = tieLength < 3 * spatium ? 0.1 * spatium : 0.15 * spatium;
    2457              : 
    2458         4666 :     bool increaseArc = outwardMargin - 0.5 * badArcIntersectionLimit < inwardMargin;
    2459         4666 :     bool correctOutwards = inwardMargin < badArcIntersectionLimit && increaseArc;
    2460         4666 :     bool correctInwards = outwardMargin < badArcIntersectionLimit && !increaseArc;
    2461              : 
    2462         4666 :     if (!correctInwards && !correctOutwards) {
    2463         1441 :         return;
    2464              :     }
    2465              : 
    2466         3225 :     bool isSmallTie = tieLength < 2.0 * spatium && tieHeight < 0.7 * spatium;
    2467              :     // For ties this small, try to fit them within a staff space before changing their arc
    2468         3225 :     if (isSmallTie) {
    2469         2354 :         Tie* tie2 = tieSegment->tie();
    2470         2354 :         bool isInside = tie2->isInside();
    2471         2354 :         bool isOuterOfChord = tie2->isOuterTieOfChord(Grip::START) || tie2->isOuterTieOfChord(Grip::END);
    2472         2354 :         bool hasTiedSecondInside = tie2->hasTiedSecondInside();
    2473         2354 :         if (!isInside && !isOuterOfChord && !hasTiedSecondInside && !hasEndPointAboveNote(tieSegment)) {
    2474           60 :             double currentY = tieSegment->ups(Grip::START).p.y();
    2475           60 :             double yCorrection = -upSign * (badArcIntersectionLimit - outwardMargin);
    2476           60 :             tieSegment->addAdjustmentOffset(PointF(0.0, yCorrection), Grip::START);
    2477           60 :             tieSegment->addAdjustmentOffset(PointF(0.0, yCorrection), Grip::END);
    2478           60 :             tieSegment->ups(Grip::START).p.setY(currentY + yCorrection);
    2479           60 :             tieSegment->ups(Grip::END).p.setY(currentY + yCorrection);
    2480           60 :             computeBezier(tieSegment);
    2481           60 :             return;
    2482              :         } else {
    2483         2294 :             correctOutwards = true;
    2484              :         }
    2485              :     }
    2486              : 
    2487         3165 :     double arcCorrection = correctOutwards ? (badArcIntersectionLimit - inwardMargin) : (badArcIntersectionLimit - outwardMargin);
    2488         3165 :     double maxArcCorrection = 0.75 * tieHeight;
    2489         3165 :     double rest = arcCorrection > maxArcCorrection ? arcCorrection - maxArcCorrection : 0.0;
    2490         3165 :     arcCorrection = std::min(arcCorrection, maxArcCorrection);
    2491         3165 :     if (correctOutwards) {
    2492         2837 :         if (rest > 0) {
    2493         1940 :             rest *= upSign;
    2494         1940 :             double currentY = tieSegment->ups(Grip::START).p.y();
    2495         1940 :             tieSegment->addAdjustmentOffset(PointF(0.0, rest), Grip::START);
    2496         1940 :             tieSegment->addAdjustmentOffset(PointF(0.0, rest), Grip::END);
    2497         1940 :             tieSegment->ups(Grip::START).p.setY(currentY + rest);
    2498         1940 :             tieSegment->ups(Grip::END).p.setY(currentY + rest);
    2499              :         }
    2500         2837 :         computeBezier(tieSegment, PointF(0.0, -arcCorrection));
    2501          328 :     } else if (correctInwards) {
    2502          328 :         computeBezier(tieSegment, PointF(0.0, arcCorrection));
    2503              :     }
    2504              : }
    2505              : 
    2506          147 : bool SlurTieLayout::hasEndPointAboveNote(TieSegment* tieSegment)
    2507              : {
    2508          147 :     Note* startNote = tieSegment->tie()->startNote();
    2509          147 :     Note* endNote = tieSegment->tie()->endNote();
    2510              : 
    2511          147 :     if ((tieSegment->isSingleBeginType() && !startNote) || (tieSegment->isSingleEndType() && !endNote)) {
    2512            0 :         return false;
    2513              :     }
    2514              : 
    2515          147 :     Chord* startChord = startNote->chord();
    2516          147 :     PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
    2517              : 
    2518          147 :     Chord* endChord = endNote->chord();
    2519          147 :     PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
    2520              : 
    2521          147 :     PointF tieStartPos = tieSegment->ups(Grip::START).pos();
    2522          147 :     PointF tieEndPos = tieSegment->ups(Grip::END).pos();
    2523              : 
    2524          227 :     return (tieStartPos.x() < startNotePos.x() + startNote->width() && !startNote->shouldHideFret())
    2525          227 :            || (tieEndPos.x() > endNotePos.x() && !endNote->shouldHideFret());
    2526              : }
    2527              : 
    2528        96608 : void SlurTieLayout::resolveVerticalTieCollisions(const std::vector<TieSegment*>& stackedTies)
    2529              : {
    2530        96608 :     if (stackedTies.size() < 2) {
    2531        96514 :         return;
    2532              :     }
    2533              : 
    2534           94 :     std::list<TieSegment*> downwardTies;
    2535           94 :     std::list<TieSegment*> upwardTies;
    2536          391 :     for (TieSegment* tieSegment : stackedTies) {
    2537          297 :         if (!tieSegment->tie()->up()) {
    2538          154 :             downwardTies.push_front(tieSegment);
    2539              :         } else {
    2540          143 :             upwardTies.push_back(tieSegment);
    2541              :         }
    2542              :     }
    2543              : 
    2544          110 :     auto fixTieCollision = [](TieSegment* thisTie, TieSegment* nextTie) {
    2545          110 :         double spatium = thisTie->spatium();
    2546          110 :         bool up = thisTie->tie()->up();
    2547          110 :         int upSign = up ? -1 : 1;
    2548              : 
    2549          110 :         double thisTieOuterY = up ? thisTie->ldata()->bbox().top() : thisTie->ldata()->bbox().bottom();
    2550          110 :         double nextTieInnerY = (up ? nextTie->ldata()->bbox().top() : nextTie->ldata()->bbox().bottom())
    2551          110 :                                - upSign * 2 * nextTie->ldata()->midThickness();
    2552          110 :         double clearanceMargin = 0.15 * spatium;
    2553          110 :         bool collision = upSign * (nextTieInnerY - thisTieOuterY) < clearanceMargin;
    2554          110 :         if (!collision) {
    2555          110 :             return;
    2556              :         }
    2557              : 
    2558            0 :         if (!thisTie->score()) {
    2559            0 :             return;
    2560              :         }
    2561            0 :         Staff* staff = thisTie->score() ? thisTie->score()->staff(thisTie->vStaffIdx()) : nullptr;
    2562            0 :         if (!staff) {
    2563            0 :             return;
    2564              :         }
    2565              : 
    2566            0 :         Fraction tick = thisTie->tick();
    2567            0 :         double yMidPoint = 0.5 * (thisTieOuterY + nextTieInnerY);
    2568            0 :         double halfLineDist = 0.5 * staff->lineDistance(tick) * spatium;
    2569            0 :         int midLine = round(yMidPoint / halfLineDist);
    2570            0 :         bool insideStaff = midLine >= -1 && midLine <= 2 * (staff->lines(tick) - 1) + 1;
    2571            0 :         if (insideStaff) {
    2572            0 :             yMidPoint = midLine * halfLineDist;
    2573              :         }
    2574              : 
    2575            0 :         double thisTieYCorrection = yMidPoint - upSign * 0.5 * clearanceMargin - thisTieOuterY;
    2576            0 :         double nextTieYCorrection = yMidPoint + upSign * 0.5 * clearanceMargin - nextTieInnerY;
    2577              : 
    2578            0 :         double thisShoulderOff = thisTie->adjustmentOffset(Grip::BEZIER1).y();
    2579            0 :         double nextShoulderOff = nextTie->adjustmentOffset(Grip::BEZIER1).y();
    2580              :         // Subtract it otherwise it gets summed twice
    2581            0 :         thisTie->addAdjustmentOffset(PointF(0.0, -thisShoulderOff), Grip::BEZIER1);
    2582            0 :         thisTie->addAdjustmentOffset(PointF(0.0, -thisShoulderOff), Grip::BEZIER2);
    2583            0 :         nextTie->addAdjustmentOffset(PointF(0.0, -nextShoulderOff), Grip::BEZIER1);
    2584            0 :         nextTie->addAdjustmentOffset(PointF(0.0, -nextShoulderOff), Grip::BEZIER2);
    2585              : 
    2586            0 :         computeBezier(thisTie, PointF(0.0, -upSign * (thisShoulderOff + thisTieYCorrection)));
    2587            0 :         computeBezier(nextTie, PointF(0.0, -upSign * (nextShoulderOff + nextTieYCorrection)));
    2588              :     };
    2589              : 
    2590           94 :     if (upwardTies.size() >= 2) {
    2591          180 :         for (auto it = upwardTies.begin(); std::next(it, 1) != upwardTies.end(); ++it) {
    2592           49 :             TieSegment* thisTie = *it;
    2593           49 :             TieSegment* nextTie = *(std::next(it, 1));
    2594           49 :             fixTieCollision(thisTie, nextTie);
    2595              :         }
    2596              :     }
    2597              : 
    2598           94 :     if (downwardTies.size() >= 2) {
    2599          218 :         for (auto it = downwardTies.begin(); std::next(it, 1) != downwardTies.end(); ++it) {
    2600           61 :             TieSegment* thisTie = *it;
    2601           61 :             TieSegment* nextTie = *(std::next(it, 1));
    2602           61 :             fixTieCollision(thisTie, nextTie);
    2603              :         }
    2604              :     }
    2605           94 : }
    2606              : 
    2607         1751 : void SlurTieLayout::computeUp(Slur* slur, LayoutContext& ctx)
    2608              : {
    2609         1751 :     switch (slur->slurDirection()) {
    2610           97 :     case DirectionV::UP:
    2611           97 :         slur->setUp(true);
    2612           97 :         break;
    2613           89 :     case DirectionV::DOWN:
    2614           89 :         slur->setUp(false);
    2615           89 :         break;
    2616         1565 :     case DirectionV::AUTO:
    2617              :     {
    2618              :         //
    2619              :         // assumption:
    2620              :         // slurs have only chords or rests as start/end elements
    2621              :         //
    2622         1565 :         ChordRest* chordRest1 = slur->startCR();
    2623         1565 :         ChordRest* chordRest2 = slur->endCR();
    2624         1565 :         if (chordRest1 == 0 || chordRest2 == 0) {
    2625            0 :             slur->setUp(true);
    2626            0 :             break;
    2627              :         }
    2628         1565 :         Chord* chord1 = slur->startCR()->isChord() ? toChord(slur->startCR()) : 0;
    2629         1565 :         Chord* chord2 = slur->endCR()->isChord() ? toChord(slur->endCR()) : 0;
    2630         1543 :         if (chord2 && !chord2->staff()->isDrumStaff(chord2->tick())
    2631         3108 :             && slur->startCR()->measure()->system() != slur->endCR()->measure()->system()) {
    2632              :             // HACK: if the end chord is in a different system, it may have never been laid out yet.
    2633              :             // But we need to know its direction to decide slur direction, so need to compute it here.
    2634          437 :             for (Note* note : chord2->notes()) {
    2635          219 :                 note->updateLine(); // because chord direction is based on note lines
    2636              :             }
    2637              : 
    2638          218 :             ChordLayout::computeUp(chord2, ctx);
    2639              :         }
    2640              : 
    2641         1565 :         if (chord1 && chord1->beam() && chord1->beam()->cross()) {
    2642              :             // TODO: stem direction is not finalized, so we cannot use it here
    2643            0 :             slur->setUp(true);
    2644            0 :             break;
    2645              :         }
    2646              : 
    2647         1565 :         slur->setUp(!(chordRest1->up()));
    2648              : 
    2649              :         // Check if multiple voices
    2650         1565 :         bool multipleVoices = false;
    2651         1565 :         Measure* m1 = chordRest1->measure();
    2652         3222 :         while (m1 && m1->tick() <= chordRest2->tick()) {
    2653         1923 :             if ((m1->hasVoices(chordRest1->staffIdx(), slur->tick(), slur->ticks() + chordRest2->ticks()))
    2654         1923 :                 && chord1) {
    2655          266 :                 multipleVoices = true;
    2656          266 :                 break;
    2657              :             }
    2658         1657 :             m1 = m1->nextMeasure();
    2659              :         }
    2660         1565 :         if (multipleVoices) {
    2661              :             // slurs go on the stem side
    2662          266 :             if (chordRest1->voice() > 0 || chordRest2->voice() > 0) {
    2663          179 :                 slur->setUp(false);
    2664              :             } else {
    2665           87 :                 slur->setUp(true);
    2666              :             }
    2667         1299 :         } else if (chord1 && chord2 && !chord1->isGrace() && isDirectionMixture(chord1, chord2, ctx)) {
    2668              :             // slurs go above if there are mixed direction stems between c1 and c2
    2669              :             // but grace notes are exceptions
    2670          161 :             slur->setUp(true);
    2671         1138 :         } else if (chord1 && chord2 && chord1->isGrace() && chord2 != chord1->parent() && isDirectionMixture(chord1, chord2, ctx)) {
    2672            0 :             slur->setUp(true);
    2673              :         }
    2674              :     }
    2675         1565 :     break;
    2676              :     }
    2677         1751 : }
    2678              : 
    2679         9622 : void SlurTieLayout::computeBezier(TieSegment* tieSeg, PointF shoulderOffset)
    2680              : {
    2681         9622 :     const PointF tieStart = tieSeg->ups(Grip::START).p + tieSeg->ups(Grip::START).off;
    2682         9622 :     const PointF tieEnd = tieSeg->ups(Grip::END).p + tieSeg->ups(Grip::END).off;
    2683         9622 :     if (!muse::RealIsEqualOrMore(tieEnd.x(), tieStart.x())) {
    2684         4452 :         return;
    2685              :     }
    2686              : 
    2687         6286 :     PointF tieEndNormalized = tieEnd - tieStart;  // normalize to zero
    2688         6286 :     if (muse::RealIsNull(tieEndNormalized.x())) {
    2689         1116 :         return;
    2690              :     }
    2691              : 
    2692         5170 :     const double tieAngle = atan(tieEndNormalized.y() / tieEndNormalized.x()); // angle required from tie start to tie end--zero if horizontal
    2693         5170 :     Transform t;
    2694         5170 :     t.rotateRadians(-tieAngle);  // rotate so that we are working with horizontal ties regardless of endpoint height difference
    2695         5170 :     tieEndNormalized = t.map(tieEndNormalized);  // apply that rotation
    2696         5170 :     shoulderOffset = t.map(shoulderOffset);  // also apply to shoulderOffset
    2697              : 
    2698         5170 :     const double _spatium = tieSeg->spatium();
    2699         5170 :     double tieLengthInSp = tieEndNormalized.x() / _spatium;
    2700              : 
    2701         5170 :     const double minShoulderHeight = tieSeg->minShoulderHeight();
    2702         5170 :     const double maxShoulderHeight = tieSeg->maxShoulderHeight();
    2703         5170 :     double shoulderH = minShoulderHeight + _spatium * 0.3 * sqrt(std::abs(tieLengthInSp - 1));
    2704         5170 :     shoulderH = std::clamp(shoulderH, minShoulderHeight, maxShoulderHeight);
    2705              : 
    2706         5170 :     shoulderH -= shoulderOffset.y();
    2707              : 
    2708         5170 :     PointF shoulderAdjustOffset = tieSeg->tie()->up() ? PointF(0.0, shoulderOffset.y()) : PointF(0.0, -shoulderOffset.y());
    2709         5170 :     tieSeg->addAdjustmentOffset(shoulderAdjustOffset, Grip::BEZIER1);
    2710         5170 :     tieSeg->addAdjustmentOffset(shoulderAdjustOffset, Grip::BEZIER2);
    2711              : 
    2712         5170 :     if (!tieSeg->tie()->up()) {
    2713         1481 :         shoulderH = -shoulderH;
    2714              :     }
    2715              : 
    2716         5170 :     double shoulderW = 0.6; // TODO: style
    2717              : 
    2718         5170 :     const double tieWidth = tieEndNormalized.x();
    2719         5170 :     const double bezier1X = (tieWidth - tieWidth * shoulderW) * .5 + shoulderOffset.x();
    2720         5170 :     const double bezier2X = bezier1X + tieWidth * shoulderW + shoulderOffset.x();
    2721              : 
    2722         5170 :     const PointF tieDrag = PointF(tieWidth * .5, 0.0);
    2723              : 
    2724         5170 :     const PointF bezier1(bezier1X, -shoulderH);
    2725         5170 :     const PointF bezier2(bezier2X, -shoulderH);
    2726              : 
    2727         5170 :     computeMidThickness(tieSeg, tieLengthInSp);
    2728              : 
    2729         5170 :     PointF tieThickness(0.0, 0.0);
    2730         5170 :     if (tieSeg->tie()->styleType() == SlurStyleType::Solid) {
    2731         5142 :         tieThickness.setY(tieSeg->ldata()->midThickness());
    2732              :     }
    2733              : 
    2734         5170 :     const PointF bezier1Offset = t.map(tieSeg->ups(Grip::BEZIER1).off);
    2735         5170 :     const PointF bezier2Offset = t.map(tieSeg->ups(Grip::BEZIER2).off);
    2736              : 
    2737              :     //-----------------------------------calculate p6
    2738         5170 :     const PointF bezier1Final = bezier1 + bezier1Offset;
    2739         5170 :     const PointF bezier2Final = bezier2 + bezier2Offset;
    2740              : 
    2741         5170 :     const PointF tieShoulder = 0.5 * (bezier1Final + bezier2Final);
    2742              :     //-----------------------------------
    2743              : 
    2744         5170 :     PainterPath path = PainterPath();
    2745         5170 :     path.moveTo(PointF());
    2746         5170 :     path.cubicTo(bezier1 + bezier1Offset - tieThickness, bezier2 + bezier2Offset - tieThickness, tieEndNormalized);
    2747         5170 :     if (tieSeg->tie()->styleType() == SlurStyleType::Solid) {
    2748         5142 :         path.cubicTo(bezier2 + bezier2Offset + tieThickness, bezier1 + bezier1Offset + tieThickness, PointF());
    2749              :     }
    2750              : 
    2751              :     // translate back
    2752         5170 :     t.reset();
    2753         5170 :     t.translate(tieStart.x(), tieStart.y());
    2754         5170 :     t.rotateRadians(tieAngle);
    2755         5170 :     path = t.map(path);
    2756         5170 :     tieSeg->mutldata()->path.set_value(path);
    2757              : 
    2758         5170 :     tieSeg->ups(Grip::BEZIER1).p = t.map(bezier1);
    2759         5170 :     tieSeg->ups(Grip::BEZIER2).p = t.map(bezier2);
    2760         5170 :     tieSeg->ups(Grip::END).p = t.map(tieEndNormalized) - tieSeg->ups(Grip::END).off;
    2761         5170 :     tieSeg->ups(Grip::DRAG).p = t.map(tieDrag);
    2762         5170 :     tieSeg->ups(Grip::SHOULDER).p = t.map(tieShoulder);
    2763              : 
    2764         5170 :     fillShape(tieSeg, tieLengthInSp);
    2765         5170 : }
    2766              : 
    2767          953 : void SlurTieLayout::computeBezier(SlurSegment* slurSeg, PointF shoulderOffset)
    2768              : {
    2769              :     /* ************************************************
    2770              :      * LEGEND: pp1 = start point
    2771              :      *         pp2 = end point
    2772              :      *         p2 = end point (in slur coordinates)
    2773              :      *         p3 = first bezier point (in slur coord.)
    2774              :      *         p4 = second bezier point (in slur coord.)
    2775              :      *         p5 = whole slur drag point (in slur coord.)
    2776              :      *         p6 = shoulder drag point (in slur coord.)
    2777              :      * REMEMBER! ups().pos() = ups().p + ups().off
    2778              :      * ***********************************************/
    2779              :     // Avoid bad staff line intersections
    2780          953 :     if (slurSeg->autoplace()) {
    2781          947 :         adjustEndPoints(slurSeg);
    2782              :     }
    2783              :     // If end point adjustment is locked, restore the endpoints to
    2784              :     // where they were before
    2785          953 :     if (slurSeg->isEndPointsEdited()) {
    2786            4 :         slurSeg->ups(Grip::START).off += slurSeg->endPointOff1();
    2787            4 :         slurSeg->ups(Grip::END).off += slurSeg->endPointOff2();
    2788              :     }
    2789              :     // Get start and end points (have been calculated before)
    2790          953 :     PointF pp1 = slurSeg->ups(Grip::START).p + slurSeg->ups(Grip::START).off;
    2791          953 :     PointF pp2 = slurSeg->ups(Grip::END).p + slurSeg->ups(Grip::END).off;
    2792              :     // Keep track of the original value before it gets changed
    2793          953 :     PointF oldp1 = pp1;
    2794          953 :     PointF oldp2 = pp2;
    2795              : 
    2796              :     // Set up coordinate transforms
    2797              :     // CAUTION: transform operations are applies in reverse order to how
    2798              :     // they are added to the transformation.
    2799          953 :     double slurAngle = atan((pp2.y() - pp1.y()) / (pp2.x() - pp1.x()));
    2800          953 :     Transform rotate;
    2801          953 :     rotate.rotateRadians(-slurAngle);
    2802          953 :     Transform toSlurCoordinates;
    2803          953 :     toSlurCoordinates.rotateRadians(-slurAngle);
    2804          953 :     toSlurCoordinates.translate(-pp1.x(), -pp1.y());
    2805          953 :     Transform toSystemCoordinates = toSlurCoordinates.inverted();
    2806              :     // Transform p2 and shoulder offset
    2807          953 :     PointF p2 = toSlurCoordinates.map(pp2);
    2808          953 :     shoulderOffset = rotate.map(shoulderOffset);
    2809              : 
    2810              :     // COMPUTE DEFAULT SLUR SHAPE
    2811              :     // Compute default shoulder height and width
    2812          953 :     double _spatium  = slurSeg->spatium();
    2813              :     double shoulderW; // expressed as fraction of slur-length
    2814          953 :     double d = p2.x() / _spatium;
    2815              : 
    2816          953 :     if (d < 0) {
    2817              :         //! NOTE A negative d means that end point is before the start point.
    2818              :         //! This only exists as a temporary state when horizontal spacing hasn't yet been computed,
    2819              :         //! and it makes no sense for any of the following calculations
    2820            0 :         return;
    2821              :     }
    2822              : 
    2823          953 :     if (d < 2) {
    2824           28 :         shoulderW = 0.60;
    2825          925 :     } else if (d < 10) {
    2826          475 :         shoulderW = 0.5;
    2827          450 :     } else if (d < 18) {
    2828          281 :         shoulderW = 0.6;
    2829              :     } else {
    2830          169 :         shoulderW = 0.7;
    2831              :     }
    2832              : 
    2833          953 :     double shoulderH = computeShoulderHeight(slurSeg, d, shoulderOffset);
    2834              : 
    2835          953 :     double c    = p2.x();
    2836          953 :     double c1   = (c - c * shoulderW) * .5 + shoulderOffset.x();
    2837          953 :     double c2   = c1 + c * shoulderW + shoulderOffset.x();
    2838          953 :     PointF p3(c1, -shoulderH);
    2839          953 :     PointF p4(c2, -shoulderH);
    2840              :     // Set Bezier points default position
    2841          953 :     slurSeg->ups(Grip::BEZIER1).p  = toSystemCoordinates.map(p3);
    2842          953 :     slurSeg->ups(Grip::BEZIER2).p  = toSystemCoordinates.map(p4);
    2843              :     // Add offsets
    2844          953 :     p3 += shoulderOffset + rotate.map(slurSeg->ups(Grip::BEZIER1).off);
    2845          953 :     p4 += shoulderOffset + rotate.map(slurSeg->ups(Grip::BEZIER2).off);
    2846          953 :     slurSeg->ups(Grip::BEZIER1).off += rotate.inverted().map(shoulderOffset);
    2847          953 :     slurSeg->ups(Grip::BEZIER2).off += rotate.inverted().map(shoulderOffset);
    2848              : 
    2849              :     // ADAPT SLUR SHAPE AND ENDPOINT POSITION
    2850              :     // to clear collisions with underlying items
    2851          953 :     if (slurSeg->autoplace() && !slurSeg->isTappingHalfSlurSegment()) {
    2852          915 :         avoidCollisions(slurSeg, pp1, p2, p3, p4, toSystemCoordinates, slurAngle);
    2853              :     }
    2854              : 
    2855              :     // Re-check end points for bad staff line collisions
    2856          953 :     slurSeg->ups(Grip::START).p = pp1 - slurSeg->ups(Grip::START).off;
    2857          953 :     slurSeg->ups(Grip::END).p = toSystemCoordinates.map(p2) - slurSeg->ups(Grip::END).off;
    2858          953 :     adjustEndPoints(slurSeg);
    2859          953 :     PointF newpp1 = slurSeg->ups(Grip::START).p + slurSeg->ups(Grip::START).off;
    2860          953 :     PointF difference = rotate.map(newpp1 - pp1);
    2861          953 :     pp1 = newpp1;
    2862          953 :     pp2 = slurSeg->ups(Grip::END).p + slurSeg->ups(Grip::END).off;
    2863          953 :     p3 -= difference;
    2864          953 :     p4 -= difference;
    2865              :     // Keep track of how much the end points position has changed
    2866          953 :     if (!slurSeg->isEndPointsEdited()) {
    2867          949 :         slurSeg->setEndPointOff1(pp1 - oldp1);
    2868          949 :         slurSeg->setEndPointOff2(pp2 - oldp2);
    2869              :     } else {
    2870            4 :         slurSeg->setEndPointOff1(PointF());
    2871            4 :         slurSeg->setEndPointOff2(PointF());
    2872              :     }
    2873              :     // Recompute the transformation because pp1 and pp1 may have changed
    2874          953 :     toSlurCoordinates.reset();
    2875          953 :     toSlurCoordinates.rotateRadians(-slurAngle);
    2876          953 :     toSlurCoordinates.translate(-pp1.x(), -pp1.y());
    2877          953 :     toSystemCoordinates = toSlurCoordinates.inverted();
    2878          953 :     p2 = toSlurCoordinates.map(pp2);
    2879              : 
    2880              :     // Calculate p5 and p6
    2881          953 :     PointF p5 = 0.5 * p2; // mid-point between pp1 and p2
    2882          953 :     PointF p6 = 0.5 * (p3 + p4); // mid-point between p3 and p4
    2883              : 
    2884              :     // Update all slur points after collision avoidance
    2885          953 :     slurSeg->ups(Grip::BEZIER1).p  = toSystemCoordinates.map(p3) - slurSeg->ups(Grip::BEZIER1).off;
    2886          953 :     slurSeg->ups(Grip::BEZIER2).p  = toSystemCoordinates.map(p4) - slurSeg->ups(Grip::BEZIER2).off;
    2887          953 :     slurSeg->ups(Grip::DRAG).p     = toSystemCoordinates.map(p5);
    2888          953 :     slurSeg->ups(Grip::SHOULDER).p = toSystemCoordinates.map(p6);
    2889              : 
    2890              :     // Set slur thickness
    2891          953 :     computeMidThickness(slurSeg, p2.x() / slurSeg->spatium());
    2892              : 
    2893          953 :     PointF thick(0.0, 0.0);
    2894          953 :     if (slurSeg->slur()->styleType() == SlurStyleType::Solid) {
    2895          924 :         thick.setY(slurSeg->ldata()->midThickness());
    2896              :     }
    2897              : 
    2898              :     // Set path
    2899          953 :     PainterPath path = PainterPath();
    2900          953 :     path.moveTo(PointF());
    2901          953 :     path.cubicTo(p3 - thick, p4 - thick, p2);
    2902          953 :     if (slurSeg->slur()->styleType() == SlurStyleType::Solid) {
    2903          924 :         path.cubicTo(p4 + thick, p3 + thick, PointF());
    2904              :     }
    2905              : 
    2906          953 :     path = toSystemCoordinates.map(path);
    2907          953 :     slurSeg->mutldata()->path.set_value(path);
    2908              : 
    2909          953 :     fillShape(slurSeg, p2.x() / slurSeg->spatium());
    2910          953 : }
    2911              : 
    2912          953 : double SlurTieLayout::computeShoulderHeight(SlurSegment* slurSeg, double slurLengthInSp, PointF shoulderOffset)
    2913              : {
    2914          953 :     double spatium = slurSeg->spatium();
    2915              : 
    2916          953 :     if (slurSeg->isTappingHalfSlurSegment()) {
    2917           32 :         double shoulderH = (slurSeg->staffType()->isTabStaff() ? 1.0 : 0.6) * spatium;
    2918           32 :         return slurSeg->slur()->up() ? shoulderH : -shoulderH;
    2919              :     }
    2920              : 
    2921          921 :     double shoulderH = sqrt(slurLengthInSp / 4) * spatium;
    2922              : 
    2923              :     static constexpr double shoulderReduction = 0.75;
    2924          921 :     if (isOverBeams(slurSeg->slur())) {
    2925           14 :         shoulderH *= shoulderReduction;
    2926              :     }
    2927              : 
    2928          921 :     shoulderH -= shoulderOffset.y();
    2929          921 :     if (!slurSeg->slur()->up()) {
    2930          517 :         shoulderH = -shoulderH;
    2931              :     }
    2932              : 
    2933          921 :     return shoulderH;
    2934              : }
    2935              : 
    2936            0 : double SlurTieLayout::defaultStemLengthStart(TremoloTwoChord* tremolo)
    2937              : {
    2938            0 :     return TremoloLayout::extendedStemLenWithTwoNoteTremolo(tremolo,
    2939              :                                                             tremolo->chord1()->defaultStemLength(),
    2940            0 :                                                             tremolo->chord2()->defaultStemLength()).first;
    2941              : }
    2942              : 
    2943            0 : double SlurTieLayout::defaultStemLengthEnd(TremoloTwoChord* tremolo)
    2944              : {
    2945            0 :     return TremoloLayout::extendedStemLenWithTwoNoteTremolo(tremolo,
    2946              :                                                             tremolo->chord1()->defaultStemLength(),
    2947            0 :                                                             tremolo->chord2()->defaultStemLength()).second;
    2948              : }
    2949              : 
    2950         1257 : bool SlurTieLayout::isDirectionMixture(const Chord* c1, const Chord* c2, LayoutContext& ctx)
    2951              : {
    2952         1257 :     if (c1->track() != c2->track()) {
    2953           11 :         return false;
    2954              :     }
    2955         1246 :     const bool up = c1->up();
    2956         1246 :     if (c2->isGrace() && c2->up() != up) {
    2957            6 :         return true;
    2958              :     }
    2959         1240 :     if (c1->isGraceBefore() && c2->isGraceAfter() && c1->ownershipParent() == c2->ownershipParent()) {
    2960            2 :         const Chord* mainChord = toChord(c1->ownershipParent());
    2961            2 :         if (mainChord->stem() && mainChord->up() != up) {
    2962            0 :             return true;
    2963              :         }
    2964              :     }
    2965         1240 :     const track_idx_t track = c1->track();
    2966         4814 :     for (const Segment* seg = c1->segment(); seg && seg->tick() <= c2->tick(); seg = seg->next1(SegmentType::ChordRest)) {
    2967         3774 :         if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() == c2->tick()) {
    2968              :             // if slur ends at a grace-note-before, we don't need to look at the main note
    2969           45 :             return false;
    2970              :         }
    2971         3729 :         EngravingItem* e = seg->element(track);
    2972         3729 :         if (!e || !e->isChord()) {
    2973          192 :             continue;
    2974              :         }
    2975         3537 :         Chord* c = toChord(e);
    2976         3537 :         const Measure* m = c->measure();
    2977         3537 :         const System* c1Sys = c1->measure()->system();
    2978         3537 :         const System* cSys = m->system();
    2979         3537 :         if (!c->staff()->isDrumStaff(c->tick()) && c1Sys && cSys && c1Sys != cSys) {
    2980              :             // This chord is on a different system and may not have been laid out yet
    2981           16 :             for (Note* note : c->notes()) {
    2982            8 :                 note->updateLine();     // because chord direction is based on note lines
    2983              :             }
    2984            8 :             ChordLayout::computeUp(c, ctx);
    2985              :         }
    2986              : 
    2987         3537 :         if (c->up() != up) {
    2988          155 :             return true;
    2989              :         }
    2990              :     }
    2991         1040 :     return false;
    2992              : }
    2993              : 
    2994          929 : bool SlurTieLayout::shouldHideSlurSegment(SlurSegment* item)
    2995              : {
    2996          929 :     if (item->isHammerOnPullOffSegment()) {
    2997           61 :         return false;
    2998              :     }
    2999              : 
    3000          868 :     if (item->configuration()->specificSlursLayoutWorkaround()) {
    3001            0 :         Slur* slur = item->slur();
    3002            0 :         if (slur->connectedElement() == Slur::ConnectedElement::GLISSANDO) {
    3003            0 :             return false;
    3004              :         }
    3005              :     }
    3006              : 
    3007          868 :     const StaffType* stType = item->staffType();
    3008          868 :     if (stType && stType->isHiddenElementOnTab(Sid::slurShowTabCommon, Sid::slurShowTabSimple)) {
    3009            0 :         return true;
    3010              :     }
    3011              : 
    3012          868 :     return false;
    3013              : }
    3014              : 
    3015         4937 : void SlurTieLayout::addLineAttachPoints(TieSegment* segment)
    3016              : {
    3017              :     // Add tie attach point to start and end note of segment
    3018         4937 :     Tie* tie = segment->tie();
    3019         4937 :     Note* startNote = tie->startNote();
    3020         4937 :     Note* endNote = tie->endNote();
    3021              : 
    3022         4937 :     const bool singleOrBegin = segment->spannerSegmentType() == SpannerSegmentType::SINGLE
    3023         4937 :                                || segment->spannerSegmentType() == SpannerSegmentType::BEGIN;
    3024              : 
    3025         4937 :     const bool singleOrEnd = segment->spannerSegmentType() == SpannerSegmentType::SINGLE
    3026         4937 :                              || segment->spannerSegmentType() == SpannerSegmentType::END;
    3027              : 
    3028         4937 :     if (startNote && singleOrBegin) {
    3029         4348 :         startNote->addStartLineAttachPoint(segment->ups(Grip::START).pos(), tie);
    3030              :     }
    3031         4937 :     if (endNote && singleOrEnd) {
    3032         4369 :         endNote->addEndLineAttachPoint(segment->ups(Grip::END).pos(), tie);
    3033              :     }
    3034         4937 : }
    3035              : 
    3036          340 : void SlurTieLayout::addLineAttachPoints(PartialTieSegment* segment)
    3037              : {
    3038              :     // Add tie attach point to parent note
    3039          340 :     PartialTie* tie = segment->partialTie();
    3040          340 :     Note* note = tie ? tie->note() : nullptr;
    3041          340 :     if (!note) {
    3042            0 :         return;
    3043              :     }
    3044              : 
    3045          340 :     const bool isOutgoing = tie->isOutgoing();
    3046              : 
    3047          340 :     if (isOutgoing) {
    3048          110 :         note->addStartLineAttachPoint(segment->ups(Grip::START).pos(), tie);
    3049              :     } else {
    3050          230 :         note->addEndLineAttachPoint(segment->ups(Grip::END).pos(), tie);
    3051              :     }
    3052              : }
    3053              : 
    3054         4799 : void SlurTieLayout::calculateDirection(Tie* item)
    3055              : {
    3056         4799 :     if (!item->startNote() && !item->endNote()) {
    3057            0 :         return;
    3058              :     }
    3059         4799 :     const bool tieHasBothNotes = item->startNote() && item->endNote();
    3060              : 
    3061         4799 :     const Note* primaryNote = item->startNote() ? item->startNote() : item->endNote();
    3062         4799 :     const Chord* primaryChord = primaryNote->chord();
    3063         4799 :     const Measure* primaryMeasure = primaryChord->measure();
    3064              : 
    3065         4799 :     const Note* secondaryNote = tieHasBothNotes ? item->endNote() : nullptr;
    3066         4799 :     const Chord* secondaryChord = secondaryNote ? secondaryNote->chord() : nullptr;
    3067         4799 :     const Measure* secondaryMeasure = secondaryChord ? secondaryChord->measure() : nullptr;
    3068              : 
    3069         4799 :     if (item->slurDirection() == DirectionV::AUTO) {
    3070         4664 :         std::vector<Note*> notes = primaryChord->notes();
    3071         4664 :         size_t n = notes.size();
    3072         4664 :         StaffType* st = item->staff()->staffType(primaryNote ? primaryNote->tick() : Fraction(0, 1));
    3073         4664 :         bool simpleException = st && st->isSimpleTabStaff();
    3074              :         // if there are multiple voices, the tie direction goes on stem side
    3075         4664 :         if (primaryMeasure->hasVoices(primaryChord->staffIdx(), primaryChord->tick(), primaryChord->actualTicks())) {
    3076          322 :             item->setUp(simpleException ? isUpVoice(primaryChord->voice()) : primaryChord->up());
    3077         4342 :         } else if (tieHasBothNotes && secondaryMeasure->hasVoices(secondaryChord->staffIdx(), secondaryChord->tick(),
    3078              :                                                                   secondaryChord->actualTicks())) {
    3079           15 :             item->setUp(simpleException ? isUpVoice(secondaryChord->voice()) : secondaryChord->up());
    3080         4327 :         } else if (n == 1) {
    3081              :             //
    3082              :             // single note
    3083              :             //
    3084         3716 :             if (tieHasBothNotes && primaryChord->up() != secondaryChord->up()) {
    3085              :                 // if stem direction is mixed, always up
    3086          165 :                 item->setUp(true);
    3087              :             } else {
    3088         3551 :                 item->setUp(!primaryChord->up());
    3089              :             }
    3090              :         } else {
    3091              :             //
    3092              :             // chords
    3093              :             //
    3094              :             // first, find pivot point in chord (below which all ties curve down and above which all ties curve up)
    3095          611 :             Note* pivotPoint = nullptr;
    3096          611 :             bool multiplePivots = false;
    3097         2340 :             for (size_t i = 0; i < n - 1; ++i) {
    3098         1729 :                 if (!notes[i]->tieFor()) {
    3099          174 :                     continue; // don't include notes that don't have ties
    3100              :                 }
    3101         5066 :                 for (size_t j = i + 1; j < n; ++j) {
    3102         3511 :                     if (!notes[j]->tieFor()) {
    3103          148 :                         continue;
    3104              :                     }
    3105         3363 :                     int noteDiff = compareNotesPos(notes[i], notes[j]);
    3106         3363 :                     if (!multiplePivots && std::abs(noteDiff) <= 1) {
    3107              :                         // TODO: Fix unison ties somehow--if noteDiff == 0 then we need to determine which of the unison is 'lower'
    3108          180 :                         if (pivotPoint) {
    3109           18 :                             multiplePivots = true;
    3110           18 :                             pivotPoint = nullptr;
    3111              :                         } else {
    3112          162 :                             pivotPoint = noteDiff < 0 ? notes[i] : notes[j];
    3113              :                         }
    3114              :                     }
    3115              :                 }
    3116              :             }
    3117          611 :             if (!pivotPoint) {
    3118              :                 // if the pivot point was not found (either there are no unisons/seconds or there are more than one),
    3119              :                 // determine if this note is in the lower or upper half of this chord
    3120          467 :                 int notesAbove = 0, tiesAbove = 0;
    3121          467 :                 int notesBelow = 0, tiesBelow = 0;
    3122          467 :                 int unisonTies = 0;
    3123         2019 :                 for (size_t i = 0; i < n; ++i) {
    3124         1552 :                     if (notes[i] == primaryNote) {
    3125              :                         // skip counting if this note is the current note or if this note doesn't have a tie
    3126          467 :                         continue;
    3127              :                     }
    3128         1085 :                     int noteDiff = compareNotesPos(primaryNote, notes[i]);
    3129         1085 :                     if (noteDiff == 0) {  // unison
    3130            0 :                         if (notes[i]->tieFor()) {
    3131            0 :                             unisonTies++;
    3132              :                         }
    3133              :                     }
    3134         1085 :                     if (noteDiff < 0) { // the note is above startNote
    3135          523 :                         notesAbove++;
    3136          523 :                         if (notes[i]->tieFor()) {
    3137          447 :                             tiesAbove++;
    3138              :                         }
    3139              :                     }
    3140         1085 :                     if (noteDiff > 0) { // the note is below startNote
    3141          562 :                         notesBelow++;
    3142          562 :                         if (notes[i]->tieFor()) {
    3143          432 :                             tiesBelow++;
    3144              :                         }
    3145              :                     }
    3146              :                 }
    3147              : 
    3148          467 :                 if (tiesAbove == 0 && tiesBelow == 0 && unisonTies == 0) {
    3149              :                     // this is the only tie in the chord.
    3150           64 :                     if (notesAbove == notesBelow) {
    3151            4 :                         item->setUp(!primaryChord->up());
    3152              :                     } else {
    3153           60 :                         item->setUp(notesAbove < notesBelow);
    3154              :                     }
    3155          403 :                 } else if (tiesAbove == tiesBelow) {
    3156              :                     // this note is dead center, so its tie should go counter to the stem direction
    3157           24 :                     item->setUp(!primaryChord->up());
    3158              :                 } else {
    3159          379 :                     item->setUp(tiesAbove < tiesBelow);
    3160              :                 }
    3161          144 :             } else if (pivotPoint == primaryNote) {
    3162              :                 // the current note is the lower of the only second or unison in the chord; tie goes down.
    3163           28 :                 item->setUp(false);
    3164              :             } else {
    3165              :                 // if lower than the pivot, tie goes down, otherwise up
    3166          116 :                 int noteDiff = compareNotesPos(primaryNote, pivotPoint);
    3167          116 :                 item->setUp(noteDiff >= 0);
    3168              :             }
    3169              :         }
    3170         4664 :     } else {
    3171          135 :         item->setUp(item->slurDirection() == DirectionV::UP ? true : false);
    3172              :     }
    3173              : }
    3174              : 
    3175         4762 : void SlurTieLayout::calculateIsInside(Tie* item)
    3176              : {
    3177         4762 :     if (item->tiePlacement() != TiePlacement::AUTO) {
    3178            0 :         item->setIsInside(item->tiePlacement() == TiePlacement::INSIDE);
    3179            0 :         return;
    3180              :     }
    3181              : 
    3182         4762 :     const Note* startN = item->startNote();
    3183         4762 :     const Chord* startChord = startN ? startN->chord() : nullptr;
    3184         4762 :     const Note* endN = item->endNote();
    3185         4762 :     const Chord* endChord = endN ? endN->chord() : nullptr;
    3186              : 
    3187         4762 :     if (!startChord && !endChord) {
    3188            0 :         item->setIsInside(false);
    3189            0 :         return;
    3190              :     }
    3191              : 
    3192         4762 :     const bool startIsSingleNote = startChord ? startChord->notes().size() <= 1 : false;
    3193         4762 :     const bool endIsSingleNote = endChord ? endChord->notes().size() <= 1 : false;
    3194              : 
    3195         4762 :     const bool shouldPlaceInside = startChord && endChord ? startIsSingleNote && endIsSingleNote : startIsSingleNote || endIsSingleNote;
    3196              : 
    3197         4762 :     if (shouldPlaceInside) {
    3198         4033 :         item->setIsInside(item->style().styleV(Sid::tiePlacementSingleNote).value<TiePlacement>() == TiePlacement::INSIDE);
    3199              :     } else {
    3200          729 :         item->setIsInside(item->style().styleV(Sid::tiePlacementChord).value<TiePlacement>() == TiePlacement::INSIDE);
    3201              :     }
    3202              : }
    3203              : 
    3204          929 : void SlurTieLayout::layoutSegment(SlurSegment* item, const PointF& p1, const PointF& p2)
    3205              : {
    3206          929 :     SlurSegment::LayoutData* ldata = item->mutldata();
    3207          929 :     if (shouldHideSlurSegment(item)) {
    3208            0 :         ldata->setIsSkipDraw(true);
    3209            0 :         return;
    3210              :     }
    3211              : 
    3212          929 :     ldata->setIsSkipDraw(false);
    3213              : 
    3214          929 :     ldata->setPos(PointF());
    3215          929 :     item->ups(Grip::START).p = p1;
    3216          929 :     item->ups(Grip::END).p   = p2;
    3217          929 :     item->setExtraHeight(0.0);
    3218              : 
    3219              :     //Adjust Y pos to staff type yOffset before other calculations
    3220          929 :     ldata->moveY(item->staffOffsetY());
    3221              : 
    3222          929 :     computeBezier(item);
    3223              : }
    3224              : 
    3225         6123 : void SlurTieLayout::computeMidThickness(SlurTieSegment* slurTieSeg, double slurTieLengthInSp)
    3226              : {
    3227         6123 :     Staff* staff = slurTieSeg->score() ? slurTieSeg->score()->staff(slurTieSeg->vStaffIdx()) : nullptr;
    3228         6123 :     const double mag = staff ? staff->staffMag(slurTieSeg->slurTie()->tick()) : 1.0;
    3229         6123 :     const double scalingFactor = slurTieSeg->slurTie()->scalingFactor();
    3230              : 
    3231         6123 :     if (slurTieSeg->slurTie()->styleType() != SlurStyleType::Solid) {
    3232           57 :         slurTieSeg->mutldata()->midThickness.set_value(0.5 * slurTieSeg->dottedWidth() * mag * scalingFactor);
    3233           57 :         return;
    3234              :     }
    3235              : 
    3236         6066 :     const double endWidth = slurTieSeg->endWidth();
    3237         6066 :     const double midWidth = slurTieSeg->midWidth();
    3238         6066 :     const double minTieLength = mag * slurTieSeg->style().styleS(Sid::minTieLength).val();
    3239         6066 :     const double shortTieLimit = mag * 4.0;
    3240         6066 :     const double minTieThickness = mag * (0.15 * slurTieSeg->spatium() - endWidth);
    3241         6066 :     const double normalThickness = mag * (midWidth - endWidth);
    3242              : 
    3243         6066 :     bool invalid = muse::RealIsEqualOrMore(minTieLength, shortTieLimit);
    3244              : 
    3245              :     double finalThickness;
    3246         6066 :     if (slurTieLengthInSp > shortTieLimit || invalid) {
    3247         3074 :         finalThickness = normalThickness;
    3248              :     } else {
    3249         2992 :         const double A = 1 / (shortTieLimit - minTieLength);
    3250         2992 :         const double B = normalThickness - minTieThickness;
    3251         2992 :         const double C = shortTieLimit * minTieThickness - minTieLength * normalThickness;
    3252         2992 :         finalThickness = A * (B * slurTieLengthInSp + C);
    3253              :     }
    3254              : 
    3255         6066 :     finalThickness = std::min(finalThickness, normalThickness * scalingFactor);
    3256              : 
    3257         6066 :     slurTieSeg->mutldata()->midThickness.set_value(finalThickness);
    3258              : }
    3259              : 
    3260         6123 : void SlurTieLayout::fillShape(SlurTieSegment* slurTieSeg, double slurTieLengthInSp)
    3261              : {
    3262         6123 :     Shape shape(Shape::Type::Composite);
    3263         6123 :     PointF startPoint = slurTieSeg->ups(Grip::START).pos();
    3264              : 
    3265         6123 :     double midThickness = 2 * slurTieSeg->ldata()->midThickness();
    3266         6123 :     int nbShapes = round(5.0 * slurTieLengthInSp);
    3267         6123 :     nbShapes = std::max(nbShapes, 20);
    3268         6123 :     nbShapes = std::min(nbShapes, 50);
    3269        12246 :     const CubicBezier b(startPoint, slurTieSeg->ups(Grip::BEZIER1).pos(), slurTieSeg->ups(Grip::BEZIER2).pos(),
    3270        18369 :                         slurTieSeg->ups(Grip::END).pos());
    3271       199419 :     for (int i = 1; i <= nbShapes; i++) {
    3272       193296 :         double percent = pow(sin(0.5 * M_PI * (double(i) / double(nbShapes))), 2);
    3273       193296 :         const PointF point = b.pointAtPercent(percent);
    3274       193296 :         RectF re = RectF(startPoint, point).normalized();
    3275       193296 :         double approxThicknessAtPercent = midThickness;
    3276       193296 :         if (slurTieSeg->slurTie()->styleType() == SlurStyleType::Solid) {
    3277       190839 :             approxThicknessAtPercent *= (1 - 2 * std::abs(0.5 - percent));
    3278              :         }
    3279       193296 :         if (re.height() < approxThicknessAtPercent) {
    3280        88756 :             double adjust = (approxThicknessAtPercent - re.height()) * .5;
    3281        88756 :             re.adjust(0.0, -adjust, 0.0, adjust);
    3282              :         }
    3283       193296 :         shape.add(re, slurTieSeg);
    3284       193296 :         startPoint = point;
    3285              :     }
    3286              : 
    3287         6123 :     slurTieSeg->mutldata()->setShape(shape);
    3288         6123 : }
        

Generated by: LCOV version 2.0-1