LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - restlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 78.8 % 491 387
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              : 
      23              : #include "restlayout.h"
      24              : 
      25              : #include "beamlayout.h"
      26              : #include "chordlayout.h"
      27              : #include "tlayout.h"
      28              : 
      29              : #include "dom/beam.h"
      30              : #include "dom/measure.h"
      31              : #include "dom/staff.h"
      32              : #include "dom/system.h"
      33              : 
      34              : using namespace muse;
      35              : using namespace mu::engraving;
      36              : 
      37              : namespace mu::engraving::rendering::score {
      38        66760 : void RestLayout::layoutRest(const Rest* item, Rest::LayoutData* ldata, const LayoutContext& ctx)
      39              : {
      40        66760 :     if (item->isGap()) {
      41          152 :         if (item->debugDrawGap()) {
      42            0 :             ldata->sym = item->getSymbol(item->durationType().type(), 16, 5);
      43            0 :             fillShape(item, ldata, ctx.conf());
      44            0 :             ldata->setPos(PointF(0.0, 10 * item->spatium()));
      45              :         } else {
      46          152 :             ldata->reset();
      47              :         }
      48          152 :         return;
      49              :     }
      50              : 
      51              :     //! NOTE The types are listed here explicitly to show what types there are (see Rest::add method)
      52              :     //! and accordingly show what depends on.
      53        66608 :     for (EngravingItem* e : item->el()) {
      54            0 :         switch (e->type()) {
      55            0 :         case ElementType::DEAD_SLAPPED: {
      56            0 :             DeadSlapped* ds = item_cast<DeadSlapped*>(e);
      57              :             LD_INDEPENDENT;
      58            0 :             TLayout::layoutDeadSlapped(ds, ds->mutldata());
      59            0 :         } break;
      60            0 :         case ElementType::SYMBOL: {
      61            0 :             Symbol* s = item_cast<Symbol*>(e);
      62              :             // LD_X not clear yet
      63            0 :             TLayout::layoutSymbol(s, s->mutldata(), ctx);
      64            0 :         } break;
      65            0 :         case ElementType::IMAGE: {
      66            0 :             Image* im = item_cast<Image*>(e);
      67              :             LD_INDEPENDENT;
      68            0 :             TLayout::layoutImage(im, im->mutldata());
      69            0 :         } break;
      70            0 :         default:
      71            0 :             UNREACHABLE;
      72              :         }
      73              :     }
      74              : 
      75        66608 :     if (item->deadSlapped()) {
      76            0 :         ldata->setIsSkipDraw(true);
      77            0 :         return;
      78              :     }
      79        66608 :     ldata->setIsSkipDraw(false);
      80              : 
      81        66608 :     double spatium = item->spatium();
      82              : 
      83        66608 :     ldata->setPosX(0.0);
      84        66608 :     const StaffType* stt = item->staffType();
      85        66608 :     if (stt && stt->isTabStaff()) {
      86              :         // if rests are shown and note values are shown as duration symbols
      87         1451 :         if (stt->showRests() && stt->genDurations()) {
      88            0 :             DurationType type = item->durationType().type();
      89            0 :             int dots = item->durationType().dots();
      90              :             // if rest is whole measure, convert into actual type and dot values
      91            0 :             if (type == DurationType::V_MEASURE && item->measure()) {
      92            0 :                 Fraction ticks = item->measure()->ticks();
      93            0 :                 TDuration dur  = TDuration(ticks).type();
      94            0 :                 type           = dur.type();
      95            0 :                 dots           = dur.dots();
      96              :             }
      97              :             // symbol needed; if not exist, create, if exists, update duration
      98            0 :             if (!item->tabDur()) {
      99            0 :                 const_cast<Rest*>(item)->setTabDur(new TabDurationSymbol(const_cast<Rest*>(item), stt, type, dots));
     100              :             } else {
     101            0 :                 item->tabDur()->setDuration(type, dots, stt);
     102              :             }
     103              : // needed?        _tabDur->setTrack(track());
     104            0 :             TLayout::layoutTabDurationSymbol(item->tabDur(), item->tabDur()->mutldata());
     105            0 :             ldata->setBbox(item->tabDur()->ldata()->bbox());
     106            0 :             ldata->setPos(0.0, 0.0);                   // no rest is drawn: reset any position might be set for it
     107            0 :             return;
     108              :         }
     109              :         // if no rests or no duration symbols, delete any dur. symbol and chain into standard staff mngmt
     110              :         // this is to ensure horiz space is reserved for rest, even if they are not displayed
     111              :         // Rest::draw() will skip their drawing, if not needed
     112         1451 :         if (item->tabDur()) {
     113            0 :             delete item->tabDur();
     114            0 :             const_cast<Rest*>(item)->setTabDur(nullptr);
     115              :         }
     116              :     }
     117              : 
     118        66608 :     const_cast<Rest*>(item)->setDotLine(Rest::getDotline(item->durationType().type()));
     119              : 
     120        66608 :     double yOff = item->offset().y();
     121        66608 :     const Staff* stf = item->staff();
     122        66608 :     const StaffType* st = stf ? stf->staffTypeForElement(item) : 0;
     123        66608 :     double lineDist = st ? st->lineDistance().val() : 1.0;
     124        66608 :     int userLine   = RealIsNull(yOff) ? 0 : lrint(yOff / (lineDist * spatium));
     125        66608 :     int lines      = st ? st->lines() : 5;
     126              : 
     127        66608 :     int naturalLine = computeNaturalLine(lines); // Measured in 1sp steps
     128        66608 :     int voiceOffset = computeVoiceOffset(item, ldata); // Measured in 1sp steps
     129        66608 :     int wholeRestOffset = computeWholeOrBreveRestOffset(item, voiceOffset, lines);
     130        66608 :     int finalLine = naturalLine + voiceOffset + wholeRestOffset;
     131              : 
     132        66608 :     ldata->sym = item->getSymbol(item->durationType().type(), finalLine + userLine, lines);
     133              : 
     134        66608 :     ldata->setPosY(finalLine * lineDist * spatium);
     135        66608 :     if (!item->shouldNotBeDrawn()) {
     136        64946 :         fillShape(item, ldata, ctx.conf());
     137              :     }
     138              : 
     139        66608 :     const_cast<Rest*>(item)->checkDots();
     140        66608 :     double visibleX = item->symWidthNoLedgerLines(ldata) + ctx.conf().styleAbsolute(Sid::dotNoteDistance) * item->mag();
     141        66608 :     double visibleDX = ctx.conf().styleAbsolute(Sid::dotDotDistance) * item->mag();
     142        66608 :     double invisibleX = item->symWidthNoLedgerLines(ldata);
     143        66608 :     double y = item->dotLine() * item->spatium() * .5;
     144        66778 :     for (NoteDot* dot : item->dotList()) {
     145          170 :         NoteDot::LayoutData* dotldata = dot->mutldata();
     146          170 :         TLayout::layoutNoteDot(dot, dotldata);
     147          170 :         if (dot->visible()) {
     148          147 :             dotldata->setPos(visibleX, y);
     149          147 :             visibleX += visibleDX;
     150              :         } else {
     151           23 :             invisibleX +=  0.1 * item->spatium();
     152           23 :             dotldata->setPos(invisibleX, y);
     153           23 :             invisibleX += item->symWidth(SymId::augmentationDot) * dot->mag();
     154              :         }
     155              :     }
     156              : }
     157              : 
     158       304627 : void RestLayout::fillShape(const Rest* item, Rest::LayoutData* ldata, const LayoutConfiguration& conf)
     159              : {
     160       304627 :     switch (item->type()) {
     161       304090 :     case ElementType::REST:
     162       304090 :         fillShape(toRest(item), static_cast<Rest::LayoutData*>(ldata));
     163       304090 :         break;
     164          537 :     case ElementType::MMREST:
     165          537 :         fillShape(static_cast<const MMRest*>(item), static_cast<MMRest::LayoutData*>(ldata), conf);
     166          537 :         break;
     167            0 :     default:
     168            0 :         UNREACHABLE;
     169              :         return;
     170              :     }
     171       304627 : }
     172              : 
     173        87846 : void RestLayout::resolveVerticalRestConflicts(LayoutContext& ctx, Segment* segment, staff_idx_t staffIdx)
     174              : {
     175        87846 :     std::vector<Rest*> rests;
     176        87846 :     std::vector<Chord*> chords;
     177              : 
     178        87846 :     collectChordsAndRest(segment, staffIdx, chords, rests);
     179              : 
     180        87846 :     if (rests.empty()) {
     181        77679 :         return;
     182              :     }
     183              : 
     184        10167 :     collectChordsOverlappingRests(segment, staffIdx, chords);
     185              : 
     186        21262 :     for (Rest* rest : rests) {
     187        11095 :         rest->verticalClearance().reset();
     188              :     }
     189              : 
     190        10167 :     const Staff* staff = ctx.dom().staff(staffIdx);
     191        10167 :     if (!chords.empty()) {
     192          640 :         resolveRestVSChord(rests, chords, staff, segment);
     193              :     }
     194              : 
     195        10167 :     if (rests.size() < 2) {
     196         9280 :         return;
     197              :     }
     198              : 
     199          887 :     resolveRestVSRest(rests, staff, segment, ctx);
     200       174805 : }
     201              : 
     202          649 : void RestLayout::resolveRestVSChord(std::vector<Rest*>& rests, std::vector<Chord*>& chords, const Staff* staff, Segment* segment)
     203              : {
     204          649 :     Fraction tick = segment->tick();
     205          649 :     int lines = staff->lines(tick);
     206          649 :     double spatium = staff->spatium(tick);
     207          649 :     double lineDistance = staff->lineDistance(tick) * spatium;
     208              : 
     209         1320 :     for (Rest* rest : rests) {
     210          671 :         if (!rest->visible() || !rest->autoplace()) {
     211          132 :             continue;
     212              :         }
     213          539 :         bool isWholeOrHalf = rest->isWholeRest() || rest->durationType() == DurationType::V_HALF;
     214          539 :         double minRestToChordClearance = isWholeOrHalf ? 0.55 * spatium : 0.35 * spatium;
     215          539 :         RestVerticalClearance& restVerticalClearance = rest->verticalClearance();
     216         1088 :         for (Chord* chord : chords) {
     217          549 :             if (!chord->visible() || !chord->autoplace()) {
     218          161 :                 continue;
     219              :             }
     220              : 
     221          549 :             bool restAbove = rest->voice() < chord->voice() || (chord->slash() && !(rest->voice() % 2));
     222          549 :             int upSign = restAbove ? -1 : 1;
     223          549 :             double restYOffset = rest->offset().y();
     224          549 :             bool ignoreYOffset = (restAbove && restYOffset > 0) || (!restAbove && restYOffset < 0);
     225          549 :             PointF offset = ignoreYOffset ? PointF(0, restYOffset) : PointF(0, 0);
     226              : 
     227          549 :             Shape chordShape = chord->shape().translate(chord->pos());
     228          549 :             chordShape.removeInvisibles();
     229         1098 :             chordShape.removeTypes({ ElementType::ARPEGGIO });
     230          549 :             if (chordShape.empty()) {
     231            0 :                 continue;
     232              :             }
     233              : 
     234          549 :             double clearance = 0.0;
     235          549 :             Shape restShape = rest->shape().translate(rest->pos() - offset);
     236          549 :             if (chord->segment() == rest->segment()) {
     237          467 :                 clearance = restAbove
     238          467 :                             ? restShape.verticalClearance(chordShape)
     239          324 :                             : chordShape.verticalClearance(restShape);
     240              :             } else {
     241           82 :                 Note* limitNote = restAbove ? chord->upNote() : chord->downNote();
     242           82 :                 RectF noteShape = limitNote->symBbox(limitNote->noteHead()).translated(limitNote->pos());
     243           82 :                 clearance = restAbove ? noteShape.top() - restShape.bottom() : restShape.top() - noteShape.bottom();
     244           82 :                 minRestToChordClearance = 0.0;
     245              :             }
     246              : 
     247          549 :             double margin = clearance - minRestToChordClearance;
     248          549 :             int marginInSteps = floor(margin / lineDistance);
     249          549 :             if (restAbove) {
     250          172 :                 restVerticalClearance.setBelow(marginInSteps);
     251              :             } else {
     252          377 :                 restVerticalClearance.setAbove(marginInSteps);
     253              :             }
     254          549 :             if (margin > 0) {
     255          161 :                 continue;
     256              :             }
     257              : 
     258          388 :             rest->verticalClearance().setLocked(true);
     259          388 :             bool outAboveStaff = restAbove && restShape.bottom() + margin < minRestToChordClearance;
     260          388 :             bool outBelowStaff = !restAbove && restShape.top() - margin > (lines - 1) * lineDistance - minRestToChordClearance;
     261          388 :             bool useHalfSpaceSteps = (outAboveStaff || outBelowStaff) && !isWholeOrHalf;
     262              :             double yMove;
     263          388 :             if (useHalfSpaceSteps) {
     264          186 :                 int steps = ceil(std::abs(margin) / (lineDistance / 2));
     265          186 :                 yMove = steps * lineDistance / 2 * upSign;
     266          186 :                 rest->mutldata()->moveY(yMove);
     267              :             } else {
     268          202 :                 int steps = ceil(std::abs(margin) / lineDistance);
     269          202 :                 yMove = steps * lineDistance * upSign;
     270          202 :                 rest->mutldata()->moveY(yMove);
     271              :             }
     272          388 :             for (Rest* mergedRest : rest->ldata()->mergedRests) {
     273            0 :                 mergedRest->mutldata()->moveY(yMove);
     274              :             }
     275          388 :             if (isWholeOrHalf) {
     276           84 :                 updateSymbol(rest, rest->mutldata()); // Because it may need to use the symbol with ledger line now
     277              :             }
     278          710 :         }
     279              :     }
     280          649 : }
     281              : 
     282          896 : void RestLayout::resolveRestVSRest(std::vector<Rest*>& rests, const Staff* staff,
     283              :                                    Segment* segment, LayoutContext& ctx,
     284              :                                    bool considerBeams)
     285              : {
     286          896 :     if (rests.empty()) {
     287            0 :         return;
     288              :     }
     289              : 
     290          896 :     Fraction tick = segment->tick();
     291          896 :     double spatium = staff->spatium(tick);
     292          896 :     double lineDistance = staff->lineDistance(tick) * spatium;
     293          896 :     int lines = staff->lines(tick);
     294          896 :     const double minRestToRestClearance = 0.55 * spatium;
     295              : 
     296         1824 :     for (size_t i = 0; i < rests.size() - 1; ++i) {
     297          928 :         Rest* rest1 = rests[i];
     298          928 :         if (!rest1->visible() || !rest1->autoplace()) {
     299          196 :             continue;
     300              :         }
     301              : 
     302          919 :         RestVerticalClearance& rest1Clearance = rest1->verticalClearance();
     303          919 :         Shape shape1 = rest1->shape().translate(rest1->pos() - rest1->offset());
     304              : 
     305          919 :         Rest* rest2 = rests[i + 1];
     306          919 :         if (!rest2->visible() || !rest2->autoplace()) {
     307           45 :             continue;
     308              :         }
     309              : 
     310          874 :         if (muse::contains(rest1->ldata()->mergedRests, rest2) || muse::contains(rest2->ldata()->mergedRests, rest1)) {
     311            0 :             continue;
     312              :         }
     313              : 
     314          874 :         Shape shape2 = rest2->shape().translate(rest2->pos() - rest2->offset());
     315          874 :         RestVerticalClearance& rest2Clearance = rest2->verticalClearance();
     316              : 
     317              :         double clearance;
     318          874 :         bool firstAbove = rest1->voice() < rest2->voice();
     319          874 :         if (firstAbove) {
     320          874 :             clearance = shape1.verticalClearance(shape2);
     321              :         } else {
     322            0 :             clearance = shape2.verticalClearance(shape1);
     323              :         }
     324          874 :         double margin = clearance - minRestToRestClearance;
     325          874 :         int marginInSteps = floor(margin / lineDistance);
     326          874 :         if (firstAbove) {
     327          874 :             rest1Clearance.setBelow(marginInSteps);
     328          874 :             rest2Clearance.setAbove(marginInSteps);
     329              :         } else {
     330            0 :             rest1Clearance.setAbove(marginInSteps);
     331            0 :             rest2Clearance.setBelow(marginInSteps);
     332              :         }
     333              : 
     334          874 :         if (margin > 0) {
     335          142 :             continue;
     336              :         }
     337              : 
     338          732 :         int steps = ceil(std::abs(margin) / lineDistance);
     339              :         // Move the two rests away from each other
     340          732 :         int step1 = floor(double(steps) / 2);
     341          732 :         int step2 = ceil(double(steps) / 2);
     342          732 :         int maxStep1 = firstAbove ? rest1Clearance.above() : rest1Clearance.below();
     343          732 :         int maxStep2 = firstAbove ? rest2Clearance.below() : rest2Clearance.above();
     344          732 :         maxStep1 = std::max(maxStep1, 0);
     345          732 :         maxStep2 = std::max(maxStep2, 0);
     346          732 :         if (step1 > maxStep1) {
     347           28 :             step2 += step1 - maxStep1; // First rest is locked, try move the second more
     348              :         }
     349          732 :         if (step2 > maxStep2) {
     350            5 :             step1 += step2 - maxStep2; // Second rest is locked, try move the first more
     351              :         }
     352          732 :         step1 = std::min(step1, maxStep1);
     353          732 :         step2 = std::min(step2, maxStep2);
     354          732 :         rest1->mutldata()->moveY(step1 * lineDistance * (firstAbove ? -1 : 1));
     355          732 :         rest2->mutldata()->moveY(step2 * lineDistance * (firstAbove ? 1 : -1));
     356              : 
     357          732 :         Beam* beam1 = rest1->beam();
     358          732 :         Beam* beam2 = rest2->beam();
     359          732 :         if (beam1 && beam2 && considerBeams) {
     360            0 :             shape1 = rest1->shape().translate(rest1->pos() - rest1->offset());
     361            0 :             shape2 = rest2->shape().translate(rest2->pos() - rest2->offset());
     362              : 
     363            0 :             ChordRest* beam1Start = beam1->elements().front();
     364            0 :             ChordRest* beam1End = beam1->elements().back();
     365            0 :             double y1Start = BeamLayout::chordBeamAnchorY(beam1, beam1Start) - beam1Start->pagePos().y();
     366            0 :             double y1End = BeamLayout::chordBeamAnchorY(beam1, beam1End) - beam1End->pagePos().y();
     367            0 :             double beam1Ymid = 0.5 * (y1Start + y1End);
     368              : 
     369            0 :             ChordRest* beam2Start = beam2->elements().front();
     370            0 :             ChordRest* beam2End = beam2->elements().back();
     371            0 :             double y2Start = BeamLayout::chordBeamAnchorY(beam2, beam2Start) - beam2Start->pagePos().y();
     372            0 :             double y2End = BeamLayout::chordBeamAnchorY(beam2, beam2End) - beam2End->pagePos().y();
     373            0 :             double beam2Ymid = 0.5 * (y2Start + y2End);
     374              : 
     375            0 :             double centerY = 0.5 * (beam1Ymid + beam2Ymid);
     376              : 
     377            0 :             double upperBound = shape1.bottom();
     378            0 :             double lowerBound = shape2.top();
     379            0 :             int steps2 = 0;
     380            0 :             if (centerY < upperBound) {
     381            0 :                 steps2 = floor((centerY - upperBound) / lineDistance);
     382            0 :             } else if (centerY > lowerBound) {
     383            0 :                 steps2 = ceil((centerY - lowerBound) / lineDistance);
     384              :             }
     385            0 :             double moveY = steps2 * lineDistance;
     386            0 :             rest1->mutldata()->moveY(moveY);
     387            0 :             rest2->mutldata()->moveY(moveY);
     388            0 :             shape1.translate(PointF(0.0, moveY));
     389            0 :             shape2.translate(PointF(0.0, moveY));
     390              : 
     391            0 :             double halfLineDistance = 0.5 * lineDistance;
     392            0 :             if (shape1.bottom() < -halfLineDistance) {
     393            0 :                 rest1->mutldata()->moveY(halfLineDistance);
     394            0 :             } else if (centerY >= (lines - 1) * lineDistance + halfLineDistance) {
     395            0 :                 rest2->mutldata()->moveY(-halfLineDistance);
     396              :             }
     397              : 
     398            0 :             rest1->verticalClearance().setLocked(true);
     399            0 :             rest2->verticalClearance().setLocked(true);
     400            0 :             TLayout::layoutBeam(beam1, ctx);
     401            0 :             TLayout::layoutBeam(beam2, ctx);
     402              :         }
     403              : 
     404          732 :         bool rest1IsWholeOrHalf = rest1->isWholeRest() || rest1->durationType() == DurationType::V_HALF;
     405          732 :         bool rest2IsWholeOrHalf = rest2->isWholeRest() || rest2->durationType() == DurationType::V_HALF;
     406              : 
     407          732 :         if (rest1IsWholeOrHalf) {
     408           35 :             updateSymbol(rest1, rest1->mutldata());
     409              :         }
     410          732 :         if (rest2IsWholeOrHalf) {
     411           37 :             updateSymbol(rest2, rest2->mutldata());
     412              :         }
     413         1061 :     }
     414              : }
     415              : 
     416         7388 : void RestLayout::alignRests(const System* system, LayoutContext& ctx)
     417              : {
     418         7388 :     RestGroups restGroups = computeRestGroups(system, ctx);
     419              : 
     420        20516 :     for (RestGroup& group : restGroups) {
     421        13128 :         if (group.size() <= 1) {
     422        12786 :             continue;
     423              :         }
     424              : 
     425          342 :         Rest* firstRest = group.front();
     426          342 :         const bool alignUpwards = firstRest->voice() == 0;
     427          342 :         const double lineDist = firstRest->staff()->lineDistance(firstRest->tick()) * firstRest->spatium();
     428              : 
     429          342 :         double yOuterRest = alignUpwards ? DBL_MAX : -DBL_MAX;
     430         1286 :         for (Rest* rest : group) {
     431          944 :             double yRest = rest->ldata()->pos().y();
     432          944 :             yOuterRest = alignUpwards ? std::min(yOuterRest, yRest) : std::max(yOuterRest, yRest);
     433              :         }
     434         1286 :         for (Rest* rest : group) {
     435          944 :             double yCur = rest->ldata()->pos().y();
     436          944 :             double yResult = yOuterRest;
     437          944 :             double restVertClearance = lineDist * (alignUpwards ? rest->verticalClearance().above() : rest->verticalClearance().below());
     438          944 :             double vertPadding = lineDist;
     439          944 :             restVertClearance = std::max(0.0, restVertClearance - vertPadding);
     440          944 :             double requiredMove = yOuterRest - yCur;
     441          944 :             if (std::abs(requiredMove) > restVertClearance) {
     442            3 :                 yResult = alignUpwards ? yCur - restVertClearance : yCur + restVertClearance;
     443              :             }
     444          944 :             rest->mutldata()->setPosY(yResult);
     445          944 :             if (rest->isWholeRest() || rest->durationType() == DurationType::V_HALF) {
     446           70 :                 updateSymbol(rest, rest->mutldata());
     447              :             }
     448              :         }
     449              :     }
     450         7388 : }
     451              : 
     452         7388 : RestGroups RestLayout::computeRestGroups(const System* system, LayoutContext& ctx)
     453              : {
     454         7388 :     RestGroups restGroups;
     455              : 
     456        18583 :     for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
     457        58219 :         for (const MeasureBase* mb : system->measures()) {
     458        47024 :             if (!(mb->isMeasure() && toMeasure(mb)->hasVoices(staffIdx))) {
     459        45704 :                 continue;
     460              :             }
     461              : 
     462         1320 :             const Measure* measure = toMeasure(mb);
     463         1320 :             track_idx_t sTrack = staff2track(staffIdx);
     464              : 
     465         1320 :             InterruptionPoints interruptionPoints = computeInterruptionPoints(measure, staffIdx);
     466              : 
     467         6600 :             for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
     468         5280 :                 restGroups.push_back(RestGroup());
     469              : 
     470         5280 :                 std::list<Fraction>& interruptionPointsOnThisVoice = interruptionPoints[voice];
     471         5280 :                 track_idx_t track = sTrack + voice;
     472              : 
     473        31864 :                 for (const Segment* segment = measure->first(SegmentType::ChordRest); segment;
     474        26584 :                      segment = segment->next(SegmentType::ChordRest)) {
     475        26584 :                     if (interruptionPointsOnThisVoice.size() > 0 && segment->rtick() >= interruptionPointsOnThisVoice.front()) {
     476         7848 :                         restGroups.push_back(RestGroup());
     477         7848 :                         interruptionPointsOnThisVoice.pop_front();
     478              :                     }
     479              : 
     480        26584 :                     EngravingItem* item = segment->element(track);
     481        26584 :                     if (!(item && item->isRest() && item->visible())) {
     482        23981 :                         continue;
     483              :                     }
     484         2603 :                     Rest* rest = toRest(item);
     485         2603 :                     if (rest->staffMove() == 0 && !rest->isGap() && rest->alignWithOtherRests()) {
     486         2457 :                         restGroups.back().push_back(rest);
     487              :                     }
     488              :                 }
     489              :             }
     490         1320 :         }
     491              :     }
     492              : 
     493         7388 :     return restGroups;
     494            0 : }
     495              : 
     496         1320 : InterruptionPoints RestLayout::computeInterruptionPoints(const Measure* measure, staff_idx_t staffIdx)
     497              : {
     498              :     // Start with sets to get automatic uniqueness and ordering
     499         1320 :     std::array<std::set<Fraction>, VOICES> interruptionPointSets;
     500              : 
     501         1320 :     track_idx_t sTrack = staffIdx * VOICES;
     502         1320 :     track_idx_t eTrack = sTrack + VOICES;
     503              : 
     504              :     // Compute all-voices interruptions
     505         7966 :     for (const Segment* segment = measure->first(SegmentType::ChordRest); segment; segment = segment->next(SegmentType::ChordRest)) {
     506        32059 :         for (track_idx_t track = sTrack; track < eTrack; ++track) {
     507        25787 :             EngravingItem* item = segment->element(track);
     508        25787 :             if (!item) {
     509        14721 :                 continue;
     510              :             }
     511        11066 :             const bool gapRest = item->isRest() && toRest(item)->isGap();
     512              :             // TODO: The fact that "merged" rests exists in both voices and are just graphically overlapped is a messy way of
     513              :             // doing it and way too fragile, because it means that any logic that may move one rest can break the "merging".
     514              :             // A more solid way of merging rests would be to *delete* the second voice rests, i.e. turn them into gap rests [M.S.].
     515        11066 :             const bool hasMergedRest = item->isRest() && !toRest(item)->ldata()->mergedRests.empty();
     516        11066 :             const bool invisible = item->isChord() ? toChord(item)->allElementsInvisible() : !item->visible();
     517        11066 :             if (gapRest || hasMergedRest || invisible) {
     518         1870 :                 for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
     519         1496 :                     interruptionPointSets[voice].insert(segment->rtick());
     520         1496 :                     interruptionPointSets[voice].insert(segment->rtick() + toChordRest(item)->actualTicks());
     521              :                 }
     522          374 :                 break;
     523              :             }
     524              :         }
     525              :     }
     526              : 
     527        10495 :     auto interruptHere = [&](ChordRest* chordRest) {
     528        10495 :         Fraction crTicks = chordRest->ticks();
     529        10495 :         if (crTicks > Fraction(1, 8)) {
     530         6163 :             return true;
     531              :         }
     532              : 
     533         4332 :         BeamMode beamModeChordRest = Groups::baseBeamMode(chordRest);
     534         4332 :         switch (beamModeChordRest) {
     535          386 :         case BeamMode::BEGIN:
     536              :             // Beam certainly starts here
     537          386 :             return true;
     538          148 :         case BeamMode::BEGIN16:
     539              :         case BeamMode::BEGIN32:
     540              :         case BeamMode::MID:
     541              :         case BeamMode::END:
     542              :             // Beam certainly doesn not start here
     543          148 :             return false;
     544         3798 :         default:
     545         3798 :             break;
     546              :         }
     547              : 
     548              :         // If BeamMode is AUTO or NONE check measure beaming
     549         3798 :         const Groups& groups = chordRest->staff()->group(chordRest->tick());
     550         3798 :         BeamMode beamModeMeasure = groups.beamMode(chordRest->rtick().ticks(), chordRest->durationType().type());
     551         3798 :         return beamModeMeasure == BeamMode::BEGIN;
     552              :     };
     553              : 
     554              :     // Compute voice-spefic interruptions
     555         6600 :     for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
     556         5280 :         track_idx_t track = sTrack + voice;
     557        31864 :         for (const Segment* segment = measure->first(SegmentType::ChordRest); segment; segment = segment->next(SegmentType::ChordRest)) {
     558        26584 :             if (ChordRest* thisCR = toChordRest(segment->element(track))) {
     559        11143 :                 if (thisCR->isRest()) {
     560         2808 :                     const Segment* prevSegment = segment->prevWithElementsOnTrack(track);
     561         2808 :                     bool hasAdjacentRest = prevSegment && prevSegment->element(track)->isRest();
     562         2808 :                     if (hasAdjacentRest) {
     563          648 :                         continue;
     564              :                     }
     565              :                 }
     566        10495 :                 if (interruptHere(thisCR)) {
     567         6561 :                     interruptionPointSets[voice].insert(segment->rtick());
     568              :                 }
     569              :             }
     570              :         }
     571              :     }
     572              : 
     573              :     // Convert sets to lists
     574         1320 :     InterruptionPoints interruptionPoints;
     575         6600 :     for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
     576         5280 :         const std::set<Fraction>& set = interruptionPointSets[voice];
     577        10560 :         interruptionPoints[voice] = std::list(set.begin(), set.end());
     578              :     }
     579              : 
     580         1320 :     return interruptionPoints;
     581         1320 : }
     582              : 
     583         7388 : void RestLayout::checkFullMeasureRestCollisions(const System* system, LayoutContext& ctx)
     584              : {
     585        18583 :     for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
     586        58219 :         for (MeasureBase* mb : system->measures()) {
     587        47024 :             if (!(mb->isMeasure() && toMeasure(mb)->hasVoices(staffIdx))) {
     588        46863 :                 continue;
     589              :             }
     590              : 
     591         1320 :             Measure* measure = toMeasure(mb);
     592         1320 :             Rest* fullMeasureRest = nullptr;
     593              : 
     594         1320 :             Segment* firstCRSeg = measure->first(SegmentType::ChordRest);
     595         1320 :             IF_ASSERT_FAILED(firstCRSeg) {
     596            0 :                 continue;
     597              :             }
     598         1320 :             track_idx_t startTrack = staff2track(staffIdx);
     599         1320 :             track_idx_t endTrack = startTrack + VOICES;
     600         5949 :             for (track_idx_t track = startTrack; track < endTrack; ++track) {
     601         4792 :                 EngravingItem* item = firstCRSeg->element(track);
     602         4792 :                 if (item && item->isRest() && toRest(item)->isFullMeasureRest()) {
     603          163 :                     fullMeasureRest = toRest(item);
     604          163 :                     break;
     605              :                 }
     606              :             }
     607              : 
     608         1320 :             if (!fullMeasureRest) {
     609         1157 :                 continue;
     610              :             }
     611              : 
     612          163 :             double xRest = fullMeasureRest->pagePos().x() - system->pagePos().x();
     613          163 :             Shape restShape = fullMeasureRest->shape().translate(PointF(xRest, fullMeasureRest->ldata()->pos().y()));
     614              : 
     615          163 :             Shape measureShape;
     616         1195 :             for (const Segment& segment : measure->segments()) {
     617         1032 :                 if (!segment.isActive()) {
     618           31 :                     continue;
     619              :                 }
     620         1001 :                 double xSegment = segment.pagePos().x() - system->pagePos().x();
     621         1001 :                 measureShape.add(segment.staffShape(staffIdx).translated(PointF(xSegment, 0.0)));
     622              :             }
     623          163 :             measureShape.remove_if([] (const ShapeElement& shapeEl) {
     624         1917 :                 const EngravingItem* shapeItem = shapeEl.item();
     625         3826 :                 return shapeItem && ((shapeItem->isRest() && toRest(shapeItem)->isFullMeasureRest())
     626         3826 :                                      || shapeItem->isBarLine() || shapeItem->isAccidental() || shapeItem->isFermata());
     627              :             });
     628              : 
     629          163 :             if (measureShape.size() == 0) {
     630            2 :                 continue;
     631              :             }
     632              : 
     633          161 :             const double spatium = fullMeasureRest->spatium();
     634          161 :             const double lineDist = fullMeasureRest->staff()->lineDistance(fullMeasureRest->tick()) * spatium;
     635          161 :             const double minHorizontalDistance = 4 * spatium;
     636          161 :             const double minVertClearance = 0.75 * spatium;
     637              : 
     638          161 :             bool alignAbove = fullMeasureRest->voice() == 0;
     639          161 :             double verticalClearance = alignAbove ? restShape.verticalClearance(measureShape, minHorizontalDistance)
     640            1 :                                        : measureShape.verticalClearance(restShape, minHorizontalDistance);
     641              : 
     642          161 :             if (verticalClearance < minVertClearance) {
     643           65 :                 double diff = minVertClearance - verticalClearance;
     644           65 :                 int stepMoves = std::ceil(diff / lineDist);
     645           65 :                 double yMove = stepMoves * lineDist;
     646           65 :                 fullMeasureRest->mutldata()->moveY(alignAbove ? -yMove : yMove);
     647           65 :                 updateSymbol(fullMeasureRest, fullMeasureRest->mutldata());
     648              :             }
     649          165 :         }
     650              :     }
     651         7388 : }
     652              : 
     653       304090 : void RestLayout::fillShape(const Rest* item, Rest::LayoutData* ldata)
     654              : {
     655       304090 :     Shape shape(Shape::Type::Composite);
     656              : 
     657       304090 :     if ((!item->isGap() || item->debugDrawGap()) && !item->shouldNotBeDrawn()) {
     658       298079 :         shape.add(ChordLayout::chordRestShape(item));
     659       298079 :         shape.add(item->symBbox(ldata->sym), item);
     660       298840 :         for (const NoteDot* dot : item->dotList()) {
     661          761 :             if (dot->addToSkyline()) {
     662          750 :                 shape.add(item->symBbox(SymId::augmentationDot).translated(dot->pos()), dot);
     663              :             }
     664              :         }
     665              :     }
     666              : 
     667       304090 :     for (const EngravingItem* e : item->el()) {
     668            0 :         if (e->addToSkyline()) {
     669            0 :             shape.add(e->shape().translate(e->pos()));
     670              :         }
     671              :     }
     672              : 
     673       304090 :     ldata->setShape(shape);
     674       304090 : }
     675              : 
     676          537 : void RestLayout::fillShape(const MMRest* item, MMRest::LayoutData* ldata, const LayoutConfiguration& conf)
     677              : {
     678          537 :     Shape shape(Shape::Type::Composite);
     679              : 
     680          537 :     double vStrokeHeight = conf.styleAbsolute(Sid::mmRestHBarVStrokeHeight);
     681          537 :     shape.add(RectF(0.0, -(vStrokeHeight * .5), ldata->restWidth, vStrokeHeight), item);
     682          537 :     if (item->showNumber()) {
     683          525 :         shape.add(item->numberRect().translated(item->numberPos()), item);
     684              :     }
     685              : 
     686          537 :     ldata->setShape(shape);
     687          537 : }
     688              : 
     689        66608 : int RestLayout::computeNaturalLine(int lines)
     690              : {
     691        66608 :     int line = (lines % 2) ? floor(double(lines) / 2) : ceil(double(lines) / 2);
     692        66608 :     return line;
     693              : }
     694              : 
     695        66608 : int RestLayout::computeVoiceOffset(const Rest* item, Rest::LayoutData* ldata)
     696              : {
     697        66608 :     ldata->mergedRests.clear();
     698        66608 :     Segment* s = item->segment();
     699        66608 :     Measure* measure = item->measure();
     700        66608 :     bool offsetVoices = s && measure && (item->voice() > 0 || measure->hasVoices(item->staffIdx(), item->tick(), item->actualTicks()));
     701        66608 :     if (offsetVoices && item->voice() == 0) {
     702              :         // do not offset voice 1 rest if there exists a matching invisible rest in voice 2;
     703         1278 :         EngravingItem* e = s->element(item->track() + 1);
     704         1278 :         if (e && e->isRest() && !e->visible() && !toRest(e)->isGap()) {
     705            0 :             Rest* r = toRest(e);
     706            0 :             if (r->globalTicks() == item->globalTicks()) {
     707            0 :                 offsetVoices = false;
     708              :             }
     709              :         }
     710              :     }
     711              : 
     712        66608 :     if (offsetVoices && item->voice() < 2) {
     713              :         // in slash notation voices 1 and 2 are not offset outside the staff
     714              :         // if the staff contains slash notation then only offset rests in voices 3 and 4
     715         2611 :         track_idx_t baseTrack = item->staffIdx() * VOICES;
     716        13034 :         for (voice_idx_t v = 0; v < VOICES; ++v) {
     717        10433 :             EngravingItem* e = s->element(baseTrack + v);
     718        10433 :             if (e && e->isChord() && toChord(e)->slash()) {
     719           10 :                 offsetVoices = false;
     720           10 :                 break;
     721              :             }
     722              :         }
     723              :     }
     724              : 
     725        66608 :     if (offsetVoices && item->staff()->shouldMergeMatchingRests()) {
     726              :         // automatically merge matching rests if nothing in any other voice
     727              :         // this is not always the right thing to do do, but is useful in choral music
     728              :         // and can be enabled via a staff property
     729            0 :         bool matchFound = false;
     730            0 :         track_idx_t baseTrack = item->staffIdx() * VOICES;
     731            0 :         for (voice_idx_t v = 0; v < VOICES; ++v) {
     732            0 :             if (v == item->voice()) {
     733            0 :                 continue;
     734              :             }
     735            0 :             EngravingItem* e = s->element(baseTrack + v);
     736              :             // try to find match in any other voice
     737            0 :             if (e) {
     738            0 :                 if (e->isRest()) {
     739            0 :                     Rest* r = toRest(e);
     740            0 :                     if (r->globalTicks() == item->globalTicks() && r->durationType() == item->durationType()) {
     741            0 :                         matchFound = true;
     742            0 :                         ldata->mergedRests.push_back(r);
     743            0 :                         continue;
     744              :                     }
     745              :                 }
     746              :                 // no match found; no sense looking for anything else
     747            0 :                 matchFound = false;
     748            0 :                 break;
     749              :             }
     750              :         }
     751            0 :         if (matchFound) {
     752            0 :             offsetVoices = false;
     753              :         }
     754              :     }
     755              : 
     756        66608 :     if (!offsetVoices) {
     757        63902 :         return 0;
     758              :     }
     759              : 
     760         2706 :     bool up = item->voice() == 0 || item->voice() == 2;
     761         2706 :     int upSign = up ? -1 : 1;
     762         2706 :     int voiceLineOffset = item->style().styleB(Sid::multiVoiceRestTwoSpaceOffset) ? 2 : 1;
     763              : 
     764         2706 :     return voiceLineOffset * upSign;
     765              : }
     766              : 
     767        66608 : int RestLayout::computeWholeOrBreveRestOffset(const Rest* item, int voiceOffset, int lines)
     768              : {
     769        66608 :     int lineMove = 0;
     770        66608 :     if (item->isWholeRest()) {
     771        55812 :         bool moveToLineAbove = (lines > 5)
     772        55812 :                                || ((lines > 1 || voiceOffset == -1 || voiceOffset == 2) && !(voiceOffset == -2 || voiceOffset == 1));
     773        55812 :         if (moveToLineAbove) {
     774        55710 :             lineMove = -1;
     775              :         }
     776        10796 :     } else if (item->isBreveRest() && lines == 1) {
     777            0 :         lineMove = 1;
     778              :     }
     779              : 
     780        66608 :     return lineMove;
     781              : }
     782              : 
     783          291 : void RestLayout::updateSymbol(const Rest* item, Rest::LayoutData* ldata)
     784              : {
     785          291 :     Fraction t = item->tick();
     786          291 :     Staff* st = item->staff();
     787          291 :     double lineDistance = st->lineDistance(t) * item->spatium();
     788          291 :     int lines = st->lines(t);
     789              : 
     790          291 :     double y = item->pos().y();
     791          291 :     int line = floor(y / lineDistance);
     792              : 
     793          291 :     ldata->sym = item->getSymbol(item->durationType().type(), line, lines);
     794          291 : }
     795              : } // namespace mu::engraving::rendering::score
        

Generated by: LCOV version 2.0-1