LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - horizontalspacing.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 90.0 % 1149 1034
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 <cfloat>
      23              : 
      24              : #include "horizontalspacing.h"
      25              : #include "chordbracketlayout.h"
      26              : #include "parenthesislayout.h"
      27              : 
      28              : #include "dom/barline.h"
      29              : #include "dom/beam.h"
      30              : #include "dom/chord.h"
      31              : #include "dom/chordbracket.h"
      32              : #include "dom/engravingitem.h"
      33              : #include "dom/glissando.h"
      34              : #include "dom/lyrics.h"
      35              : #include "dom/measure.h"
      36              : #include "dom/note.h"
      37              : #include "dom/rest.h"
      38              : #include "dom/score.h"
      39              : #include "dom/stemslash.h"
      40              : #include "dom/staff.h"
      41              : #include "dom/stem.h"
      42              : #include "dom/system.h"
      43              : #include "dom/tie.h"
      44              : #include "dom/timesig.h"
      45              : 
      46              : using namespace mu::engraving;
      47              : using namespace mu::engraving::rendering::score;
      48              : 
      49         7186 : double HorizontalSpacing::computeSpacingForFullSystem(System* system, double stretchReduction, double squeezeFactor,
      50              :                                                       bool overrideMinMeasureWidth)
      51              : {
      52         7201 :     TRACEFUNC;
      53              : 
      54         7186 :     if (system->score()->allStavesInvisible()) {
      55            0 :         return 0.0;
      56              :     }
      57              : 
      58         7186 :     HorizontalSpacingContext ctx;
      59         7186 :     ctx.system = system;
      60         7186 :     ctx.spatium = system->spatium();
      61              : 
      62         7186 :     ctx.stretchReduction = stretchReduction;
      63         7186 :     ctx.squeezeFactor = squeezeFactor;
      64         7186 :     ctx.spacingDensity = system->style().styleD(Sid::spacingDensity);
      65         7186 :     ctx.overrideMinMeasureWidth = overrideMinMeasureWidth;
      66              : 
      67         7186 :     ctx.xCur = system->leftMargin();
      68         7186 :     ctx.xLeftBarrier = system->leftMargin();
      69         7186 :     ctx.systemIsFull = true;
      70              : 
      71         7186 :     std::vector<Measure*> measureGroup;
      72         7186 :     measureGroup.reserve(system->measures().size());
      73              : 
      74        37628 :     for (MeasureBase* mb : system->measures()) {
      75        30442 :         if (mb->isHBox()) {
      76          147 :             spaceMeasureGroup(measureGroup, ctx);
      77          147 :             measureGroup.clear();
      78              : 
      79          147 :             mb->mutldata()->setPosX(ctx.xCur);
      80          147 :             mb->computeMinWidth();
      81          147 :             ctx.xCur += mb->width();
      82              :         } else {
      83        30295 :             measureGroup.push_back(toMeasure(mb));
      84              :         }
      85              :     }
      86              : 
      87         7186 :     spaceMeasureGroup(measureGroup, ctx);
      88              : 
      89         7186 :     return ctx.xCur;
      90         7186 : }
      91              : 
      92        54783 : double HorizontalSpacing::updateSpacingForLastAddedMeasure(System* system, bool startOfContinuousLayoutRegion)
      93              : {
      94        54798 :     TRACEFUNC;
      95              : 
      96        54783 :     if (system->score()->allStavesInvisible()) {
      97          189 :         return 0.0;
      98              :     }
      99              : 
     100        54594 :     HorizontalSpacingContext ctx;
     101        54594 :     ctx.system = system;
     102        54594 :     ctx.spatium = system->spatium();
     103        54594 :     ctx.xLeftBarrier = system->leftMargin();
     104        54594 :     ctx.spacingDensity = system->style().styleD(Sid::spacingDensity);
     105              : 
     106        54594 :     size_t measureCount = system->measures().size();
     107        54594 :     IF_ASSERT_FAILED(measureCount > 0) {
     108            0 :         return 0.0;
     109              :     }
     110              : 
     111        54594 :     MeasureBase* secondToLast = measureCount >= 2 ? system->measures()[measureCount - 2] : nullptr;
     112        54594 :     MeasureBase* last = system->measures()[measureCount - 1];
     113              : 
     114        54594 :     if (secondToLast) {
     115        47491 :         ctx.xCur = secondToLast->x();
     116        47491 :         if (secondToLast->isHBox() || last->isHBox() || startOfContinuousLayoutRegion) {
     117          249 :             ctx.xCur += secondToLast->width();
     118              :         }
     119              :     } else {
     120         7103 :         ctx.xCur = system->leftMargin();
     121              :     }
     122              : 
     123        54594 :     if (last->isHBox()) {
     124          211 :         last->mutldata()->setPosX(ctx.xCur);
     125          211 :         last->computeMinWidth();
     126          211 :         ctx.xCur += last->width();
     127        54383 :     } else if (!secondToLast || secondToLast->isHBox() || startOfContinuousLayoutRegion) {
     128        21423 :         spaceMeasureGroup({ toMeasure(last) }, ctx);
     129              :     } else {
     130        47242 :         ctx.startMeas = toMeasure(last);
     131       141726 :         spaceMeasureGroup({ toMeasure(secondToLast), toMeasure(last) }, ctx);
     132              :     }
     133              : 
     134        54594 :     return ctx.xCur;
     135        54783 : }
     136              : 
     137            3 : void HorizontalSpacing::squeezeSystemToFit(System* system, double& curSysWidth, double targetSysWidth)
     138              : {
     139            5 :     TRACEFUNC;
     140              : 
     141            3 :     Measure* firstMeasure = system->firstMeasure();
     142            3 :     if (!firstMeasure) {
     143            0 :         return;
     144              :     }
     145              : 
     146              :     static constexpr double STRETCH_REDUCTION_STEP = 0.33;
     147              :     static constexpr double SQUEEZE_STEP = 0.2;
     148              : 
     149            3 :     double squeezeFactor = 1 - SQUEEZE_STEP; // Reduces shape paddings
     150            3 :     double stretchReduction = 1 - STRETCH_REDUCTION_STEP; // Reduces duration stretch
     151              : 
     152            7 :     while (curSysWidth > targetSysWidth && muse::RealIsEqualOrMore(squeezeFactor, 0.0)) {
     153            4 :         curSysWidth = HorizontalSpacing::computeSpacingForFullSystem(system, stretchReduction, squeezeFactor, true);
     154            4 :         squeezeFactor -= SQUEEZE_STEP;
     155            4 :         stretchReduction -= STRETCH_REDUCTION_STEP;
     156              :     }
     157              : 
     158            3 :     if (curSysWidth < targetSysWidth) {
     159            3 :         return;
     160              :     }
     161              : 
     162              :     // Things don't fit without collisions, so give up and allow collisions
     163            0 :     double smallerStep = 0.25 * SQUEEZE_STEP;
     164            0 :     double widthReduction = 1 - smallerStep;
     165            0 :     while (curSysWidth > targetSysWidth && muse::RealIsEqualOrMore(widthReduction, 0.0)) {
     166            0 :         for (MeasureBase* mb : system->measures()) {
     167            0 :             if (!mb->isMeasure()) {
     168            0 :                 continue;
     169              :             }
     170              : 
     171            0 :             Measure* m = toMeasure(mb);
     172            0 :             double prevWidth = m->width();
     173            0 :             for (Segment& segment : m->segments()) {
     174            0 :                 if (!segment.isChordRestType()) {
     175            0 :                     continue;
     176              :                 }
     177            0 :                 double curSegmentWidth = segment.width();
     178            0 :                 segment.setWidth(curSegmentWidth * widthReduction);
     179              :             }
     180            0 :             m->respaceSegments();
     181            0 :             curSysWidth += m->width() - prevWidth;
     182              :         }
     183            0 :         widthReduction -= smallerStep;
     184              :     }
     185            3 : }
     186              : 
     187         6144 : void HorizontalSpacing::justifySystem(System* system, double curSysWidth, double targetSystemWidth)
     188              : {
     189         6144 :     double rest = targetSystemWidth - curSysWidth;
     190         6144 :     if (muse::RealIsNull(rest)) {
     191            0 :         return;
     192              :     }
     193              : 
     194         6144 :     IF_ASSERT_FAILED(rest > 0) {
     195            0 :         return;
     196              :     }
     197              : 
     198         6144 :     std::vector<Spring> springs;
     199              : 
     200        35424 :     for (MeasureBase* mb : system->measures()) {
     201        29280 :         if (!mb->isMeasure()) {
     202          139 :             continue;
     203              :         }
     204       138247 :         for (Segment& s : toMeasure(mb)->segments()) {
     205       109106 :             if (s.isChordRestType() && s.ticks() > Fraction(0, 1) && s.visible() && s.enabled() && !s.allElementsInvisible()) {
     206        60188 :                 double springConst = 1 / s.stretch();
     207        60188 :                 double width = s.width() - s.widthOffset();
     208        60188 :                 double preTension = width * springConst;
     209        60188 :                 springs.emplace_back(springConst, width, preTension, &s);
     210              :             }
     211              :         }
     212              :     }
     213              : 
     214         6144 :     stretchSegmentsToWidth(springs, rest);
     215              : 
     216         6144 :     double x = system->leftMargin();
     217        35424 :     for (MeasureBase* mb : system->measures()) {
     218        29280 :         if (mb->isMeasure()) {
     219        29141 :             toMeasure(mb)->respaceSegments();
     220              :         }
     221        29280 :         mb->mutldata()->setPosX(x);
     222        29280 :         x += mb->width();
     223              :     }
     224         6144 : }
     225              : 
     226        61716 : void HorizontalSpacing::spaceMeasureGroup(const std::vector<Measure*>& measureGroup, HorizontalSpacingContext& ctx)
     227              : {
     228        61716 :     if (measureGroup.empty()) {
     229          110 :         return;
     230              :     }
     231              : 
     232        61606 :     measureGroup.front()->mutldata()->setPosX(ctx.xCur);
     233              : 
     234        61606 :     std::vector<Segment*> segList;
     235        61606 :     segList.reserve(2 * measureGroup.size());
     236              : 
     237        61606 :     int startSegIdx = -1;
     238       193526 :     for (Measure* measure : measureGroup) {
     239       131920 :         if (measure == ctx.startMeas) {
     240        47242 :             startSegIdx = int(segList.size());
     241              :         }
     242       611657 :         for (Segment& segment : measure->segments()) {
     243       479737 :             segList.push_back(&segment);
     244              :         }
     245              :     }
     246              : 
     247        61606 :     IF_ASSERT_FAILED(!segList.empty()) {
     248            0 :         return;
     249              :     }
     250              : 
     251        61606 :     std::vector<SegmentPosition> segmentPositions = spaceSegments(segList, startSegIdx, ctx);
     252        61606 :     moveRightAlignedSegments(segmentPositions, ctx);
     253              : 
     254        61606 :     setPositionsAndWidths(segmentPositions);
     255              : 
     256        61606 :     if (!ctx.overrideMinMeasureWidth) {
     257        61602 :         enforceMinimumMeasureWidths(measureGroup);
     258              :     }
     259              : 
     260        61606 :     Measure* lastMeas = measureGroup.back();
     261        61606 :     ctx.xCur = lastMeas->x() + lastMeas->width();
     262        61606 : }
     263              : 
     264        61606 : std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*>& segList, int startSegIdx,
     265              :                                                                                  HorizontalSpacingContext& ctx)
     266              : {
     267        61606 :     std::vector<SegmentPosition> placedSegments;
     268        61606 :     placedSegments.reserve(segList.size());
     269              : 
     270       541343 :     for (size_t i = 0; i < segList.size(); ++i) {
     271       479737 :         Segment* curSeg = segList[i];
     272       479737 :         if (ignoreSegmentForSpacing(curSeg)) {
     273        29088 :             placedSegments.emplace_back(curSeg, ctx.xCur, /*ignoreForSpacing*/ true);
     274       191872 :             continue;
     275              :         }
     276              : 
     277       450649 :         if (static_cast<int>(i) < startSegIdx) {
     278       162784 :             placedSegments.emplace_back(curSeg, curSeg->xPosInSystemCoords());
     279       162784 :             ctx.xCur = curSeg->xPosInSystemCoords() + curSeg->width();
     280       162784 :             continue;
     281              :         }
     282              : 
     283       287865 :         curSeg->clearWidthOffset();
     284              : 
     285       287865 :         if (placedSegments.empty() || ignoreAllSegmentsForSpacing(placedSegments)) {
     286        14364 :             double xFirst = getFirstSegmentXPos(curSeg, ctx);
     287        14364 :             ctx.xCur += xFirst;
     288        14364 :             placedSegments.emplace_back(curSeg, ctx.xCur);
     289              :         } else {
     290       273501 :             spaceAgainstPreviousSegments(curSeg, placedSegments, ctx);
     291              :         }
     292              : 
     293       287865 :         double leadingSpace = curSeg->extraLeadingSpace().toAbsolute(ctx.spatium);
     294       287865 :         placedSegments.back().xPosInSystemCoords += leadingSpace;
     295              : 
     296       287865 :         if (curSeg->isChordRestType()) {
     297       171215 :             bool isFirstCROfSystem = curSeg->rtick().isZero() && curSeg->measure()->isFirstInSystem();
     298       171215 :             if (isFirstCROfSystem) {
     299        14273 :                 double xMinSystemHeaderDist = ctx.system->leftMargin() + curSeg->style().styleAbsolute(
     300        14273 :                     Sid::systemHeaderMinStartOfSystemDistance);
     301        14273 :                 placedSegments.back().xPosInSystemCoords = std::max(placedSegments.back().xPosInSystemCoords, xMinSystemHeaderDist);
     302        14273 :                 ctx.xCur = std::max(ctx.xCur, xMinSystemHeaderDist);
     303              :             }
     304              : 
     305       171215 :             double chordRestSegWidth = chordRestSegmentNaturalWidth(curSeg, ctx);
     306              : 
     307       171215 :             Segment* nextSeg = i < segList.size() - 1 ? segList[i + 1] : nullptr;
     308       171215 :             if (nextSeg) {
     309       171215 :                 double nextSegLeadingSpace = nextSeg->extraLeadingSpace().toAbsolute(ctx.spatium);
     310       171215 :                 if (!muse::RealIsNull(nextSegLeadingSpace)) {
     311            0 :                     nextSegLeadingSpace = std::max(nextSegLeadingSpace, -chordRestSegWidth);
     312            0 :                     curSeg->addWidthOffset(nextSegLeadingSpace);
     313              :                 }
     314       171215 :                 if (nextSeg->isChordRestType()) {
     315        85929 :                     applyCrossBeamSpacingCorrection(curSeg, nextSeg, chordRestSegWidth);
     316              :                 }
     317              :             }
     318              : 
     319       171215 :             ctx.xCur += chordRestSegWidth;
     320              :         }
     321              :     }
     322              : 
     323        61606 :     if (ctx.systemIsFull) {
     324         7223 :         checkLyricsAgainstRightMargin(placedSegments, ctx);
     325         7223 :         checkLargeTimeSigAgainstRightMargin(placedSegments);
     326              :     }
     327              : 
     328        61606 :     return placedSegments;
     329            0 : }
     330              : 
     331       479737 : bool HorizontalSpacing::ignoreSegmentForSpacing(const Segment* segment)
     332              : {
     333       479737 :     return !segment->enabled() || !segment->isActive() || segment->allElementsInvisible();
     334              : }
     335              : 
     336       273552 : bool HorizontalSpacing::ignoreAllSegmentsForSpacing(const std::vector<SegmentPosition>& segmentPositions)
     337              : {
     338       275868 :     for (const SegmentPosition& segPos : segmentPositions) {
     339       275817 :         if (!segPos.ignoreForSpacing) {
     340       273501 :             return false;
     341              :         }
     342              :     }
     343              : 
     344           51 :     return true;
     345              : }
     346              : 
     347       273501 : void HorizontalSpacing::spaceAgainstPreviousSegments(Segment* segment, std::vector<SegmentPosition>& prevSegPositions,
     348              :                                                      HorizontalSpacingContext& ctx)
     349              : {
     350       273501 :     double x = -DBL_MAX;
     351       273501 :     int prevCRSegmentsCount = 0;
     352              : 
     353       273501 :     if (segment->measure()->isFirstInSystem()) {
     354        70501 :         checkLyricsAgainstLeftMargin(segment, x, ctx);
     355              :     }
     356              : 
     357       273501 :     bool segmentHasTimeSigAboveStaff = segment->hasTimeSigAboveStaves();
     358              : 
     359       694482 :     for (size_t i = prevSegPositions.size(); i > 0; --i) {
     360       636734 :         const SegmentPosition& prevSegPos = prevSegPositions[i - 1];
     361       636734 :         if (prevSegPos.ignoreForSpacing) {
     362        43116 :             continue;
     363              :         }
     364              : 
     365       593618 :         Segment* prevSeg = prevSegPos.segment;
     366       593618 :         double xPrevSeg = prevSegPos.xPosInSystemCoords;
     367              : 
     368       593618 :         if (prevSeg->isChordRestType()) {
     369       356236 :             ++prevCRSegmentsCount;
     370              :         }
     371              : 
     372       593618 :         bool timeSigAboveBarlineCase = segmentHasTimeSigAboveStaff && prevSeg->isEndBarLineType() && prevSeg->tick() == segment->tick();
     373            0 :         bool timeSigAboveKeySigCase = segmentHasTimeSigAboveStaff && prevSeg->isType(SegmentType::KeySig)
     374       593618 :                                       && prevSeg->tick() == segment->tick();
     375            0 :         bool timeSigAboveRepeatKeySigCase = segmentHasTimeSigAboveStaff && prevSeg->isType(SegmentType::KeySigRepeatAnnounce)
     376       593618 :                                             && prevSeg->tick() == segment->tick();
     377              : 
     378       593618 :         if (timeSigAboveBarlineCase) {
     379            0 :             x = xPrevSeg + prevSeg->minRight() - 0.5 * prevSeg->style().styleAbsolute(Sid::barWidth); // align to the preceding barline
     380       593618 :         } else if (timeSigAboveKeySigCase) {
     381            0 :             x = xPrevSeg + segment->minLeft(); // align to the preceding keySig
     382       593618 :         } else if (timeSigAboveRepeatKeySigCase) {
     383            0 :             x = xPrevSeg + prevSeg->minRight();
     384              :         } else {
     385       593618 :             double minHorDist = minHorizontalDistance(prevSeg, segment, ctx.squeezeFactor);
     386       593618 :             minHorDist = std::max(minHorDist, spaceLyricsAgainstBarlines(prevSeg, segment, ctx));
     387       593618 :             double xNonCollision = xPrevSeg + minHorDist;
     388       593618 :             x = std::max(x, xNonCollision);
     389              :         }
     390              : 
     391       593618 :         if (x > ctx.xCur || timeSigAboveBarlineCase) {
     392       124526 :             if (prevCRSegmentsCount > 0) {
     393        23932 :                 double spaceIncrease = (x - ctx.xCur) / prevCRSegmentsCount;
     394        23932 :                 double xMovement = spaceIncrease;
     395        24508 :                 for (size_t j = i; j < prevSegPositions.size(); ++j) {
     396          576 :                     SegmentPosition& segPos = prevSegPositions[j];
     397          576 :                     if (segPos.ignoreForSpacing) {
     398           85 :                         continue;
     399              :                     }
     400          491 :                     segPos.xPosInSystemCoords += xMovement;
     401          491 :                     if (segPos.segment->isChordRestType()) {
     402           98 :                         xMovement += spaceIncrease;
     403              :                     }
     404              :                 }
     405              :             }
     406              : 
     407       124526 :             ctx.xCur = x;
     408              :         }
     409              : 
     410       593618 :         if (stopCheckingPreviousSegments(prevSegPos, SegmentPosition(segment, ctx.xCur))) {
     411       215753 :             break;
     412              :         }
     413              :     }
     414              : 
     415       273501 :     double xSeg = segment->isRightAligned() ? x : ctx.xCur;
     416              : 
     417       273501 :     prevSegPositions.emplace_back(segment, xSeg);
     418       273501 : }
     419              : 
     420       593618 : bool HorizontalSpacing::stopCheckingPreviousSegments(const SegmentPosition& prevSegPos, const SegmentPosition& curSegPos)
     421              : {
     422       593618 :     Segment* prevSeg = prevSegPos.segment;
     423       593618 :     Segment* curSeg = curSegPos.segment;
     424              : 
     425       593618 :     Fraction prevSegEndTick = prevSeg->tick() + prevSeg->ticks();
     426       593618 :     Fraction curSegTick = curSeg->tick();
     427              : 
     428       593618 :     if (prevSegEndTick >= curSegTick || curSeg->hasTimeSigAboveStaves()) {
     429       369454 :         return false;
     430              :     }
     431              : 
     432       224164 :     if (!curSeg->isChordRestType()) {
     433        88652 :         return true;
     434              :     }
     435              : 
     436       135512 :     bool curHasLyricsOrHarmony = false;
     437       135512 :     double curX = curSegPos.xPosInSystemCoords;
     438       135512 :     double curMinLeft = DBL_MAX;
     439       323710 :     for (const Shape& shape : curSeg->shapes()) {
     440       603391 :         for (const ShapeElement& shapeEl : shape.elements()) {
     441       415193 :             curMinLeft = std::min(curMinLeft, shapeEl.left());
     442       415193 :             if (const EngravingItem* item = shapeEl.item()) {
     443       415193 :                 if (item->isLyrics() || item->isHarmony()) {
     444        14875 :                     curHasLyricsOrHarmony = true;
     445              :                 }
     446              :             }
     447              :         }
     448       135512 :     }
     449       135512 :     curMinLeft += curX;
     450              : 
     451       135512 :     bool prevHasLyricsOrHarmony = false;
     452       135512 :     double prevX = prevSegPos.xPosInSystemCoords;
     453       135512 :     double prevMinRight = -DBL_MAX;
     454       323710 :     for (const Shape& shape : prevSeg->shapes()) {
     455       578762 :         for (const ShapeElement& shapeEl : shape.elements()) {
     456       390564 :             prevMinRight = std::max(prevMinRight, shapeEl.right());
     457       390564 :             if (const EngravingItem* item = shapeEl.item()) {
     458       390564 :                 if (item->isLyrics() || item->isHarmony()) {
     459         6478 :                     prevHasLyricsOrHarmony = true;
     460              :                 }
     461              :             }
     462              :         }
     463       135512 :     }
     464       135512 :     prevMinRight += prevX;
     465              : 
     466       135512 :     if (curHasLyricsOrHarmony == prevHasLyricsOrHarmony && curMinLeft > prevMinRight) {
     467       125789 :         return true;
     468              :     }
     469              : 
     470         9723 :     if (prevSeg->measure() != curSeg->measure() && prevSeg->measure() != curSeg->measure()->prevMM()) {
     471         1312 :         return true;
     472              :     }
     473              : 
     474         8411 :     return false;
     475              : }
     476              : 
     477        70501 : void HorizontalSpacing::checkLyricsAgainstLeftMargin(Segment* segment, double& x, HorizontalSpacingContext& ctx)
     478              : {
     479       178485 :     for (const Shape& staffShape : segment->shapes()) {
     480       322623 :         for (const ShapeElement& shapeEl : staffShape.elements()) {
     481       214639 :             if (shapeEl.item() && shapeEl.item()->isLyrics()) {
     482         3026 :                 x = std::max(x, ctx.xLeftBarrier - shapeEl.left());
     483              :             }
     484              :         }
     485        70501 :     }
     486        70501 : }
     487              : 
     488         7223 : void HorizontalSpacing::checkLyricsAgainstRightMargin(std::vector<SegmentPosition>& segPositions, const HorizontalSpacingContext& ctx)
     489              : {
     490         7223 :     const double systemEdge = segPositions.back().xPosInSystemCoords + segPositions.back().segment->minRight();
     491         7223 :     const MStyle& style = ctx.system->style();
     492         7223 :     const bool lyricsDashForce = style.styleB(Sid::lyricsDashForce);
     493         7223 :     const bool lyricsMelismaForce = style.styleB(Sid::lyricsMelismaForce);
     494         7223 :     const double minSpaceForDash = lyricsDashForce ? style.styleAbsolute(Sid::lyricsDashPad) + style.styleAbsolute(Sid::lyricsDashMinLength)
     495         7161 :                                    + style.styleAbsolute(Sid::lineEndToBarlineDistance) + segPositions.back().segment->minRight() : 0.0;
     496         7223 :     const double minSpaceForMelisma = lyricsMelismaForce ? style.styleAbsolute(Sid::lyricsMelismaPad) + style.styleAbsolute(
     497              :         Sid::lyricsMelismaMinLength)
     498            0 :                                       + style.styleAbsolute(Sid::lineEndToBarlineDistance) + segPositions.back().segment->minRight() : 0.0;
     499              : 
     500         7223 :     int chordRestSegmentsCount = 0;
     501              : 
     502        36587 :     for (size_t i = segPositions.size(); i > 1; --i) {
     503        35116 :         SegmentPosition& segPos = segPositions[i - 1];
     504        35116 :         double x = segPos.xPosInSystemCoords;
     505        35116 :         Segment* seg = segPos.segment;
     506        35116 :         if (!seg->measure()->isLastInSystem()) {
     507         5752 :             break;
     508              :         }
     509        29364 :         if (seg->isChordRestType()) {
     510        18574 :             ++chordRestSegmentsCount;
     511              :         } else {
     512        10790 :             continue;
     513              :         }
     514        18574 :         double xMaxLyrics = x;
     515        44848 :         for (const Shape& shape : seg->shapes()) {
     516        84728 :             for (const ShapeElement& shapeEl : shape.elements()) {
     517        58454 :                 if (shapeEl.item() && shapeEl.item()->isLyrics()) {
     518         1315 :                     const Lyrics* lyrics = toLyrics(shapeEl.item());
     519         1315 :                     bool hasMelisma = lyrics->separator() && lyrics->separator()->isEndMelisma();
     520         1315 :                     bool hasDash = lyrics->syllabic() == LyricsSyllabic::BEGIN || lyrics->syllabic() == LyricsSyllabic::MIDDLE;
     521         1315 :                     double rightEdge = x + shapeEl.right();
     522         1315 :                     if (hasDash) {
     523           20 :                         rightEdge += minSpaceForDash;
     524         1295 :                     } else if (hasMelisma) {
     525           16 :                         rightEdge += minSpaceForMelisma;
     526              :                     }
     527         1315 :                     xMaxLyrics = std::max(xMaxLyrics, rightEdge);
     528              :                 }
     529              :             }
     530        18574 :         }
     531        18574 :         if (xMaxLyrics > systemEdge) {
     532          110 :             double lyricsOvershoot = xMaxLyrics - systemEdge;
     533          110 :             double spaceIncrease = lyricsOvershoot / chordRestSegmentsCount;
     534          110 :             double xMovement = spaceIncrease;
     535          220 :             for (size_t j = i; j < segPositions.size(); ++j) {
     536          110 :                 SegmentPosition& sp = segPositions[j];
     537          110 :                 sp.xPosInSystemCoords += xMovement;
     538          110 :                 if (sp.segment->isChordRestType()) {
     539            0 :                     xMovement += spaceIncrease;
     540              :                 }
     541              :             }
     542              :         }
     543              :     }
     544         7223 : }
     545              : 
     546       593618 : double HorizontalSpacing::spaceLyricsAgainstBarlines(Segment* firstSeg, Segment* secondSeg, const HorizontalSpacingContext& ctx)
     547              : {
     548       949854 :     if (!(firstSeg->isType(SegmentType::ChordRest) && secondSeg->isType(SegmentType::BarLineTypes))
     549       949854 :         && !(firstSeg->isType(SegmentType::BarLineTypes) && secondSeg->isType(SegmentType::ChordRest))) {
     550       377847 :         return 0.0;
     551              :     }
     552              : 
     553       215771 :     double w = 0.0;
     554              : 
     555       215771 :     bool crSegIsBefore = firstSeg->isChordRestType();
     556       215771 :     Segment* crSegment = crSegIsBefore ? firstSeg : secondSeg;
     557       215771 :     Segment* barlineSegment = crSegIsBefore ? secondSeg : firstSeg;
     558              : 
     559       215771 :     staff_idx_t nstaves = crSegment->score()->nstaves();
     560              : 
     561       532748 :     for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
     562       316977 :         if (!ctx.system->staff(staffIdx)->show()) {
     563       266243 :             continue;
     564              :         }
     565              : 
     566       311915 :         staff_idx_t nextStaff = ctx.system->nextVisibleStaff(staffIdx);
     567       311915 :         if (nextStaff == muse::nidx) {
     568       215771 :             continue;
     569              :         }
     570              : 
     571        96144 :         BarLine* barline = toBarLine(barlineSegment->element(staff2track(staffIdx)));
     572        96144 :         if (!barline || !barline->spanStaff()) {
     573        45410 :             continue;
     574              :         }
     575              : 
     576        50734 :         Shape barlineShape = barline->shape().translate(barline->pos());
     577              : 
     578        50734 :         track_idx_t startTrack = staff2track(staffIdx);
     579        50734 :         track_idx_t endTrack = staff2track(nextStaff) + VOICES;
     580       456778 :         for (track_idx_t track = startTrack; track < endTrack; ++track) {
     581       406044 :             ChordRest* chordRest = toChordRest(crSegment->element(track));
     582       406044 :             if (!chordRest) {
     583       311323 :                 continue;
     584              :             }
     585              : 
     586        99501 :             for (Lyrics* lyrics : chordRest->lyrics()) {
     587         4780 :                 if (!(lyrics->avoidBarlines() && lyrics->visible() && lyrics->autoplace())) {
     588            0 :                     continue;
     589              :                 }
     590         4780 :                 staff_idx_t lyricsStaffIdx = lyrics->staffIdx();
     591         4780 :                 if ((lyricsStaffIdx == staffIdx && lyrics->placeBelow()) || (lyricsStaffIdx == nextStaff && lyrics->placeAbove())) {
     592         1808 :                     Shape lyricsShape = lyrics->shape().translate(lyrics->pos());
     593         1808 :                     double minDist = crSegIsBefore ? lyricsShape.right() - barlineShape.left() : barlineShape.right() - lyricsShape.left();
     594         1808 :                     const double padding = 0.3 * lyrics->fontMetrics().xHeight();
     595         1808 :                     minDist += padding;
     596         1808 :                     w = std::max(w, minDist);
     597         1808 :                 }
     598              :             }
     599              :         }
     600        50734 :     }
     601              : 
     602       215771 :     return w;
     603              : }
     604              : 
     605         7223 : void HorizontalSpacing::checkLargeTimeSigAgainstRightMargin(std::vector<SegmentPosition>& segPositions)
     606              : {
     607         7223 :     SegmentPosition& cautionaryTSSegPos = segPositions.back();
     608         7223 :     if (!cautionaryTSSegPos.segment->hasTimeSigAboveStaves()) {
     609         7223 :         return;
     610              :     }
     611              : 
     612            0 :     const MStyle& style = cautionaryTSSegPos.segment->style();
     613            0 :     TimeSigVSMargin timeSigVSMargin = style.styleB(Sid::timeSigCenterOnBarline)
     614            0 :                                       ? style.styleV(Sid::timeSigVSMarginCentered).value<TimeSigVSMargin>()
     615            0 :                                       : style.styleV(Sid::timeSigVSMarginNonCentered).value<TimeSigVSMargin>();
     616            0 :     if (timeSigVSMargin != TimeSigVSMargin::RIGHT_ALIGN_TO_BARLINE) {
     617            0 :         return;
     618              :     }
     619              : 
     620            0 :     double xMax = 0.0;
     621            0 :     for (auto iter = segPositions.rbegin(); iter != segPositions.rend(); ++iter) {
     622            0 :         if (iter->segment->isType(SegmentType::EndBarLine | SegmentType::KeySigAnnounce)) {
     623            0 :             xMax = std::max(xMax, iter->xPosInSystemCoords + iter->segment->minRight());
     624              :         }
     625              :     }
     626              : 
     627            0 :     if (muse::RealIsNull(xMax)) {
     628            0 :         return;
     629              :     }
     630              : 
     631            0 :     double curTSPos = cautionaryTSSegPos.xPosInSystemCoords;
     632            0 :     double xMaxPos = xMax - cautionaryTSSegPos.segment->minRight();
     633            0 :     if (xMaxPos < curTSPos) {
     634            0 :         cautionaryTSSegPos.xPosInSystemCoords = xMaxPos;
     635              :     }
     636              : }
     637              : 
     638        61606 : void HorizontalSpacing::moveRightAlignedSegments(std::vector<SegmentPosition>& placedSegments, const HorizontalSpacingContext& ctx)
     639              : {
     640       541343 :     for (size_t i = 0; i < placedSegments.size(); ++i) {
     641       479737 :         Segment* segment = placedSegments[i].segment;
     642       479737 :         if (!segment->isRightAligned()) {
     643       477988 :             continue;
     644              :         }
     645              : 
     646         1749 :         double x = DBL_MAX;
     647              : 
     648         6217 :         for (size_t j = i + 1; j < placedSegments.size(); ++j) {
     649         5402 :             SegmentPosition& segPos = placedSegments[j];
     650         5402 :             if (segPos.ignoreForSpacing) {
     651          152 :                 continue;
     652              :             }
     653              : 
     654         5250 :             Segment* followingSeg = segPos.segment;
     655         5250 :             if (followingSeg->hasTimeSigAboveStaves()) {
     656            0 :                 continue;
     657              :             }
     658         5250 :             if (followingSeg->measure() != segment->measure() && followingSeg->rtick().isNotZero()) {
     659          934 :                 break;
     660              :             }
     661         4316 :             double followingSegX = segPos.xPosInSystemCoords;
     662         4316 :             double minDist = minHorizontalDistance(segment, followingSeg, ctx.squeezeFactor);
     663              : 
     664         4316 :             if (followingSeg->isRightAligned() && muse::RealIsNull(minDist)) {
     665           49 :                 continue;
     666              :             }
     667              : 
     668         4267 :             x = std::min(x, followingSegX - minDist);
     669              :         }
     670              : 
     671         1749 :         if (x != DBL_MAX) {
     672         1749 :             placedSegments[i].xPosInSystemCoords = x;
     673         1749 :             SegmentPosition& nextSegPos = placedSegments[i + 1];
     674         1763 :             for (size_t j = i + 2; nextSegPos.ignoreForSpacing && j < placedSegments.size(); ++j) {
     675           14 :                 nextSegPos = placedSegments[j];
     676              :             }
     677         1749 :             double nextSegX = nextSegPos.xPosInSystemCoords;
     678         1749 :             Segment* prevCRSeg = segment->prev(SegmentType::ChordRest);
     679         1749 :             if (prevCRSeg) {
     680         1712 :                 prevCRSeg->addWidthOffset(-nextSegX + x);
     681              :             }
     682              :         }
     683              :     }
     684        61606 : }
     685              : 
     686       330768 : void HorizontalSpacing::checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx,
     687              :                                                            double& curMinDist)
     688              : {
     689       330768 :     std::vector<ChordRest*> itemsToCheck;
     690       330768 :     itemsToCheck.reserve(VOICES);
     691              : 
     692      2676536 :     for (EngravingItem* el : nextSeg->elist()) {
     693      2345768 :         if (el && el->vStaffIdx() == staffIdx) {
     694       326065 :             ChordRest* cr = toChordRest(el);
     695       326065 :             if (cr->beam() && cr->beam()->cross()) {
     696          172 :                 itemsToCheck.push_back(toChordRest(el));
     697              :             }
     698              :         }
     699              :     }
     700              : 
     701       330768 :     for (ChordRest* cr : itemsToCheck) {
     702          172 :         Chord* potentialCollidingChord = nullptr;
     703          475 :         for (ChordRest* beamElement : cr->beam()->elements()) {
     704          409 :             if (beamElement->isChord() && beamElement->parent() == thisSeg) {
     705          106 :                 potentialCollidingChord = toChord(beamElement);
     706          106 :                 break;
     707              :             }
     708              :         }
     709              : 
     710          172 :         if (!potentialCollidingChord || potentialCollidingChord->up() != cr->up() || !potentialCollidingChord->stem()) {
     711          172 :             return;
     712              :         }
     713              : 
     714           46 :         bool checkStemCollision = (cr->up() && potentialCollidingChord->vStaffIdx() > cr->vStaffIdx())
     715           46 :                                   || (!cr->up() && potentialCollidingChord->vStaffIdx() < cr->vStaffIdx());
     716           30 :         if (!checkStemCollision) {
     717           30 :             return;
     718              :         }
     719              : 
     720            0 :         Stem* stem = potentialCollidingChord->stem();
     721            0 :         Shape prevStemShape = stem->shape().translated(stem->pos() + potentialCollidingChord->pos());
     722              :         // We don't know where this stem is vertically, but we know from the beam arrangement that
     723              :         // it's going to be crossed anyway, so we can space to it as if it had infinite height.
     724            0 :         prevStemShape.adjust(0.0, -100000, 0.0, 100000);
     725              : 
     726            0 :         const Shape& staffShape = nextSeg->staffShape(staffIdx);
     727              : 
     728            0 :         double minDist = minHorizontalDistance(prevStemShape, staffShape, stem->spatium());
     729              : 
     730            0 :         curMinDist = std::max(curMinDist, minDist);
     731            0 :     }
     732       330768 : }
     733              : 
     734       171215 : double HorizontalSpacing::chordRestSegmentNaturalWidth(Segment* segment, HorizontalSpacingContext& ctx)
     735              : {
     736       171215 :     double durationStretch = computeSegmentDurationStretch(segment, segment->prev(SegmentType::ChordRest));
     737              : 
     738       171215 :     Measure* measure = segment->measure();
     739       171215 :     double userStretch = std::clamp(measure->userStretch(), 0.1, 10.0); // TODO: enforce via UI, not here
     740              : 
     741       171215 :     double segTotalStretch = durationStretch * userStretch;
     742       171215 :     segment->setStretch(segTotalStretch);
     743              : 
     744              :     static constexpr Spatium DEFAULT_QUARTER_NOTE_SPACE = 3.5_sp;
     745       171215 :     double defaultQuarterNoteSpace = DEFAULT_QUARTER_NOTE_SPACE.toAbsolute(measure->spatium());
     746              : 
     747       171215 :     double naturalWidth = defaultQuarterNoteSpace * segTotalStretch * ctx.stretchReduction / ctx.spacingDensity;
     748              : 
     749       171215 :     return naturalWidth;
     750              : }
     751              : 
     752       171215 : double HorizontalSpacing::computeSegmentDurationStretch(const Segment* curSeg, const Segment* prevSeg)
     753              : {
     754       171215 :     if (curSeg->isMMRestSegment()) {
     755          409 :         return durationStretchForMMRests(curSeg);
     756              :     }
     757              : 
     758       170806 :     Fraction segTicks = curSeg->ticks();
     759       170806 :     Fraction shortestCR = curSeg->shortestChordRest();
     760       170806 :     Fraction prevShortestCR = prevSeg ? prevSeg->shortestChordRest() : Fraction(0, 1);
     761              : 
     762       170806 :     bool hasAdjacent = curSeg->isChordRestType() && shortestCR == segTicks;
     763       170806 :     bool prevHasAdjacent = prevSeg && (prevSeg->isChordRestType() && prevShortestCR == prevSeg->ticks());
     764              : 
     765              :     double durStretch;
     766       170806 :     const MStyle& style = curSeg->style();
     767       170806 :     double slope = style.styleD(Sid::measureSpacing);
     768              : 
     769       170806 :     if (hasAdjacent || curSeg->measure()->isMMRest()) {
     770       168860 :         durStretch = durationStretchForTicks(slope, segTicks);
     771              :     } else {
     772              :         // Polyrythms
     773         1946 :         if (prevSeg && !prevHasAdjacent && prevShortestCR < shortestCR) {
     774          546 :             durStretch = durationStretchForTicks(slope, prevShortestCR) * (segTicks / prevShortestCR).toDouble();
     775              :         } else {
     776         1400 :             durStretch = durationStretchForTicks(slope, shortestCR) * (segTicks / shortestCR).toDouble();
     777              :         }
     778              :     }
     779              : 
     780       170806 :     if (style.styleB(Sid::scaleRythmicSpacingForSmallNotes) && needsCueSizeSpacing(curSeg)) {
     781          670 :         durStretch *= curSeg->style().styleD(Sid::smallNoteMag);
     782              :     }
     783              : 
     784       170806 :     return durStretch;
     785              : }
     786              : 
     787          409 : double HorizontalSpacing::durationStretchForMMRests(const Segment* segment)
     788              : {
     789              :     static constexpr Fraction QUARTER = Fraction(1, 4);
     790              :     static constexpr int MIN_MMREST_COUNT  = 2;
     791              : 
     792          409 :     const MStyle& style = segment->style();
     793          409 :     const Measure* measure = segment->measure();
     794              : 
     795          409 :     Fraction timeSig = measure->timesig();
     796          409 :     bool constantWidth = style.styleB(Sid::mmRestConstantWidth);
     797          409 :     int mmRestWidthIncrementCap = style.styleI(Sid::mmRestMaxWidthIncrease);
     798              : 
     799          409 :     Fraction baseDuration = constantWidth ? style.styleI(Sid::mmRestReferenceWidth) * QUARTER : timeSig;
     800          409 :     Fraction durationIncrement = constantWidth ? QUARTER : Fraction(1, timeSig.denominator());
     801          409 :     int incrementCount = std::max(measure->mmRestCount() - MIN_MMREST_COUNT, 0);
     802          409 :     incrementCount = std::min(incrementCount, mmRestWidthIncrementCap);
     803              : 
     804          409 :     Fraction resultDuration = baseDuration + incrementCount * durationIncrement;
     805              : 
     806          818 :     return durationStretchForTicks(style.styleD(Sid::measureSpacing), resultDuration);
     807              : }
     808              : 
     809       171215 : double HorizontalSpacing::durationStretchForTicks(double slope, const Fraction& ticks)
     810              : {
     811              :     static constexpr Fraction REFERENCE_DURATION = Fraction(1, 4);
     812              : 
     813       171215 :     Fraction durationRatio = ticks / REFERENCE_DURATION;
     814              : 
     815       171215 :     return pow(slope, log2(durationRatio.toDouble()));
     816              : }
     817              : 
     818       102908 : bool HorizontalSpacing::needsCueSizeSpacing(const Segment* segment)
     819              : {
     820       102908 :     IF_ASSERT_FAILED(segment->isChordRestType()) {
     821            0 :         return false;
     822              :     }
     823              : 
     824       102908 :     bool hasCueSizedCR = false;
     825       131349 :     for (EngravingItem* item : segment->elist()) {
     826       129526 :         if (!item) {
     827        24871 :             continue;
     828              :         }
     829       104655 :         const ChordRest* cr = toChordRest(item);
     830       104655 :         if (cr->isSmall()) {
     831          690 :             hasCueSizedCR = true;
     832       103965 :         } else if (cr->actualTicks() == segment->ticks()) {
     833       101085 :             return false;
     834              :         }
     835              :     }
     836              : 
     837         1823 :     return hasCueSizedCR;
     838              : }
     839              : 
     840        85929 : void HorizontalSpacing::applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width)
     841              : {
     842        85929 :     CrossBeamSpacing crossBeamSpacing = computeCrossBeamSpacing(thisSeg, nextSeg);
     843              : 
     844        85929 :     const Score* score = thisSeg->score();
     845        85929 :     const PaddingTable& paddingTable = score->paddingTables().horizontalPaddingTable();
     846        85929 :     const MStyle& style = score->style();
     847              : 
     848        85929 :     double displacement = score->noteHeadWidth() - style.styleAbsolute(Sid::stemWidth);
     849              : 
     850        85929 :     if (crossBeamSpacing.upDown && crossBeamSpacing.canBeAdjusted) {
     851           27 :         thisSeg->addWidthOffset(displacement);
     852           27 :         width += displacement;
     853        85902 :     } else if (crossBeamSpacing.downUp && crossBeamSpacing.canBeAdjusted) {
     854           23 :         thisSeg->addWidthOffset(-displacement);
     855           23 :         width -= displacement;
     856              :     }
     857              : 
     858        85929 :     if (crossBeamSpacing.upDown) {
     859           27 :         if (crossBeamSpacing.hasOpposingBeamlets) {
     860            0 :             double minBeamletClearance = style.styleAbsolute(Sid::beamMinLen) * 2.0 + paddingTable.at(ElementType::BEAM).at(
     861            0 :                 ElementType::BEAM);
     862            0 :             width = std::max(width, displacement + minBeamletClearance);
     863              :         } else {
     864           27 :             width = std::max(width, 2 * displacement);
     865              :         }
     866              :     }
     867              : 
     868        85929 :     if (crossBeamSpacing.preventCrossStaffKerning) {
     869           38 :         double padding = crossBeamSpacing.ensureMinStemDistance ? paddingTable.at(ElementType::STEM).at(ElementType::STEM)
     870           11 :                          : style.styleAbsolute(Sid::minNoteDistance);
     871           38 :         width = std::max(width, score->noteHeadWidth() + padding);
     872        85891 :     } else if (crossBeamSpacing.ensureMinStemDistance) {
     873            4 :         width = std::max(width, paddingTable.at(ElementType::STEM).at(ElementType::STEM));
     874              :     }
     875        85929 : }
     876              : 
     877       328350 : double HorizontalSpacing::minStemDistOnNonAdjacentCross(const Segment* thisSeg, const Segment* nextSeg)
     878              : {
     879              :     // Extreme edge-case of chords that are a) adjacent in a cross-staff beam but b) on non-adjacent segments
     880              :     // (which causes them to escape the previous cross-staff spacing checks) c) stemmed up->down (see #27786)
     881       328350 :     const PaddingTable& paddingTable = thisSeg->score()->paddingTables().horizontalPaddingTable();
     882       328350 :     double dist = -DBL_MAX;
     883      2217078 :     for (EngravingItem* item : nextSeg->elist()) {
     884      1888728 :         if (!item || !item->isChord()) {
     885      1888701 :             continue;
     886              :         }
     887              : 
     888       200280 :         Chord* chord = toChord(item);
     889       200280 :         if (!chord->stem() || chord->up() || !(chord->beam() && chord->beam()->cross())) {
     890       200193 :             continue;
     891              :         }
     892              : 
     893           87 :         Beam* beam = chord->beam();
     894           87 :         std::vector<ChordRest*> beamElements = beam->elements();
     895           87 :         Chord* prevChordOnBeam = nullptr;
     896          218 :         for (size_t i = 0; i < beamElements.size(); ++i) {
     897          218 :             if (beamElements[i] == chord) {
     898           87 :                 if (i > 0) {
     899           63 :                     ChordRest* prevChordRest = beamElements[i - 1];
     900           63 :                     prevChordOnBeam = prevChordRest->isChord() ? toChord(prevChordRest) : nullptr;
     901              :                 }
     902           87 :                 break;
     903              :             }
     904              :         }
     905              : 
     906           87 :         if (!prevChordOnBeam || prevChordOnBeam->parent() != thisSeg || !prevChordOnBeam->up() || !prevChordOnBeam->stem()) {
     907           60 :             continue;
     908              :         }
     909              : 
     910           27 :         double minStemDist = prevChordOnBeam->x() + prevChordOnBeam->stem()->x() - (chord->x() + chord->stem()->x());
     911           27 :         minStemDist += paddingTable.at(ElementType::STEM).at(ElementType::STEM);
     912              : 
     913           27 :         dist = std::max(dist, minStemDist);
     914           87 :     }
     915              : 
     916       328350 :     return dist;
     917              : }
     918              : 
     919        85929 : HorizontalSpacing::CrossBeamSpacing HorizontalSpacing::computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg)
     920              : {
     921        85929 :     CrossBeamSpacing crossBeamType;
     922              : 
     923        85929 :     if (!thisSeg->isChordRestType() || !nextSeg || !nextSeg->isChordRestType()) {
     924            0 :         return crossBeamType;
     925              :     }
     926              : 
     927        85929 :     bool preventCrossStaffKerning = false;
     928        85929 :     bool ensureMinStemDistance = false;
     929       550957 :     for (EngravingItem* e : thisSeg->elist()) {
     930       465028 :         if (!e || !e->isChord() || !e->visible() || !e->staff()->visible()) {
     931       371190 :             continue;
     932              :         }
     933              : 
     934        93838 :         Chord* thisChord = toChord(e);
     935        93838 :         ChordRest* nextCR = toChordRest(nextSeg->element(thisChord->track()));
     936        93838 :         Chord* nextChord = nextCR && nextCR->isChord() ? toChord(nextCR) : nullptr;
     937        93838 :         if (!nextChord) {
     938        18644 :             continue;
     939              :         }
     940              : 
     941        75194 :         int thisStaffMove = thisChord->staffMove();
     942        75194 :         int nextStaffMove = nextChord->staffMove();
     943        75194 :         if (thisStaffMove == nextStaffMove) {
     944        75126 :             continue;
     945              :         }
     946              : 
     947           68 :         preventCrossStaffKerning = thisStaffMove > nextStaffMove;
     948          106 :         ensureMinStemDistance = (thisStaffMove > nextStaffMove && thisChord->up() && !nextChord->up())
     949          106 :                                 || (thisStaffMove < nextStaffMove && thisChord->up() == nextChord->up());
     950              :     }
     951              : 
     952        85929 :     crossBeamType.preventCrossStaffKerning = preventCrossStaffKerning;
     953        85929 :     crossBeamType.ensureMinStemDistance = ensureMinStemDistance;
     954              : 
     955        85929 :     bool upDown = false;
     956        85929 :     bool downUp = false;
     957        85929 :     bool canBeAdjusted = true;
     958        85929 :     bool hasOpposingBeamlets = false;
     959              : 
     960              :     // Spacing can be adjusted for cross-beam cases only if there aren't
     961              :     // chords in other voices in this or next segment.
     962       440098 :     for (EngravingItem* e : thisSeg->elist()) {
     963       377147 :         if (!e || !e->isChordRest() || !e->staff()->visible()) {
     964       264709 :             continue;
     965              :         }
     966       112438 :         ChordRest* thisCR = toChordRest(e);
     967       112438 :         if (!thisCR->visible() || thisCR->isFullMeasureRest() || (thisCR->isRest() && toRest(thisCR)->isGap())) {
     968         3707 :             continue;
     969              :         }
     970       108731 :         Beam* beam = thisCR->beam();
     971       108731 :         if (!beam && thisCR->actualTicks() <= thisSeg->ticks()) {
     972        72347 :             canBeAdjusted = false;
     973              :         }
     974       108731 :         if (!beam || (beam->elements().size() == 2 && thisCR->up())) {
     975        79184 :             continue;
     976              :         }
     977        70556 :         for (EngravingItem* ee : nextSeg->elist()) {
     978        63987 :             if (!ee || !ee->isChordRest() || !ee->staff()->visible()) {
     979        30746 :                 continue;
     980              :             }
     981        33241 :             ChordRest* nextCR = toChordRest(ee);
     982        33241 :             if (!nextCR->visible() || nextCR->isFullMeasureRest() || (thisCR->isRest() && toRest(thisCR)->isGap())) {
     983           16 :                 continue;
     984              :             }
     985        33225 :             if (!nextCR->beam()) {
     986         6188 :                 canBeAdjusted = false;
     987         6188 :                 continue;
     988              :             }
     989        27037 :             if (nextCR->beam() != beam) {
     990         4009 :                 continue;
     991              :             }
     992        23028 :             if (thisCR->up() == nextCR->up()) {
     993        22978 :                 return crossBeamType;
     994              :             }
     995           50 :             if (thisCR->up() && !nextCR->up()) {
     996           27 :                 if (thisCR->beamlet() && nextCR->beamlet()) {
     997            0 :                     hasOpposingBeamlets = true;
     998              :                 }
     999           27 :                 upDown = true;
    1000              :             }
    1001           50 :             if (!thisCR->up() && nextCR->up()) {
    1002           23 :                 downUp = true;
    1003              :             }
    1004           50 :             if (upDown && downUp) {
    1005            0 :                 return crossBeamType;
    1006              :             }
    1007              :         }
    1008              :     }
    1009              : 
    1010        62951 :     crossBeamType.upDown = upDown;
    1011        62951 :     crossBeamType.downUp = downUp;
    1012        62951 :     crossBeamType.canBeAdjusted = canBeAdjusted;
    1013        62951 :     crossBeamType.hasOpposingBeamlets = hasOpposingBeamlets;
    1014              : 
    1015        62951 :     return crossBeamType;
    1016              : }
    1017              : 
    1018       131313 : double HorizontalSpacing::computeMinMeasureWidth(Measure* m)
    1019              : {
    1020       131313 :     const MStyle& style = m->style();
    1021       131313 :     double minWidth = style.styleAbsolute(Sid::minMeasureWidth);
    1022       131313 :     double maxSysWidth = m->system()->width() - m->system()->leftMargin();
    1023              : 
    1024       131313 :     if (maxSysWidth <= 0) {
    1025            0 :         maxSysWidth = minWidth;
    1026              :     }
    1027       131313 :     minWidth = std::min(minWidth, maxSysWidth);
    1028              : 
    1029       131313 :     if (m->ticks() < m->timesig()) {
    1030          407 :         minWidth *= (m->ticks() / m->timesig()).toDouble();
    1031              :     }
    1032              : 
    1033       131313 :     Segment* firstCRSegment = m->findFirstR(SegmentType::ChordRest, Fraction(0, 1));
    1034       131313 :     if (!firstCRSegment) {
    1035            0 :         return minWidth;
    1036              :     }
    1037       131313 :     if (firstCRSegment == m->firstEnabled()) {
    1038       103232 :         return minWidth;
    1039              :     }
    1040              : 
    1041        28081 :     double startPosition = firstCRSegment->x() - firstCRSegment->minLeft();
    1042        28081 :     if (firstCRSegment->hasAccidentals()) {
    1043         1241 :         startPosition -= style.styleAbsolute(Sid::barAccidentalDistance);
    1044              :     } else {
    1045        26840 :         startPosition -= style.styleAbsolute(Sid::barNoteDistance);
    1046              :     }
    1047              : 
    1048        28081 :     minWidth += startPosition;
    1049        28081 :     minWidth = std::min(minWidth, maxSysWidth);
    1050              : 
    1051        28081 :     return minWidth;
    1052              : }
    1053              : 
    1054        61602 : void HorizontalSpacing::enforceMinimumMeasureWidths(const std::vector<Measure*> measureGroup)
    1055              : {
    1056       193500 :     for (size_t i = 0; i < measureGroup.size(); ++i) {
    1057       131898 :         Measure* measure = measureGroup[i];
    1058       131898 :         if (measure->isMMRest()) {
    1059          585 :             continue; // minimum mmRest width is already enforced during spacing
    1060              :         }
    1061       131313 :         double minWidth = computeMinMeasureWidth(measure);
    1062       131313 :         double diff = minWidth - measure->width();
    1063       131313 :         if (diff > 0) {
    1064         3776 :             stretchMeasureToTargetWidth(measure, minWidth);
    1065         9047 :             for (size_t j = i + 1; j < measureGroup.size(); ++j) {
    1066         5271 :                 measureGroup[j]->mutldata()->moveX(diff);
    1067              :             }
    1068              :         }
    1069              :     }
    1070        61602 : }
    1071              : 
    1072         3776 : void HorizontalSpacing::stretchMeasureToTargetWidth(Measure* m, double targetWidth)
    1073              : {
    1074         3776 :     IF_ASSERT_FAILED(targetWidth > m->width()) {
    1075            0 :         return;
    1076              :     }
    1077              : 
    1078         3776 :     std::vector<Spring> springs;
    1079        12451 :     for (Segment& s : m->segments()) {
    1080         8675 :         if (s.isChordRestType() && s.visible() && s.enabled() && !s.allElementsInvisible()) {
    1081         3833 :             double springConst = 1 / s.stretch();
    1082         3833 :             double width = s.width() - s.widthOffset();
    1083         3833 :             double preTension = width * springConst;
    1084         3833 :             springs.emplace_back(springConst, width, preTension, &s);
    1085              :         }
    1086              :     }
    1087              : 
    1088         3776 :     stretchSegmentsToWidth(springs, targetWidth - m->width());
    1089              : 
    1090         3776 :     m->respaceSegments();
    1091         3776 : }
    1092              : 
    1093         9920 : void HorizontalSpacing::stretchSegmentsToWidth(std::vector<Spring>& springs, double width)
    1094              : {
    1095         9920 :     if (springs.empty() || muse::RealIsEqualOrLess(width, 0.0)) {
    1096            6 :         return;
    1097              :     }
    1098              : 
    1099       189102 :     std::sort(springs.begin(), springs.end(), [](const Spring& a, const Spring& b) { return a.preTension < b.preTension; });
    1100         9914 :     double inverseSpringConst = 0.0;
    1101         9914 :     double force = 0.0;
    1102              : 
    1103              :     //! NOTE springs.cbegin() != springs.cend() because of the emptiness check at the top
    1104         9914 :     auto spring = springs.cbegin();
    1105              :     do {
    1106        61319 :         inverseSpringConst += 1 / spring->springConst;
    1107        61319 :         width += spring->width;
    1108        61319 :         force = width / inverseSpringConst;
    1109        61319 :         ++spring;
    1110        61319 :     } while (spring != springs.cend() && !(force < spring->preTension));
    1111              : 
    1112        73935 :     for (const Spring& spring2 : springs) {
    1113        64021 :         if (force > spring2.preTension) {
    1114        61319 :             double newWidth = force / spring2.springConst;
    1115        61319 :             spring2.segment->setWidth(newWidth + spring2.segment->widthOffset());
    1116              :         }
    1117              :     }
    1118              : }
    1119              : 
    1120        61606 : void HorizontalSpacing::setPositionsAndWidths(const std::vector<SegmentPosition>& segmentPositions)
    1121              : {
    1122        61606 :     size_t segmentsSize = segmentPositions.size();
    1123       541343 :     for (size_t i = 0; i < segmentsSize; ++i) {
    1124       479737 :         const SegmentPosition& segPos = segmentPositions[i];
    1125       479737 :         Segment* curSeg = segPos.segment;
    1126       479737 :         double curX = segPos.xPosInSystemCoords;
    1127              : 
    1128       479737 :         if (segPos.ignoreForSpacing) {
    1129        29074 :             curSeg->setWidth(0.0);
    1130        29074 :             continue;
    1131              :         }
    1132              : 
    1133       450663 :         Segment* nextSeg = curSeg;
    1134       450663 :         double nextX = curX;
    1135       479084 :         for (size_t j = i + 1; j < segmentsSize; ++j) {
    1136       417478 :             const SegmentPosition& nextSegPos = segmentPositions[j];
    1137       417478 :             if (!nextSegPos.ignoreForSpacing) {
    1138       389057 :                 nextSeg = nextSegPos.segment;
    1139       389057 :                 nextX = nextSegPos.xPosInSystemCoords;
    1140       389057 :                 break;
    1141              :             }
    1142              :         }
    1143              : 
    1144       450663 :         Measure* curSegMeasure = curSeg->measure();
    1145       450663 :         Measure* nextSegMeasure = nextSeg->measure();
    1146              : 
    1147       898022 :         bool leadingNonCRException = ((nextSeg->isKeySigType() || nextSeg->isTimeSigType()
    1148       898022 :                                        || nextSeg->isClefType()) && !curSeg->isType(SegmentType::BarLineTypes));
    1149        70314 :         bool computeWidthByDifferenceFromNext = curSegMeasure == nextSegMeasure || nextSeg->isStartRepeatBarLineType()
    1150       520977 :                                                 || leadingNonCRException;
    1151              : 
    1152       450663 :         double segWidth = computeWidthByDifferenceFromNext ? nextX - curX : curSeg->minRight();
    1153              : 
    1154       450663 :         curSeg->setWidth(segWidth);
    1155              : 
    1156       450663 :         if (curSegMeasure != nextSegMeasure) {
    1157        70314 :             curSegMeasure->setWidth(curX + segWidth - curSegMeasure->x());
    1158        70314 :             nextSegMeasure->mutldata()->setPosX(curX + segWidth);
    1159              :         }
    1160              : 
    1161       450663 :         curSeg->setXPosInSystemCoords(curX);
    1162              : 
    1163       450663 :         if (nextSeg == curSeg) {
    1164        61620 :             double nextSegWidth = 0.0;
    1165        61620 :             if (nextSeg->hasTimeSigAboveStaves()) {
    1166            0 :                 assert(i > 0);
    1167            0 :                 if (nextSeg->makeSpaceForTimeSigAboveStaves()) {
    1168            0 :                     nextSegWidth = nextSeg->minRight();
    1169              :                 }
    1170            0 :                 Segment* prevSeg = segmentPositions[i - 1].segment;
    1171            0 :                 double xDiff = segmentPositions[i - 1].xPosInSystemCoords - curX;
    1172            0 :                 double minSpaceForPrevSeg = xDiff + prevSeg->minRight();
    1173            0 :                 if (prevSeg->trailer()) {
    1174            0 :                     minSpaceForPrevSeg += prevSeg->style().styleAbsolute(Sid::systemTrailerRightMargin);
    1175              :                 }
    1176            0 :                 nextSegWidth = std::max(nextSegWidth, minSpaceForPrevSeg);
    1177        61620 :             } else if (nextSeg->trailer()) {
    1178          990 :                 nextSegWidth = nextSeg->minRight() + nextSeg->style().styleAbsolute(Sid::systemTrailerRightMargin);
    1179              :             } else {
    1180        60630 :                 nextSegWidth = nextSeg->minRight();
    1181              :             }
    1182        61620 :             nextSeg->setWidth(nextSegWidth);
    1183        61620 :             nextSegMeasure->setWidth(nextX + nextSegWidth - nextSegMeasure->x());
    1184        61620 :             nextSeg->setXPosInSystemCoords(nextX);
    1185              :         }
    1186              :     }
    1187        61606 : }
    1188              : 
    1189        14364 : double HorizontalSpacing::getFirstSegmentXPos(Segment* segment, HorizontalSpacingContext& ctx)
    1190              : {
    1191        14364 :     const MStyle& style = segment->style();
    1192              : 
    1193        14364 :     double x = 0.0;
    1194        14364 :     switch (segment->segmentType()) {
    1195         4203 :     case SegmentType::BeginBarLine:
    1196         4203 :         return 0.0;
    1197            8 :     case SegmentType::ChordRest:
    1198              :     {
    1199            8 :         Shape leftBarrier(RectF(0.0, -0.5 * DBL_MAX, 0.0, DBL_MAX));
    1200            8 :         x = minLeft(segment, leftBarrier);
    1201            8 :         x += style.styleAbsolute(segment->hasAccidentals() ? Sid::barAccidentalDistance : Sid::barNoteDistance);
    1202            8 :         x = std::max(x, style.styleAbsolute(Sid::systemHeaderMinStartOfSystemDistance));
    1203            8 :         break;
    1204            8 :     }
    1205        10153 :     case SegmentType::Clef:
    1206              :     case SegmentType::HeaderClef:
    1207        10153 :         x = style.styleAbsolute(Sid::clefLeftMargin);
    1208        10153 :         break;
    1209            0 :     case SegmentType::KeySig:
    1210            0 :         x = style.styleAbsolute(Sid::keysigLeftMargin);
    1211            0 :         break;
    1212            0 :     case SegmentType::TimeSig:
    1213            0 :         x = style.styleAbsolute(Sid::timesigLeftMargin);
    1214            0 :         break;
    1215            0 :     default:
    1216            0 :         x = 0.0;
    1217              :     }
    1218              : 
    1219        10161 :     return x * ctx.squeezeFactor;
    1220              : }
    1221              : 
    1222       861385 : double HorizontalSpacing::minHorizontalDistance(const Shape& f, const Shape& s, double spatium, double squeezeFactor)
    1223              : {
    1224       861385 :     double dist = -DBL_MAX;        // min real
    1225       861385 :     double absoluteMinPadding = 0.1 * spatium * squeezeFactor;
    1226      2434313 :     for (const ShapeElement& r2 : s.elements()) {
    1227      1572928 :         if (r2.isNull()) {
    1228         2090 :             continue;
    1229              :         }
    1230              : 
    1231      1570838 :         const EngravingItem* item2 = r2.item();
    1232      1570838 :         double by1 = r2.top();
    1233      1570838 :         double by2 = r2.bottom();
    1234      5662785 :         for (const ShapeElement& r1 : f.elements()) {
    1235      4091947 :             if (r1.isNull()) {
    1236      3000609 :                 continue;
    1237              :             }
    1238              : 
    1239      4090930 :             const EngravingItem* item1 = r1.item();
    1240              : 
    1241      4090930 :             double ay1 = r1.top();
    1242      4090930 :             double ay2 = r1.bottom();
    1243      4090930 :             double verticalClearance = computeVerticalClearance(item1, item2, spatium) * squeezeFactor;
    1244      4090930 :             bool intersection = mu::engraving::intersects(ay1, ay2, by1, by2, verticalClearance);
    1245      4090930 :             double padding = 0;
    1246      4090930 :             KerningType kerningType = KerningType::NON_KERNING;
    1247      4090930 :             if (item1 && item2) {
    1248      4090904 :                 padding = computePadding(item1, item2);
    1249      4090904 :                 padding *= squeezeFactor;
    1250      4090904 :                 padding = std::max(padding, absoluteMinPadding);
    1251      4090904 :                 kerningType = computeKerning(item1, item2);
    1252              :             }
    1253              : 
    1254      4090930 :             if (kerningType == KerningType::ALLOW_COLLISION) {
    1255        41889 :                 continue;
    1256              :             }
    1257              : 
    1258      7006744 :             if (kerningType == KerningType::NON_KERNING
    1259      1678752 :                 || intersection
    1260      1091338 :                 || (r1.width() == 0 || r2.width() == 0)  // Temporary hack: shapes of zero-width are assumed to collide with everyghin
    1261      5727793 :                 || (!item1 && item2 && item2->isLyrics())) {
    1262      2957703 :                 dist = std::max(dist, r1.right() - r2.left() + padding);
    1263      2957703 :                 continue;
    1264              :             }
    1265              : 
    1266      1091338 :             switch (kerningType) {
    1267            0 :             case KerningType::KERN_UNTIL_LEFT_EDGE:
    1268            0 :                 dist = std::max(dist, r1.left() - r2.left());
    1269            0 :                 break;
    1270            0 :             case KerningType::KERN_UNTIL_CENTER:
    1271            0 :                 dist = std::max(dist, r1.left() + 0.5 * r1.width() - r2.left());
    1272            0 :                 break;
    1273         1965 :             case KerningType::KERN_UNTIL_RIGHT_EDGE:
    1274         1965 :                 dist = std::max(dist, r1.right() - r2.left());
    1275         1965 :                 break;
    1276      1089373 :             default:
    1277      1089373 :                 break;
    1278              :             }
    1279              :         }
    1280              :     }
    1281       861385 :     return dist;
    1282              : }
    1283              : 
    1284              : // Logic moved from Shape
    1285       859495 : double HorizontalSpacing::shapeSpatium(const Shape& s)
    1286              : {
    1287       859503 :     for (auto it = s.elements().begin(); it != s.elements().end(); ++it) {
    1288       813917 :         if (it->item()) {
    1289       813909 :             return it->item()->spatium();
    1290              :         }
    1291              :     }
    1292        45586 :     return 0.0;
    1293              : }
    1294              : 
    1295              : //---------------------------------------------------------
    1296              : //   minHorizontalDistance
    1297              : //    calculate the minimum layout distance to Segment ns
    1298              : //---------------------------------------------------------
    1299              : 
    1300       597934 : double HorizontalSpacing::minHorizontalDistance(const Segment* f, const Segment* ns, double squeezeFactor)
    1301              : {
    1302       597934 :     if (f->segmentType() & SegmentType::BarLineTypes) {
    1303       170157 :         if (ns->isStartRepeatBarLineType()) {
    1304          308 :             if (f->isBeginBarLineType()) {
    1305           62 :                 return 0.0;
    1306              :             }
    1307          246 :             double prevBarlineWidth = f->minRight();
    1308          246 :             double thickBarlineWidth = f->style().styleAbsolute(Sid::endBarWidth);
    1309          246 :             return std::max(prevBarlineWidth - thickBarlineWidth, 0.0);
    1310              :         }
    1311       169849 :         if (ns->isMMRestSegment()) {
    1312          272 :             return f->minRight() + f->style().styleAbsolute(Sid::barNoteDistance);
    1313              :         }
    1314              :     }
    1315              : 
    1316       597354 :     if (f->isHeaderClefType() && ns->hasTimeSigAboveStaves()) {
    1317            0 :         Segment* possibleKeySig = f->nextActive();
    1318            0 :         if (possibleKeySig && possibleKeySig->isKeySigType() && !ignoreSegmentForSpacing(possibleKeySig)) {
    1319            0 :             return 0.0;
    1320              :         }
    1321            0 :         return f->minRight() + ns->minLeft() + f->style().styleAbsolute(Sid::headerToLineStartDistance);
    1322              :     }
    1323              : 
    1324       597354 :     bool systemHeaderGap = needsHeaderSpacingExceptions(f, ns);
    1325              : 
    1326       597354 :     double ww = -DBL_MAX;          // can remain negative
    1327       597354 :     double d = 0.0;
    1328       597354 :     Score* score = f->score();
    1329      1465626 :     for (unsigned staffIdx = 0; staffIdx < f->shapes().size(); ++staffIdx) {
    1330       868272 :         if (score->staff(staffIdx) && !score->staff(staffIdx)->show()) {
    1331         9780 :             continue;
    1332              :         }
    1333              : 
    1334       858492 :         const Shape& fshape = f->staffShape(staffIdx);
    1335       858492 :         double sp = shapeSpatium(fshape);
    1336       858492 :         d = minHorizontalDistance(fshape, ns->staffShape(staffIdx), sp, squeezeFactor);
    1337       858492 :         if (systemHeaderGap) {
    1338              :             // first chordrest of a staff should clear the widest header for any staff
    1339              :             // so make sure segment is as wide as it needs to be
    1340        47764 :             d = std::max(d, f->staffShape(staffIdx).right());
    1341              :         }
    1342              : 
    1343              :         // Multi-staff ChordBrackets are stored on their owner chords
    1344              :         // and are therefore not fully represented in every visually spanned staff Shape.
    1345              :         // Add the missing bracket collisions before combining the per-staff minimum distances.
    1346       858492 :         if (f->isChordRestType() || ns->isChordRestType()) {
    1347       721763 :             ChordBracketLayout::updateHorizontalSpacing(f, ns, staffIdx, squeezeFactor, d);
    1348              :         }
    1349              : 
    1350       858492 :         if (f->isChordRestType() && ns->isChordRestType()) {
    1351       330768 :             checkCollisionsWithCrossStaffStems(f, ns, staffIdx, d);
    1352              :         }
    1353              : 
    1354       858492 :         ww = std::max(ww, d);
    1355              :     }
    1356       597354 :     double w = std::max(ww, 0.0);        // non-negative
    1357              : 
    1358              :     // Header exceptions that need additional space (more than the padding)
    1359       597354 :     double absoluteMinHeaderDist = 1.5 * f->spatium() * squeezeFactor;
    1360       597354 :     if (systemHeaderGap) {
    1361        31787 :         if (f->isTimeSigType()) {
    1362        13155 :             double timeSigHeaderDist = squeezeFactor * f->style().styleAbsolute(Sid::systemHeaderTimeSigDistance);
    1363        13155 :             w = std::max(w, f->minRight() + timeSigHeaderDist);
    1364              :         } else {
    1365        18632 :             double headerDist = squeezeFactor * f->style().styleAbsolute(Sid::systemHeaderDistance);
    1366        18632 :             w = std::max(w, f->minRight() + headerDist);
    1367              :         }
    1368        31787 :         if (ns && ns->isStartRepeatBarLineType()) {
    1369              :             // Align the thin barline of the start repeat to the header
    1370          360 :             w -= f->style().styleAbsolute(Sid::endBarWidth) + f->style().styleAbsolute(Sid::endBarDistance);
    1371              :         }
    1372        31787 :         double diff = w - f->minRight() - ns->minLeft();
    1373        31787 :         if (diff < absoluteMinHeaderDist) {
    1374         2477 :             w += absoluteMinHeaderDist - diff;
    1375              :         }
    1376              :     }
    1377              : 
    1378              :     // Multimeasure rest exceptions that need special handling
    1379       597354 :     if (f->measure() && f->measure()->isMMRest()) {
    1380         2567 :         const PaddingTable& paddingTable = f->score()->paddingTables().horizontalPaddingTable();
    1381         2567 :         if (ns->isChordRestType()) {
    1382         1120 :             double minDist = f->minRight();
    1383         1120 :             if (f->isClefType()) {
    1384            6 :                 minDist += paddingTable.at(ElementType::CLEF).at(ElementType::REST);
    1385         1114 :             } else if (f->isKeySigType()) {
    1386           21 :                 minDist += paddingTable.at(ElementType::KEYSIG).at(ElementType::REST);
    1387         1093 :             } else if (f->isTimeSigType()) {
    1388          316 :                 minDist += paddingTable.at(ElementType::TIMESIG).at(ElementType::REST);
    1389              :             }
    1390         1120 :             w = std::max(w, minDist);
    1391         1447 :         } else if (f->isChordRestType()) {
    1392          540 :             double minWidth = f->style().styleAbsolute(Sid::minMMRestWidth);
    1393          540 :             if (!f->style().styleB(Sid::oldStyleMultiMeasureRests)) {
    1394          540 :                 minWidth += f->style().styleAbsolute(Sid::multiMeasureRestMargin);
    1395              :             }
    1396          540 :             w = std::max(w, minWidth);
    1397              :         }
    1398              :     }
    1399              : 
    1400              :     // Allocate space to ensure minimum length of hanging ties or gliss at start of system
    1401              :     // These only occur when one segment is a ChordRest and the other isn't
    1402              :     // Allocate space to ensure minimum length of partial ties
    1403       597354 :     if (f->isChordRestType() == ns->isChordRestType()) {
    1404       328350 :         w = std::max(w, minStemDistOnNonAdjacentCross(f, ns));
    1405       328350 :         return w;
    1406              :     }
    1407              : 
    1408       269004 :     const bool repeatSeg = f->isStartRepeatBarLineType() || ns->isStartRepeatBarLineType();
    1409       383876 :     const bool endOfSystem = f->isChordRestType() && !(ns->isChordRestType() || ns->isStartRepeatBarLineType())
    1410       383876 :                              && (f->measure()->isLastInSystem() || f->measure()->next()->isHBox());
    1411       269004 :     const bool systemEnd = repeatSeg || endOfSystem;
    1412              : 
    1413       269004 :     computeHangingLineWidth(f, ns, w, systemHeaderGap, systemEnd);
    1414              : 
    1415       269004 :     return w;
    1416              : }
    1417              : 
    1418       597354 : bool HorizontalSpacing::needsHeaderSpacingExceptions(const Segment* seg, const Segment* nextSeg)
    1419              : {
    1420              :     static const std::unordered_set<SegmentType> HEADER_SEGMENT_TYPES = { SegmentType::HeaderClef,
    1421              :                                                                           SegmentType::KeySig,
    1422              :                                                                           SegmentType::KeySigStartRepeatAnnounce,
    1423              :                                                                           SegmentType::Ambitus,
    1424              :                                                                           SegmentType::TimeSig,
    1425       597384 :                                                                           SegmentType::TimeSigStartRepeatAnnounce };
    1426              : 
    1427       597354 :     return muse::contains(HEADER_SEGMENT_TYPES, seg->segmentType())
    1428       660631 :            && seg->rtick().isZero() && !seg->hasTimeSigAboveStaves()
    1429        62997 :            && (nextSeg->measure()->isFirstInSystem() || nextSeg->measure()->prev()->isHBox())
    1430      1257985 :            && (nextSeg->isStartRepeatBarLineType() || nextSeg->isChordRestType());
    1431              : }
    1432              : 
    1433              : //---------------------------------------------------------
    1434              : //   minLeft
    1435              : //    Calculate minimum distance needed to the left shape
    1436              : //    sl. Sl is the same for all staves.
    1437              : //---------------------------------------------------------
    1438              : 
    1439            8 : double HorizontalSpacing::minLeft(const Segment* seg, const Shape& ls)
    1440              : {
    1441            8 :     double distance = 0.0;
    1442            8 :     double sp = shapeSpatium(ls);
    1443           16 :     for (const Shape& sh : seg->shapes()) {
    1444            8 :         double d = minHorizontalDistance(ls, sh, sp, 1.0);
    1445            8 :         if (d > distance) {
    1446            0 :             distance = d;
    1447              :         }
    1448              :     }
    1449            8 :     return distance;
    1450              : }
    1451              : 
    1452      4090904 : double HorizontalSpacing::computePadding(const EngravingItem* item1, const EngravingItem* item2)
    1453              : {
    1454      4090904 :     if (item1->isParenthesis() || item2->isParenthesis()) {
    1455        72047 :         return ParenthesisLayout::computeParenthesisPadding(item1, item2);
    1456              :     }
    1457      4018857 :     const PaddingTable& paddingTable = item1->score()->paddingTables().horizontalPaddingTable();
    1458      4018857 :     ElementType type1 = item1->type();
    1459      4018857 :     ElementType type2 = item2->type();
    1460              : 
    1461      4018857 :     double padding = paddingTable.at(type1).at(type2);
    1462      4018857 :     double scaling = (item1->mag() + item2->mag()) / 2;
    1463              : 
    1464      4018857 :     if (type1 == ElementType::NOTE && isSpecialNotePaddingType(type2)) {
    1465       595587 :         computeNotePadding(toNote(item1), item2, padding, scaling);
    1466      3423270 :     } else if (type1 == ElementType::LYRICS && isSpecialLyricsPaddingType(type2)) {
    1467        20742 :         computeLyricsPadding(toLyrics(item1), item2, padding);
    1468      3402528 :     } else if (type2 == ElementType::CHORD_BRACKET) {
    1469            0 :         computeChordBracketPadding(item1, toChordBracket(item2), padding);
    1470              :     } else {
    1471      3402528 :         padding *= scaling;
    1472              :     }
    1473              : 
    1474      4018857 :     if (!item1->isLedgerLine() && item2->isRest()) {
    1475       339595 :         computeLedgerRestPadding(toRest(item2), padding);
    1476              :     }
    1477              : 
    1478      4018857 :     return padding;
    1479              : }
    1480              : 
    1481       972558 : bool HorizontalSpacing::isSpecialNotePaddingType(ElementType type)
    1482              : {
    1483       972558 :     switch (type) {
    1484       595587 :     case ElementType::NOTE:
    1485              :     case ElementType::REST:
    1486              :     case ElementType::STEM:
    1487       595587 :         return true;
    1488       376971 :     default:
    1489       376971 :         return false;
    1490              :     }
    1491              : }
    1492              : 
    1493       595587 : void HorizontalSpacing::computeNotePadding(const Note* note, const EngravingItem* item2, double& padding, double scaling)
    1494              : {
    1495       595587 :     const MStyle& style = note->style();
    1496              : 
    1497       595587 :     bool sameVoiceNoteOrStem = (item2->isNote() || item2->isStem()) && note->track() == item2->track();
    1498       595587 :     bool areAdjacentByDuration = (note->rtick() + note->chord()->actualTicks()) == item2->rtick();
    1499       595587 :     if (sameVoiceNoteOrStem || areAdjacentByDuration) {
    1500       521548 :         bool intersection = note->shape().translate(note->pos()).intersects(item2->shape().translate(item2->pos()));
    1501       521548 :         if (intersection) {
    1502       339278 :             padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::minNoteDistance)));
    1503              :         }
    1504              :     }
    1505              : 
    1506       595587 :     padding *= scaling;
    1507              : 
    1508       595587 :     if (!(item2->isNote() || item2->isRest())) {
    1509       216705 :         return;
    1510              :     }
    1511              : 
    1512       378882 :     if (note->isGrace() && item2->isNote() && toNote(item2)->isGrace()) {
    1513              :         // Grace-to-grace
    1514         3937 :         padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::graceToGraceNoteDist)));
    1515       374945 :     } else if (note->isGrace() && (item2->isRest() || (item2->isNote() && !toNote(item2)->isGrace()))) {
    1516              :         // Grace-to-main
    1517         6601 :         padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::graceToMainNoteDist)));
    1518       368344 :     } else if (!note->isGrace() && item2->isNote() && toNote(item2)->isGrace()) {
    1519              :         // Main-to-grace
    1520         3584 :         padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::graceToMainNoteDist)));
    1521              :     }
    1522              : 
    1523       378882 :     if (!note->fretString().empty() && item2->isNote()) { // This is a TAB fret mark
    1524              :         static constexpr double tabFretPaddingIncrease = 1.5; // TODO: style?
    1525         3493 :         padding *= tabFretPaddingIncrease;
    1526              :     }
    1527              : 
    1528       378882 :     if (!item2->isNote()) {
    1529        54035 :         return;
    1530              :     }
    1531              : 
    1532       324847 :     const Note* note2 = toNote(item2);
    1533       324847 :     if (note->lineAttachPoints().empty() || note2->lineAttachPoints().empty()) {
    1534       307613 :         return;
    1535              :     }
    1536              : 
    1537              :     // Allocate space for minTieLength, minGlissandoLength & minBendLength
    1538        40726 :     for (LineAttachPoint laPoint1 : note->lineAttachPoints()) {
    1539        23492 :         if (!laPoint1.line()->addToSkyline()) {
    1540           28 :             continue;
    1541              :         }
    1542        55720 :         for (LineAttachPoint laPoint2 : note2->lineAttachPoints()) {
    1543        32256 :             if (laPoint1.line() != laPoint2.line()) {
    1544        25629 :                 continue;
    1545              :             }
    1546              : 
    1547         6627 :             double minEndPointsDistance = 0.0;
    1548         6627 :             if (laPoint1.line()->isTie()) {
    1549         5394 :                 minEndPointsDistance = style.styleAbsolute(Sid::minTieLength);
    1550         1233 :             } else if (laPoint1.line()->isGlissando()) {
    1551          518 :                 bool straight = toGlissando(laPoint1.line())->glissandoType() == GlissandoType::STRAIGHT;
    1552              :                 double minGlissandoLength = straight
    1553          518 :                                             ? style.styleAbsolute(Sid::minStraightGlissandoLength)
    1554          110 :                                             : style.styleAbsolute(Sid::minWigglyGlissandoLength);
    1555          518 :                 minEndPointsDistance = minGlissandoLength;
    1556          715 :             } else if (laPoint1.line()->isGuitarBend()) {
    1557          706 :                 double minBendLength = 2 * note->spatium(); // TODO: style
    1558          706 :                 minEndPointsDistance = minBendLength;
    1559            9 :             } else if (laPoint1.line()->isNoteLine()) {
    1560            9 :                 minEndPointsDistance = style.styleAbsolute(Sid::minStraightGlissandoLength);
    1561              :             }
    1562              : 
    1563         6627 :             double lapPadding = (laPoint1.pos().x() - note->width()) + minEndPointsDistance - laPoint2.pos().x();
    1564         6627 :             lapPadding *= scaling;
    1565              : 
    1566         6627 :             padding = std::max(padding, lapPadding);
    1567              :         }
    1568              :     }
    1569              : }
    1570              : 
    1571       339595 : void HorizontalSpacing::computeLedgerRestPadding(const Rest* rest2, double& padding)
    1572              : {
    1573       339595 :     SymId restSym = rest2->ldata()->sym();
    1574       339595 :     switch (restSym) {
    1575          639 :     case SymId::restWholeLegerLine:
    1576              :     case SymId::restDoubleWholeLegerLine:
    1577              :     case SymId::restHalfLegerLine:
    1578          639 :         padding += rest2->ldata()->bbox().left();
    1579          639 :         return;
    1580       338956 :     default:
    1581       338956 :         return;
    1582              :     }
    1583              : }
    1584              : 
    1585        38503 : bool HorizontalSpacing::isSpecialLyricsPaddingType(ElementType type)
    1586              : {
    1587        38503 :     switch (type) {
    1588        20742 :     case ElementType::NOTE:
    1589              :     case ElementType::REST:
    1590              :     case ElementType::LYRICS:
    1591        20742 :         return true;
    1592        17761 :     default:
    1593        17761 :         return false;
    1594              :     }
    1595              : }
    1596              : 
    1597        20742 : void HorizontalSpacing::computeLyricsPadding(const Lyrics* lyrics1, const EngravingItem* item2, double& padding)
    1598              : {
    1599        20742 :     const MStyle& style = lyrics1->style();
    1600              : 
    1601        20742 :     bool leaveSpaceForMelisma = lyrics1->separator() && lyrics1->separator()->isEndMelisma() && style.styleB(Sid::lyricsMelismaForce);
    1602        20742 :     if (leaveSpaceForMelisma) {
    1603            0 :         double spaceForMelisma = style.styleAbsolute(Sid::lyricsMelismaMinLength) + 2
    1604            0 :                                  * style.styleAbsolute(Sid::lyricsMelismaPad);
    1605            0 :         padding = std::max(padding, spaceForMelisma);
    1606            0 :         return;
    1607              :     }
    1608              : 
    1609        20742 :     if (item2->isLyrics()) {
    1610        10722 :         LyricsSyllabic syllabicType = lyrics1->syllabic();
    1611         9385 :         bool leaveSpaceForDash = (syllabicType == LyricsSyllabic::BEGIN || syllabicType == LyricsSyllabic::MIDDLE)
    1612        20107 :                                  && style.styleB(Sid::lyricsDashForce);
    1613        10722 :         if (leaveSpaceForDash) {
    1614         1577 :             double spaceForDash = style.styleAbsolute(Sid::lyricsDashMinLength) + 2 * style.styleAbsolute(Sid::lyricsDashPad);
    1615         1577 :             padding = std::max(padding, spaceForDash);
    1616              :         }
    1617              :     }
    1618              : }
    1619              : 
    1620            0 : void HorizontalSpacing::computeChordBracketPadding(const EngravingItem* item1, const ChordBracket* chordBracket, double& padding)
    1621              : {
    1622            0 :     const Chord* chord = chordBracket->chord();
    1623            0 :     const Chord* itemChord = toChord(item1->findAncestor(ElementType::CHORD));
    1624            0 :     if (chord && itemChord && chord->segment() == itemChord->segment() && chord->part() == itemChord->part()) {
    1625              :         // Padding a right-handed chord bracket to chords in the same Segment and part: use the same padding values as the left-handed case
    1626            0 :         padding = item1->score()->paddingTables().horizontalPaddingTable().at(ElementType::CHORD_BRACKET).at(item1->type());
    1627              :     }
    1628            0 : }
    1629              : 
    1630      4090904 : KerningType HorizontalSpacing::computeKerning(const EngravingItem* item1, const EngravingItem* item2)
    1631              : {
    1632      4090904 :     if (item1->isArticulationOrFermata() || item2->isArticulationOrFermata()) {
    1633        63526 :         return computeArticulationAndFermataKerning(item1, item2);
    1634              :     }
    1635              : 
    1636      4027378 :     if (ignoreItems(item1, item2)) {
    1637           18 :         return KerningType::ALLOW_COLLISION;
    1638              :     }
    1639              : 
    1640      4027360 :     if (isSameVoiceKerningLimited(item1) && isSameVoiceKerningLimited(item2) && item1->track() == item2->track()) {
    1641      1044411 :         return KerningType::NON_KERNING;
    1642              :     }
    1643              : 
    1644      5018198 :     if ((isNeverKernable(item1) || isNeverKernable(item2))
    1645      5018198 :         && !(isAlwaysKernable(item1) || isAlwaysKernable(item2))) {
    1646      1294437 :         return KerningType::NON_KERNING;
    1647              :     }
    1648              : 
    1649      1688512 :     return doComputeKerningType(item1, item2);
    1650              : }
    1651              : 
    1652      4090930 : double HorizontalSpacing::computeVerticalClearance(const EngravingItem* item1, const EngravingItem* item2, double spatium)
    1653              : {
    1654              :     // To be possibly expanded to more cases
    1655      4090930 :     if (item2 && item2->isAccidental()) {
    1656       518766 :         return 0.1 * spatium;
    1657      3572164 :     } else if ((item1 && item1->isParenthesis()) || (item2 && item2->isParenthesis())) {
    1658        68243 :         return 0.1 * spatium;
    1659              :     }
    1660              : 
    1661      3503921 :     return 0.2 * spatium;
    1662              : }
    1663              : 
    1664      5934943 : bool HorizontalSpacing::isSameVoiceKerningLimited(const EngravingItem* item)
    1665              : {
    1666      5934943 :     ElementType type = item->type();
    1667              : 
    1668      5934943 :     switch (type) {
    1669      3121634 :     case ElementType::NOTE:
    1670              :     case ElementType::NOTEDOT:
    1671              :     case ElementType::REST:
    1672              :     case ElementType::STEM:
    1673              :     case ElementType::CHORDLINE:
    1674              :     case ElementType::BREATH:
    1675      3121634 :         return true;
    1676      2813309 :     default:
    1677      2813309 :         return false;
    1678              :     }
    1679              : }
    1680              : 
    1681      5018198 : bool HorizontalSpacing::isNeverKernable(const EngravingItem* item)
    1682              : {
    1683      5018198 :     ElementType type = item->type();
    1684              : 
    1685      5018198 :     switch (type) {
    1686       146252 :     case ElementType::CLEF:
    1687       146252 :         if (toClef(item)->isMidMeasureClef()) {
    1688        29074 :             return false;
    1689              :         }
    1690              :     // fall through
    1691              :     case ElementType::KEYSIG:
    1692              :     case ElementType::BAR_LINE:
    1693      1210962 :         return true;
    1694       136640 :     case ElementType::TIMESIG:
    1695       136640 :         return !toTimeSig(item)->isAboveStaves();
    1696      3641522 :     default:
    1697      3641522 :         return false;
    1698              :     }
    1699              : }
    1700              : 
    1701      2678421 : bool HorizontalSpacing::isAlwaysKernable(const EngravingItem* item)
    1702              : {
    1703      2678421 :     return item->isTextBase() || item->isChordLine() || item->isParenthesis() || item->isFretDiagram();
    1704              : }
    1705              : 
    1706      4027378 : bool HorizontalSpacing::ignoreItems(const EngravingItem* item1, const EngravingItem* item2)
    1707              : {
    1708      4027378 :     if (item1->isRest() && toRest(item1)->isFullMeasureRest()) {
    1709              :         // Full-measure rest must ignore cross-stave notes
    1710       144122 :         return item1->staffIdx() != item2->staffIdx();
    1711              :     }
    1712              : 
    1713      3883256 :     return false;
    1714              : }
    1715              : 
    1716      1688512 : KerningType HorizontalSpacing::doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2)
    1717              : {
    1718      1688512 :     ElementType type1 = item1->type();
    1719      1688512 :     switch (type1) {
    1720        14123 :     case ElementType::BAR_LINE:
    1721        14123 :         return item2->isLyrics() || item2->isHarmony() || item2->isFretDiagram() ? KerningType::ALLOW_COLLISION : KerningType::NON_KERNING;
    1722          288 :     case ElementType::CHORDLINE:
    1723          288 :         return item2->isBarLine() ? KerningType::ALLOW_COLLISION : KerningType::KERNING;
    1724        53806 :     case ElementType::HARMONY:
    1725              :     case ElementType::FRET_DIAGRAM:
    1726        53806 :         return item2->isHarmony() || item2->isFretDiagram() ? KerningType::NON_KERNING : KerningType::KERNING;
    1727        38464 :     case ElementType::LYRICS
    1728        38464 :         : return computeLyricsKerningType(toLyrics(item1), item2);
    1729       348543 :     case ElementType::NOTE:
    1730       348543 :         return computeNoteKerningType(toNote(item1), item2);
    1731        17852 :     case ElementType::CLEF:
    1732        17852 :         return item2->isNote() ? KerningType::KERN_UNTIL_RIGHT_EDGE : KerningType::KERNING;
    1733         5453 :     case ElementType::STEM_SLASH:
    1734         5453 :         return computeStemSlashKerningType(toStemSlash(item1), item2);
    1735        41016 :     case ElementType::PARENTHESIS:
    1736        41016 :         return item2->isBarLine() ? KerningType::NON_KERNING : KerningType::KERNING;
    1737          146 :     case ElementType::GUITAR_BEND_SEGMENT:
    1738          146 :         return item2->isNote() || item2->isRest() ? KerningType::NON_KERNING : KerningType::KERNING;
    1739      1168821 :     default:
    1740      1168821 :         return KerningType::KERNING;
    1741              :     }
    1742              : }
    1743              : 
    1744       348543 : KerningType HorizontalSpacing::computeNoteKerningType(const Note* note, const EngravingItem* item2)
    1745              : {
    1746       348543 :     if (item2->isClef()) {
    1747         1767 :         return KerningType::KERN_UNTIL_RIGHT_EDGE;
    1748              :     }
    1749              : 
    1750       346776 :     EngravingObject* nextParent = item2->ownershipParent();
    1751       346776 :     if (nextParent && nextParent->isNote() && toNote(nextParent)->isTrillCueNote()) {
    1752          380 :         return KerningType::NON_KERNING;
    1753              :     }
    1754              : 
    1755       346396 :     Chord* c = note->chord();
    1756       346396 :     if (!c) {
    1757            0 :         return KerningType::KERNING;
    1758              :     }
    1759       346396 :     if (item2->isLyrics() && c->isMelismaEnd()) {
    1760          317 :         Note* melismaEndNote = c->up() ? c->downNote() : c->upNote();
    1761          317 :         return note == melismaEndNote ? KerningType::NON_KERNING : KerningType::KERNING;
    1762              :     }
    1763       346079 :     if (c->allowKerningAbove() && c->allowKerningBelow()) {
    1764       337450 :         return KerningType::KERNING;
    1765              :     }
    1766              : 
    1767         8629 :     if (c->up() && note->ldata()->pos().x() > 0) {
    1768              :         // Offset seconds can always be kerned into
    1769          224 :         return KerningType::KERNING;
    1770              :     }
    1771              : 
    1772         8405 :     bool kerningAbove = item2->canvasPos().y() < note->canvasPos().y();
    1773         8405 :     if (kerningAbove && !c->allowKerningAbove()) {
    1774         2116 :         return KerningType::NON_KERNING;
    1775              :     }
    1776         6289 :     if (!kerningAbove && !c->allowKerningBelow()) {
    1777         2272 :         return KerningType::NON_KERNING;
    1778              :     }
    1779              : 
    1780         4017 :     return KerningType::KERNING;
    1781              : }
    1782              : 
    1783         5453 : KerningType HorizontalSpacing::computeStemSlashKerningType(const StemSlash* stemSlash, const EngravingItem* item2)
    1784              : {
    1785         5453 :     if (!stemSlash->chord() || !stemSlash->chord()->beam() || !item2->ownershipParent()) {
    1786         4818 :         return KerningType::KERNING;
    1787              :     }
    1788              : 
    1789          635 :     EngravingObject* nextParent = item2->ownershipParent();
    1790          635 :     Chord* nextChord = nullptr;
    1791          635 :     if (nextParent->isChord()) {
    1792          616 :         nextChord = toChord(nextParent);
    1793           19 :     } else if (nextParent->isNote()) {
    1794            4 :         nextChord = toChord(nextParent->ownershipParent());
    1795              :     }
    1796          635 :     if (!nextChord) {
    1797           15 :         return KerningType::KERNING;
    1798              :     }
    1799              : 
    1800          620 :     if (nextChord->beam() && nextChord->beam() == stemSlash->chord()->beam()) {
    1801              :         // Stem slash is allowed to collide with items from the same grace notes group
    1802          106 :         return KerningType::ALLOW_COLLISION;
    1803              :     }
    1804              : 
    1805          514 :     return KerningType::KERNING;
    1806              : }
    1807              : 
    1808        38464 : KerningType HorizontalSpacing::computeLyricsKerningType(const Lyrics* lyrics1, const EngravingItem* item2)
    1809              : {
    1810        38464 :     if (item2->isLyrics()) {
    1811        10722 :         const Lyrics* lyrics2 = toLyrics(item2);
    1812        10722 :         if (lyrics1->verse() == lyrics2->verse()) {
    1813         8029 :             return KerningType::NON_KERNING;
    1814              :         }
    1815              :     }
    1816              : 
    1817        30435 :     if ((item2->isNote() || item2->isRest()) && lyrics1->style().styleB(Sid::lyricsMelismaForce)) {
    1818            0 :         LyricsLine* melismaLine = lyrics1->separator();
    1819            0 :         if (melismaLine && melismaLine->isEndMelisma() && item2->tick() >= melismaLine->tick2()) {
    1820            0 :             return KerningType::NON_KERNING;
    1821              :         }
    1822              :     }
    1823              : 
    1824        30435 :     return KerningType::ALLOW_COLLISION;
    1825              : }
    1826              : 
    1827        63526 : KerningType HorizontalSpacing::computeArticulationAndFermataKerning(const EngravingItem* item1, const EngravingItem* item2)
    1828              : {
    1829        63526 :     if (item1->isArticulationOrFermata()) {
    1830        35873 :         if (item2->isArticulationOrFermata()) {
    1831         7584 :             bool firstAbove = item1->isArticulationFamily() ? toArticulation(item1)->up() : item1->placeAbove();
    1832         7584 :             bool secondAbove = item2->isArticulationFamily() ? toArticulation(item2)->up() : item2->placeAbove();
    1833         7584 :             if (firstAbove == secondAbove) {
    1834         5546 :                 return KerningType::NON_KERNING;
    1835              :             }
    1836              :         }
    1837              :     }
    1838              : 
    1839        57980 :     return KerningType::KERNING;
    1840              : }
    1841              : 
    1842       269004 : void HorizontalSpacing::computeHangingLineWidth(const Segment* firstSeg, const Segment* nextSeg, double& width, bool systemHeaderGap,
    1843              :                                                 bool systemEnd)
    1844              : {
    1845       269004 :     const MStyle& style = firstSeg->style();
    1846       269004 :     const Segment* crSeg = firstSeg->isChordRestType() ? firstSeg : nextSeg;
    1847       269004 :     const Segment* otherSeg = firstSeg->isChordRestType() ? nextSeg : firstSeg;
    1848       269004 :     const bool incoming = !firstSeg->isChordRestType();
    1849       269004 :     const Measure* crMeasure = crSeg->measure();
    1850       269004 :     const size_t ntracks = crSeg->score()->ntracks();
    1851              : 
    1852       269004 :     std::vector<double> tieLengths;
    1853              : 
    1854      1853364 :     for (track_idx_t track = 0; track < ntracks; track++) {
    1855              :         // Hanging lines only occur at start or end of a measure
    1856      1584360 :         const ChordRest* cr = incoming ? crMeasure->firstChordRest(track) : crMeasure->lastChordRest(track);
    1857      1584360 :         if (!cr || !cr->isChord() || cr->segment() != crSeg) {
    1858      1478110 :             continue;
    1859              :         }
    1860              : 
    1861       106250 :         const double headerLineMargin = systemHeaderGap ? otherSeg->style().styleAbsolute(Sid::headerToLineStartDistance)
    1862        84046 :                                         : otherSeg->style().styleAbsolute(Sid::repeatBarlineDotSeparation);
    1863       106250 :         const double endSystemMargin = style.styleAbsolute(Sid::lineEndToBarlineDistance);
    1864              : 
    1865       238435 :         for (const Note* note : toChord(cr)->notes()) {
    1866       132185 :             const bool lineBack = note->spannerBack().size()
    1867       132185 :                                   || (note->tieBack() && !note->tieBack()->segmentsEmpty());
    1868       132185 :             const bool lineFor = note->spannerFor().size()
    1869       132185 :                                  || (note->tieFor() && !note->tieFor()->segmentsEmpty());
    1870       132185 :             if (!(lineBack || lineFor) || note->lineAttachPoints().empty()) {
    1871       124486 :                 continue;
    1872              :             }
    1873              : 
    1874        16394 :             for (const LineAttachPoint& lap : note->lineAttachPoints()) {
    1875         8695 :                 if (lap.start() == incoming) {
    1876         8478 :                     continue;
    1877              :                 }
    1878         4136 :                 const Spanner* attachedLine = toSpanner(lap.line());
    1879         4136 :                 if (!attachedLine->addToSkyline()) {
    1880           62 :                     continue;
    1881              :                 }
    1882              : 
    1883              :                 // Partial ties are adjusted wherever they are in the system, other spanners are only adjusted over system breaks
    1884         4074 :                 if (!(systemEnd || systemHeaderGap) && !attachedLine->isPartialTie()) {
    1885         2426 :                     continue;
    1886              :                 }
    1887              : 
    1888         1648 :                 double minLength = 0.0;
    1889         1648 :                 if (attachedLine->isGlissando()) {
    1890            0 :                     bool straight = toGlissando(attachedLine)->glissandoType() == GlissandoType::STRAIGHT;
    1891            0 :                     minLength = straight ? style.styleAbsolute(Sid::minStraightGlissandoLength)
    1892            0 :                                 : style.styleAbsolute(Sid::minWigglyGlissandoLength);
    1893         1648 :                 } else if (attachedLine->isNoteLine()) {
    1894            0 :                     minLength = style.styleAbsolute(Sid::minStraightGlissandoLength);
    1895              :                 }
    1896              : 
    1897         1648 :                 const double notePosX = note->pos().x() + toChord(cr)->pos().x();
    1898         1648 :                 const double lineNoteEndPos = (incoming ? width : 0.0) + notePosX + lap.pos().x();
    1899              :                 const double lineSegEndPos
    1900         1648 :                     = (incoming ? otherSeg->minRight() + headerLineMargin : width + otherSeg->minLeft() - endSystemMargin);
    1901              : 
    1902         1648 :                 const double lineStartPointX = incoming ? lineSegEndPos : lineNoteEndPos;
    1903         1648 :                 const double lineEndPointX = incoming ? lineNoteEndPos : lineSegEndPos;
    1904         1648 :                 const double lineLength = lineEndPointX - lineStartPointX;
    1905              : 
    1906         1648 :                 if (attachedLine->isTie()) {
    1907         1431 :                     tieLengths.push_back(lineLength);
    1908         1431 :                     continue;
    1909              :                 }
    1910              : 
    1911          217 :                 if (lineLength < minLength) {
    1912            0 :                     width += minLength - lineLength;
    1913              :                 }
    1914              :             }
    1915              :         }
    1916              :     }
    1917              : 
    1918       269004 :     if (tieLengths.empty()) {
    1919       267952 :         return;
    1920              :     }
    1921              : 
    1922         1052 :     const double maxLength = *std::max_element(tieLengths.begin(), tieLengths.end());
    1923         1052 :     const double oldWidth = width;
    1924         2483 :     for (double& tieLength : tieLengths) {
    1925              :         // Adjust for new width
    1926         1431 :         tieLength += width - oldWidth;
    1927              :         const double minLength
    1928         1431 :             = muse::RealIsEqual(tieLength, maxLength) ? style.styleAbsolute(Sid::minHangingTieLength) : style.styleAbsolute(
    1929         1431 :                   Sid::minTieLength);
    1930              : 
    1931         1431 :         if (tieLength < minLength) {
    1932          163 :             width += minLength - tieLength;
    1933              :         }
    1934              :     }
    1935       269004 : }
        

Generated by: LCOV version 2.0-1