LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - systemlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 82.7 % 1894 1566
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              : #include <unordered_set>
      24              : 
      25              : #include "systemlayout.h"
      26              : 
      27              : #include "realfn.h"
      28              : 
      29              : #include "style/defaultstyle.h"
      30              : 
      31              : #include "dom/barline.h"
      32              : #include "dom/beam.h"
      33              : #include "dom/box.h"
      34              : #include "dom/bracket.h"
      35              : #include "dom/bracketitem.h"
      36              : #include "dom/chord.h"
      37              : #include "dom/dynamic.h"
      38              : #include "dom/factory.h"
      39              : #include "dom/guitarbend.h"
      40              : #include "dom/instrumentname.h"
      41              : #include "dom/layoutbreak.h"
      42              : #include "dom/lyrics.h"
      43              : #include "dom/measure.h"
      44              : #include "dom/measurenumber.h"
      45              : #include "dom/mmrest.h"
      46              : #include "dom/mmrestrange.h"
      47              : #include "dom/note.h"
      48              : #include "dom/ornament.h"
      49              : #include "dom/page.h"
      50              : #include "dom/pagelockindicator.h"
      51              : #include "dom/part.h"
      52              : #include "dom/parenthesis.h"
      53              : #include "dom/pedal.h"
      54              : #include "dom/playcounttext.h"
      55              : #include "dom/rest.h"
      56              : #include "dom/score.h"
      57              : #include "dom/slur.h"
      58              : #include "dom/spacer.h"
      59              : #include "dom/staff.h"
      60              : #include "dom/stafflines.h"
      61              : #include "dom/system.h"
      62              : #include "dom/systemlockindicator.h"
      63              : #include "dom/tie.h"
      64              : #include "dom/timesig.h"
      65              : #include "dom/tremolosinglechord.h"
      66              : #include "dom/tremolotwochord.h"
      67              : #include "dom/tuplet.h"
      68              : #include "dom/volta.h"
      69              : #include "dom/whammybar.h"
      70              : 
      71              : #include "tlayout.h"
      72              : #include "alignmentlayout.h"
      73              : #include "autoplace.h"
      74              : #include "beamlayout.h"
      75              : #include "beamtremololayout.h"
      76              : #include "boxlayout.h"
      77              : #include "chordlayout.h"
      78              : #include "guitarbendlayout.h"
      79              : #include "guitardivelayout.h"
      80              : #include "harmonylayout.h"
      81              : #include "lyricslayout.h"
      82              : #include "measurelayout.h"
      83              : #include "mmrestlayout.h"
      84              : #include "tupletlayout.h"
      85              : #include "restlayout.h"
      86              : #include "slurtielayout.h"
      87              : #include "horizontalspacing.h"
      88              : #include "dynamicslayout.h"
      89              : #include "stavesharinglayout.h"
      90              : #include "systemheaderlayout.h"
      91              : 
      92              : #include "defer.h"
      93              : #include "log.h"
      94              : 
      95              : using namespace mu::engraving;
      96              : using namespace mu::engraving::rendering::score;
      97              : 
      98              : //---------------------------------------------------------
      99              : //   collectSystem
     100              : //---------------------------------------------------------
     101              : 
     102        13158 : System* SystemLayout::collectSystem(LayoutContext& ctx)
     103              : {
     104        13173 :     TRACEFUNC;
     105              : 
     106        13158 :     if (!ctx.state().curMeasure()) {
     107         3685 :         return nullptr;
     108              :     }
     109              : 
     110         9473 :     const MeasureBase* measure = ctx.dom().systems().empty() ? 0 : ctx.dom().systems().back()->measures().back();
     111         9473 :     if (measure) {
     112         5624 :         measure = measure->mbWithPrecedingSectionBreak();
     113              :     }
     114              : 
     115         9473 :     bool firstSysLongName = ctx.conf().styleV(Sid::firstSystemInstNameVisibility).value<InstrumentLabelVisibility>()
     116         9473 :                             == InstrumentLabelVisibility::LONG;
     117         9473 :     if (measure) {
     118         3332 :         ctx.mutState().setFirstSystem(measure->sectionBreak() && !ctx.conf().isFloatMode());
     119         3332 :         if (const LayoutBreak* layoutBreak = measure->sectionBreakElement()) {
     120          116 :             ctx.mutState().setFirstSystemIndent(ctx.state().firstSystem()
     121           58 :                                                 && ctx.conf().firstSystemIndent()
     122          116 :                                                 && layoutBreak->firstSystemIndentation());
     123          116 :             ctx.mutState().setStartWithLongNames(
     124           58 :                 ctx.state().firstSystem() && firstSysLongName && layoutBreak->startWithLongNames());
     125              :         }
     126              :     } else {
     127         6141 :         ctx.mutState().setStartWithLongNames(ctx.state().firstSystem() && firstSysLongName);
     128              :     }
     129              : 
     130         9473 :     System* system = getNextSystem(ctx);
     131        20499 :     for (SysStaff* staff : system->staves()) {
     132        11026 :         staff->skyline().clear();
     133              :     }
     134              : 
     135              :     LAYOUT_CALL() << LAYOUT_ITEM_INFO(system);
     136              : 
     137         9473 :     double curSysWidth = 0.0;
     138         9473 :     double leadingHBoxesWidth = 0.0;
     139         9473 :     double targetSystemWidth = ctx.conf().styleD(Sid::pagePrintableWidth) * DPI;
     140         9473 :     system->setWidth(targetSystemWidth);
     141              : 
     142              :     // save state of measure
     143         9473 :     MeasureBase* breakMeasure = nullptr;
     144              : 
     145         9473 :     System* oldSystem = nullptr;
     146              : 
     147         9473 :     MeasureState prevMeasureState;
     148         9473 :     prevMeasureState.curHeader = ctx.state().curMeasure()->header();
     149         9473 :     prevMeasureState.curTrailer = ctx.state().curMeasure()->trailer();
     150              : 
     151         9473 :     const RangeLock* systemLock = ctx.conf().viewMode() == LayoutMode::PAGE || ctx.conf().viewMode() == LayoutMode::SYSTEM
     152         9473 :                                   ? ctx.dom().systemLocks()->lockStartingAt(ctx.state().curMeasure()) : nullptr;
     153              : 
     154         9473 :     if (systemLock) {
     155           79 :         StaveSharingLayout::updateStaveSharingForFullSystem(systemLock->startMB(), systemLock->endMB(), ctx);
     156              :     }
     157              : 
     158        34456 :     while (ctx.state().curMeasure()) {      // collect measure for system
     159        34456 :         oldSystem = ctx.mutState().curMeasure()->system();
     160        34456 :         system->appendMeasure(ctx.mutState().curMeasure());
     161              : 
     162        34456 :         bool sharedTrackMapChanged = false;
     163        34456 :         if (!systemLock) {
     164        78591 :             for (Part* p : ctx.dom().parts()) {
     165        44477 :                 if (p->isSharedPart()) {
     166          382 :                     SharedPart* sp = toSharedPart(p);
     167          382 :                     prevMeasureState.sharedTrackMaps[sp] = sp->trackMapAtTick(ctx.mutState().curMeasure()->tick());
     168              :                 }
     169              :             }
     170              : 
     171        34114 :             sharedTrackMapChanged = StaveSharingLayout::updateStaveSharingForLastAddedMeasure(system, ctx);
     172              :         }
     173              : 
     174        34456 :         MeasureLayout::layoutMeasure(ctx.mutState().curMeasure(), ctx);
     175              : 
     176        34456 :         if (ctx.state().curMeasure()->isMeasure()) {
     177        32019 :             Measure* m = toMeasure(ctx.mutState().curMeasure());
     178        32019 :             if (!(oldSystem && oldSystem->page() && oldSystem->page() != ctx.state().page())) {
     179        32006 :                 MeasureLayout::computePreSpacingItems(m, ctx);
     180              :             }
     181              : 
     182        32019 :             if (measureHasCrossStuffOrModifiedBeams(m)) {
     183           10 :                 updateCrossBeams(system, ctx);
     184              :             }
     185              : 
     186        32019 :             if (m->isFirstInSystem()) {
     187         7177 :                 leadingHBoxesWidth = curSysWidth;
     188         7177 :                 SystemLayout::layoutSystem(system, ctx, curSysWidth);
     189         7177 :                 MeasureLayout::addSystemHeader(m, ctx.state().firstSystem(), ctx);
     190              :             } else {
     191        24842 :                 bool createHeader = ctx.state().prevMeasure()->isHBox() && toHBox(ctx.state().prevMeasure())->createSystemHeader();
     192        24842 :                 if (createHeader) {
     193           47 :                     MeasureLayout::addSystemHeader(m, false, ctx);
     194        24795 :                 } else if (m->header()) {
     195          752 :                     MeasureLayout::removeSystemHeader(m);
     196              :                 }
     197              :             }
     198              : 
     199        32019 :             MeasureLayout::createEndBarLines(m, true, ctx);
     200              : 
     201        32019 :             if (m->noBreak() || systemLock) {
     202          420 :                 MeasureLayout::removeSystemTrailer(m, ctx);
     203              :             } else {
     204        31599 :                 MeasureLayout::addSystemTrailer(m, m->nextMeasure(), ctx);
     205              :             }
     206              : 
     207        32019 :             MeasureLayout::setRepeatCourtesiesAndParens(m, ctx);
     208              : 
     209        32019 :             MeasureLayout::updateGraceNotes(m, ctx);
     210              : 
     211        32019 :             if (!systemLock) {
     212        31682 :                 curSysWidth = HorizontalSpacing::updateSpacingForLastAddedMeasure(system);
     213              :             }
     214         2437 :         } else if (ctx.state().curMeasure()->isHBox()) {
     215          149 :             if (!systemLock) {
     216          144 :                 curSysWidth = HorizontalSpacing::updateSpacingForLastAddedMeasure(system);
     217              :             }
     218              :         } else {
     219              :             // vbox:
     220         2288 :             MeasureLayout::getNextMeasure(ctx);
     221         2288 :             SystemLayout::layout2(system, ctx);         // compute staff distances
     222         2288 :             return system;
     223              :         }
     224              : 
     225        31826 :         bool doBreak = !systemLock && system->measures().size() > 1 && curSysWidth > targetSystemWidth
     226        63994 :                        && !ctx.state().prevMeasure()->noBreak();
     227        32168 :         if (doBreak) {
     228         1652 :             breakMeasure = ctx.mutState().curMeasure();
     229         1652 :             system->removeLastMeasure();
     230         1652 :             ctx.mutState().curMeasure()->setSystem(oldSystem);
     231         1652 :             while (ctx.state().prevMeasure() && ctx.state().prevMeasure()->noBreak() && system->measures().size() > 1) {
     232            0 :                 ctx.mutState().setTick(ctx.state().tick() - ctx.state().curMeasure()->ticks());
     233            0 :                 if (ctx.state().curMeasure()->isMeasure()) {
     234            0 :                     ctx.mutState().setMeasureNumber(toMeasure(ctx.state().curMeasure())->measureNumber());
     235              :                 }
     236              : 
     237            0 :                 ctx.mutState().setNextMeasure(ctx.mutState().curMeasure());
     238            0 :                 ctx.mutState().setCurMeasure(ctx.mutState().prevMeasure());
     239            0 :                 ctx.mutState().setPrevMeasure(ctx.mutState().curMeasure()->prev());
     240              : 
     241            0 :                 system->removeLastMeasure();
     242            0 :                 ctx.mutState().curMeasure()->setSystem(oldSystem);
     243              :             }
     244              : 
     245         1652 :             if (sharedTrackMapChanged) {
     246              :                 // The last added measure caused sharedTrackMap to change. Now we are removing that measure so we must restore it.
     247            0 :                 StaveSharingLayout::updateStaveSharingForFullSystem(system->first(), system->last(), ctx);
     248              :             }
     249              : 
     250         1652 :             break;
     251              :         }
     252              : 
     253        30516 :         if (oldSystem && system != oldSystem && muse::contains(ctx.state().systemList(), oldSystem)) {
     254           46 :             oldSystem->clear();
     255              :         }
     256              : 
     257        30516 :         if (ctx.state().prevMeasure() && ctx.state().prevMeasure()->isMeasure() && ctx.state().prevMeasure()->system() == system) {
     258        23216 :             Measure* m = toMeasure(ctx.mutState().prevMeasure());
     259              : 
     260        23216 :             MeasureLayout::createEndBarLines(m, false, ctx);
     261              : 
     262        23216 :             if (m->trailer()) {
     263          400 :                 MeasureLayout::removeSystemTrailer(m, ctx);
     264              :             }
     265              : 
     266        23216 :             MeasureLayout::setRepeatCourtesiesAndParens(m, ctx);
     267              : 
     268        23216 :             MeasureLayout::updateGraceNotes(m, ctx);
     269              : 
     270        23216 :             if (!systemLock) {
     271        22957 :                 curSysWidth = HorizontalSpacing::updateSpacingForLastAddedMeasure(system);
     272              :             }
     273              : 
     274        23216 :             if (ctx.state().curMeasure()->isMeasure()) {
     275        23147 :                 MeasureLayout::updateKeySignatures(toMeasure(ctx.state().curMeasure()), ctx);
     276              :             }
     277              :         }
     278              : 
     279        30516 :         const MeasureBase* mb = ctx.state().curMeasure();
     280        30516 :         const MeasureBase* next = mb->nextMM();
     281        30516 :         bool lineBreak  = false;
     282        30516 :         switch (ctx.conf().viewMode()) {
     283        30516 :         case LayoutMode::PAGE:
     284              :         case LayoutMode::SYSTEM:
     285        61008 :             lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak() || mb->isEndOfSystemLock() || mb->isEndOfPageLock()
     286        61008 :                         || (next && next->isStartOfSystemLock()) || (next && next->isStartOfPageLock());
     287        30516 :             break;
     288            0 :         case LayoutMode::FLOAT:
     289              :         case LayoutMode::LINE:
     290              :         case LayoutMode::HORIZONTAL_FIXED:
     291            0 :             lineBreak = false;
     292            0 :             break;
     293              :         }
     294              : 
     295              :         // preserve state of next measure (which is about to become current measure)
     296        30516 :         if (ctx.state().nextMeasure()) {
     297        26711 :             MeasureBase* nmb = ctx.mutState().nextMeasure();
     298        26711 :             if (nmb->isMeasure() && ctx.conf().styleB(Sid::createMultiMeasureRests)) {
     299          441 :                 Measure* nm = toMeasure(nmb);
     300          441 :                 if (nm->hasMMRest()) {
     301           86 :                     nmb = nm->mmRest();
     302              :                 }
     303              :             }
     304        26711 :             if (nmb->isMeasure()) {
     305        26558 :                 prevMeasureState.clear();
     306        26558 :                 prevMeasureState.measure = toMeasure(nmb);
     307        26558 :                 prevMeasureState.measurePos = nmb->x();
     308        26558 :                 prevMeasureState.measureWidth = nmb->width();
     309        96144 :                 for (Segment& seg : toMeasure(nmb)->segments()) {
     310        69586 :                     prevMeasureState.elementPositions.emplace(&seg, seg.ldata()->pos());
     311        69586 :                     prevMeasureState.elementWidths.emplace(&seg, seg.width());
     312        74418 :                     for (EngravingItem* item : seg.annotations()) {
     313         4832 :                         if (item->isHarmony() || item->isFretDiagram()) {
     314         2333 :                             prevMeasureState.elementPositions.emplace(item, item->ldata()->pos());
     315              :                         }
     316              :                     }
     317              :                 }
     318              :             }
     319        26711 :             if (!ctx.state().curMeasure()->noBreak()) {
     320              :                 // current measure is not a nobreak,
     321              :                 // so next measure could possibly start a system
     322        26628 :                 prevMeasureState.curHeader = nmb->header();
     323              :             }
     324        26711 :             if (!nmb->noBreak()) {
     325              :                 // next measure is not a nobreak
     326              :                 // so it could possibly end a system
     327        26628 :                 prevMeasureState.curTrailer = nmb->trailer();
     328              :             }
     329              :         }
     330              : 
     331        30516 :         MeasureLayout::getNextMeasure(ctx);
     332              : 
     333              :         // ElementType nt = lc.curMeasure ? lc.curMeasure->type() : ElementType::INVALID;
     334        30516 :         mb = ctx.state().curMeasure();
     335        30516 :         if (lineBreak || !mb || mb->isVBoxBase()) {
     336         5533 :             break;
     337              :         }
     338              :     }
     339              : 
     340         7185 :     assert(ctx.state().prevMeasure());
     341              : 
     342         7185 :     if (ctx.state().endTick() < ctx.state().prevMeasure()->tick()) {
     343              :         // we've processed the entire range
     344              :         // but we need to continue layout until we reach a system whose last measure is the same as previous layout
     345          332 :         MeasureBase* curMB = ctx.mutState().curMeasure();
     346          332 :         Measure* m = curMB && curMB->isMeasure() ? toMeasure(curMB) : nullptr;
     347          332 :         bool curMeasureMayHaveJoinedBeams = m && BeamLayout::measureMayHaveBeamsJoinedIntoNext(m);
     348          332 :         if (ctx.state().prevMeasure() == ctx.state().systemOldMeasure() && !curMeasureMayHaveJoinedBeams) {
     349              :             // If current measure has possible beams joining to the next, we need to continue layout. This needs a better solution in future. [M.S.]
     350              :             // this system ends in the same place as the previous layout
     351              :             // ok to stop
     352          279 :             if (m) {
     353              :                 // we may have previously processed first measure(s) of next system
     354              :                 // so now we must restore to original state
     355          142 :                 if (m->repeatStart()) {
     356            3 :                     Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0, 1));
     357            3 :                     if (!s->enabled()) {
     358            0 :                         s->setEnabled(true);
     359              :                     }
     360              :                 }
     361          142 :                 const MeasureBase* pbmb = ctx.state().prevMeasure()->mbWithPrecedingSectionBreak();
     362          142 :                 bool localFirstSystem = pbmb->sectionBreak() && !ctx.conf().isMode(LayoutMode::FLOAT);
     363          142 :                 MeasureBase* nm = breakMeasure ? breakMeasure : m;
     364          142 :                 if (prevMeasureState.curHeader) {
     365          142 :                     MeasureLayout::addSystemHeader(m, localFirstSystem, ctx);
     366              :                 } else {
     367            0 :                     MeasureLayout::removeSystemHeader(m);
     368              :                 }
     369              :                 for (;;) {
     370              :                     // TODO: what if the nobreak group takes the entire system - is this correct?
     371          142 :                     if (prevMeasureState.curTrailer && !m->noBreak()) {
     372            0 :                         MeasureLayout::addSystemTrailer(m, m->nextMeasure(), ctx);
     373              :                     } else {
     374          142 :                         MeasureLayout::removeSystemTrailer(m, ctx);
     375              :                     }
     376              : 
     377          142 :                     MeasureLayout::setRepeatCourtesiesAndParens(m, ctx);
     378              : 
     379          142 :                     MeasureLayout::updateGraceNotes(m, ctx);
     380              : 
     381          142 :                     prevMeasureState.restoreMeasure(ctx);
     382          142 :                     MeasureLayout::layoutMeasureElements(m, ctx);
     383          142 :                     BeamLayout::restoreBeams(m, ctx);
     384          142 :                     SystemLayout::restoreOldSystemLayout(m->system(), ctx);
     385              : 
     386          142 :                     if (m == nm || !m->noBreak()) {
     387          142 :                         break;
     388              :                     }
     389            0 :                     m = m->nextMeasure();
     390              :                 }
     391              :             }
     392          279 :             ctx.mutState().setRangeDone(true);
     393              :         }
     394              :     }
     395              : 
     396         7185 :     if (ctx.dom().allStavesInvisible()) {
     397              :         // Edge case. Can only happen if all instruments have been deleted.
     398            3 :         return system;
     399              :     }
     400              : 
     401              :     /*************************************************************
     402              :      * SYSTEM NOW HAS A COMPLETE SET OF MEASURES
     403              :      * Now perform all operation to finalize system.
     404              :      * **********************************************************/
     405              : 
     406              :     // Break cross-measure beams
     407         7182 :     if (ctx.state().prevMeasure() && ctx.state().prevMeasure()->isMeasure()) {
     408         7150 :         Measure* pm = toMeasure(ctx.mutState().prevMeasure());
     409         7150 :         BeamLayout::breakCrossMeasureBeams(pm, ctx);
     410              :     }
     411              : 
     412              :     // Hide empty staves
     413         7182 :     hideEmptyStaves(system, ctx, ctx.state().firstSystem());
     414              : 
     415              :     // Re-create shapes to account for newly hidden/unhidden staves
     416              :     // (and for potential forgotten shape updates, for example in MeasureLayout::setRepeatCourtesiesAndParens)
     417        37602 :     for (MeasureBase* mb : system->measures()) {
     418        30420 :         if (mb->isMeasure()) {
     419       146597 :             for (Segment& seg : toMeasure(mb)->segments()) {
     420       116324 :                 seg.createShapes();
     421              :             }
     422              :         }
     423              :     }
     424              : 
     425              :     // Relayout system to account for newly hidden/unhidden staves
     426         7182 :     SystemLayout::layoutSystem(system, ctx, leadingHBoxesWidth);
     427              : 
     428              :     // Create end barlines and system trailer if needed (cautionary time/key signatures etc)
     429         7182 :     Measure* lm  = system->lastMeasure();
     430         7182 :     if (lm) {
     431         7174 :         MeasureLayout::createEndBarLines(lm, true, ctx);
     432         7174 :         Measure* nm = lm->nextMeasure();
     433         7174 :         if (nm) {
     434         3356 :             MeasureLayout::addSystemTrailer(lm, nm, ctx);
     435              :         }
     436              :     }
     437              : 
     438         7182 :     updateTimeSigAboveStavesXPos(system, ctx);
     439              : 
     440              :     // Recompute spacing to account for the last changes (barlines, hidden staves, etc)
     441         7182 :     curSysWidth = HorizontalSpacing::computeSpacingForFullSystem(system);
     442              : 
     443         7182 :     if (curSysWidth > targetSystemWidth) {
     444            3 :         HorizontalSpacing::squeezeSystemToFit(system, curSysWidth, targetSystemWidth);
     445              :     }
     446              : 
     447         7182 :     if (shouldBeJustified(system, curSysWidth, targetSystemWidth, ctx)) {
     448         6144 :         HorizontalSpacing::justifySystem(system, curSysWidth, targetSystemWidth);
     449              :     }
     450              : 
     451         7182 :     clearBigTimeSigNotShown(system, ctx);
     452              : 
     453              :     // LAYOUT MEASURES
     454         7182 :     bool createBrackets = false;
     455        37602 :     for (MeasureBase* mb : system->measures()) {
     456        30420 :         if (mb->isMeasure()) {
     457        30273 :             mb->setSystem(system);
     458        30273 :             Measure* m = toMeasure(mb);
     459        30273 :             MeasureLayout::layoutMeasureElements(m, ctx);
     460        30273 :             MeasureLayout::layoutStaffLines(m, ctx);
     461        30273 :             if (createBrackets) {
     462           99 :                 SystemHeaderLayout::addBrackets(system, toMeasure(mb), ctx);
     463           99 :                 createBrackets = false;
     464              :             }
     465          147 :         } else if (mb->isHBox()) {
     466          147 :             HBox* curHBox = toHBox(mb);
     467          147 :             TLayout::layoutMeasureBase(curHBox, ctx);
     468          147 :             createBrackets = curHBox->createSystemHeader();
     469              :         }
     470              :     }
     471              : 
     472         7182 :     layoutSystemElements(system, ctx);
     473         7182 :     SystemLayout::layout2(system, ctx);     // compute staff distances
     474              : 
     475         7461 :     if (ctx.state().rangeDone() && oldSystem && !oldSystem->measures().empty() && oldSystem->measures().front()->tick() >= system->endTick()
     476         7461 :         && !(oldSystem->page() && oldSystem->page() != ctx.state().page())) {
     477              :         // We may have unfinished layouts of certain elements in the next system
     478              :         // - ties & bends (in LayoutChords::updateLineAttachPoints())
     479              :         // - fret diagrams & harmony (in layoutMeasure)
     480              :         // Restore them to the correct state.
     481           72 :         SystemLayout::restoreOldSystemLayout(oldSystem, ctx);
     482              :     }
     483              : 
     484         7182 :     return system;
     485        13158 : }
     486              : 
     487         7182 : bool SystemLayout::shouldBeJustified(System* system, double curSysWidth, double targetSystemWidth, LayoutContext& ctx)
     488              : {
     489         7182 :     bool shouldJustify = true;
     490              : 
     491         7182 :     MeasureBase* lm = system->measures().back();
     492         7182 :     if ((curSysWidth / targetSystemWidth) < ctx.conf().styleD(Sid::lastSystemFillLimit)) {
     493         1211 :         shouldJustify = false;
     494         1211 :         const MeasureBase* lastMb = ctx.state().curMeasure();
     495              : 
     496              :         // For systems with a section break, don't justify
     497         1211 :         if (lm && lm->sectionBreak()) {
     498            6 :             lastMb = nullptr;
     499              :         }
     500              : 
     501              :         // Justify if system is followed by a measure or HBox
     502         1240 :         while (lastMb) {
     503          202 :             if (lastMb->isMeasure() || lastMb->isHBox()) {
     504          173 :                 shouldJustify = true;
     505          173 :                 break;
     506              :             }
     507              :             // Frames can contain section breaks too, account for that here
     508           29 :             if (lastMb->sectionBreak()) {
     509            0 :                 shouldJustify = false;
     510            0 :                 break;
     511              :             }
     512           29 :             lastMb = lastMb->nextMeasure();
     513              :         }
     514              :     }
     515              : 
     516         7182 :     return shouldJustify && !MScore::noHorizontalStretch;
     517              : }
     518              : 
     519        10195 : void SystemLayout::layoutSystemLockIndicators(System* system, LayoutContext& ctx)
     520              : {
     521              :     UNUSED(ctx);
     522              : 
     523        10195 :     const std::vector<SystemLockIndicator*> lockIndicators = system->systemLockIndicators();
     524              :     // In PAGE view, at most ONE lock indicator can exist per system.
     525        10195 :     assert(lockIndicators.size() <= 1);
     526        10195 :     system->deleteSystemLockIndicators();
     527              : 
     528        10195 :     const RangeLock* lock = system->systemLock();
     529        10195 :     if (!lock) {
     530        10096 :         return;
     531              :     }
     532              : 
     533           99 :     SystemLockIndicator* lockIndicator = Factory::createSystemLockIndicator(system, lock);
     534           99 :     lockIndicator->setTrack(0);
     535           99 :     system->addSystemLockIndicator(lockIndicator);
     536              : 
     537           99 :     TLayout::layoutIndicatorIcon(lockIndicator, lockIndicator->mutldata());
     538        10195 : }
     539              : 
     540        10195 : void SystemLayout::layoutPageLockIndicators(System* system)
     541              : {
     542        10195 :     Page* page = system->page();
     543        10195 :     system->deletePageLockIndicator();
     544              : 
     545        10195 :     IF_ASSERT_FAILED(!page->systems().empty()) {
     546            0 :         return;
     547              :     }
     548              : 
     549        10195 :     const RangeLock* lock = page->pageLock();
     550        10195 :     if (!lock || page->systems().back() != system) {
     551        10139 :         return;
     552              :     }
     553              : 
     554           56 :     PageLockIndicator* lockIndicator = Factory::createPageLockIndicator(system, lock);
     555           56 :     lockIndicator->setTrack(0);
     556           56 :     system->setPageLockIndicator(lockIndicator);
     557              : 
     558           56 :     TLayout::layoutPageLockIndicator(lockIndicator, lockIndicator->mutldata());
     559              : }
     560              : 
     561              : //---------------------------------------------------------
     562              : //   getNextSystem
     563              : //---------------------------------------------------------
     564              : 
     565         9473 : System* SystemLayout::getNextSystem(LayoutContext& ctx)
     566              : {
     567         9473 :     bool isVBox = ctx.state().curMeasure()->isVBox();
     568         9473 :     System* system = nullptr;
     569         9473 :     if (ctx.state().systemList().empty()) {
     570         8361 :         system = Factory::createSystem(ctx.mutDom().score());
     571         8361 :         ctx.mutState().setSystemOldMeasure(nullptr);
     572              :     } else {
     573         1112 :         system = muse::takeFirst(ctx.mutState().systemList());
     574         1112 :         ctx.mutState().setSystemOldMeasure(system->measures().empty() ? 0 : system->measures().back());
     575         1112 :         system->clear();       // remove measures from system
     576              :     }
     577         9473 :     ctx.mutDom().systems().push_back(system);
     578         9473 :     if (!isVBox) {
     579         7328 :         size_t nstaves = ctx.dom().nstaves();
     580         7328 :         system->adjustStavesNumber(nstaves);
     581        18351 :         for (staff_idx_t i = 0; i < nstaves; ++i) {
     582        11023 :             system->staff(i)->setShow(ctx.dom().staff(i)->show());
     583              :         }
     584              :     }
     585         9473 :     return system;
     586              : }
     587              : 
     588              : enum class StaffHideMode {
     589              :     HIDE_WHEN_STAFF_EMPTY,
     590              :     HIDE_WHEN_INSTRUMENT_EMPTY,
     591              :     ALWAYS_SHOW
     592              : };
     593              : 
     594         5711 : static StaffHideMode computeHideMode(const System* system, const Staff* staff, const staff_idx_t staffIdx, const bool globalHideIfEmpty,
     595              :                                      bool& hasSystemSpecificOverrides)
     596              : {
     597         5711 :     if (Part* part = staff->part(); part && part->isSharedPart() && staff != part->staves().front()) {
     598           50 :         return StaffHideMode::HIDE_WHEN_STAFF_EMPTY;
     599              :     }
     600              : 
     601              :     // Check for system-specific overrides
     602         5661 :     bool hasSystemSpecificOverrideHide = false;
     603         5661 :     bool hasSystemSpecificOverrideDontHide = false;
     604              : 
     605        28480 :     for (const MeasureBase* mb : system->measures()) {
     606        22819 :         if (!mb->isMeasure()) {
     607           94 :             continue;
     608              :         }
     609              : 
     610        22725 :         const Measure* measure = toMeasure(mb);
     611        22725 :         const AutoOnOff hideIfEmpty = measure->hideStaffIfEmpty(staffIdx);
     612              : 
     613        22725 :         hasSystemSpecificOverrideHide |= (hideIfEmpty == AutoOnOff::ON);
     614        22725 :         hasSystemSpecificOverrideDontHide |= (hideIfEmpty == AutoOnOff::OFF);
     615              :     }
     616              : 
     617         5661 :     if (hasSystemSpecificOverrideDontHide) {
     618            0 :         hasSystemSpecificOverrides = true;
     619            0 :         return StaffHideMode::ALWAYS_SHOW;
     620         5661 :     } else if (hasSystemSpecificOverrideHide) {
     621            0 :         hasSystemSpecificOverrides = true;
     622            0 :         return StaffHideMode::HIDE_WHEN_STAFF_EMPTY;
     623              :     }
     624              : 
     625              :     // Consider staff setting
     626         5661 :     AutoOnOff staffHideMode = staff->hideWhenEmpty();
     627         5661 :     switch (staffHideMode) {
     628            9 :     case AutoOnOff::ON:
     629            9 :         return StaffHideMode::HIDE_WHEN_STAFF_EMPTY;
     630            6 :     case AutoOnOff::OFF:
     631            6 :         return StaffHideMode::ALWAYS_SHOW;
     632         5646 :     case AutoOnOff::AUTO:
     633         5646 :         break;
     634              :     }
     635              : 
     636              :     // Consider part setting
     637         5646 :     AutoOnOff partHideMode = staff->part()->hideWhenEmpty();
     638         5646 :     switch (partHideMode) {
     639            6 :     case AutoOnOff::ON:
     640            6 :         break;
     641            6 :     case AutoOnOff::OFF:
     642            6 :         return StaffHideMode::ALWAYS_SHOW;
     643         5634 :     case AutoOnOff::AUTO:
     644              :         // Consider global setting
     645         5634 :         if (!globalHideIfEmpty) {
     646         5590 :             return StaffHideMode::ALWAYS_SHOW;
     647              :         }
     648              :     }
     649              : 
     650           50 :     return staff->part()->hideStavesWhenIndividuallyEmpty()
     651           50 :            ? StaffHideMode::HIDE_WHEN_STAFF_EMPTY
     652           50 :            : StaffHideMode::HIDE_WHEN_INSTRUMENT_EMPTY;
     653              : }
     654              : 
     655         5711 : static bool computeShowSysStaff(const System* system, const Staff* staff, const staff_idx_t staffIdx,
     656              :                                 const Fraction& stick, const SpannerMap::IntervalList& spanners,
     657              :                                 const StaffHideMode hideMode)
     658              : {
     659         5711 :     if (hideMode == StaffHideMode::ALWAYS_SHOW) {
     660         5602 :         return true;
     661              :     }
     662              : 
     663              :     // Check if there are spanners
     664          113 :     for (const auto& spanner : spanners) {
     665            4 :         if (spanner.value->staff() == staff
     666            0 :             && !spanner.value->systemFlag()
     667            4 :             && !(spanner.stop == stick.ticks() && !spanner.value->isSlur())) {
     668            0 :             return true;
     669              :         }
     670              :     }
     671              : 
     672              :     // Check if the staff is empty in the system
     673          431 :     for (const MeasureBase* m : system->measures()) {
     674          356 :         if (!m->isMeasure()) {
     675            0 :             continue;
     676              :         }
     677          356 :         const Measure* measure = toMeasure(m);
     678          356 :         if (!measure->isEmpty(staffIdx)) {
     679           34 :             return true;
     680              :         }
     681              :     }
     682              : 
     683              :     // check if notes moved into this staff
     684           75 :     Part* part = staff->part();
     685           75 :     const size_t n = part->nstaves();
     686           75 :     if (n > 1) {
     687           66 :         staff_idx_t idx = part->staves().front()->idx();
     688          198 :         for (staff_idx_t i = 0; i < n; ++i) {
     689          132 :             staff_idx_t st = idx + i;
     690              : 
     691          744 :             for (MeasureBase* mb : system->measures()) {
     692          612 :                 if (!mb->isMeasure()) {
     693            0 :                     continue;
     694              :                 }
     695              : 
     696          612 :                 const Measure* m = toMeasure(mb);
     697          612 :                 bool empty = m->isEmpty(st);
     698          612 :                 if (hideMode == StaffHideMode::HIDE_WHEN_INSTRUMENT_EMPTY && !empty) {
     699            0 :                     return true;
     700          612 :                 } else if (empty) {
     701          604 :                     continue;
     702              :                 }
     703              : 
     704           50 :                 for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
     705          210 :                     for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
     706          168 :                         ChordRest* cr = s->cr(st * VOICES + voice);
     707          168 :                         int staffMove = cr ? cr->staffMove() : 0;
     708          168 :                         if (!cr || cr->isRest() || cr->staffMove() == 0) {
     709              :                             // The case staffMove == 0 has already been checked by measure->isEmpty()
     710          168 :                             continue;
     711              :                         }
     712            0 :                         if (staffIdx == st + staffMove) {
     713            0 :                             return true;
     714              :                         }
     715              :                     }
     716              :                 }
     717              :             }
     718              :         }
     719              :     }
     720              : 
     721           75 :     return false;
     722              : }
     723              : 
     724         7182 : void SystemLayout::hideEmptyStaves(System* system, LayoutContext& ctx, bool isFirstSystem)
     725              : {
     726         7182 :     if (ctx.dom().nstaves() == 0) {
     727              :         // No staves, nothing to hide
     728         5029 :         return;
     729              :     }
     730              : 
     731         7182 :     if (ctx.dom().nstaves() == 1) {
     732              :         // One staff, show if not manually hidden score-wide
     733         5029 :         const bool show = ctx.dom().staves().front()->show();
     734         5029 :         system->staves().front()->setShow(show);
     735         5029 :         system->setHasStaffVisibilityIndicator(false);
     736         5029 :         return;
     737              :     }
     738              : 
     739         2153 :     const Fraction stick = system->first()->tick();
     740         2153 :     const Fraction etick = system->last()->endTick();
     741         2153 :     const auto& spanners = ctx.dom().spannerMap().findOverlapping(stick.ticks(), etick.ticks() - 1);
     742              : 
     743         2153 :     const bool globalHideIfEmpty = ctx.conf().styleB(Sid::hideEmptyStaves)
     744         2153 :                                    && !(isFirstSystem && ctx.conf().styleB(Sid::dontHideStavesInFirstSystem));
     745              : 
     746         2153 :     bool hasSystemSpecificOverrides = false;
     747         2153 :     bool hasHiddenStaves = false;
     748         2153 :     bool systemIsEmpty = true;
     749         2153 :     staff_idx_t staffIdx = 0;
     750              : 
     751         7993 :     for (const Staff* staff : ctx.dom().staves()) {
     752         5840 :         SysStaff* ss = system->staff(staffIdx);
     753              : 
     754         5840 :         DEFER {
     755         5840 :             ++staffIdx;
     756         5840 :         };
     757              : 
     758         5840 :         if (!staff->show()) {
     759          129 :             ss->setShow(false);
     760          129 :             continue;
     761              :         }
     762              : 
     763         5711 :         const StaffHideMode hideMode = computeHideMode(system, staff, staffIdx, globalHideIfEmpty, hasSystemSpecificOverrides);
     764         5711 :         const bool show = computeShowSysStaff(system, staff, staffIdx, stick, spanners, hideMode);
     765         5711 :         ss->setShow(show);
     766         5711 :         if (show) {
     767         5636 :             systemIsEmpty = false;
     768              :         } else {
     769           75 :             hasHiddenStaves = true;
     770              :         }
     771         5840 :     }
     772              : 
     773         2153 :     system->setHasStaffVisibilityIndicator(hasSystemSpecificOverrides || hasHiddenStaves);
     774              : 
     775              :     // If the system is empty, unhide the staves with `showIfEntireSystemEmpty` set to true, if any
     776         2153 :     const Staff* firstVisible = nullptr;
     777         2153 :     if (systemIsEmpty) {
     778            7 :         for (const Staff* staff : ctx.dom().staves()) {
     779            6 :             SysStaff* ss  = system->staff(staff->idx());
     780            6 :             if (staff->showIfEntireSystemEmpty() && !ss->show()) {
     781            0 :                 ss->setShow(true);
     782            0 :                 systemIsEmpty = false;
     783            6 :             } else if (!firstVisible && staff->show()) {
     784            1 :                 firstVisible = staff;
     785              :             }
     786              :         }
     787              :     }
     788              : 
     789              :     // If there are no such staves, unhide the first one
     790         2153 :     if (systemIsEmpty) {
     791            1 :         const Staff* staff = firstVisible ? firstVisible : ctx.dom().staves().front();
     792            1 :         SysStaff* ss = system->staff(staff->idx());
     793            1 :         ss->setShow(true);
     794              :     }
     795              : }
     796              : 
     797            0 : bool SystemLayout::canChangeSysStaffVisibility(const System* system, const staff_idx_t staffIdx)
     798              : {
     799            0 :     if (system->staves().size() <= 1) {
     800              :         // Only one staff; always visible
     801            0 :         return false;
     802              :     }
     803              : 
     804            0 :     const Staff* staff = system->score()->staff(staffIdx);
     805              : 
     806            0 :     if (system->staff(staffIdx)->show()) {
     807              :         // SysStaff is visible; check if can hide
     808            0 :         const Fraction stick = system->first()->tick();
     809            0 :         const Fraction etick = system->last()->endTick();
     810            0 :         const auto& spanners = system->score()->spannerMap().findOverlapping(stick.ticks(), etick.ticks() - 1);
     811              : 
     812            0 :         return !computeShowSysStaff(system, staff, staffIdx, system->first()->tick(), spanners, StaffHideMode::HIDE_WHEN_STAFF_EMPTY);
     813              :     }
     814              : 
     815              :     // SysStaff is hidden; check if can show
     816            0 :     if (!staff->show()) {
     817            0 :         return false;
     818              :     }
     819              : 
     820            0 :     return true;
     821              : }
     822              : 
     823         7182 : void SystemLayout::updateTimeSigAboveStavesXPos(System* system, LayoutContext& ctx)
     824              : {
     825         7182 :     if (ctx.conf().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>() != TimeSigPlacement::ABOVE_STAVES) {
     826         7182 :         return;
     827              :     }
     828              : 
     829            0 :     staff_idx_t nstaves = ctx.dom().nstaves();
     830            0 :     bool centerOnBarline = ctx.conf().styleB(Sid::timeSigCenterOnBarline);
     831              : 
     832            0 :     for (MeasureBase* mb : system->measures()) {
     833            0 :         if (!mb->isMeasure()) {
     834            0 :             continue;
     835              :         }
     836            0 :         Measure* measure = toMeasure(mb);
     837            0 :         for (Segment& seg : measure->segments()) {
     838            0 :             if (!seg.isType(SegmentType::TimeSigTypes)) {
     839            0 :                 continue;
     840              :             }
     841              : 
     842            0 :             std::set<TimeSig*> timeSigToKeep;
     843            0 :             for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
     844            0 :                 TimeSig* timeSig = toTimeSig(seg.element(staff2track(staffIdx)));
     845            0 :                 if (!timeSig || !timeSig->showOnThisStaff()) {
     846            0 :                     continue;
     847              :                 }
     848              : 
     849            0 :                 timeSigToKeep.insert(timeSig);
     850            0 :                 if (system->staff(staffIdx)->show()) {
     851            0 :                     continue;
     852              :                 }
     853              : 
     854            0 :                 staff_idx_t nextVisStaff = system->nextVisibleStaff(staffIdx);
     855            0 :                 if (nextVisStaff == muse::nidx) {
     856            0 :                     continue;
     857              :                 }
     858              : 
     859            0 :                 TimeSig* nextVisTimeSig = toTimeSig(seg.element(staff2track(nextVisStaff)));
     860            0 :                 if (nextVisTimeSig) {
     861            0 :                     timeSigToKeep.insert(nextVisTimeSig);
     862              :                 }
     863              :             }
     864              : 
     865            0 :             Segment* prevBarlineSeg = nullptr;
     866            0 :             Segment* prevRepeatAnnounceTimeSigSeg = nullptr;
     867            0 :             if (centerOnBarline) {
     868            0 :                 for (Segment* prevSeg = seg.prev1(); prevSeg && prevSeg->tick() == seg.tick(); prevSeg = prevSeg->prev1()) {
     869            0 :                     if (prevSeg->isEndBarLineType()) {
     870            0 :                         prevBarlineSeg = prevSeg;
     871            0 :                     } else if (prevSeg->isTimeSigRepeatAnnounceType()) {
     872            0 :                         prevRepeatAnnounceTimeSigSeg = prevSeg;
     873              :                     }
     874              :                 }
     875              :             }
     876              : 
     877            0 :             for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
     878            0 :                 TimeSig* timeSig = toTimeSig(seg.element(staff2track(staffIdx)));
     879            0 :                 if (!timeSig) {
     880            0 :                     continue;
     881              :                 }
     882            0 :                 Parenthesis* leftParen = timeSig->leftParen();
     883            0 :                 Parenthesis* rightParen = timeSig->rightParen();
     884            0 :                 if (!muse::contains(timeSigToKeep, timeSig)) {
     885            0 :                     timeSig->mutldata()->reset(); // Eliminates the shape
     886            0 :                     if (leftParen) {
     887            0 :                         leftParen->mutldata()->reset();
     888              :                     }
     889            0 :                     if (rightParen) {
     890            0 :                         rightParen->mutldata()->reset();
     891              :                     }
     892            0 :                     continue;
     893              :                 }
     894              : 
     895            0 :                 if (prevBarlineSeg && prevBarlineSeg->system() == system && !prevRepeatAnnounceTimeSigSeg) {
     896              :                     // Center timeSig on its segment
     897            0 :                     RectF bbox = timeSig->ldata()->bbox();
     898            0 :                     double newXPos = -0.5 * (bbox.right() + bbox.left());
     899            0 :                     timeSig->mutldata()->setPosX(newXPos);
     900            0 :                 } else if (!seg.isTimeSigRepeatAnnounceType()) {
     901              :                     // Left-align to parenthesis if present
     902            0 :                     double xLeftParens = DBL_MAX;
     903            0 :                     if (leftParen) {
     904            0 :                         xLeftParens = leftParen->x() + leftParen->ldata()->bbox().left();
     905              :                     }
     906            0 :                     if (xLeftParens != DBL_MAX) {
     907            0 :                         timeSig->mutldata()->moveX(-xLeftParens);
     908              :                     }
     909              :                 } else {
     910              :                     // TimeSigRepeatAnnounce: right-align to segment
     911            0 :                     double xRight = -DBL_MAX;
     912            0 :                     xRight = std::max(xRight, timeSig->shape().right() + timeSig->x());
     913            0 :                     if (leftParen) {
     914            0 :                         xRight = std::max(xRight, leftParen->ldata()->bbox().right() + leftParen->x());
     915              :                     }
     916            0 :                     if (rightParen) {
     917            0 :                         xRight = std::max(xRight, rightParen->ldata()->bbox().right() + rightParen->x());
     918              :                     }
     919            0 :                     if (xRight != -DBL_MAX) {
     920            0 :                         timeSig->mutldata()->moveX(-xRight);
     921              :                     }
     922              :                 }
     923              :             }
     924              : 
     925            0 :             seg.createShapes();
     926            0 :         }
     927              :     }
     928              : }
     929              : 
     930         7182 : void SystemLayout::clearBigTimeSigNotShown(System* system, LayoutContext& ctx)
     931              : {
     932         7182 :     if (ctx.conf().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>() == TimeSigPlacement::NORMAL) {
     933         7170 :         return;
     934              :     }
     935              : 
     936           78 :     for (MeasureBase* mb : system->measures()) {
     937           66 :         if (!mb->isMeasure()) {
     938            0 :             continue;
     939              :         }
     940          238 :         for (Segment& seg : toMeasure(mb)->segments()) {
     941          172 :             if (!seg.isType(SegmentType::TimeSigTypes)) {
     942          168 :                 continue;
     943              :             }
     944           38 :             for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
     945           34 :                 TimeSig* ts = toTimeSig(seg.element(staff2track(staffIdx)));
     946           34 :                 if (!ts) {
     947            0 :                     continue;
     948              :                 }
     949           34 :                 if (!ts->showOnThisStaff() || ts->effectiveStaffIdx() == muse::nidx) {
     950           22 :                     ts->mutldata()->reset(); // Deletes shape
     951              :                 }
     952              :             }
     953              :         }
     954              :     }
     955              : }
     956              : 
     957         7388 : void SystemLayout::layoutSticking(const std::vector<Sticking*> stickings, System* system, LayoutContext& ctx)
     958              : {
     959         7388 :     if (stickings.empty()) {
     960         7377 :         return;
     961              :     }
     962              : 
     963          142 :     for (Sticking* sticking : stickings) {
     964          131 :         TLayout::layoutItem(sticking, ctx);
     965              :     }
     966              : 
     967           11 :     std::vector<EngravingItem*> stickingItems(stickings.begin(), stickings.end());
     968           11 :     AlignmentLayout::alignItemsForSystem(stickingItems, system);
     969           11 : }
     970              : 
     971         7388 : void SystemLayout::layoutLyrics(const ElementsToLayout& elements, LayoutContext& ctx)
     972              : {
     973         7388 :     System* system = elements.system;
     974              :     // NOTE: in continuous view, this means we layout spanners for the entire score.
     975              :     // TODO: find way to optimize this and only layout where necessary.
     976         7388 :     Fraction stick = system->measures().front()->tick();
     977         7388 :     Fraction etick = system->measures().back()->endTick();
     978              : 
     979         7394 :     for (Spanner* sp : elements.partialLyricsLines) {
     980            6 :         TLayout::layoutSystem(sp, system, ctx);
     981              :     }
     982              : 
     983              :     //-------------------------------------------------------------
     984              :     // Lyric
     985              :     //-------------------------------------------------------------
     986              :     // Layout lyrics dashes and melisma
     987              :     // NOTE: loop on a *copy* of unmanagedSpanners because in some cases
     988              :     // the underlying operation may invalidate some of the iterators.
     989              :     // TODO: figure out why lyrics lines are in this "unmanagedSpanners" container
     990         7388 :     bool dashOnFirstNoteSyllable = ctx.conf().style().styleB(Sid::lyricsShowDashIfSyllableOnFirstNote);
     991         7388 :     std::set<Spanner*> unmanagedSpanners = ctx.dom().unmanagedSpanners();
     992         7886 :     for (Spanner* sp : unmanagedSpanners) {
     993          498 :         if (!sp->systemFlag() && sp->staff() && !sp->staff()->show()) {
     994            0 :             continue;
     995              :         }
     996          498 :         bool dashOnFirst = dashOnFirstNoteSyllable && !toLyricsLine(sp)->isEndMelisma();
     997          498 :         if (sp->tick() >= etick || sp->tick2() < stick || (sp->tick2() == stick && !dashOnFirst)) {
     998          156 :             continue;
     999              :         }
    1000          342 :         TLayout::layoutSystem(sp, system, ctx);
    1001              :     }
    1002         7388 :     LyricsLayout::computeVerticalPositions(system, ctx);
    1003         7388 : }
    1004              : 
    1005         7388 : void SystemLayout::layoutVoltas(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
    1006              : {
    1007         7388 :     System* system = elementsToLayout.system;
    1008              : 
    1009         7388 :     processLines(system, ctx, elementsToLayout.voltas);
    1010              : 
    1011         7388 :     std::set<Volta*> alignedVoltas;
    1012         7667 :     for (size_t i = 0; i < elementsToLayout.voltas.size(); ++i) {
    1013          279 :         Volta* volta1 = toVolta(elementsToLayout.voltas[i]);
    1014          279 :         if (alignedVoltas.count(volta1)) {
    1015          134 :             continue;
    1016              :         }
    1017              : 
    1018          145 :         std::vector<EngravingItem*> voltasToAlign;
    1019          145 :         voltasToAlign.push_back(volta1->backSegment());
    1020              : 
    1021          312 :         for (size_t j = i + 1; j < elementsToLayout.voltas.size(); ++j) {
    1022          167 :             Volta* volta2 = toVolta(elementsToLayout.voltas[j]);
    1023          167 :             if (volta2->staffIdx() == volta1->staffIdx() && volta2->tick() == volta1->tick2()) {
    1024          134 :                 voltasToAlign.push_back(volta2->frontSegment());
    1025          134 :                 volta1 = volta2;
    1026              :             }
    1027              :         }
    1028              : 
    1029          145 :         if (voltasToAlign.size() > 1) {
    1030          109 :             AlignmentLayout::alignItemsGroup(voltasToAlign, system);
    1031          352 :             for (EngravingItem* vs : voltasToAlign) {
    1032          243 :                 alignedVoltas.insert(toVoltaSegment(vs)->volta());
    1033              :             }
    1034              :         }
    1035          145 :     }
    1036         7388 : }
    1037              : 
    1038         7388 : void SystemLayout::layoutDynamicExpressionAndHairpins(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
    1039              : {
    1040         7388 :     System* system = elementsToLayout.system;
    1041              : 
    1042         7388 :     std::vector<EngravingItem*> dynamicsExprAndHairpinsToAlign;
    1043              : 
    1044         8529 :     for (Dynamic* dynamic : elementsToLayout.dynamics) {
    1045         1141 :         TLayout::layoutItem(dynamic, ctx);
    1046         1141 :         if (dynamic->autoplace()) {
    1047         1141 :             Autoplace::autoplaceSegmentElement(dynamic, dynamic->mutldata());
    1048         1141 :             dynamicsExprAndHairpinsToAlign.push_back(dynamic);
    1049              :         }
    1050              :     }
    1051              : 
    1052         7422 :     for (Expression* e : elementsToLayout.expressions) {
    1053           34 :         TLayout::layoutItem(e, ctx);
    1054           34 :         if (e->addToSkyline()) {
    1055           34 :             dynamicsExprAndHairpinsToAlign.push_back(e);
    1056              :         }
    1057              :     }
    1058              : 
    1059         7388 :     processLines(system, ctx, elementsToLayout.hairpins);
    1060              : 
    1061        12234 :     for (SpannerSegment* spannerSegment : system->spannerSegments()) {
    1062         4846 :         if (spannerSegment->isHairpinSegment()) {
    1063          472 :             dynamicsExprAndHairpinsToAlign.push_back(spannerSegment);
    1064              :         }
    1065              :     }
    1066              : 
    1067         7388 :     AlignmentLayout::alignItemsWithTheirSnappingChain(dynamicsExprAndHairpinsToAlign, system);
    1068         7388 : }
    1069              : 
    1070         7388 : void SystemLayout::layoutParenthesisAndBigTimeSigs(const ElementsToLayout& elementsToLayout)
    1071              : {
    1072         7388 :     System* system = elementsToLayout.system;
    1073              : 
    1074         7388 :     for (Parenthesis* e : elementsToLayout.parenthesis) {
    1075            0 :         Segment* s = e->segment();
    1076            0 :         if (s->isType(SegmentType::TimeSigTypes)) {
    1077            0 :             EngravingItem* el = s->element(e->track());
    1078            0 :             TimeSig* timeSig = el ? toTimeSig(el) : nullptr;
    1079            0 :             if (!timeSig) {
    1080            0 :                 continue;
    1081              :             }
    1082            0 :             TimeSigPlacement timeSigPlacement = timeSig->style().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>();
    1083            0 :             if (timeSigPlacement == TimeSigPlacement::ACROSS_STAVES) {
    1084            0 :                 if (!timeSig->showOnThisStaff()) {
    1085            0 :                     e->mutldata()->reset();
    1086              :                 }
    1087            0 :                 continue;
    1088              :             }
    1089              :         }
    1090              : 
    1091            0 :         staff_idx_t si = e->staffIdx();
    1092            0 :         Measure* m = s->measure();
    1093            0 :         system->staff(si)->skyline().add(e->shape().translate(e->pos() + s->pos() + m->pos() + e->staffOffset()));
    1094              :     }
    1095              : 
    1096         7388 :     for (TimeSig* timeSig : elementsToLayout.timeSigAboveStaves) {
    1097            0 :         Autoplace::autoplaceSegmentElement(timeSig, timeSig->mutldata());
    1098              :     }
    1099         7388 : }
    1100              : 
    1101         2518 : static void autoplaceHarmony(EngravingItem* harmony)
    1102              : {
    1103         2518 :     FretDiagram* fdParent = harmony->parent()->isFretDiagram() ? toFretDiagram(harmony->parent()) : nullptr;
    1104         2518 :     if (fdParent && !fdParent->visible()) {
    1105            0 :         harmony->mutldata()->moveY(-fdParent->pos().y());
    1106              :     }
    1107         2518 :     Autoplace::autoplaceSegmentElement(harmony, harmony->mutldata());
    1108         2518 : }
    1109              : 
    1110         7358 : void SystemLayout::layoutHarmonies(const std::vector<Harmony*> harmonies, System* system, LayoutContext& ctx)
    1111              : {
    1112         7358 :     if (!ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
    1113         4658 :         for (Harmony* harmony : harmonies) {
    1114         1650 :             autoplaceHarmony(harmony);
    1115              :         }
    1116         3008 :         return;
    1117              :     }
    1118              : 
    1119              :     // Only vertically align one chord symbol per tick & staff
    1120         4350 :     std::set<std::pair<Fraction, staff_idx_t> > harmonyPositions;
    1121         4350 :     std::vector<EngravingItem*> harmonyItemsAlign;
    1122         4350 :     std::vector<EngravingItem*> harmonyItemsNoAlign;
    1123              : 
    1124         5194 :     for (Harmony* h : harmonies) {
    1125          844 :         if (muse::contains(harmonyPositions, { h->tick(), h->staffIdx() })) {
    1126            3 :             harmonyItemsNoAlign.push_back(h);
    1127            3 :             continue;
    1128              :         }
    1129              : 
    1130          841 :         TLayout::layoutHarmony(h, h->mutldata(), ctx);
    1131          841 :         autoplaceHarmony(h);
    1132          841 :         harmonyItemsAlign.push_back(h);
    1133          841 :         harmonyPositions.insert({ h->tick(), h->staffIdx() });
    1134              :     }
    1135              : 
    1136         4350 :     AlignmentLayout::alignItemsForSystem(harmonyItemsAlign, system);
    1137              : 
    1138         4353 :     for (EngravingItem* harmony : harmonyItemsNoAlign) {
    1139            3 :         autoplaceHarmony(harmony);
    1140              :     }
    1141         4350 : }
    1142              : 
    1143          105 : void SystemLayout::layoutFretDiagrams(const ElementsToLayout& elements, System* system, LayoutContext& ctx)
    1144              : {
    1145          105 :     if (!ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
    1146           89 :         for (FretDiagram* fretDiag : elements.fretDiagrams) {
    1147           59 :             Autoplace::autoplaceSegmentElement(fretDiag, fretDiag->mutldata());
    1148              :         }
    1149              : 
    1150           54 :         for (Harmony* harmony : elements.harmonies) {
    1151           24 :             autoplaceHarmony(harmony);
    1152              :         }
    1153              : 
    1154           89 :         for (FretDiagram* fretDiag : elements.fretDiagrams) {
    1155           59 :             if (Harmony* harmony = fretDiag->harmony()) {
    1156           20 :                 SkylineLine& skl = system->staff(fretDiag->staffIdx())->skyline().north();
    1157           20 :                 Segment* s = fretDiag->segment();
    1158           20 :                 Shape harmShape = harmony->ldata()->shape().translated(harmony->pos() + fretDiag->pos() + s->pos() + s->measure()->pos());
    1159           20 :                 skl.add(harmShape);
    1160           20 :             }
    1161              :         }
    1162              : 
    1163           30 :         return;
    1164              :     }
    1165              : 
    1166              :     // Only vertically align one fd per tick & staff
    1167           75 :     std::set<std::pair<Fraction, staff_idx_t> > fretHarmonyPositions;
    1168           75 :     std::vector<EngravingItem*> fretItemsAlign;
    1169           75 :     std::vector<EngravingItem*> fretOrHarmonyItemsNoAlign;
    1170           75 :     std::vector<Harmony*> harmonyItemsAlign(elements.harmonies.begin(), elements.harmonies.end());
    1171              : 
    1172          364 :     for (FretDiagram* fd : elements.fretDiagrams) {
    1173          289 :         if (muse::contains(fretHarmonyPositions, { fd->tick(), fd->staffIdx() })) {
    1174            0 :             fretOrHarmonyItemsNoAlign.push_back(fd);
    1175            0 :             if (fd->harmony()) {
    1176            0 :                 harmonyItemsAlign.erase(std::remove(harmonyItemsAlign.begin(), harmonyItemsAlign.end(), fd->harmony()));
    1177            0 :                 fretOrHarmonyItemsNoAlign.push_back(fd->harmony());
    1178              :             }
    1179            0 :             continue;
    1180              :         }
    1181              : 
    1182          289 :         TLayout::layoutFretDiagram(fd, fd->mutldata(), ctx);
    1183          289 :         Autoplace::autoplaceSegmentElement(fd, fd->mutldata());
    1184          289 :         fretItemsAlign.push_back(fd);
    1185          289 :         fretHarmonyPositions.insert({ fd->tick(), fd->staffIdx() });
    1186              :     }
    1187              : 
    1188              :     // Find harmony with no fret diagram at the same tick as a fret diagram
    1189          302 :     for (Harmony* h : elements.harmonies) {
    1190          227 :         if (h->getParentFretDiagram()) {
    1191          223 :             continue;
    1192              :         }
    1193            4 :         if (muse::contains(fretHarmonyPositions, { h->tick(), h->staffIdx() })) {
    1194            0 :             harmonyItemsAlign.erase(std::remove(harmonyItemsAlign.begin(), harmonyItemsAlign.end(), h));
    1195            0 :             fretOrHarmonyItemsNoAlign.push_back(h);
    1196            0 :             continue;
    1197              :         }
    1198              : 
    1199            4 :         Autoplace::autoplaceSegmentElement(h, h->mutldata());
    1200            4 :         fretHarmonyPositions.insert({ h->tick(), h->staffIdx() });
    1201              :     }
    1202              : 
    1203              :     // align 1 fret diagram per tick & staff
    1204           75 :     AlignmentLayout::alignItemsForSystem(fretItemsAlign, system);
    1205              : 
    1206           75 :     layoutHarmonies(harmonyItemsAlign, system, ctx);
    1207              : 
    1208              :     // autoplace everything else
    1209           75 :     for (EngravingItem* item : fretOrHarmonyItemsNoAlign) {
    1210            0 :         if (item->isFretDiagram()) {
    1211            0 :             Autoplace::autoplaceSegmentElement(item, item->mutldata());
    1212            0 :             Harmony* harmony = toFretDiagram(item)->harmony();
    1213            0 :             if (harmony) {
    1214            0 :                 autoplaceHarmony(harmony);
    1215              :             }
    1216            0 :         } else if (item->isHarmony()) {
    1217            0 :             autoplaceHarmony(item);
    1218              :         }
    1219              :     }
    1220           75 : }
    1221              : 
    1222         7396 : void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx)
    1223              : {
    1224         7411 :     TRACEFUNC;
    1225              : 
    1226         7396 :     if (ctx.dom().nstaves() == 0) {
    1227            0 :         return;
    1228              :     }
    1229              : 
    1230              :     // Continuous view does not center items between staves (it collects no pages and doesn't call centerPendingSystems)
    1231         7396 :     if (!ctx.conf().isLinearMode()) {
    1232         7396 :         ctx.mutState().addSystemNeedingCentering(system);
    1233              :     }
    1234              : 
    1235         7396 :     ElementsToLayout elementsToLayout(system);
    1236              : 
    1237        39334 :     for (MeasureBase* mb : system->measures()) {
    1238        31938 :         if (!mb->isMeasure()) {
    1239          148 :             continue;
    1240              :         }
    1241        31790 :         if (ctx.conf().isLinearMode() && (mb->tick() < ctx.state().startTick() || mb->tick() > ctx.state().endTick())) {
    1242              :             // in continuous view, entire score is one system but we only need to process the range
    1243            0 :             continue;
    1244              :         }
    1245              : 
    1246        31790 :         Measure* measure = toMeasure(mb);
    1247              : 
    1248        31790 :         MeasureLayout::layoutMeasureNumber(measure, ctx);
    1249        31790 :         MMRestLayout::layoutMMRestRange(measure, ctx);
    1250        31790 :         MeasureLayout::layoutPlayCountText(measure, ctx);
    1251        31790 :         MeasureLayout::layoutTimeTickAnchors(measure, ctx);
    1252              : 
    1253        31790 :         collectElementsToLayout(measure, elementsToLayout, ctx);
    1254              :     }
    1255              : 
    1256         7396 :     const std::vector<Segment*>& sl = elementsToLayout.segments;
    1257         7396 :     if (sl.empty()) {
    1258            8 :         return;
    1259              :     }
    1260              : 
    1261        55607 :     for (Chord* chord : elementsToLayout.chords) {
    1262        48219 :         GraceNotesGroup& graceBefore = chord->graceNotesBefore();
    1263        48219 :         GraceNotesGroup& graceAfter = chord->graceNotesAfter();
    1264        48219 :         TLayout::layoutGraceNotesGroup2(&graceBefore, graceBefore.mutldata());
    1265        48219 :         TLayout::layoutGraceNotesGroup2(&graceAfter, graceAfter.mutldata());
    1266        48219 :         if (TabDurationSymbol* symbol = chord->tabDur()) {
    1267           24 :             TLayout::layoutTabDurationSymbol2(symbol, symbol->mutldata());
    1268           24 :             ChordLayout::fillShape(chord, chord->mutldata());
    1269              :         }
    1270              :     }
    1271              : 
    1272        97071 :     for (ChordRest* cr : elementsToLayout.chordRests) {
    1273        89683 :         BeamLayout::layoutNonCrossBeams(cr, ctx);
    1274              :     }
    1275              : 
    1276         7388 :     RestLayout::alignRests(elementsToLayout.system, ctx);
    1277         7388 :     RestLayout::checkFullMeasureRestCollisions(elementsToLayout.system, ctx);
    1278              : 
    1279        59709 :     for (BarLine* bl : elementsToLayout.barlines) {
    1280        52321 :         TLayout::updateBarlineShape(bl, bl->mutldata(), ctx);
    1281              :     }
    1282              : 
    1283         7388 :     createSkylines(elementsToLayout, ctx);
    1284              : 
    1285              :     /* The skyline grows as elements are laid out, so whatever is laid out first ends up closest to the
    1286              :      * staff. We changed the stacking order of lyrics, from after pedals to before dynamics, so we use
    1287              :      * this style to keep compatibility for <5.0 scores. This will no longer be necessary if we implement
    1288              :      * a proper customisable stacking order. */
    1289         7388 :     const LyricsStackingOrder lyricsStackingOrder = ctx.conf().styleV(Sid::lyricsStackingOrder).value<LyricsStackingOrder>();
    1290              : 
    1291         7388 :     layoutTiesAndBends(elementsToLayout, ctx);
    1292              : 
    1293         7388 :     if (ctx.conf().isLinearMode()) {
    1294              :         // TODO: get rid of this
    1295            0 :         doLayoutNoteSpannersLinear(system, ctx);
    1296              :     }
    1297              : 
    1298        55607 :     for (Chord* c : elementsToLayout.chords) {
    1299        48219 :         ChordLayout::layoutArticulations(c, ctx);
    1300        48219 :         ChordLayout::layoutArticulations2(c, ctx);
    1301        48219 :         ChordLayout::layoutChordBaseFingering(c, system, ctx);
    1302              :     }
    1303              : 
    1304         7388 :     layoutTuplets(elementsToLayout.chordRests, ctx);
    1305              : 
    1306         7388 :     collectSpannersToLayout(elementsToLayout, ctx);
    1307              : 
    1308         7388 :     processLines(system, ctx, elementsToLayout.slurs);
    1309              : 
    1310         8261 :     for (Spanner* sp : elementsToLayout.slurs) {
    1311          873 :         Slur* slur = toSlur(sp);
    1312          873 :         ChordRest* scr = toChordRest(slur->startElement());
    1313          873 :         ChordRest* ecr = toChordRest(slur->endElement());
    1314          873 :         if (scr && scr->isChord()) {
    1315          873 :             ChordLayout::layoutArticulations3(toChord(scr), slur, ctx);
    1316              :         }
    1317          873 :         if (ecr && ecr->isChord()) {
    1318          860 :             ChordLayout::layoutArticulations3(toChord(ecr), slur, ctx);
    1319              :         }
    1320          873 :         if (slur->isHammerOnPullOff()) {
    1321           61 :             StaffType* staffType = slur->staff()->staffType(slur->tick());
    1322           71 :             if ((staffType->isTabStaff() && ctx.conf().styleB(Sid::hopoAlignLettersTabStaves))
    1323           71 :                 || (!staffType->isTabStaff() && ctx.conf().styleB(Sid::hopoAlignLettersStandardStaves))) {
    1324           61 :                 AlignmentLayout::alignHopoLetters(toHammerOnPullOff(slur), system);
    1325              :             }
    1326              :         }
    1327              :     }
    1328              : 
    1329         7388 :     processLines(system, ctx, elementsToLayout.trills);
    1330              : 
    1331         7388 :     layoutSticking(elementsToLayout.stickings, system, ctx);
    1332              : 
    1333         7717 :     for (EngravingItem* item : elementsToLayout.fermatasAndTremoloBars) {
    1334          329 :         TLayout::layoutItem(item, ctx);
    1335              :     }
    1336              : 
    1337         7544 :     for (FiguredBass* item : elementsToLayout.figuredBass) {
    1338          156 :         TLayout::layoutItem(item, ctx);
    1339          156 :         if (item->autoplace()) {
    1340          156 :             Autoplace::autoplaceSegmentElement(item, item->mutldata(), true);
    1341              :         }
    1342              :     }
    1343              : 
    1344         7388 :     if (lyricsStackingOrder == LyricsStackingOrder::LYRICS_BEFORE_DYNAMICS) {
    1345         4813 :         layoutLyrics(elementsToLayout, ctx);
    1346              :     }
    1347              : 
    1348         7388 :     layoutDynamicExpressionAndHairpins(elementsToLayout, ctx);
    1349              : 
    1350         7388 :     processLines(system, ctx, elementsToLayout.allOtherSpanners);
    1351              : 
    1352        11239 :     for (MeasureNumber* mno : elementsToLayout.measureNumbers) {
    1353         3851 :         if (!mno->visible()) {
    1354            0 :             continue;
    1355              :         }
    1356         3851 :         Autoplace::autoplaceMeasureElement(mno, mno->mutldata());
    1357        11553 :         system->staff(mno->staffIdx())->skyline().add(mno->ldata()->bbox().translated(mno->measure()->pos() + mno->pos()
    1358        11553 :                                                                                       + mno->staffOffset()), mno);
    1359              :     }
    1360              : 
    1361         7388 :     for (MMRestRange* mmrr : elementsToLayout.mmrRanges) {
    1362            0 :         Autoplace::autoplaceMeasureElement(mmrr, mmrr->mutldata());
    1363            0 :         system->staff(mmrr->staffIdx())->skyline().add(mmrr->ldata()->bbox().translated(mmrr->measure()->pos() + mmrr->pos()), mmrr);
    1364              :     }
    1365              : 
    1366         7388 :     processLines(system, ctx, elementsToLayout.ottavas);
    1367         7388 :     processLines(system, ctx, elementsToLayout.pedal, /*align=*/ true);
    1368              : 
    1369         7388 :     if (lyricsStackingOrder == LyricsStackingOrder::LYRICS_AFTER_PEDALS) {
    1370         2575 :         layoutLyrics(elementsToLayout, ctx);
    1371              :     }
    1372              : 
    1373         7420 :     for (HarpPedalDiagram* hpd : elementsToLayout.harpDiagrams) {
    1374           32 :         TLayout::layoutItem(hpd, ctx);
    1375              :     }
    1376              : 
    1377         7388 :     bool hasFretDiagram = elementsToLayout.fretDiagrams.size() > 0;
    1378         7388 :     if (!hasFretDiagram) {
    1379         7283 :         layoutHarmonies(elementsToLayout.harmonies, system, ctx);
    1380              :     }
    1381              : 
    1382         8522 :     for (StaffTextBase* st : elementsToLayout.staffText) {
    1383         1134 :         TLayout::layoutItem(st, ctx);
    1384              :     }
    1385              : 
    1386         7479 :     for (InstrumentChange* ic : elementsToLayout.instrChanges) {
    1387           91 :         TLayout::layoutItem(ic, ctx);
    1388              :     }
    1389              : 
    1390         7538 :     for (EngravingItem* item : elementsToLayout.playTechCapoStringTunTripletFeel) {
    1391          150 :         TLayout::layoutItem(item, ctx);
    1392              :     }
    1393              : 
    1394         7388 :     if (hasFretDiagram) {
    1395          105 :         layoutFretDiagrams(elementsToLayout, system, ctx);
    1396              :     }
    1397              : 
    1398         7524 :     for (SystemText* systemText : elementsToLayout.systemText) {
    1399          136 :         TLayout::layoutSystemText(systemText, systemText->mutldata());
    1400              :     }
    1401              : 
    1402         7388 :     layoutVoltas(elementsToLayout, ctx);
    1403              : 
    1404         7515 :     for (RehearsalMark* rm : elementsToLayout.rehMarks) {
    1405          127 :         TLayout::layoutItem(rm, ctx);
    1406              :     }
    1407              : 
    1408         7388 :     std::vector<EngravingItem*> tempoElementsToAlign;
    1409         8524 :     for (TempoText* tt : elementsToLayout.tempoText) {
    1410         1136 :         TLayout::layoutItem(tt, ctx);
    1411         1136 :         tempoElementsToAlign.push_back(tt);
    1412              :     }
    1413              : 
    1414         7388 :     processLines(system, ctx, elementsToLayout.tempoChangeLines);
    1415        13078 :     for (SpannerSegment* spannerSeg : system->spannerSegments()) {
    1416         5690 :         if (spannerSeg->isGradualTempoChangeSegment()) {
    1417           22 :             tempoElementsToAlign.push_back(spannerSeg);
    1418              :         }
    1419              :     }
    1420              : 
    1421         7394 :     for (PlayCountText* pt : elementsToLayout.playCountText) {
    1422            6 :         TLayout::layoutPlayCountText(pt, pt->mutldata());
    1423            6 :         if (pt->autoplace()) {
    1424            6 :             Autoplace::autoplaceSegmentElement(pt, pt->mutldata());
    1425              :         }
    1426              :     }
    1427              : 
    1428         7388 :     AlignmentLayout::alignItemsWithTheirSnappingChain(tempoElementsToAlign, system);
    1429              : 
    1430         7515 :     for (RehearsalMark* rehearsMark : elementsToLayout.rehMarks) {
    1431          127 :         Autoplace::autoplaceSegmentElement(rehearsMark, rehearsMark->mutldata());
    1432              :     }
    1433              : 
    1434         7909 :     for (EngravingItem* item : elementsToLayout.markersAndJumps) {
    1435          521 :         TLayout::layoutItem(item, ctx);
    1436              :     }
    1437              : 
    1438         7388 :     for (Image* image : elementsToLayout.images) {
    1439            0 :         TLayout::layoutItem(image, ctx);
    1440              :     }
    1441              : 
    1442         7388 :     layoutParenthesisAndBigTimeSigs(elementsToLayout);
    1443         7404 : }
    1444              : 
    1445        31790 : void SystemLayout::collectElementsToLayout(Measure* measure, ElementsToLayout& elements, const LayoutContext& ctx)
    1446              : {
    1447        31790 :     elements.measures.push_back(measure);
    1448              : 
    1449        31790 :     System* system = elements.system;
    1450        78630 :     for (size_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
    1451        46840 :         if (measure->showMeasureNumberOnStaff(staffIdx)) {
    1452         3851 :             if (MeasureNumber* mno = measure->measureNumber(staffIdx)) {
    1453         3851 :                 elements.measureNumbers.push_back(mno);
    1454              :             }
    1455              :         }
    1456              : 
    1457        46840 :         if (!system->staff(staffIdx)->show()) {
    1458          981 :             continue;
    1459              :         }
    1460              : 
    1461        45859 :         MMRestRange* mmrr = measure->mmRangeText(staffIdx);
    1462        45859 :         if (mmrr && mmrr->addToSkyline()) {
    1463            0 :             elements.mmrRanges.push_back(mmrr);
    1464              :         }
    1465              : 
    1466        48655 :         for (EngravingItem* item : measure->el()) {
    1467         2796 :             if (item->effectiveStaffIdx() == staffIdx && (item->isMarker() || item->isJump())) {
    1468          521 :                 elements.markersAndJumps.push_back(item);
    1469              :             }
    1470              :         }
    1471              :     }
    1472              : 
    1473        31790 :     track_idx_t nTracks = ctx.dom().ntracks();
    1474       152136 :     for (Segment* s = measure->first(); s; s = s->next()) {
    1475       120346 :         if (s->isChordRestType() || !s->annotations().empty()) {
    1476        65106 :             elements.segments.push_back(s);
    1477              :         }
    1478              : 
    1479       659283 :         for (track_idx_t track = 0; track < nTracks; /* intentionally empty*/ ) {
    1480       538937 :             if (s->hasTimeSigAboveStaves()) {
    1481            0 :                 TimeSig* timeSig = toTimeSig(s->element(track));
    1482            0 :                 if (timeSig && timeSig->showOnThisStaff()) {
    1483            0 :                     elements.timeSigAboveStaves.push_back(timeSig);
    1484              :                 }
    1485            0 :                 track += VOICES;
    1486            0 :                 continue;
    1487            0 :             }
    1488              : 
    1489       538937 :             if (!system->staff(track2staff(track))->show()) {
    1490         3016 :                 track += VOICES;
    1491         3016 :                 continue;
    1492              :             }
    1493              : 
    1494       535921 :             if (s->isType(SegmentType::BarLineTypes)) {
    1495        52333 :                 if (BarLine* bl = toBarLine(s->element(track))) {
    1496        52321 :                     elements.barlines.push_back(bl);
    1497              :                 }
    1498        52333 :                 track += VOICES;
    1499        52333 :                 continue;
    1500        52333 :             }
    1501              : 
    1502       483588 :             if (s->isChordRestType()) {
    1503       361328 :                 if (ChordRest* cr = toChordRest(s->element(track))) {
    1504        89683 :                     elements.chordRests.push_back(cr);
    1505        89683 :                     if (cr->isChord()) {
    1506        48219 :                         elements.chords.push_back(toChord(cr));
    1507              : 
    1508        49124 :                         auto collectBends = [&elements, system] (Chord* chord) {
    1509       107378 :                             for (Note* note : chord->notes()) {
    1510        58900 :                                 for (Spanner* sp : note->spannerBack()) {
    1511          646 :                                     if (sp->isGuitarBend()) {
    1512          409 :                                         elements.guitarBends.push_back(toGuitarBend(sp));
    1513              :                                     }
    1514              :                                 }
    1515              : 
    1516        58900 :                                 for (Spanner* sp : note->spannerFor()) {
    1517          646 :                                     if (sp->isGuitarBend()) {
    1518          409 :                                         GuitarBend* bend = toGuitarBend(sp);
    1519          409 :                                         Note* endNote = bend->endNote();
    1520          409 :                                         if (bend->bendType() == GuitarBendType::SLIGHT_BEND
    1521          409 :                                             || (endNote && endNote->tick() >= system->endTick())) {
    1522           17 :                                             elements.guitarBends.push_back(bend);
    1523              :                                         }
    1524              :                                     }
    1525              :                                 }
    1526              :                             }
    1527        49124 :                         };
    1528              : 
    1529        48219 :                         Chord* chord = toChord(cr);
    1530        48894 :                         for (Chord* grace : chord->graceNotesBefore()) {
    1531          675 :                             collectBends(grace);
    1532              :                         }
    1533        48219 :                         collectBends(chord);
    1534        48449 :                         for (Chord* grace : chord->graceNotesAfter()) {
    1535          230 :                             collectBends(grace);
    1536              :                         }
    1537              :                     }
    1538              :                 }
    1539       361328 :                 ++track;
    1540       361328 :                 continue;
    1541       361328 :             }
    1542              : 
    1543       122260 :             ++track;
    1544              :         }
    1545              : 
    1546       127608 :         for (EngravingItem* item : s->annotations()) {
    1547         7262 :             if (!item->systemFlag() && !system->staff(item->staffIdx())->show()) {
    1548           13 :                 continue;
    1549              :             }
    1550         7249 :             switch (item->type()) {
    1551          131 :             case ElementType::STICKING:
    1552          131 :                 elements.stickings.push_back(toSticking(item));
    1553          131 :                 break;
    1554          329 :             case ElementType::FERMATA:
    1555              :             case ElementType::TREMOLOBAR:
    1556          329 :                 elements.fermatasAndTremoloBars.push_back(item);
    1557          329 :                 break;
    1558          156 :             case ElementType::FIGURED_BASS:
    1559          156 :                 elements.figuredBass.push_back(toFiguredBass(item));
    1560          156 :                 break;
    1561         1141 :             case ElementType::DYNAMIC:
    1562         1141 :                 elements.dynamics.push_back(toDynamic(item));
    1563         1141 :                 break;
    1564           34 :             case ElementType::EXPRESSION:
    1565           34 :                 elements.expressions.push_back(toExpression(item));
    1566           34 :                 break;
    1567           32 :             case ElementType::HARP_DIAGRAM:
    1568           32 :                 elements.harpDiagrams.push_back(toHarpPedalDiagram(item));
    1569           32 :                 break;
    1570          348 :             case ElementType::FRET_DIAGRAM:
    1571          348 :                 elements.fretDiagrams.push_back(toFretDiagram(item));
    1572          348 :                 if (Harmony* h = toFretDiagram(item)->harmony()) {
    1573          243 :                     elements.harmonies.push_back(h);
    1574              :                 }
    1575          348 :                 break;
    1576         1134 :             case ElementType::STAFF_TEXT:
    1577              :             case ElementType::STAVE_SHARING_LABEL:
    1578         1134 :                 elements.staffText.push_back(toStaffTextBase(item));
    1579         1134 :                 break;
    1580           91 :             case ElementType::INSTRUMENT_CHANGE:
    1581           91 :                 elements.instrChanges.push_back(toInstrumentChange(item));
    1582           91 :                 break;
    1583          150 :             case ElementType::PLAYTECH_ANNOTATION:
    1584              :             case ElementType::CAPO:
    1585              :             case ElementType::STRING_TUNINGS:
    1586              :             case ElementType::TRIPLET_FEEL:
    1587          150 :                 elements.playTechCapoStringTunTripletFeel.push_back(item);
    1588          150 :                 break;
    1589          136 :             case ElementType::SYSTEM_TEXT:
    1590          136 :                 elements.systemText.push_back(toSystemText(item));
    1591          136 :                 break;
    1592          127 :             case ElementType::REHEARSAL_MARK:
    1593          127 :                 elements.rehMarks.push_back(toRehearsalMark(item));
    1594          127 :                 break;
    1595         1136 :             case ElementType::TEMPO_TEXT:
    1596         1136 :                 elements.tempoText.push_back(toTempoText(item));
    1597         1136 :                 break;
    1598            0 :             case ElementType::IMAGE:
    1599            0 :                 elements.images.push_back(toImage(item));
    1600            0 :                 break;
    1601            0 :             case ElementType::PARENTHESIS:
    1602            0 :                 elements.parenthesis.push_back(toParenthesis(item));
    1603            0 :                 break;
    1604         2275 :             case ElementType::HARMONY:
    1605         2275 :                 elements.harmonies.push_back(toHarmony(item));
    1606         2275 :                 break;
    1607            6 :             case ElementType::PLAY_COUNT_TEXT:
    1608            6 :                 elements.playCountText.push_back(toPlayCountText(item));
    1609            6 :                 break;
    1610           23 :             default:
    1611           23 :                 break;
    1612              :             }
    1613              :         }
    1614              :     }
    1615        31790 : }
    1616              : 
    1617         7388 : void SystemLayout::collectSpannersToLayout(ElementsToLayout& elements, const LayoutContext& ctx)
    1618              : {
    1619         7388 :     const System* system = elements.system;
    1620              : 
    1621              :     // NOTE: in continuous view, this means we layout spanners for the entire score.
    1622              :     // TODO: find way to optimize this and only layout where necessary.
    1623         7388 :     Fraction stick = system->measures().front()->tick();
    1624         7388 :     Fraction etick = system->measures().back()->endTick();
    1625              : 
    1626         7388 :     auto spanners = ctx.dom().spannerMap().findOverlapping(stick.ticks(), etick.ticks());
    1627         7388 :     std::sort(spanners.begin(), spanners.end(), [](const auto& sp1, const auto& sp2) {
    1628         3341 :         return sp1.value->tick() < sp2.value->tick();
    1629              :     });
    1630              : 
    1631         7388 :     std::vector<Spanner*> allSpanners;
    1632         7388 :     allSpanners.reserve(spanners.size());
    1633         9777 :     for (auto item : spanners) {
    1634         2389 :         allSpanners.push_back(item.value);
    1635              :     }
    1636              : 
    1637         9777 :     for (Spanner* spanner : allSpanners) {
    1638         2389 :         if (!spanner->systemFlag() && !system->staff(spanner->staffIdx())->show()) {
    1639            4 :             continue;
    1640              :         }
    1641         2385 :         if (spanner->tick() >= etick || spanner->tick2() < stick) {
    1642           97 :             continue;
    1643              :         }
    1644              : 
    1645         2288 :         if (spanner->tick2() == stick) {
    1646              :             // Only these two spanner types are laid out if at the end of the previous system
    1647              :             // TODO: pedal makes sense, but slur doesn't... to figure out
    1648           60 :             if (spanner->isSlur() && !toSlur(spanner)->isCrossStaff() && !toSlur(spanner)->hasCrossBeams()) {
    1649            8 :                 elements.slurs.push_back(spanner);
    1650           52 :             } else if (spanner->isPedal() && toPedal(spanner)->connect45HookToNext()) {
    1651            0 :                 elements.pedal.push_back(spanner);
    1652              :             }
    1653              :         } else {
    1654         2228 :             switch (spanner->type()) {
    1655          889 :             case ElementType::SLUR:
    1656              :             case ElementType::HAMMER_ON_PULL_OFF:
    1657          889 :                 if (!toSlur(spanner)->isCrossStaff() && !toSlur(spanner)->hasCrossBeams()) {
    1658          865 :                     elements.slurs.push_back(spanner);
    1659              :                 }
    1660          889 :                 break;
    1661          119 :             case ElementType::PEDAL:
    1662          119 :                 elements.pedal.push_back(spanner);
    1663          119 :                 break;
    1664          116 :             case ElementType::TRILL:
    1665          116 :                 elements.trills.push_back(spanner);
    1666          116 :                 break;
    1667          279 :             case ElementType::VOLTA:
    1668          279 :                 elements.voltas.push_back(spanner);
    1669          279 :                 break;
    1670          472 :             case ElementType::HAIRPIN:
    1671          472 :                 elements.hairpins.push_back(spanner);
    1672          472 :                 break;
    1673           22 :             case ElementType::GRADUAL_TEMPO_CHANGE:
    1674           22 :                 elements.tempoChangeLines.push_back(spanner);
    1675           22 :                 break;
    1676            6 :             case ElementType::PARTIAL_LYRICSLINE:
    1677            6 :                 elements.partialLyricsLines.push_back(spanner);
    1678            6 :                 break;
    1679          162 :             case ElementType::OTTAVA:
    1680          162 :                 if (!spanner->staff()->staffType()->isTabStaff()) {
    1681          161 :                     elements.ottavas.push_back(spanner);
    1682              :                 }
    1683          162 :                 break;
    1684          163 :             default:
    1685          163 :                 elements.allOtherSpanners.push_back(spanner);
    1686          163 :                 break;
    1687              :             }
    1688              :         }
    1689              :     }
    1690         7388 : }
    1691              : 
    1692         7388 : void SystemLayout::createSkylines(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
    1693              : {
    1694         7388 :     System* system = elementsToLayout.system;
    1695        18583 :     for (size_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
    1696        11195 :         SysStaff* ss = system->staff(staffIdx);
    1697        11195 :         Skyline& skyline = ss->skyline();
    1698        11195 :         skyline.clear();
    1699        58035 :         for (Measure* m : elementsToLayout.measures) {
    1700        46840 :             if (m->staffLines(staffIdx)->addToSkyline()) {
    1701        46822 :                 ss->skyline().add(m->staffLines(staffIdx)->ldata()->bbox().translated(m->pos()), m->staffLines(staffIdx));
    1702              :             }
    1703       223086 :             for (Segment& s : m->segments()) {
    1704       176246 :                 if (!s.enabled()) {
    1705         1753 :                     continue;
    1706              :                 }
    1707       174493 :                 PointF p(s.pos() + m->pos());
    1708       174493 :                 if (s.isType(SegmentType::BarLineTypes)) {
    1709        53000 :                     BarLine* bl = toBarLine(s.element(staffIdx * VOICES));
    1710        53000 :                     if (bl && bl->addToSkyline()) {
    1711        52983 :                         skyline.add(bl->shape().translated(bl->pos() + p + bl->staffOffset()));
    1712              :                     }
    1713       121493 :                 } else if (s.isType(SegmentType::TimeSigTypes)) {
    1714         6312 :                     TimeSig* ts = toTimeSig(s.element(staffIdx * VOICES));
    1715         6312 :                     if (ts && ts->addToSkyline() && ts->showOnThisStaff()) {
    1716         6138 :                         TimeSigPlacement timeSigPlacement = ts->style().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>();
    1717         6138 :                         if (timeSigPlacement != TimeSigPlacement::ACROSS_STAVES) {
    1718         6126 :                             skyline.add(ts->shape().translate(ts->pos() + p + ts->staffOffset()));
    1719              :                         }
    1720              :                     }
    1721              :                 } else {
    1722       115181 :                     track_idx_t strack = staffIdx * VOICES;
    1723       115181 :                     track_idx_t etrack = strack + VOICES;
    1724      1387193 :                     for (EngravingItem* e : s.elist()) {
    1725      1272012 :                         if (!e) {
    1726       961559 :                             continue;
    1727              :                         }
    1728       310453 :                         track_idx_t effectiveTrack = e->vStaffIdx() * VOICES + e->voice();
    1729       310453 :                         if (effectiveTrack < strack || effectiveTrack >= etrack) {
    1730       196459 :                             continue;
    1731              :                         }
    1732              : 
    1733              :                         // add element to skyline
    1734       113994 :                         if (e->addToSkyline()) {
    1735       113720 :                             const PointF offset = e->staffOffset();
    1736       113720 :                             Shape shape = e->shape();
    1737              :                             // add grace notes to skyline
    1738       113720 :                             if (e->isChord()) {
    1739        48351 :                                 Chord* chord = toChord(e);
    1740        48351 :                                 GraceNotesGroup& graceBefore = chord->graceNotesBefore();
    1741        48351 :                                 GraceNotesGroup& graceAfter = chord->graceNotesAfter();
    1742        48351 :                                 if (!graceBefore.empty()) {
    1743          515 :                                     skyline.add(graceBefore.shape().translate(graceBefore.pos() + p + offset));
    1744              :                                 }
    1745        48351 :                                 if (!graceAfter.empty()) {
    1746          180 :                                     skyline.add(graceAfter.shape().translate(graceAfter.pos() + p + offset));
    1747              :                                 }
    1748              : 
    1749              :                                 // If present, add ornament cue note to skyline
    1750        48351 :                                 Ornament* ornament = chord->findOrnament();
    1751        48351 :                                 if (ornament) {
    1752          386 :                                     Chord* cue = ornament->cueNoteChord();
    1753          386 :                                     if (cue && cue->upNote()->visible()) {
    1754           16 :                                         skyline.add(cue->shape().translate(cue->pos() + p + cue->staffOffset()));
    1755              :                                     }
    1756              :                                 }
    1757              : 
    1758              :                                 // Don't include cross-staff arpeggios
    1759        48351 :                                 shape.remove_if([chord](ShapeElement& s) {
    1760       137663 :                                     return (s.item()->isArpeggio() || s.item()->isChordBracket()) && toArpeggio(
    1761       137663 :                                         s.item()) == chord->spanArpeggio();
    1762              :                                 });
    1763        48351 :                                 Arpeggio* arp = chord->spanArpeggio();
    1764        48351 :                                 if (arp) {
    1765          311 :                                     RectF staffBbox = ss->bbox();
    1766          311 :                                     RectF arpBbox = arp->ldata()->bbox().translated(e->pos() + p + offset);
    1767          311 :                                     if (chord->track() == arp->track()) {
    1768          233 :                                         staffBbox.setTop(arpBbox.top());
    1769           78 :                                     } else if (chord->track() == arp->endTrack()) {
    1770           74 :                                         staffBbox.setBottom(arpBbox.bottom());
    1771              :                                     }
    1772          311 :                                     shape.add(arpBbox & staffBbox, arp);
    1773              :                                 }
    1774              :                             }
    1775       113720 :                             skyline.add(shape.translate(e->pos() + p + offset));
    1776       113720 :                         }
    1777              : 
    1778              :                         // add tremolo to skyline
    1779       113994 :                         if (e->isChord()) {
    1780        48355 :                             Chord* ch = item_cast<Chord*>(e);
    1781              :                             // tremoloSingleChord is added directly to chord shape
    1782        48355 :                             if (ch->tremoloTwoChord()) {
    1783          220 :                                 TremoloTwoChord* t = ch->tremoloTwoChord();
    1784          220 :                                 Chord* c1 = t->chord1();
    1785          220 :                                 Chord* c2 = t->chord2();
    1786          220 :                                 if (c1 && !c1->staffMove() && c2 && !c2->staffMove()) {
    1787          220 :                                     if (t->chord() == e && t->addToSkyline()) {
    1788          110 :                                         skyline.add(t->shape().translate(t->pos() + e->pos() + p));
    1789              :                                     }
    1790              :                                 }
    1791              :                             }
    1792              :                         }
    1793              : 
    1794              :                         // add beams to skline
    1795       113994 :                         if (e->isChordRest()) {
    1796        90801 :                             ChordRest* cr = toChordRest(e);
    1797        90801 :                             if (BeamLayout::isStartOfNonCrossBeam(cr)) {
    1798         4211 :                                 Beam* b = cr->beam();
    1799         4211 :                                 b->addSkyline(skyline);
    1800              :                             }
    1801              :                         }
    1802              :                     }
    1803              :                 }
    1804              :             }
    1805              :         }
    1806              :     }
    1807         7388 : }
    1808              : 
    1809            0 : void SystemLayout::doLayoutTies(System* system, const std::vector<Segment*>& sl, const Fraction& stick, const Fraction& etick,
    1810              :                                 LayoutContext& ctx)
    1811              : {
    1812              :     UNUSED(etick);
    1813              : 
    1814            0 :     for (Segment* s : sl) {
    1815            0 :         for (EngravingItem* e : s->elist()) {
    1816            0 :             if (!e || !e->isChord()) {
    1817            0 :                 continue;
    1818              :             }
    1819            0 :             Chord* c = toChord(e);
    1820            0 :             for (Chord* ch : c->graceNotes()) {
    1821            0 :                 layoutTies(ch, system, stick, ctx);
    1822              :             }
    1823            0 :             layoutTies(c, system, stick, ctx);
    1824              :         }
    1825              :     }
    1826            0 : }
    1827              : 
    1828         7388 : void SystemLayout::layoutTuplets(const std::vector<ChordRest*>& chordRests, LayoutContext& ctx)
    1829              : {
    1830         7388 :     std::set<Tuplet*> laidoutTuplets;
    1831        97071 :     for (auto revIter = chordRests.rbegin(); revIter != chordRests.rend(); ++revIter) {
    1832        89683 :         ChordRest* cr = *revIter;
    1833        89683 :         Tuplet* tuplet = cr->topTuplet();
    1834              : 
    1835        89683 :         if (!tuplet || muse::contains(laidoutTuplets, tuplet)) {
    1836        88149 :             continue;
    1837              :         }
    1838              : 
    1839         1534 :         TupletLayout::layoutTupletAndNestedTuplets(tuplet, ctx); // this lays out also the inner tuplets
    1840         1534 :         laidoutTuplets.insert(tuplet);
    1841              :     }
    1842         7388 : }
    1843              : 
    1844         7388 : void SystemLayout::layoutTiesAndBends(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
    1845              : {
    1846         7388 :     System* system = elementsToLayout.system;
    1847         7388 :     if (elementsToLayout.measures.empty()) {
    1848            0 :         return;
    1849              :     }
    1850         7388 :     Fraction stick = elementsToLayout.measures.front()->tick();
    1851              : 
    1852        55607 :     for (Chord* chord : elementsToLayout.chords) {
    1853        48894 :         for (Chord* grace : chord->graceNotesBefore()) {
    1854          675 :             layoutTies(grace, system, stick, ctx);
    1855              :         }
    1856        48219 :         layoutTies(chord, system, stick, ctx);
    1857        48449 :         for (Chord* grace : chord->graceNotesAfter()) {
    1858          230 :             layoutTies(grace, system, stick, ctx);
    1859              :         }
    1860              :     }
    1861              : 
    1862         7388 :     GuitarDiveLayout::updateDiveSequences(elementsToLayout.guitarBends, ctx);
    1863              : 
    1864         7814 :     for (GuitarBend* bend : elementsToLayout.guitarBends) {
    1865              :         /* Bends which cross a system break are collected by both systems they touch, so we
    1866              :          * lay out only the segment which belongs to this one: */
    1867          426 :         TLayout::layoutGuitarBend(bend, ctx, system);
    1868              :     }
    1869              : }
    1870              : 
    1871            0 : void SystemLayout::layoutNoteAnchoredSpanners(System* system, Chord* chord)
    1872              : {
    1873              :     // Add all spanners attached to notes, otherwise these will be removed if outside of the layout range
    1874            0 :     for (Note* note : chord->notes()) {
    1875            0 :         for (Spanner* spanner : note->spannerFor()) {
    1876            0 :             for (SpannerSegment* spannerSeg : spanner->spannerSegments()) {
    1877            0 :                 spannerSeg->moveToSystem(system);
    1878              :             }
    1879              :         }
    1880              :     }
    1881            0 : }
    1882              : 
    1883            0 : void SystemLayout::doLayoutNoteSpannersLinear(System* system, LayoutContext& ctx)
    1884              : {
    1885            0 :     constexpr Fraction start = Fraction(0, 1);
    1886            0 :     for (Measure* measure = system->firstMeasure(); measure; measure = measure->nextMeasure()) {
    1887            0 :         for (Segment* segment = measure->first(); segment; segment = segment->next()) {
    1888            0 :             if (!segment->isChordRestType()) {
    1889            0 :                 continue;
    1890              :             }
    1891            0 :             for (EngravingItem* e : segment->elist()) {
    1892            0 :                 if (!e || !e->isChord()) {
    1893            0 :                     continue;
    1894              :                 }
    1895            0 :                 Chord* c = toChord(e);
    1896            0 :                 for (Chord* ch : c->graceNotes()) {
    1897            0 :                     layoutTies(ch, system, start, ctx);
    1898            0 :                     layoutNoteAnchoredSpanners(system, ch);
    1899              :                 }
    1900            0 :                 layoutTies(c, system, start, ctx);
    1901            0 :                 layoutNoteAnchoredSpanners(system, c);
    1902              :             }
    1903              :         }
    1904              :     }
    1905            0 : }
    1906              : 
    1907        59104 : void SystemLayout::processLines(System* system, LayoutContext& ctx, const std::vector<Spanner*>& lines, bool align)
    1908              : {
    1909        59104 :     std::vector<SpannerSegment*> segments;
    1910        61309 :     for (Spanner* sp : lines) {
    1911         2205 :         SpannerSegment* ss = TLayout::layoutSystem(sp, system, ctx);        // create/layout spanner segment for this system
    1912         2205 :         if (ss->autoplace()) {
    1913         2199 :             segments.push_back(ss);
    1914              :         }
    1915              :     }
    1916              : 
    1917        59104 :     if (align && segments.size() > 1) {
    1918           19 :         const size_t nstaves = system->staves().size();
    1919           19 :         const double defaultY = segments[0]->ldata()->pos().y();
    1920           19 :         std::vector<double> yAbove(nstaves, -DBL_MAX);
    1921           19 :         std::vector<double> yBelow(nstaves, -DBL_MAX);
    1922              : 
    1923           87 :         for (SpannerSegment* ss : segments) {
    1924           68 :             if (ss->visible()) {
    1925           66 :                 double& staffY = ss->spanner() && ss->spanner()->placeAbove() ? yAbove[ss->staffIdx()] : yBelow[ss->staffIdx()];
    1926           66 :                 staffY = std::max(staffY, ss->ldata()->pos().y());
    1927              :             }
    1928              :         }
    1929           87 :         for (SpannerSegment* ss : segments) {
    1930           68 :             const double& staffY = ss->spanner() && ss->spanner()->placeAbove() ? yAbove[ss->staffIdx()] : yBelow[ss->staffIdx()];
    1931           68 :             if (staffY > -DBL_MAX) {
    1932           68 :                 ss->mutldata()->setPosY(staffY);
    1933              :             } else {
    1934            0 :                 ss->mutldata()->setPosY(defaultY);
    1935              :             }
    1936              :         }
    1937           19 :     }
    1938              : 
    1939        59104 :     if (segments.size() > 0 && segments.front()->isSlurSegment()) {
    1940          385 :         SlurTieLayout::adjustOverlappingSlurs(system->spannerSegments());
    1941              :     }
    1942              : 
    1943              :     //
    1944              :     // Fix harmonic marks and vibrato overlaps
    1945              :     //
    1946        59104 :     SpannerSegment* prevSegment = nullptr;
    1947        59104 :     bool fixed = false;
    1948              : 
    1949        61303 :     for (SpannerSegment* ss : segments) {
    1950         2199 :         if (fixed) {
    1951            6 :             fixed = false;
    1952            6 :             prevSegment = ss;
    1953            6 :             continue;
    1954              :         }
    1955         2193 :         if (prevSegment) {
    1956         1135 :             if (prevSegment->visible()
    1957         1133 :                 && ss->visible()
    1958         1125 :                 && prevSegment->isHarmonicMarkSegment()
    1959           16 :                 && ss->isVibratoSegment()
    1960         2268 :                 && muse::RealIsEqual(prevSegment->x(), ss->x())) {
    1961            2 :                 double diff = ss->ldata()->bbox().bottom() - prevSegment->ldata()->bbox().bottom()
    1962            2 :                               + prevSegment->ldata()->bbox().top();
    1963            2 :                 prevSegment->mutldata()->moveY(diff);
    1964            2 :                 fixed = true;
    1965              :             }
    1966         1135 :             if (prevSegment->visible()
    1967         1133 :                 && ss->visible()
    1968         1125 :                 && prevSegment->isVibratoSegment()
    1969           21 :                 && ss->isHarmonicMarkSegment()
    1970         2268 :                 && muse::RealIsEqual(prevSegment->x(), ss->x())) {
    1971            4 :                 double diff = prevSegment->ldata()->bbox().bottom() - ss->ldata()->bbox().bottom()
    1972            4 :                               + ss->ldata()->bbox().top();
    1973            4 :                 ss->mutldata()->moveY(diff);
    1974            4 :                 fixed = true;
    1975              :             }
    1976              :         }
    1977              : 
    1978         2193 :         prevSegment = ss;
    1979              :     }
    1980              : 
    1981              :     //
    1982              :     // add shapes to skyline
    1983              :     //
    1984        61303 :     for (SpannerSegment* ss : segments) {
    1985         2199 :         if (ss->addToSkyline()) {
    1986         2157 :             staff_idx_t stfIdx = ss->effectiveStaffIdx();
    1987         2157 :             if (stfIdx == muse::nidx) {
    1988            0 :                 continue;
    1989              :             }
    1990         2157 :             system->staff(stfIdx)->skyline().add(ss->shape().translate(ss->pos()));
    1991         2157 :             if (ss->isHammerOnPullOffSegment()) {
    1992           61 :                 TLayout::layoutHammerOnPullOffSegment(toHammerOnPullOffSegment(ss), ctx);
    1993              :             }
    1994              :         }
    1995              :     }
    1996        59104 : }
    1997              : 
    1998        49124 : void SystemLayout::layoutTies(Chord* ch, System* system, const Fraction& stick, LayoutContext& ctx)
    1999              : {
    2000        49124 :     SysStaff* staff = system->staff(ch->staffIdx());
    2001        49124 :     if (!staff->show()) {
    2002            0 :         return;
    2003              :     }
    2004        49124 :     std::vector<TieSegment*> stackedForwardTies;
    2005        49124 :     std::vector<TieSegment*> stackedBackwardTies;
    2006       107378 :     for (Note* note : ch->notes()) {
    2007        58254 :         Tie* t = note->tieFor();
    2008        58254 :         if (t && !t->isLaissezVib()) {
    2009         2206 :             TieSegment* ts = SlurTieLayout::layoutTieFor(t, system);
    2010         2206 :             if (ts && ts->addToSkyline()) {
    2011         2196 :                 staff->skyline().add(ts->shape().translate(ts->pos()));
    2012         2196 :                 stackedForwardTies.push_back(ts);
    2013              :             }
    2014              :         }
    2015        58254 :         t = note->tieBack();
    2016        58254 :         if (t) {
    2017         2280 :             if (note->incomingPartialTie() || t->startNote()->tick() < stick) {
    2018          167 :                 TieSegment* ts = SlurTieLayout::layoutTieBack(t, system, ctx);
    2019          167 :                 if (ts && ts->addToSkyline()) {
    2020          167 :                     staff->skyline().add(ts->shape().translate(ts->pos()));
    2021          167 :                     stackedBackwardTies.push_back(ts);
    2022              :                 }
    2023              :             }
    2024              :         }
    2025              :     }
    2026              : 
    2027        49124 :     SlurTieLayout::layoutLaissezVibChord(ch, ctx);
    2028              : 
    2029        49124 :     if (!ch->staffType()->isTabStaff()) {
    2030        48304 :         SlurTieLayout::resolveVerticalTieCollisions(stackedForwardTies);
    2031        48304 :         SlurTieLayout::resolveVerticalTieCollisions(stackedBackwardTies);
    2032              :     }
    2033        49124 : }
    2034              : 
    2035        32019 : bool SystemLayout::measureHasCrossStuffOrModifiedBeams(const Measure* measure)
    2036              : {
    2037       132840 :     for (const Segment& seg : measure->segments()) {
    2038       100831 :         if (!seg.isChordRestType()) {
    2039        34013 :             continue;
    2040              :         }
    2041       444598 :         for (const EngravingItem* e : seg.elist()) {
    2042       377790 :             if (!e || !e->isChordRest()) {
    2043       284222 :                 continue;
    2044              :             }
    2045        93568 :             const Beam* beam = toChordRest(e)->beam();
    2046        93568 :             if (beam && (beam->cross() || beam->userModified())) {
    2047           10 :                 return true;
    2048              :             }
    2049        93558 :             const Chord* c = e->isChord() ? toChord(e) : nullptr;
    2050        93558 :             if (c && c->tremoloTwoChord()) {
    2051          228 :                 const TremoloTwoChord* trem = c->tremoloTwoChord();
    2052          228 :                 const Chord* c1 = trem->chord1();
    2053          228 :                 const Chord* c2 = trem->chord2();
    2054          228 :                 if (trem->userModified() || c1->staffMove() != c2->staffMove()) {
    2055            0 :                     return true;
    2056              :                 }
    2057              :             }
    2058        93558 :             if (e->isChord() && !toChord(e)->graceNotes().empty()) {
    2059         1565 :                 for (const Chord* grace : toChord(e)->graceNotes()) {
    2060          910 :                     if (grace->beam() && (grace->beam()->cross() || grace->beam()->userModified())) {
    2061            0 :                         return true;
    2062              :                     }
    2063              :                 }
    2064              :             }
    2065              :         }
    2066              :     }
    2067              : 
    2068        32009 :     return false;
    2069              : }
    2070              : 
    2071           10 : void SystemLayout::updateCrossBeams(System* system, LayoutContext& ctx)
    2072              : {
    2073              :     LAYOUT_CALL() << LAYOUT_ITEM_INFO(system);
    2074              : 
    2075           10 :     SystemLayout::layout2(system, ctx); // Computes staff distances, essential for the rest of the calculations
    2076              :     // Update grace cross beams
    2077           32 :     for (MeasureBase* mb : system->measures()) {
    2078           22 :         if (!mb->isMeasure()) {
    2079            0 :             continue;
    2080              :         }
    2081          340 :         for (Segment& seg : toMeasure(mb)->segments()) {
    2082          318 :             if (!seg.isChordRestType()) {
    2083           40 :                 continue;
    2084              :             }
    2085         2502 :             for (EngravingItem* e : seg.elist()) {
    2086         2224 :                 if (!e || !e->isChord()) {
    2087         1895 :                     continue;
    2088              :                 }
    2089          329 :                 for (Chord* grace : toChord(e)->graceNotes()) {
    2090            0 :                     if (grace->beam() && (grace->beam()->cross() || grace->beam()->userModified())
    2091            0 :                         && grace->beam()->elements().front() == grace) {
    2092            0 :                         BeamLayout::layout(grace->beam(), ctx);
    2093              :                     }
    2094              :                 }
    2095              :             }
    2096              :         }
    2097              :     }
    2098              :     // Update normal chords cross beams and respective segments
    2099           32 :     for (MeasureBase* mb : system->measures()) {
    2100           22 :         if (!mb->isMeasure()) {
    2101            0 :             continue;
    2102              :         }
    2103           22 :         bool somethingChanged = false;
    2104          340 :         for (Segment& seg : toMeasure(mb)->segments()) {
    2105          318 :             if (!seg.isChordRestType()) {
    2106           40 :                 continue;
    2107              :             }
    2108         2502 :             for (EngravingItem* e : seg.elist()) {
    2109         2224 :                 if (!e || !e->isChord()) {
    2110         1895 :                     continue;
    2111              :                 }
    2112          329 :                 Chord* chord = toChord(e);
    2113          599 :                 if (chord->beam() && (chord->beam()->cross() || chord->beam()->userModified())
    2114          599 :                     && chord->beam()->elements().front() == chord) {
    2115           27 :                     bool prevUp = chord->up();
    2116           27 :                     Stem* stem = chord->stem();
    2117           27 :                     double prevStemLength = stem ? stem->length() : 0.0;
    2118           27 :                     BeamLayout::layout(chord->beam(), ctx);
    2119           27 :                     if (chord->up() != prevUp || (stem && stem->length() != prevStemLength)) {
    2120              :                         // If the chord has changed direction needs to be re-laid out
    2121           18 :                         ChordLayout::layoutChords1(ctx, &seg, chord->vStaffIdx());
    2122           18 :                         somethingChanged = true;
    2123              :                     }
    2124          302 :                 } else if (chord->tremoloTwoChord()) {
    2125            0 :                     TremoloTwoChord* t = chord->tremoloTwoChord();
    2126            0 :                     Chord* c1 = t->chord1();
    2127            0 :                     Chord* c2 = t->chord2();
    2128            0 :                     if (t->userModified() || (c1->staffMove() != 0 || c2->staffMove() != 0)) {
    2129            0 :                         bool prevUp = chord->up();
    2130            0 :                         ChordLayout::computeUp(chord, ctx);
    2131            0 :                         if (chord->up() != prevUp) {
    2132            0 :                             ChordLayout::layoutChords1(ctx, &seg, chord->vStaffIdx());
    2133            0 :                             somethingChanged = true;
    2134              :                         }
    2135              :                     }
    2136              :                 }
    2137              :             }
    2138          278 :             if (somethingChanged) {
    2139          101 :                 seg.createShapes();
    2140              :             }
    2141              :         }
    2142              :     }
    2143           10 : }
    2144              : 
    2145          214 : void SystemLayout::restoreOldSystemLayout(System* system, LayoutContext& ctx)
    2146              : {
    2147          214 :     layoutSystemElements(system, ctx);
    2148          214 : }
    2149              : 
    2150        14359 : void SystemLayout::layoutSystem(System* system, LayoutContext& ctx, double leadingHBoxesWidth)
    2151              : {
    2152        14359 :     SystemHeaderLayout::updateSystemHeaderWidth(system, ctx);
    2153              : 
    2154        14359 :     size_t nstaves = system->staves().size();
    2155              : 
    2156        36089 :     for (size_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
    2157        21730 :         SysStaff* s = system->staves().at(staffIdx);
    2158        21730 :         const Staff* staff = ctx.dom().staff(staffIdx);
    2159        21730 :         if (!staff->show() || !s->show()) {
    2160          332 :             s->setbbox(RectF());
    2161          332 :             continue;
    2162              :         }
    2163              : 
    2164        21398 :         double staffMag = staff->staffMag(Fraction(0, 1));         // ??? TODO
    2165        21398 :         int staffLines = staff->lines(Fraction(0, 1));
    2166        21398 :         if (staffLines <= 1) {
    2167           40 :             double h = staff->lineDistance(Fraction(0, 1)) * staffMag * system->spatium();
    2168           40 :             s->setbbox(system->leftMargin() + leadingHBoxesWidth, -h, 0.0, 2 * h);
    2169              :         } else {
    2170        21358 :             double h = (staffLines - 1) * staff->lineDistance(Fraction(0, 1));
    2171        21358 :             h = h * staffMag * system->spatium();
    2172        21358 :             s->setbbox(system->leftMargin() + leadingHBoxesWidth, 0.0, 0.0, h);
    2173              :         }
    2174              :     }
    2175              : 
    2176              :     //---------------------------------------------------
    2177              :     //  layout brackets
    2178              :     //---------------------------------------------------
    2179              : 
    2180        14359 :     SystemHeaderLayout::setBracketsXPosition(system, system->leftMargin() + leadingHBoxesWidth);
    2181              : 
    2182        14359 :     SystemHeaderLayout::setInstrumentNamesHorizontalPos(system);
    2183        14359 :     SystemHeaderLayout::setGroupBracketsHorizontalPos(system);
    2184              : 
    2185        52012 :     for (MeasureBase* mb : system->measures()) {
    2186        37653 :         if (!mb->isMeasure()) {
    2187          203 :             continue;
    2188              :         }
    2189        37450 :         Measure* m = toMeasure(mb);
    2190        37450 :         if (m == system->measures().front() || (m->prev() && m->prev()->isHBox())) {
    2191        14396 :             MeasureLayout::createSystemBeginBarLine(m, ctx);
    2192              :         }
    2193              :     }
    2194        14359 : }
    2195              : 
    2196              : //---------------------------------------------------------
    2197              : //   layout2
    2198              : //    called after measure layout
    2199              : //    adjusts staff distance
    2200              : //---------------------------------------------------------
    2201              : 
    2202         9480 : void SystemLayout::layout2(System* system, LayoutContext& ctx)
    2203              : {
    2204         9495 :     TRACEFUNC;
    2205              :     LAYOUT_CALL() << LAYOUT_ITEM_INFO(system);
    2206              : 
    2207         9480 :     Box* vb = system->vbox();
    2208         9480 :     if (vb) {
    2209         2288 :         BoxLayout::layoutBox(vb, vb->mutldata(), ctx);
    2210         2288 :         system->setbbox(vb->ldata()->bbox());
    2211         2288 :         return;
    2212              :     }
    2213              : 
    2214         7192 :     system->setPos(0.0, 0.0);
    2215         7192 :     std::vector<std::pair<size_t, SysStaff*> > visibleStaves;
    2216              : 
    2217        18081 :     for (size_t i = 0; i < system->staves().size(); ++i) {
    2218        10889 :         const Staff* s  = ctx.dom().staff(i);
    2219        10889 :         SysStaff* ss = system->staves().at(i);
    2220        10889 :         if (s->show() && ss->show()) {
    2221        10686 :             visibleStaves.push_back(std::pair<size_t, SysStaff*>(i, ss));
    2222              :         } else {
    2223          203 :             ss->setbbox(RectF());        // already done in layout() ?
    2224              :         }
    2225              :     }
    2226              : 
    2227         7192 :     double _spatium            = system->spatium();
    2228         7192 :     double y                   = 0.0;
    2229         7192 :     double minVerticalDistance = ctx.conf().styleAbsolute(Sid::minVerticalDistance);
    2230         7192 :     double staffDistance       = ctx.conf().styleAbsolute(Sid::staffDistance);
    2231         7192 :     double akkoladeDistance    = ctx.conf().styleAbsolute(Sid::akkoladeDistance);
    2232         7192 :     if (ctx.conf().isVerticalSpreadEnabled()) {
    2233         5089 :         staffDistance       = ctx.conf().styleAbsolute(Sid::minStaffSpread);
    2234         5089 :         akkoladeDistance    = ctx.conf().styleAbsolute(Sid::minStaffSpread);
    2235              :     }
    2236              : 
    2237         7192 :     if (visibleStaves.empty()) {
    2238            0 :         return;
    2239              :     }
    2240              : 
    2241         7192 :     for (auto i = visibleStaves.begin();; ++i) {
    2242        10686 :         SysStaff* ss  = i->second;
    2243        10686 :         staff_idx_t si1 = i->first;
    2244        10686 :         const Staff* staff  = ctx.dom().staff(si1);
    2245        10686 :         auto ni = std::next(i);
    2246              : 
    2247        10686 :         double dist = staff->staffHeight();
    2248              :         double yOffset;
    2249              :         double h;
    2250        10686 :         if (staff->lines(Fraction(0, 1)) == 1) {
    2251           20 :             yOffset = _spatium * BARLINE_SPAN_1LINESTAFF_TO * 0.5;
    2252           20 :             h = _spatium * (BARLINE_SPAN_1LINESTAFF_TO - BARLINE_SPAN_1LINESTAFF_FROM) * 0.5;
    2253              :         } else {
    2254        10666 :             yOffset = 0.0;
    2255        10666 :             h = staff->staffHeight();
    2256              :         }
    2257        10686 :         if (ni == visibleStaves.end()) {
    2258         7192 :             ss->setYOff(yOffset);
    2259         7192 :             ss->setbbox(system->leftMargin(), y - yOffset, system->width() - system->leftMargin(), h);
    2260         7192 :             ss->saveLayout();
    2261         7192 :             break;
    2262              :         }
    2263              : 
    2264         3494 :         staff_idx_t si2 = ni->first;
    2265         3494 :         const Staff* staff2  = ctx.dom().staff(si2);
    2266              : 
    2267         3494 :         if (staff->part() == staff2->part()) {
    2268         1208 :             Measure* m = system->firstMeasure();
    2269         1208 :             double mag = m ? staff->staffMag(m->tick()) : 1.0;
    2270         1208 :             dist += akkoladeDistance * mag;
    2271              :         } else {
    2272         2286 :             dist += staffDistance;
    2273              :         }
    2274         3494 :         dist += staff2->absoluteFromSpatium(staff2->userDist());
    2275         3494 :         bool fixedSpace = false;
    2276        16499 :         for (const MeasureBase* mb : system->measures()) {
    2277        13005 :             if (!mb->isMeasure()) {
    2278           47 :                 continue;
    2279              :             }
    2280        12958 :             const Measure* m = toMeasure(mb);
    2281        12958 :             Spacer* sp = m->vspacerDown(si1);
    2282        12958 :             if (sp) {
    2283            0 :                 if (sp->spacerType() == SpacerType::FIXED) {
    2284            0 :                     dist = staff->staffHeight(m->tick()) + sp->absoluteGap();
    2285            0 :                     fixedSpace = true;
    2286            0 :                     break;
    2287              :                 } else {
    2288            0 :                     dist = std::max(dist, staff->staffHeight(m->tick()) + sp->absoluteGap());
    2289              :                 }
    2290              :             }
    2291        12958 :             sp = m->vspacerUp(si2);
    2292        12958 :             if (sp) {
    2293            0 :                 dist = std::max(dist, staff->staffHeight(m->tick()) + sp->absoluteGap());
    2294              :             }
    2295              :         }
    2296         3494 :         if (!fixedSpace) {
    2297              :             // check minimum distance to next staff
    2298              :             // note that in continuous view, we normally only have a partial skyline for the system
    2299              :             // a full one is only built when triggering a full layout
    2300              :             // therefore, we don't know the value we get from minDistance will actually be enough
    2301              :             // so we remember the value between layouts and increase it when necessary
    2302              :             // (the first layout on switching to continuous view gives us good initial values)
    2303              :             // the result is space is good to start and grows as needed
    2304              :             // it does not, however, shrink when possible - only by trigger a full layout
    2305              :             // (such as by toggling to page view and back)
    2306         3494 :             const double minHorizontalClearance = ctx.conf().styleAbsolute(Sid::skylineMinHorizontalClearance);
    2307         3494 :             double d = ss->skyline().minDistance(system->System::staff(si2)->skyline(), minHorizontalClearance);
    2308         3494 :             if (ctx.conf().isLineMode()) {
    2309            0 :                 double previousDist = ss->continuousDist();
    2310            0 :                 if (d > previousDist) {
    2311            0 :                     ss->setContinuousDist(d);
    2312              :                 } else {
    2313            0 :                     d = previousDist;
    2314              :                 }
    2315              :             }
    2316         3494 :             dist = std::max(dist, d + minVerticalDistance);
    2317         3494 :             dist = std::max(dist, minVertSpaceForCrossStaffBeams(system, si1, si2, ctx));
    2318              :         }
    2319         3494 :         ss->setYOff(yOffset);
    2320         3494 :         ss->setbbox(system->leftMargin(), y - yOffset, system->width() - system->leftMargin(), h);
    2321         3494 :         ss->saveLayout();
    2322         3494 :         y += dist;
    2323         3494 :     }
    2324              : 
    2325         7192 :     system->setSystemHeight(system->staff(visibleStaves.back().first)->bbox().bottom());
    2326         7192 :     system->setHeight(system->systemHeight());
    2327              : 
    2328         7192 :     SystemLayout::setMeasureHeight(system, system->systemHeight(), ctx);
    2329              : 
    2330              :     //---------------------------------------------------
    2331              :     //  layout brackets vertical position
    2332              :     //---------------------------------------------------
    2333              : 
    2334         7192 :     SystemHeaderLayout::layoutBracketsVertical(system, ctx);
    2335              : 
    2336              :     //---------------------------------------------------
    2337              :     //  layout instrument names
    2338              :     //---------------------------------------------------
    2339              : 
    2340         7192 :     SystemHeaderLayout::setInstrumentNamesVerticalPos(system, ctx);
    2341         9480 : }
    2342              : 
    2343         3494 : double SystemLayout::minVertSpaceForCrossStaffBeams(System* system, staff_idx_t staffIdx1, staff_idx_t staffIdx2, LayoutContext& ctx)
    2344              : {
    2345         3494 :     double minSpace = -DBL_MAX;
    2346         3494 :     track_idx_t startTrack = staffIdx1 * VOICES;
    2347         3494 :     track_idx_t endTrack = staffIdx2 * VOICES + VOICES;
    2348        16499 :     for (MeasureBase* mb : system->measures()) {
    2349        13005 :         if (!mb->isMeasure()) {
    2350           47 :             continue;
    2351              :         }
    2352        63545 :         for (Segment& segment : toMeasure(mb)->segments()) {
    2353        50587 :             if (!segment.isChordRestType()) {
    2354        26178 :                 continue;
    2355              :             }
    2356       219813 :             for (track_idx_t track = startTrack; track < endTrack; ++track) {
    2357       195404 :                 EngravingItem* item = segment.element(track);
    2358       195404 :                 if (!item || !item->isChord()) {
    2359       195388 :                     continue;
    2360              :                 }
    2361        21607 :                 Beam* beam = toChord(item)->beam();
    2362        21607 :                 if (!beam || !beam->autoplace() || beam->elements().front() != item) {
    2363        20230 :                     continue;
    2364              :                 }
    2365         1377 :                 if (beam->ldata()->crossStaffBeamPos != BeamBase::CrossStaffBeamPosition::BETWEEN) {
    2366         1361 :                     continue;
    2367              :                 }
    2368           16 :                 const Chord* limitingChordAbove = nullptr;
    2369           16 :                 const Chord* limitingChordBelow = nullptr;
    2370           16 :                 double limitFromAbove = -DBL_MAX;
    2371           16 :                 double limitFromBelow = DBL_MAX;
    2372           70 :                 for (const ChordRest* cr : beam->elements()) {
    2373           54 :                     if (!cr->isChord()) {
    2374            0 :                         continue;
    2375              :                     }
    2376           54 :                     const Chord* chord = toChord(cr);
    2377           54 :                     double minStemLength = BeamTremoloLayout::minStemLength(chord, beam->ldata()) * (chord->spatium() / 4);
    2378           54 :                     bool isUnderCrossBeam = cr->isBelowCrossBeam(beam);
    2379           54 :                     if (isUnderCrossBeam && chord->vStaffIdx() == staffIdx2) {
    2380           26 :                         const Note* topNote = chord->upNote();
    2381           26 :                         double noteLimit = topNote->y() - minStemLength;
    2382           26 :                         if (noteLimit < limitFromBelow) {
    2383           18 :                             limitFromBelow = noteLimit;
    2384           18 :                             limitingChordBelow = chord;
    2385              :                         }
    2386           26 :                         limitFromBelow = std::min(limitFromBelow, noteLimit);
    2387           28 :                     } else if (!isUnderCrossBeam && chord->vStaffIdx() == staffIdx1) {
    2388           28 :                         const Note* bottomNote = chord->downNote();
    2389           28 :                         double noteLimit = bottomNote->y() + minStemLength;
    2390           28 :                         if (noteLimit > limitFromAbove) {
    2391           22 :                             limitFromAbove = noteLimit;
    2392           22 :                             limitingChordAbove = chord;
    2393              :                         }
    2394              :                     }
    2395              :                 }
    2396           16 :                 if (limitingChordAbove && limitingChordBelow) {
    2397           16 :                     double minSpaceRequired = limitFromAbove - limitFromBelow;
    2398           16 :                     beam->mutldata()->limitingChordAbove = limitingChordAbove;
    2399           16 :                     beam->mutldata()->limitingChordBelow = limitingChordBelow;
    2400              : 
    2401           16 :                     const BeamSegment* topBeamSegmentForChordAbove = beam->topLevelSegmentForElement(limitingChordAbove);
    2402           16 :                     const BeamSegment* topBeamSegmentForChordBelow = beam->topLevelSegmentForElement(limitingChordBelow);
    2403           16 :                     if (topBeamSegmentForChordAbove->above == topBeamSegmentForChordBelow->above) {
    2404              :                         // In this case the two opposing stems overlap the beam height, so we must subtract it
    2405           16 :                         int strokeCount = std::min(BeamTremoloLayout::strokeCount(beam->ldata(), limitingChordAbove),
    2406           16 :                                                    BeamTremoloLayout::strokeCount(beam->ldata(), limitingChordBelow));
    2407           16 :                         double beamHeight = ctx.conf().styleAbsolute(Sid::beamWidth) + beam->beamDist() * (strokeCount - 1);
    2408           16 :                         minSpaceRequired -= beamHeight;
    2409              :                     }
    2410           16 :                     minSpace = std::max(minSpace, minSpaceRequired);
    2411              :                 }
    2412              :             }
    2413              :         }
    2414              :     }
    2415              : 
    2416         3494 :     return minSpace;
    2417              : }
    2418              : 
    2419        10195 : void SystemLayout::restoreLayout2(System* system, LayoutContext& ctx)
    2420              : {
    2421        10210 :     TRACEFUNC;
    2422        10195 :     if (system->vbox()) {
    2423         2371 :         return;
    2424              :     }
    2425              : 
    2426        19526 :     for (SysStaff* s : system->staves()) {
    2427        11702 :         s->restoreLayout();
    2428              :     }
    2429              : 
    2430         7824 :     system->setHeight(system->systemHeight());
    2431         7824 :     SystemLayout::setMeasureHeight(system, system->systemHeight(), ctx);
    2432              : 
    2433              :     // Reused systems can move across pages during partial relayout.
    2434              :     // Refresh geometry derived from SysStaff vertical positions.
    2435         7824 :     SystemHeaderLayout::layoutBracketsVertical(system, ctx);
    2436         7824 :     SystemHeaderLayout::setInstrumentNamesVerticalPos(system, ctx);
    2437        10195 : }
    2438              : 
    2439        20369 : void SystemLayout::setMeasureHeight(System* system, double height, const LayoutContext& ctx)
    2440              : {
    2441        20369 :     double spatium = system->spatium();
    2442       108134 :     for (MeasureBase* m : system->measures()) {
    2443        87765 :         MeasureBase::LayoutData* mldata = m->mutldata();
    2444        87765 :         if (m->isMeasure()) {
    2445              :             // note that the factor 2 * _spatium must be corrected for when exporting
    2446              :             // system distance in MusicXML (issue #24733)
    2447        87388 :             mldata->setBbox(0.0, -spatium, m->width(), height + 2.0 * spatium);
    2448          377 :         } else if (m->isHBox()) {
    2449          377 :             mldata->setBbox(m->absoluteFromSpatium(toHBox(m)->topGap()), 0.0, m->width(), height);
    2450          377 :             BoxLayout::layoutHBox2(toHBox(m), ctx);
    2451            0 :         } else if (m->isTBox()) {
    2452            0 :             BoxLayout::layoutTBox(toTBox(m), toTBox(m)->mutldata(), ctx);
    2453            0 :         } else if (m->isFBox()) {
    2454            0 :             BoxLayout::layoutFBox(toFBox(m), toFBox(m)->mutldata(), ctx);
    2455              :         } else {
    2456            0 :             LOGD("unhandled measure type %s", m->typeName());
    2457              :         }
    2458              :     }
    2459        20369 : }
    2460              : 
    2461              : //---------------------------------------------------------
    2462              : //   minDistance
    2463              : //    Return the minimum distance between this system and s2
    2464              : //    without any element collisions.
    2465              : //
    2466              : //    top - top system
    2467              : //    bottom   - bottom system
    2468              : //---------------------------------------------------------
    2469              : 
    2470        12066 : double SystemLayout::minDistance(const System* top, const System* bottom, const LayoutContext& ctx)
    2471              : {
    2472        12079 :     TRACEFUNC;
    2473              : 
    2474        12066 :     const LayoutConfiguration& conf = ctx.conf();
    2475        12066 :     const DomAccessor& dom = ctx.dom();
    2476              : 
    2477        12066 :     const VBox* topVBox = static_cast<VBox*>(top->vbox());
    2478        12066 :     const VBox* bottomVBox = static_cast<VBox*>(bottom->vbox());
    2479              : 
    2480        12066 :     if (topVBox && !bottomVBox) {
    2481         4415 :         return std::max(topVBox->absoluteFromSpatium(topVBox->bottomGap()),
    2482         8830 :                         bottom->minTop() + topVBox->absoluteFromSpatium(topVBox->paddingToNotationBelow()));
    2483         7651 :     } else if (!topVBox && bottomVBox) {
    2484          109 :         return std::max(bottomVBox->absoluteFromSpatium(bottomVBox->topGap()),
    2485          218 :                         top->minBottom() + bottomVBox->absoluteFromSpatium(bottomVBox->paddingToNotationAbove()));
    2486         7542 :     } else if (topVBox && bottomVBox) {
    2487          167 :         const double topToBottomGap = topVBox->absoluteFromSpatium(topVBox->bottomGap());
    2488          167 :         const double bottomToTopGap = bottomVBox->absoluteFromSpatium(bottomVBox->topGap());
    2489          167 :         if (topToBottomGap >= 0 && bottomToTopGap >= 0) {
    2490          167 :             double largestGap = std::max(bottomToTopGap, topToBottomGap);
    2491          167 :             return largestGap;
    2492              :         } else {
    2493            0 :             return topToBottomGap + bottomToTopGap;
    2494              :         }
    2495              :     }
    2496              : 
    2497         7375 :     if (top->staves().empty() || bottom->staves().empty()) {
    2498            0 :         return 0.0;
    2499              :     }
    2500              : 
    2501         7375 :     double minVerticalDistance = conf.styleAbsolute(Sid::minVerticalDistance);
    2502         7375 :     double dist = conf.isVerticalSpreadEnabled() ? conf.styleAbsolute(Sid::minSystemSpread) : conf.styleAbsolute(Sid::minSystemDistance);
    2503         7375 :     size_t firstStaff = 0;
    2504         7375 :     size_t lastStaff = 0;
    2505              : 
    2506         7381 :     for (firstStaff = 0; firstStaff < top->staves().size() - 1; ++firstStaff) {
    2507         2246 :         if (dom.staff(firstStaff)->show() && bottom->staff(firstStaff)->show()) {
    2508         2240 :             break;
    2509              :         }
    2510              :     }
    2511         7525 :     for (lastStaff = top->staves().size() - 1; lastStaff > 0; --lastStaff) {
    2512         2336 :         if (dom.staff(lastStaff)->show() && top->staff(lastStaff)->show()) {
    2513         2186 :             break;
    2514              :         }
    2515              :     }
    2516              : 
    2517         7375 :     const Staff* staff = dom.staff(firstStaff);
    2518         7375 :     double userDist = staff ? staff->absoluteFromSpatium(staff->userDist()) : 0.0;
    2519         7375 :     dist = std::max(dist, userDist);
    2520         7375 :     top->setFixedDownDistance(false);
    2521              : 
    2522         7375 :     const SysStaff* sysStaff = top->staff(lastStaff);
    2523         7375 :     const double minHorizontalClearance = conf.styleAbsolute(Sid::skylineMinHorizontalClearance);
    2524         7375 :     double sld = sysStaff ? sysStaff->skyline().minDistance(bottom->staff(firstStaff)->skyline(), minHorizontalClearance) : 0;
    2525         7375 :     sld -= sysStaff ? sysStaff->bbox().height() - minVerticalDistance : 0;
    2526              : 
    2527         7375 :     if (conf.isFloatMode()) {
    2528            0 :         return std::max(dist, sld);
    2529              :     }
    2530              : 
    2531        50320 :     for (const MeasureBase* mb1 : top->measures()) {
    2532        42949 :         if (mb1->isMeasure()) {
    2533        42809 :             const Measure* m = toMeasure(mb1);
    2534        42809 :             const Spacer* sp = m->vspacerDown(lastStaff);
    2535        42809 :             if (sp) {
    2536            4 :                 if (sp->spacerType() == SpacerType::FIXED) {
    2537            4 :                     dist = sp->absoluteGap();
    2538            4 :                     top->setFixedDownDistance(true);
    2539            4 :                     break;
    2540              :                 } else {
    2541            0 :                     dist = std::max(dist, sp->absoluteGap());
    2542              :                 }
    2543              :             }
    2544              :         }
    2545              :     }
    2546         7375 :     if (!top->hasFixedDownDistance()) {
    2547        44809 :         for (const MeasureBase* mb2 : bottom->measures()) {
    2548        37438 :             if (mb2->isMeasure()) {
    2549        37308 :                 const Measure* m = toMeasure(mb2);
    2550        37308 :                 const Spacer* sp = m->vspacerUp(firstStaff);
    2551        37308 :                 if (sp) {
    2552            0 :                     dist = std::max(dist, sp->absoluteGap());
    2553              :                 }
    2554              :             }
    2555              :         }
    2556              : 
    2557         7371 :         dist = std::max(dist, sld);
    2558              :     }
    2559         7375 :     return dist;
    2560        12066 : }
    2561              : 
    2562         4647 : static bool itemIsPlacedAbove(const EngravingItem* item)
    2563              : {
    2564         4647 :     if (item->isArticulationFamily()) {
    2565            2 :         return toArticulation(item)->up();
    2566              :     }
    2567         4645 :     if (item->isLyricsLineSegment()) {
    2568            0 :         return toLyricsLineSegment(item)->lyricsPlaceAbove();
    2569              :     }
    2570         4645 :     return item->placeAbove();
    2571              : }
    2572              : 
    2573            0 : void SystemLayout::removeElementFromSkyline(EngravingItem* element, const System* system)
    2574              : {
    2575            0 :     Skyline& skyline = system->staff(element->staffIdx())->skyline();
    2576            0 :     SkylineLine& skylineLine = itemIsPlacedAbove(element) ? skyline.north() : skyline.south();
    2577              : 
    2578            0 :     skylineLine.remove_if([element](ShapeElement& shapeEl) {
    2579            0 :         return shapeEl.item() && (element == shapeEl.item() || element == shapeEl.item()->ownershipParent());
    2580              :     });
    2581            0 : }
    2582              : 
    2583         4647 : void SystemLayout::updateSkylineForElement(EngravingItem* element, const System* system, double yMove)
    2584              : {
    2585         4647 :     Skyline& skyline = system->staff(element->staffIdx())->skyline();
    2586         4647 :     SkylineLine& skylineLine = itemIsPlacedAbove(element) ? skyline.north() : skyline.south();
    2587       139096 :     for (ShapeElement& shapeEl : skylineLine.elements()) {
    2588       134449 :         const EngravingItem* itemInSkyline = shapeEl.item();
    2589       134449 :         if (itemInSkyline && itemInSkyline->isText()) {
    2590            0 :             const EngravingItem* owner = itemInSkyline->ownershipParentItem();
    2591            0 :             if (owner && owner->isSLineSegment()) {
    2592            0 :                 itemInSkyline = owner;
    2593              :             }
    2594              :         }
    2595       134449 :         if (itemInSkyline == element) {
    2596         8236 :             shapeEl.translate(0.0, yMove);
    2597              :         }
    2598              :     }
    2599         4647 : }
    2600              : 
    2601        10198 : void SystemLayout::centerElementsBetweenStaves(const System* system)
    2602              : {
    2603        10198 :     std::vector<Gap> gaps; // populated by collectCenterableItems
    2604        10198 :     std::vector<MMRest*> mmRestsToCenter; // populated by collectCenterableItems
    2605              : 
    2606        10198 :     collectCenterableItems(system, gaps, mmRestsToCenter);
    2607              : 
    2608        10202 :     for (MMRest* mmrest : mmRestsToCenter) {
    2609            4 :         centerMMRestBetweenStaves(mmrest, system);
    2610              :     }
    2611              : 
    2612        10198 :     const double minHorizontalClearance = system->style().styleAbsolute(Sid::skylineMinHorizontalClearance);
    2613              : 
    2614        13890 :     for (const Gap& gap : gaps) {
    2615         3692 :         if (!gap.items.empty()) {
    2616          129 :             centerItemsInGap(gap, system, minHorizontalClearance);
    2617              :         }
    2618              :     }
    2619        10198 : }
    2620              : 
    2621         3964 : void SystemLayout::centerPendingSystems(LayoutContext& ctx)
    2622              : {
    2623         3967 :     for (System* system : ctx.state().systemsNeedingCentering()) {
    2624            3 :         centerElementsBetweenStaves(system);
    2625              :     }
    2626              : 
    2627         3964 :     ctx.mutState().clearSystemsNeedingCentering();
    2628         3964 : }
    2629              : 
    2630          257 : static Shape shapeRelativeToStaff(const EngravingItem* item, double systemX)
    2631              : {
    2632              :     // Offsets are applied after centering, so measure from the non-offset position
    2633          257 :     const double x = item->pageX() - systemX - item->offset().x();
    2634          257 :     const double y = item->isLyrics() ? toLyrics(item)->yRelativeToStaff() - item->offset().y() : item->ldata()->pos().y();
    2635          257 :     return item->ldata()->shape().translated(PointF(x, y));
    2636              : }
    2637              : 
    2638        10198 : void SystemLayout::collectCenterableItems(const System* system, std::vector<Gap>& gaps, std::vector<MMRest*>& mmRestsToCenter)
    2639              : {
    2640        10198 :     const size_t nStaves = system->staves().size();
    2641        10198 :     if (nStaves < 2) {
    2642         7842 :         return;
    2643              :     }
    2644              : 
    2645              :     struct GapsOfStaff {
    2646              :         size_t forItemsBelow = muse::nidx;
    2647              :         size_t forItemsAbove = muse::nidx;
    2648              :     };
    2649         2356 :     std::vector<GapsOfStaff> gapsOfStaff(nStaves);
    2650         2356 :     gaps.reserve(nStaves - 1);
    2651              : 
    2652         2356 :     staff_idx_t upperStaffIdx = system->firstVisibleStaff();
    2653         2356 :     staff_idx_t lowerStaffIdx = muse::nidx;
    2654         6048 :     for (; upperStaffIdx < nStaves; upperStaffIdx = lowerStaffIdx) {
    2655         6048 :         lowerStaffIdx = system->nextVisibleStaff(upperStaffIdx);
    2656         6048 :         if (lowerStaffIdx == muse::nidx) {
    2657         2356 :             break; // the last visible staff of the system
    2658              :         }
    2659              : 
    2660         3692 :         gapsOfStaff[upperStaffIdx].forItemsBelow = gaps.size();
    2661         3692 :         gapsOfStaff[lowerStaffIdx].forItemsAbove = gaps.size();
    2662         3692 :         gaps.emplace_back(upperStaffIdx, lowerStaffIdx, system->staff(lowerStaffIdx)->y() - system->staff(upperStaffIdx)->y());
    2663              :     }
    2664              : 
    2665         2356 :     const double systemX = system->pageX();
    2666              : 
    2667          258 :     auto addItem = [&](EngravingItem* item, bool above, bool isLyrics) {
    2668          258 :         const staff_idx_t staffIdx = item->staffIdx();
    2669          258 :         if (staffIdx >= nStaves) {
    2670            1 :             return;
    2671              :         }
    2672          258 :         const size_t gapIdx = above ? gapsOfStaff[staffIdx].forItemsAbove : gapsOfStaff[staffIdx].forItemsBelow;
    2673          258 :         if (gapIdx == muse::nidx) {
    2674              :             // The staff is hidden, or there is no staff on that side to center against
    2675            1 :             return;
    2676              :         }
    2677              : 
    2678          257 :         Shape shape = shapeRelativeToStaff(item, systemX);
    2679          702 :         shape.remove_if([](ShapeElement& shapeEl) { return shapeEl.ignoreForLayout(); });
    2680              : 
    2681              :         // Items left out of the skyline (e.g. invisible) must not take up space in the gap
    2682          257 :         const bool takesSpace = item->isLyricsLineSegment() ? toLyricsLineSegment(item)->lyricsAddToSkyline()
    2683          257 :                                 : item->addToSkyline();
    2684          257 :         if (!takesSpace) {
    2685            0 :             shape.clear();
    2686              :         }
    2687              : 
    2688          257 :         Gap& gap = gaps[gapIdx];
    2689          257 :         if (above) {
    2690              :             // Inside a gap, everything is expressed relative to its upper staff
    2691           20 :             shape.translate(PointF(0.0, gap.yStaffDiff));
    2692              :         }
    2693          257 :         gap.items.push_back(CenterableItem { item, std::move(shape), isLyrics, !above });
    2694          257 :     };
    2695              : 
    2696         4836 :     for (SpannerSegment* spannerSeg : system->spannerSegments()) {
    2697         2734 :         if ((spannerSeg->isHairpinSegment() && snapChainShouldBeCenteredBetweenStaves(spannerSeg, system))
    2698         2734 :             || (spannerSeg->isWhammyBarSegment() && whammyBarShouldBeCenteredBetweenStaves(toWhammyBarSegment(spannerSeg), system))) {
    2699          114 :             addItem(spannerSeg, spannerSeg->placeAbove(), false);
    2700         2366 :         } else if (spannerSeg->isLyricsLineSegment()) {
    2701          185 :             bool above = toLyricsLineSegment(spannerSeg)->lyricsPlaceAbove();
    2702          185 :             if (elementShouldBeCenteredBetweenStaves(spannerSeg, system, above)) {
    2703            0 :                 addItem(spannerSeg, above, true);
    2704              :             }
    2705              :         }
    2706              :     }
    2707              : 
    2708        13593 :     for (const MeasureBase* mb : system->measures()) {
    2709        11237 :         if (!mb->isMeasure()) {
    2710           54 :             continue;
    2711              :         }
    2712        51566 :         for (const Segment& seg : toMeasure(mb)->segments()) {
    2713        40383 :             if (seg.isChordRestType()) {
    2714       207352 :                 for (EngravingItem* item : seg.elist()) {
    2715       187280 :                     if (!item) {
    2716       144197 :                         continue;
    2717              :                     }
    2718        43083 :                     if (item->isMMRest() && mmRestShouldBeCenteredBetweenStaves(toMMRest(item), system)) {
    2719            4 :                         mmRestsToCenter.push_back(toMMRest(item));
    2720        43079 :                     } else if (item->isChordRest()) {
    2721        44516 :                         for (Lyrics* lyrics : toChordRest(item)->lyrics()) {
    2722         1437 :                             bool above = lyrics->placeAbove();
    2723         1437 :                             if (elementShouldBeCenteredBetweenStaves(lyrics, system, above)) {
    2724            0 :                                 addItem(lyrics, above, true);
    2725              :                             }
    2726              :                         }
    2727              :                     }
    2728              :                 }
    2729              :             }
    2730        42437 :             for (EngravingItem* item : seg.annotations()) {
    2731         2054 :                 if ((item->isDynamic() || item->isExpression()) && snapChainShouldBeCenteredBetweenStaves(item, system)) {
    2732          144 :                     addItem(item, item->placeAbove(), false);
    2733              :                 }
    2734              :             }
    2735              :         }
    2736              :     }
    2737         2356 : }
    2738              : 
    2739          188 : bool SystemLayout::shapesStackVertically(const Shape& shape1, const Shape& shape2, double minHorizontalClearance)
    2740              : {
    2741          434 :     for (const ShapeElement& shapeEl1 : shape1.elements()) {
    2742          260 :         if (shapeEl1.height() <= 0.0) {
    2743            0 :             continue;
    2744              :         }
    2745          748 :         for (const ShapeElement& shapeEl2 : shape2.elements()) {
    2746          502 :             if (shapeEl2.height() <= 0.0) {
    2747            0 :                 continue;
    2748              :             }
    2749          502 :             if (intersects(shapeEl1.left(), shapeEl1.right(), shapeEl2.left(), shapeEl2.right(), minHorizontalClearance)) {
    2750           14 :                 return true;
    2751              :             }
    2752              :         }
    2753              :     }
    2754              : 
    2755          174 :     return false;
    2756              : }
    2757              : 
    2758            0 : void SystemLayout::centerBigTimeSigsAcrossStaves(const System* system)
    2759              : {
    2760            0 :     staff_idx_t nstaves = system->score()->nstaves();
    2761            0 :     for (MeasureBase* mb : system->measures()) {
    2762            0 :         if (!mb->isMeasure()) {
    2763            0 :             continue;
    2764              :         }
    2765            0 :         for (Segment& segment : toMeasure(mb)->segments()) {
    2766            0 :             if (!segment.isType(SegmentType::TimeSigTypes)) {
    2767            0 :                 continue;
    2768              :             }
    2769            0 :             for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
    2770            0 :                 TimeSig* timeSig = toTimeSig(segment.element(staff2track(staffIdx)));
    2771            0 :                 if (!timeSig || !timeSig->showOnThisStaff()) {
    2772            0 :                     continue;
    2773              :                 }
    2774            0 :                 staff_idx_t thisStaffIdx = timeSig->effectiveStaffIdx();
    2775            0 :                 if (thisStaffIdx == muse::nidx) {
    2776            0 :                     continue;
    2777              :                 }
    2778            0 :                 staff_idx_t nextStaffIdx = thisStaffIdx;
    2779            0 :                 for (staff_idx_t idx = thisStaffIdx + 1; idx < nstaves; ++idx) {
    2780            0 :                     TimeSig* nextTimeSig = toTimeSig(segment.element(staff2track(idx)));
    2781            0 :                     if (nextTimeSig && nextTimeSig->showOnThisStaff()) {
    2782            0 :                         staff_idx_t nextTimeSigStave = nextTimeSig->effectiveStaffIdx();
    2783            0 :                         if (nextTimeSigStave != muse::nidx) {
    2784            0 :                             nextStaffIdx = system->prevVisibleStaff(nextTimeSigStave);
    2785            0 :                             break;
    2786              :                         }
    2787              :                     }
    2788            0 :                     if (idx == nstaves - 1) {
    2789            0 :                         nextStaffIdx = system->prevVisibleStaff(nstaves);
    2790            0 :                         break;
    2791              :                     }
    2792              :                 }
    2793            0 :                 double yTop = system->staff(thisStaffIdx)->y() + system->score()->staff(thisStaffIdx)->staffHeight(segment.tick());
    2794            0 :                 double yBottom = system->staff(nextStaffIdx)->y() + system->score()->staff(nextStaffIdx)->staffHeight(segment.tick());
    2795            0 :                 double newYPos = 0.5 * (yBottom - yTop);
    2796            0 :                 timeSig->mutldata()->setPosY(newYPos);
    2797              :             }
    2798              :         }
    2799              :     }
    2800            0 : }
    2801              : 
    2802          773 : bool SystemLayout::snapChainShouldBeCenteredBetweenStaves(EngravingItem* item, const System* system)
    2803              : {
    2804              :     // Snapped items are centered as one, so if any of them cannot be centered, none of them can
    2805          773 :     bool allCenterable = true;
    2806          773 :     item->ldata()->scanConnectedItemsInSnappingChain(system, [&allCenterable, system](EngravingItem* chainItem) {
    2807          953 :         allCenterable = allCenterable && elementShouldBeCenteredBetweenStaves(chainItem, system, chainItem->placeAbove());
    2808          953 :     });
    2809              : 
    2810          773 :     return allCenterable;
    2811              : }
    2812              : 
    2813         2495 : bool SystemLayout::elementShouldBeCenteredBetweenStaves(const EngravingItem* item, const System* system, bool placeAbove)
    2814              : {
    2815         2495 :     const Part* itemPart = item->part();
    2816         2495 :     IF_ASSERT_FAILED(itemPart) {
    2817            0 :         return false;
    2818              :     }
    2819              : 
    2820         2495 :     const Staff* otherStaff = item->staffToCenterAgainst(placeAbove, system);
    2821         2495 :     if (!otherStaff) {
    2822         1809 :         return false;
    2823              :     }
    2824              : 
    2825          686 :     const bool sameInstrument = otherStaff->part() == itemPart;
    2826              : 
    2827              :     Sid centerStyleId;
    2828          686 :     if (item->isLyrics() || item->isLyricsLineSegment()) {
    2829          212 :         centerStyleId = sameInstrument ? Sid::lyricsAutoCenterOnGrandStaff : Sid::lyricsAutoCenterOnVocalStaves;
    2830          474 :     } else if (item->isDynamic() || item->isExpression() || item->isHairpinSegment()) {
    2831          474 :         centerStyleId = sameInstrument ? Sid::dynamicsHairpinsAutoCenterOnGrandStaff : Sid::dynamicsHairpinsAutoCenterOnVocalStaves;
    2832              :     } else {
    2833            0 :         return false;
    2834              :     }
    2835              : 
    2836              :     AutoOnOff centerProperty;
    2837          686 :     if (item->isLyricsLineSegment() && !item->isPartialLyricsLineSegment()) {
    2838              :         // A dash or melisma line belonging to a lyric has no setting of its own: it always
    2839              :         // follows that lyric. Only partial lyrics lines are configurable individually.
    2840           49 :         const Lyrics* lyrics = toLyricsLineSegment(item)->lyrics();
    2841           49 :         centerProperty = lyrics ? lyrics->centerBetweenStaves() : AutoOnOff::AUTO;
    2842              :     } else {
    2843          637 :         centerProperty = item->getProperty(Pid::CENTER_BETWEEN_STAVES).value<AutoOnOff>();
    2844              :     }
    2845              : 
    2846          686 :     const bool centerStyle = item->style().styleB(centerStyleId);
    2847          686 :     if (centerProperty == AutoOnOff::OFF || (!centerStyle && centerProperty != AutoOnOff::ON)) {
    2848          261 :         return false;
    2849              :     }
    2850              : 
    2851          425 :     if (sameInstrument && centerProperty != AutoOnOff::ON && !itemPart->instrument()->isNormallyMultiStaveInstrument()) {
    2852           65 :         return false;
    2853              :     }
    2854              : 
    2855          360 :     if (item->isDynamic() || item->isExpression() || item->isHairpinSegment()) {
    2856          360 :         if (!(centerProperty == AutoOnOff::ON || item->appliesToAllVoicesInInstrument())) {
    2857            2 :             return false;
    2858              :         }
    2859              :     }
    2860              : 
    2861          358 :     return true;
    2862              : }
    2863              : 
    2864           36 : bool SystemLayout::mmRestShouldBeCenteredBetweenStaves(const MMRest* mmRest, const System* system)
    2865              : {
    2866           36 :     if (!mmRest->style().styleB(Sid::mmRestBetweenStaves)) {
    2867            0 :         return false;
    2868              :     }
    2869              : 
    2870           36 :     const Part* itemPart = mmRest->part();
    2871           36 :     if (itemPart->nstaves() <= 1) {
    2872           28 :         return false;
    2873              :     }
    2874              : 
    2875            8 :     staff_idx_t thisStaffIdx = mmRest->staffIdx();
    2876            8 :     staff_idx_t prevStaffIdx = system->prevVisibleStaff(thisStaffIdx);
    2877              : 
    2878            8 :     return prevStaffIdx != muse::nidx && mmRest->score()->staff(prevStaffIdx)->part() == itemPart;
    2879              : }
    2880              : 
    2881            0 : bool SystemLayout::whammyBarShouldBeCenteredBetweenStaves(const WhammyBarSegment* wbar, const System* system)
    2882              : {
    2883            0 :     staff_idx_t staffIdx = wbar->staffIdx();
    2884            0 :     Staff* thisStaff = wbar->staff();
    2885            0 :     Staff* nextStaff = wbar->score()->staff(staffIdx + 1);
    2886            0 :     bool nextIsLinkedTab = nextStaff && nextStaff->isTabStaff(wbar->tick()) && thisStaff->isLinked(nextStaff);
    2887            0 :     SysStaff* nextSysStaff = system->staff(staffIdx + 1);
    2888            0 :     return wbar->placeBelow() && nextIsLinkedTab && nextSysStaff && nextSysStaff->show();
    2889              : }
    2890              : 
    2891          257 : bool SystemLayout::elementHasAnotherStackedOutside(const EngravingItem* element, const Shape& elementShape, const SkylineLine& skylineLine)
    2892              : {
    2893          257 :     double elemShapeLeft = elementShape.left();
    2894          257 :     double elemShapeRight = elementShape.right();
    2895          257 :     double elemShapeTop = elementShape.top();
    2896          257 :     double elemShapeBottom = elementShape.bottom();
    2897              : 
    2898        15637 :     for (const ShapeElement& skylineElement : skylineLine.elements()) {
    2899        15380 :         const EngravingItem* skylineItem = skylineElement.item();
    2900        15380 :         if (!skylineItem || skylineItem == element || skylineItem->parent() == element || skylineItem->isAccidental()
    2901        30760 :             || Autoplace::itemsShouldIgnoreEachOther(element, skylineItem)) {
    2902          360 :             continue;
    2903              :         }
    2904        15020 :         bool intersectHorizontally = elemShapeRight > skylineElement.left() && elemShapeLeft < skylineElement.right();
    2905        15020 :         if (!intersectHorizontally) {
    2906        12765 :             continue;
    2907              :         }
    2908         4472 :         bool skylineElementIsStackedOnIt = skylineLine.isNorth() ? skylineElement.bottom() <= elemShapeTop
    2909         2217 :                                            : skylineElement.top() >= elemShapeBottom;
    2910         2255 :         if (skylineElementIsStackedOnIt) {
    2911            0 :             return true;
    2912              :         }
    2913              :     }
    2914              : 
    2915          257 :     return false;
    2916              : }
    2917              : 
    2918          258 : static SkylineLine skylineFacingGap(const System* system, staff_idx_t staffIdx, bool isUpperStaffOfGap, double yShift,
    2919              :                                     const std::unordered_set<const EngravingItem*>& itemsToIgnore)
    2920              : {
    2921          129 :     const SkylineLine& skyline = isUpperStaffOfGap ? system->staff(staffIdx)->skyline().south()
    2922          258 :                                  : system->staff(staffIdx)->skyline().north();
    2923              : 
    2924          516 :     SkylineLine filteredSkyline = skyline.getFilteredCopy([&itemsToIgnore](const ShapeElement& shapeEl) {
    2925         7809 :         const EngravingItem* shapeItem = shapeEl.item();
    2926        15177 :         return shapeItem && (muse::contains(itemsToIgnore, shapeItem)
    2927        15177 :                              || muse::contains(itemsToIgnore, const_cast<const EngravingItem*>(shapeItem->ownershipParentItem())));
    2928          258 :     });
    2929              : 
    2930          258 :     if (yShift != 0.0) {
    2931          129 :         filteredSkyline.translateY(yShift);
    2932              :     }
    2933              : 
    2934          258 :     return filteredSkyline;
    2935            0 : }
    2936              : 
    2937          129 : void SystemLayout::centerItemsInGap(const Gap& gap, const System* system, double minHorizontalClearance)
    2938              : {
    2939              :     /* Nothing which is itself centered in this gap may take part in measuring it */
    2940          129 :     std::unordered_set<const EngravingItem*> movableItems;
    2941          386 :     for (const CenterableItem& gapItem : gap.items) {
    2942          257 :         movableItems.insert(gapItem.item);
    2943              :     }
    2944              : 
    2945            0 :     auto getVerse = [](const EngravingItem* item) {
    2946            0 :         if (item->isLyrics()) {
    2947            0 :             return toLyrics(item)->verse();
    2948            0 :         } else if (item->isLyricsLineSegment()) {
    2949            0 :             return toLyricsLineSegment(item)->verse();
    2950              :         }
    2951            0 :         return -1;
    2952              :     };
    2953              : 
    2954              :     /* An item with something non-centered stacked outside it has no gap to be centered in: it
    2955              :      * stays where it is and joins the skyline, so that it constrains the items which do move */
    2956          129 :     while (!movableItems.empty()) {
    2957          129 :         const SkylineLine upperSkyline = skylineFacingGap(system, gap.upperStaffIdx, true, 0.0, movableItems);
    2958          129 :         const SkylineLine lowerSkyline = skylineFacingGap(system, gap.lowerStaffIdx, false, gap.yStaffDiff, movableItems);
    2959              : 
    2960          129 :         std::vector<const CenterableItem*> itemsToCenter;
    2961          129 :         bool canCenterGroup = true;
    2962          386 :         for (const CenterableItem& gapItem : gap.items) {
    2963          257 :             const EngravingItem* item = gapItem.item;
    2964          257 :             if (!muse::contains(movableItems, item)) {
    2965            0 :                 continue;
    2966              :             }
    2967              : 
    2968          257 :             const Shape itemShape = gapItem.shape.adjusted(-minHorizontalClearance, 0.0, minHorizontalClearance, 0.0);
    2969          257 :             const SkylineLine& ownSkyline = gapItem.onUpperGapStaff ? upperSkyline : lowerSkyline;
    2970          257 :             if (!itemShape.empty() && elementHasAnotherStackedOutside(item, itemShape, ownSkyline)) {
    2971            0 :                 movableItems.erase(item);
    2972            0 :                 canCenterGroup = false;
    2973              : 
    2974              :                 // Snapped items stay together:
    2975            0 :                 gapItem.item->ldata()->scanConnectedItemsInSnappingChain(system, [&movableItems](EngravingItem* chainItem) {
    2976            0 :                     movableItems.erase(chainItem);
    2977            0 :                 });
    2978              : 
    2979              :                 // A lyrics verse stays together:
    2980            0 :                 if (gapItem.isLyrics) {
    2981            0 :                     int verse = getVerse(item);
    2982            0 :                     for (const CenterableItem& i : gap.items) {
    2983            0 :                         if (i.isLyrics && i.onUpperGapStaff == gapItem.onUpperGapStaff && getVerse(i.item) == verse) {
    2984            0 :                             movableItems.erase(i.item);
    2985              :                         }
    2986              :                     }
    2987              :                 }
    2988              :             } else {
    2989          257 :                 itemsToCenter.push_back(&gapItem);
    2990              :             }
    2991          257 :         }
    2992              : 
    2993          129 :         if (!canCenterGroup) {
    2994            0 :             continue;
    2995              :         }
    2996              : 
    2997          328 :         for (const std::vector<const CenterableItem*>& group : groupItemsToCenterTogether(itemsToCenter, minHorizontalClearance)) {
    2998          199 :             centerItemGroup(group, system, upperSkyline, lowerSkyline, gap.yStaffDiff, minHorizontalClearance);
    2999          129 :         }
    3000          129 :         return;
    3001          387 :     }
    3002          129 : }
    3003              : 
    3004          129 : std::vector<std::vector<const SystemLayout::CenterableItem*> > SystemLayout::groupItemsToCenterTogether(
    3005              :     const std::vector<const CenterableItem*>& items, double minHorizontalClearance)
    3006              : {
    3007              :     /* We center lyrics as a single block so that the verses will not lose their alignment (separating the ones
    3008              :      * below the upper staff from the ones above the lower staff if they don't overlap). Other centerable items
    3009              :      * join the group if they overlap horizontally, or if they are snapped to each other, so that they will
    3010              :      * preserve their relative positions. */
    3011          129 :     const size_t nItems = items.size();
    3012          129 :     std::vector<size_t> groupIds(nItems, muse::nidx);
    3013          129 :     size_t nGroupIds = 0;
    3014              : 
    3015          386 :     for (size_t i = 0; i < nItems; ++i) {
    3016          257 :         if (groupIds[i] == muse::nidx) {
    3017          201 :             groupIds[i] = nGroupIds++;
    3018              :         }
    3019          488 :         for (size_t j = i + 1; j < nItems; ++j) {
    3020          231 :             const bool areVersesOfTheSameBlock = items[i]->isLyrics && items[j]->isLyrics
    3021          231 :                                                  && items[i]->onUpperGapStaff == items[j]->onUpperGapStaff;
    3022          231 :             const bool areSnappedTogether = items[i]->item->ldata()->itemSnappedBefore() == items[j]->item
    3023          231 :                                             || items[i]->item->ldata()->itemSnappedAfter() == items[j]->item;
    3024          231 :             const bool mustMoveTogether = areVersesOfTheSameBlock || areSnappedTogether
    3025          462 :                                           || shapesStackVertically(items[i]->shape, items[j]->shape, minHorizontalClearance);
    3026          231 :             if (!mustMoveTogether) {
    3027          173 :                 continue;
    3028              :             }
    3029           58 :             if (groupIds[j] == muse::nidx) {
    3030           56 :                 groupIds[j] = groupIds[i];
    3031            2 :             } else if (groupIds[j] != groupIds[i]) {
    3032            2 :                 const size_t idToMerge = groupIds[j];
    3033            2 :                 const size_t idToKeep = groupIds[i];
    3034           12 :                 for (size_t& groupId : groupIds) {
    3035           10 :                     if (groupId == idToMerge) {
    3036            4 :                         groupId = idToKeep;
    3037              :                     }
    3038              :                 }
    3039              :             }
    3040              :         }
    3041              :     }
    3042              : 
    3043          129 :     std::vector<size_t> groupIdToIndex(nGroupIds, muse::nidx);
    3044          129 :     std::vector<std::vector<const CenterableItem*> > groups;
    3045              : 
    3046          386 :     for (size_t i = 0; i < nItems; ++i) {
    3047          257 :         size_t& index = groupIdToIndex[groupIds[i]];
    3048          257 :         if (index == muse::nidx) {
    3049          199 :             index = groups.size();
    3050          199 :             groups.emplace_back();
    3051              :         }
    3052          257 :         groups[index].push_back(items[i]);
    3053              :     }
    3054              : 
    3055          129 :     return groups;
    3056          129 : }
    3057              : 
    3058              : // How far an item moves when its group is converged: only the half sitting above the lower staff moves to meet the upper one
    3059          257 : double SystemLayout::convergenceMoveFor(const CenterableItem* centerableItem, double convergeDistance)
    3060              : {
    3061          257 :     return centerableItem->onUpperGapStaff ? 0.0 : -convergeDistance;
    3062              : }
    3063              : 
    3064          199 : double SystemLayout::gapConvergeDistance(const std::vector<const CenterableItem*>& group, double yStaffDiff, double minHorizontalClearance)
    3065              : {
    3066              :     /* A group that has items below the upper staff and above the lower one, spreads over the whole gap and
    3067              :      * has no space left to center the items, so we bring the items together into the middle of the gap. This
    3068              :      * function calculates how much slack there is for the lower items to converge towards the upper ones
    3069              :      * before centering, so that they will be centered together. */
    3070              : 
    3071          199 :     double slack = DBL_MAX;
    3072          456 :     for (const CenterableItem* upper : group) {
    3073          257 :         if (!upper->onUpperGapStaff || upper->shape.empty()) {
    3074           20 :             continue;
    3075              :         }
    3076              : 
    3077          588 :         for (const CenterableItem* lower : group) {
    3078          351 :             if (lower->onUpperGapStaff || lower->shape.empty()) {
    3079          343 :                 continue;
    3080              :             }
    3081            8 :             if (!shapesStackVertically(upper->shape, lower->shape, minHorizontalClearance)) {
    3082            1 :                 continue;
    3083              :             }
    3084              : 
    3085            7 :             const EngravingItem* upperItem = upper->item;
    3086            7 :             const EngravingItem* lowerItem = lower->item;
    3087            7 :             const Lyrics* upperLyrics = upperItem->isLyrics() ? toLyrics(upperItem) : nullptr;
    3088            7 :             const Lyrics* lowerLyrics = lowerItem->isLyrics() ? toLyrics(lowerItem) : nullptr;
    3089              : 
    3090            7 :             if (upperLyrics && lowerLyrics) {
    3091              :                 // The distance between the top-staff and bottom-staff lyrics blocks will be like between verses:
    3092            0 :                 const double verseSpace = std::max(upperLyrics->lineSpacing(), lowerLyrics->lineSpacing())
    3093            0 :                                           * upperItem->style().styleD(Sid::lyricsLineHeight);
    3094            0 :                 const double spaceBetweenLyrics = (lowerLyrics->yRelativeToStaff() - lowerLyrics->offset().y()) + yStaffDiff
    3095            0 :                                                   - (upperLyrics->yRelativeToStaff() - upperLyrics->offset().y());
    3096            0 :                 slack = std::min(slack, spaceBetweenLyrics - verseSpace);
    3097            0 :                 continue;
    3098            0 :             }
    3099              : 
    3100              :             // If it's not lyrics, we just use the items' min distance
    3101            7 :             const double minDist = std::max(upperItem->absoluteFromSpatium(upperItem->minDistance()),
    3102            7 :                                             lowerItem->absoluteFromSpatium(lowerItem->minDistance()));
    3103            7 :             slack = std::min(slack, upper->shape.verticalClearance(lower->shape, minHorizontalClearance) - minDist);
    3104              :         }
    3105              :     }
    3106              : 
    3107              :     // Nothing to do for a group that is on one staff only, or whose halves are already as close as possible
    3108          199 :     return slack == DBL_MAX ? 0.0 : std::max(slack, 0.0);
    3109              : }
    3110              : 
    3111          199 : void SystemLayout::centerItemGroup(const std::vector<const CenterableItem*>& group, const System* system,
    3112              :                                    const SkylineLine& upperSkyline, const SkylineLine& lowerSkyline, double yStaffDiff,
    3113              :                                    double minHorizontalClearance)
    3114              : {
    3115              :     // The least space each half has on either side
    3116          199 :     double upperHalfSpaceAbove = DBL_MAX;
    3117          199 :     double upperHalfSpaceBelow = DBL_MAX;
    3118          199 :     double lowerHalfSpaceAbove = DBL_MAX;
    3119          199 :     double lowerHalfSpaceBelow = DBL_MAX;
    3120              : 
    3121          456 :     for (const CenterableItem* centerableItem : group) {
    3122          257 :         const Shape itemShape = centerableItem->shape.adjusted(-minHorizontalClearance, 0.0, minHorizontalClearance, 0.0);
    3123          257 :         if (itemShape.empty()) {
    3124            0 :             continue;
    3125              :         }
    3126              : 
    3127          257 :         const bool onUpperStaff = centerableItem->onUpperGapStaff;
    3128              :         const SkylineLine ownSkyline
    3129          257 :             = (onUpperStaff ? upperSkyline : lowerSkyline).getFilteredCopy([centerableItem](const ShapeElement& shapeEl) {
    3130        15380 :             const EngravingItem* shapeItem = shapeEl.item();
    3131        15380 :             return shapeItem && (shapeItem->isAccidental() || Autoplace::itemsShouldIgnoreEachOther(centerableItem->item, shapeItem));
    3132          257 :         });
    3133              : 
    3134          257 :         const double minDist = centerableItem->item->absoluteFromSpatium(centerableItem->item->minDistance());
    3135          257 :         const double spaceAbove = (onUpperStaff ? ownSkyline : upperSkyline).verticalClearanceBelow(itemShape) - minDist;
    3136          257 :         const double spaceBelow = (onUpperStaff ? lowerSkyline : ownSkyline).verticalClearanceAbove(itemShape) - minDist;
    3137              : 
    3138          257 :         if (onUpperStaff) {
    3139          237 :             upperHalfSpaceAbove = std::min(upperHalfSpaceAbove, spaceAbove);
    3140          237 :             upperHalfSpaceBelow = std::min(upperHalfSpaceBelow, spaceBelow);
    3141              :         } else {
    3142           20 :             lowerHalfSpaceAbove = std::min(lowerHalfSpaceAbove, spaceAbove);
    3143           20 :             lowerHalfSpaceBelow = std::min(lowerHalfSpaceBelow, spaceBelow);
    3144              :         }
    3145          257 :     }
    3146              : 
    3147          199 :     double convergeDistance = gapConvergeDistance(group, yStaffDiff, minHorizontalClearance);
    3148          199 :     if (lowerHalfSpaceAbove != DBL_MAX && upperHalfSpaceBelow != DBL_MAX) {
    3149              :         /* Convergence only measures the items of the two halves, but there might be other non-centered
    3150              :          * items in the gap, so we cap it at the space the group actually has in the gap */
    3151            7 :         convergeDistance = std::min(convergeDistance, std::max(lowerHalfSpaceAbove + upperHalfSpaceBelow, 0.0));
    3152              :     }
    3153              : 
    3154              :     // Space left above and below the entire group once converged, i.e. how far it can move as one rigid unit
    3155          199 :     const double groupSpaceAbove = std::min(upperHalfSpaceAbove, lowerHalfSpaceAbove - convergeDistance);
    3156          199 :     const double groupSpaceBelow = std::min(upperHalfSpaceBelow, lowerHalfSpaceBelow + convergeDistance);
    3157              : 
    3158          199 :     if (groupSpaceAbove == DBL_MAX || groupSpaceBelow == DBL_MAX) {
    3159            0 :         return;
    3160              :     }
    3161              : 
    3162          199 :     const double yMove = 0.5 * (groupSpaceBelow - groupSpaceAbove);
    3163              : 
    3164              :     // Move the items
    3165          456 :     for (const CenterableItem* centerableItem : group) {
    3166          257 :         const double itemMove = yMove + convergenceMoveFor(centerableItem, convergeDistance);
    3167          257 :         centerableItem->item->mutldata()->moveY(itemMove);
    3168          257 :         updateSkylineForElement(centerableItem->item, system, itemMove);
    3169              :     }
    3170              : }
    3171              : 
    3172            4 : void SystemLayout::centerMMRestBetweenStaves(MMRest* mmRest, const System* system)
    3173              : {
    3174            4 :     staff_idx_t thisIdx = mmRest->staffIdx();
    3175            4 :     IF_ASSERT_FAILED(thisIdx > 0) {
    3176            0 :         return;
    3177              :     }
    3178              : 
    3179            4 :     staff_idx_t prevIdx = system->prevVisibleStaff(thisIdx);
    3180            4 :     IF_ASSERT_FAILED(prevIdx != muse::nidx) {
    3181            0 :         return;
    3182              :     }
    3183              : 
    3184            4 :     SysStaff* thisStaff = system->staff(thisIdx);
    3185            4 :     SysStaff* prevStaff = system->staff(prevIdx);
    3186            4 :     double prevStaffHeight = system->score()->staff(prevIdx)->staffHeight(mmRest->tick());
    3187            4 :     double yStaffDiff = prevStaff->y() + prevStaffHeight - thisStaff->y();
    3188              : 
    3189            4 :     PointF mmRestDefaultNumberPosition = mmRest->numberPos() - PointF(0.0, mmRest->absoluteFromSpatium(mmRest->numberOffset()));
    3190            4 :     RectF numberBbox = mmRest->numberRect().translated(mmRestDefaultNumberPosition + mmRest->pos());
    3191            4 :     double yBaseLine = 0.5 * (yStaffDiff - numberBbox.height());
    3192            4 :     double yDiff = yBaseLine - numberBbox.top();
    3193              : 
    3194            4 :     mmRest->mutldata()->yNumberPos += yDiff;
    3195              : }
    3196              : 
    3197        26558 : void SystemLayout::MeasureState::clear()
    3198              : {
    3199        26558 :     measure = nullptr;
    3200        26558 :     measureWidth = 0.0;
    3201        26558 :     measurePos = 0.0;
    3202        26558 :     elementPositions.clear();
    3203        26558 :     elementWidths.clear();
    3204        26558 :     sharedTrackMaps.clear();
    3205        26558 : }
    3206              : 
    3207          142 : void SystemLayout::MeasureState::restoreMeasure(LayoutContext& ctx)
    3208              : {
    3209          142 :     bool mapChanged = false;
    3210          142 :     Fraction tick = measure->tick();
    3211          142 :     for (auto& [sharedPart, sharedTrackMap] : sharedTrackMaps) {
    3212            0 :         if (sharedPart->trackMapAtTick(tick) != sharedTrackMap) {
    3213            0 :             sharedPart->setTrackMapAtTick(sharedTrackMap, tick);
    3214            0 :             mapChanged = true;
    3215              :         }
    3216              :     }
    3217          142 :     if (mapChanged) {
    3218            0 :         StaveSharingLayout::updateNotationWithoutRecomputingTrackMap(measure, ctx);
    3219              :     }
    3220              : 
    3221          142 :     measure->mutldata()->setPosX(measurePos);
    3222          142 :     measure->setWidth(measureWidth);
    3223          882 :     for (auto pair : elementPositions) {
    3224          740 :         pair.first->setPos(pair.second);
    3225              :     }
    3226          880 :     for (auto pair : elementWidths) {
    3227          738 :         pair.first->setWidth(pair.second);
    3228              :     }
    3229          142 : }
        

Generated by: LCOV version 2.0-1