LCOV - code coverage report
Current view: top level - src/notation/utilities - scorerangeutilities.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 170 0
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) 2024 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 "scorerangeutilities.h"
      24              : 
      25              : #include "engraving/dom/box.h"
      26              : #include "engraving/dom/measure.h"
      27              : #include "engraving/dom/page.h"
      28              : #include "engraving/dom/score.h"
      29              : #include "engraving/dom/segment.h"
      30              : #include "engraving/dom/staff.h"
      31              : #include "engraving/dom/system.h"
      32              : 
      33              : using namespace mu::notation;
      34              : using namespace mu::engraving;
      35              : 
      36            0 : static const Box* getLeadingTrailingBox(const System* system, bool leading)
      37              : {
      38            0 :     IF_ASSERT_FAILED(system) {
      39            0 :         return nullptr;
      40              :     }
      41            0 :     const MeasureBase* mb = leading ? system->first() : system->last();
      42            0 :     return mb && mb->isBox() ? toBox(mb) : nullptr;
      43              : }
      44              : 
      45              : // Unites the bounding rects in a run of consecutive boxes (until "limit")...
      46            0 : static RectF boxRunBoundingRect(const Box* box, bool forwards, const Box* limit = nullptr)
      47              : {
      48            0 :     RectF rect = box->canvasBoundingRect();
      49            0 :     const System* system = box->system();
      50              : 
      51            0 :     const MeasureBase* mb = box;
      52            0 :     while (mb != limit) {
      53            0 :         mb = forwards ? mb->nextMM() : mb->prevMM();
      54            0 :         if (!mb || !mb->isBox() || mb->system() != system) {
      55            0 :             break;
      56              :         }
      57            0 :         rect.unite(mb->canvasBoundingRect());
      58              :     }
      59              : 
      60            0 :     if (!rect.isEmpty()) {
      61            0 :         const double padding = 0.5 * box->spatium();
      62            0 :         rect.adjust(0, -padding, 0, padding);
      63              :     }
      64              : 
      65            0 :     return rect;
      66              : }
      67              : 
      68            0 : static std::set<const System*> boxOnlySystems(const System* startSystem, const System* endSystem)
      69              : {
      70            0 :     std::set<const System*> result;
      71            0 :     IF_ASSERT_FAILED(startSystem) {
      72            0 :         return result;
      73              :     }
      74            0 :     const System* currSystem = startSystem;
      75            0 :     while (currSystem) {
      76            0 :         bool isBoxOnlySystem = true;
      77            0 :         const MeasureBase* mb = currSystem->first();
      78            0 :         while (mb && mb->system() == currSystem) {
      79            0 :             if (!mb->isBox()) {
      80            0 :                 isBoxOnlySystem = false;
      81            0 :                 break;
      82              :             }
      83            0 :             mb = mb->nextMM();
      84              :         }
      85            0 :         if (isBoxOnlySystem) {
      86            0 :             result.emplace(currSystem);
      87              :         }
      88            0 :         if (currSystem == endSystem) {
      89            0 :             break;
      90              :         }
      91            0 :         const MeasureBase* firstInNext = currSystem->last() ? currSystem->last()->nextMM() : nullptr;
      92            0 :         currSystem = firstInNext ? firstInNext->system() : nullptr;
      93              :     }
      94            0 :     return result;
      95            0 : }
      96              : 
      97            0 : std::vector<muse::RectF> ScoreRangeUtilities::boundingArea(const Score* score,
      98              :                                                            const Segment* startSegment, const Segment* endSegment,
      99              :                                                            staff_idx_t startStaffIndex, staff_idx_t endStaffIndex,
     100              :                                                            const engraving::Box* startBox,
     101              :                                                            const engraving::Box* endBox)
     102              : {
     103            0 :     if (!score || !startSegment || !endSegment || startSegment->tick() > endSegment->tick()) {
     104            0 :         return {};
     105              :     }
     106              : 
     107            0 :     const std::vector<RangeSection> sections = splitRangeBySections(startSegment, endSegment);
     108              : 
     109            0 :     std::vector<RectF> result;
     110              : 
     111            0 :     if (sections.empty()) {
     112              :         // Box-only range...
     113            0 :         if (!startBox && !endBox) {
     114            0 :             return result;
     115              :         }
     116            0 :         IF_ASSERT_FAILED(startBox && endBox) {
     117            0 :             return result;
     118              :         }
     119            0 :         const Box* currBox = startBox;
     120            0 :         const System* endSystem = endBox->system();
     121            0 :         while (currBox) {
     122            0 :             const System* currSystem = currBox->system();
     123            0 :             IF_ASSERT_FAILED(currSystem) {
     124            0 :                 break;
     125              :             }
     126            0 :             result.push_back(boxRunBoundingRect(currBox, /*forwards*/ true, endBox));
     127            0 :             if (currSystem == endSystem) {
     128            0 :                 break;
     129              :             }
     130            0 :             const MeasureBase* firstInNext = currSystem->last() ? currSystem->last()->nextMM() : nullptr;
     131            0 :             currBox = firstInNext && firstInNext->isBox() ? toBox(firstInNext) : nullptr;
     132              :         }
     133            0 :         return result;
     134              :     }
     135              : 
     136              :     //! NOTE: endSegment is exclusive, so these are the systems of the first and last segments that are actually inside
     137              :     //! the range...
     138            0 :     const System* firstSegmentSystem = sections.front().system;
     139            0 :     const System* lastSegmentSystem = sections.back().system;
     140              : 
     141              :     // Collect and add systems consisting solely of boxes...
     142            0 :     const System* startSystem = startBox ? startBox->system() : firstSegmentSystem;
     143            0 :     const System* endSystem = endBox ? endBox->system() : lastSegmentSystem;
     144            0 :     const std::set<const System*> boxSystems = startSystem ? boxOnlySystems(startSystem, endSystem) : std::set<const System*>();
     145            0 :     for (const System* boxOnlySystem : boxSystems) {
     146            0 :         const bool useStartBox = startBox && startBox->system() == boxOnlySystem;
     147            0 :         const MeasureBase* first = useStartBox ? startBox : boxOnlySystem->first();
     148              : 
     149            0 :         const bool useEndBox = endBox && endBox->system() == boxOnlySystem;
     150            0 :         const MeasureBase* last = useEndBox ? endBox : boxOnlySystem->last();
     151              : 
     152            0 :         IF_ASSERT_FAILED(first && first->isBox() && last && last->isBox()) {
     153            0 :             continue;
     154              :         }
     155              : 
     156            0 :         result.push_back(boxRunBoundingRect(toBox(first), /*forwards*/ true, toBox(last)));
     157              :     }
     158              : 
     159            0 :     if (!score->linearMode()) {
     160              :         // Handle start/end boxes that exist on a different system to the start/end segment...
     161            0 :         if (startBox && startBox->system() != firstSegmentSystem && boxSystems.find(startBox->system()) == boxSystems.end()) {
     162            0 :             result.push_back(boxRunBoundingRect(startBox, /*forwards*/ true));
     163              :         }
     164            0 :         if (endBox && endBox->system() != lastSegmentSystem && boxSystems.find(endBox->system()) == boxSystems.end()) {
     165            0 :             result.push_back(boxRunBoundingRect(endBox, /*forwards*/ false));
     166              :         }
     167              :     }
     168              : 
     169            0 :     for (size_t i = 0; i < sections.size(); ++i) {
     170            0 :         const RangeSection& section = sections.at(i);
     171              : 
     172            0 :         const staff_idx_t firstStaff = firstVisibleStaffIdx(score, section.system, startStaffIndex);
     173            0 :         const staff_idx_t lastStaff = lastVisibleStaffIdx(score, section.system, endStaffIndex);
     174            0 :         if (firstStaff == muse::nidx || lastStaff == muse::nidx) {
     175            0 :             continue;
     176              :         }
     177              : 
     178            0 :         const Box* sectionStartBox = nullptr;
     179            0 :         if (startBox && startBox->system() == section.system) {
     180              :             // startBox is on this system - it's the "section start box"...
     181            0 :             sectionStartBox = startBox;
     182            0 :         } else if (i > 0 || startBox) {
     183              :             // startBox exists but isn't on this system or the range started earlier - the section start box is the "leading" box...
     184            0 :             sectionStartBox = getLeadingTrailingBox(section.system, /*leading*/ true);
     185              :         }
     186            0 :         const Box* sectionEndBox = nullptr;
     187            0 :         if (endBox && endBox->system() == section.system) {
     188              :             // endBox is on this system - it's the "section end box"...
     189            0 :             sectionEndBox = endBox;
     190            0 :         } else if (i < sections.size() - 1 || endBox) {
     191              :             // endBox exists but isn't on this system or the range is ending later - the section end box is the "trailing" box...
     192            0 :             sectionEndBox = getLeadingTrailingBox(section.system, /*leading*/ false);
     193              :         }
     194              : 
     195            0 :         const SysStaff* segmentFirstStaff = section.system->staff(firstStaff);
     196            0 :         const SysStaff* segmentLastStaff = section.system->staff(lastStaff);
     197              : 
     198            0 :         const Staff* scoreFirstStaff = score->staff(firstStaff);
     199            0 :         const Staff* scoreLastStaff = score->staff(lastStaff);
     200              : 
     201            0 :         const double standardStaffHeight = 4 * scoreFirstStaff->spatium(Fraction(0, 1));
     202            0 :         const double firstStaffHeight = scoreFirstStaff->staffHeight();
     203            0 :         const double lastStaffHeight = scoreLastStaff->staffHeight();
     204              : 
     205            0 :         double topY = 0.0;
     206            0 :         if (firstStaffHeight < standardStaffHeight) {
     207            0 :             const double diff = standardStaffHeight - firstStaffHeight;
     208            0 :             topY -= 0.5 * diff;
     209              :         }
     210              : 
     211            0 :         double bottomY = lastStaffHeight;
     212            0 :         if (lastStaffHeight < standardStaffHeight) {
     213            0 :             const double diff = standardStaffHeight - lastStaffHeight;
     214            0 :             bottomY += 0.5 * diff;
     215              :         }
     216              : 
     217            0 :         double x1 = 0.0;
     218            0 :         if (sectionStartBox) {
     219            0 :             x1 = sectionStartBox->pageBoundingRect().left();
     220              :         } else {
     221            0 :             x1 = section.startSegment->pagePos().x();
     222              :         }
     223              : 
     224            0 :         double x2 = 0.0;
     225            0 :         if (sectionEndBox) {
     226            0 :             x2 = sectionEndBox->pageBoundingRect().right();
     227              :         } else {
     228            0 :             x2 = section.endSegment->pageBoundingRect().right();
     229              :         }
     230              : 
     231            0 :         const int padding = 0.5 * scoreFirstStaff->spatium(startSegment->tick());
     232            0 :         const double y1 = topY + segmentFirstStaff->y() + section.startSegment->pagePos().y() - padding;
     233            0 :         const double y2 = bottomY + segmentLastStaff->y() + section.endSegment->pagePos().y() + padding;
     234              : 
     235            0 :         if (!sectionStartBox && section.startSegment->measure()->firstEnabled() == section.startSegment) {
     236            0 :             x1 = section.startSegment->measure()->pagePos().x();
     237              :         }
     238              : 
     239            0 :         const RectF rect = RectF(PointF(x1, y1), PointF(x2, y2)).translated(section.system->page()->pos());
     240            0 :         result.push_back(rect);
     241              :     }
     242              : 
     243            0 :     return result;
     244            0 : }
     245              : 
     246            0 : std::vector<ScoreRangeUtilities::RangeSection> ScoreRangeUtilities::splitRangeBySections(
     247              :     const Segment* rangeStartSegment,
     248              :     const Segment* rangeEndSegment)
     249              : {
     250            0 :     std::vector<RangeSection> sections;
     251              : 
     252            0 :     const Segment* startSegment = rangeStartSegment;
     253            0 :     const Fraction rangeEndTick = rangeEndSegment->tick();
     254              : 
     255            0 :     for (const Segment* segment = startSegment; segment && segment != rangeEndSegment && segment->tick() < rangeEndTick;) {
     256            0 :         const System* currentSegmentSystem = segment->measure()->system();
     257              : 
     258            0 :         const Segment* nextSegment = segment->next1MMenabled();
     259            0 :         while (nextSegment && !nextSegment->visible()) {
     260            0 :             nextSegment = nextSegment->next1MMenabled();
     261              :         }
     262              : 
     263            0 :         if (!nextSegment) {
     264            0 :             RangeSection section;
     265            0 :             section.system = currentSegmentSystem;
     266            0 :             section.startSegment = startSegment;
     267            0 :             section.endSegment = segment;
     268              : 
     269            0 :             sections.push_back(section);
     270            0 :             break;
     271              :         }
     272              : 
     273            0 :         const System* nextSegmentSystem = nextSegment->measure()->system();
     274            0 :         if (!nextSegmentSystem) {
     275            0 :             const Measure* mmr = nextSegment->measure()->coveringMMRestOrThis();
     276            0 :             if (mmr) {
     277            0 :                 nextSegmentSystem = mmr->system();
     278              :             }
     279            0 :             if (!nextSegmentSystem) {
     280            0 :                 break;
     281              :             }
     282              :         }
     283              : 
     284            0 :         if (nextSegmentSystem != currentSegmentSystem || nextSegment->tick() >= rangeEndTick) {
     285            0 :             RangeSection section;
     286            0 :             section.system = currentSegmentSystem;
     287            0 :             section.startSegment = startSegment;
     288            0 :             section.endSegment = segment;
     289              : 
     290            0 :             sections.push_back(section);
     291            0 :             startSegment = nextSegment;
     292              :         }
     293              : 
     294            0 :         segment = nextSegment;
     295              :     }
     296              : 
     297            0 :     return sections;
     298            0 : }
     299              : 
     300            0 : staff_idx_t ScoreRangeUtilities::firstVisibleStaffIdx(const Score* score, const System* system, staff_idx_t startStaffIndex)
     301              : {
     302            0 :     for (staff_idx_t i = startStaffIndex; i < score->nstaves(); ++i) {
     303            0 :         if (system->staff(i)->show()) {
     304            0 :             return i;
     305              :         }
     306              :     }
     307              : 
     308            0 :     return muse::nidx;
     309              : }
     310              : 
     311            0 : staff_idx_t ScoreRangeUtilities::lastVisibleStaffIdx(const Score*, const System* system, staff_idx_t endStaffIndex)
     312              : {
     313            0 :     for (int i = static_cast<int>(endStaffIndex) - 1; i >= 0; --i) {
     314            0 :         if (system->staff(i)->show()) {
     315            0 :             return static_cast<staff_idx_t>(i);
     316              :         }
     317              :     }
     318              : 
     319            0 :     return muse::nidx;
     320              : }
        

Generated by: LCOV version 2.0-1