LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - scorepageviewlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 90.5 % 116 105
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 "scorepageviewlayout.h"
      23              : 
      24              : #include "containers.h"
      25              : 
      26              : #include "dom/score.h"
      27              : #include "dom/system.h"
      28              : #include "dom/bracket.h"
      29              : #include "dom/layoutbreak.h"
      30              : #include "dom/page.h"
      31              : 
      32              : #include "passresetlayoutdata.h"
      33              : #include "passlayoutindependentitems.h"
      34              : 
      35              : #include "headerfooterlayout.h"
      36              : #include "measurelayout.h"
      37              : #include "systemlayout.h"
      38              : #include "pagelayout.h"
      39              : 
      40              : #include "log.h"
      41              : 
      42              : using namespace mu::engraving;
      43              : using namespace mu::engraving::rendering::score;
      44              : 
      45         3964 : void ScorePageViewLayout::initLayoutContext(const Score* score, LayoutContext& ctx, const Fraction& stick, const Fraction& etick)
      46              : {
      47              :     LAYOUT_CALL();
      48              : 
      49         3964 :     LayoutState& state = ctx.mutState();
      50              : 
      51         3964 :     state.setEndTick(etick);
      52              : 
      53         3964 :     MeasureBase* m = nullptr;
      54              :     {
      55         3964 :         m = score->tick2measure(stick);
      56         3964 :         if (m == nullptr) {
      57            1 :             m = score->first();
      58              :         }
      59              : 
      60              :         // start layout one measure earlier to handle clefs and cautionary elements
      61         3964 :         if (m->prevMeasureMM()) {
      62          180 :             m = m->prevMeasureMM();
      63         3784 :         } else if (m->prev()) {
      64         2139 :             m = m->prev();
      65              :         }
      66              : 
      67         4087 :         while (!m->isMeasure() && m->prev()) {
      68          123 :             m = m->prev();
      69              :         }
      70              : 
      71              :         // if the first measure of the score is part of a multi measure rest
      72              :         // m->system() will return a nullptr. We need to find the multi measure
      73              :         // rest which replaces the measure range
      74         3964 :         if (!m->system() && m->isMeasure() && toMeasure(m)->hasMMRest()) {
      75            3 :             LOGD("  don’t start with mmrest");
      76            3 :             m = toMeasure(m)->mmRest();
      77              :         }
      78              :     }
      79              : 
      80         3964 :     if (!state.isLayoutAll() && m->system()) {
      81          591 :         System* system = m->system();
      82          591 :         system_idx_t systemIndex = muse::indexOf(score->systems(), system);
      83              : 
      84              :         // set current system
      85          591 :         state.setStartTick(system->measures().front()->tick());
      86          591 :         state.setCurSystem(system);
      87          591 :         state.setSystemList(muse::mid(score->systems(), systemIndex));
      88              : 
      89              :         // set current page
      90          591 :         state.setPage(system->page());
      91          591 :         page_idx_t pageIdx = score->pageIdx(state.page());
      92          591 :         if (pageIdx == muse::nidx) {
      93            0 :             pageIdx = 0;
      94              :         }
      95          591 :         state.setPageIdx(pageIdx);
      96              : 
      97          591 :         MeasureBase* nextMeasure = nullptr;
      98          591 :         if (systemIndex == 0) {
      99          476 :             nextMeasure = ctx.conf().isShowVBox() ? score->first() : score->firstMeasure();
     100              :         } else {
     101          115 :             System* prevSystem = score->systems().at(systemIndex - 1);
     102          115 :             nextMeasure = prevSystem->measures().back()->next();
     103              :         }
     104          591 :         state.setNextMeasure(nextMeasure);
     105              : 
     106          591 :         if (!state.nextMeasure()->prevMeasure()) {
     107          552 :             state.setMeasureNumber(0);
     108          552 :             state.setTick(Fraction(0, 1));
     109              :         } else {
     110           39 :             const MeasureBase* mb = state.nextMeasure()->prev();
     111           39 :             if (mb) {
     112           39 :                 mb = mb->mbWithPrecedingSectionBreak();
     113              :             }
     114              : 
     115           39 :             const LayoutBreak* layoutBreak = mb->sectionBreakElement();
     116              :             // TODO: also use mb in else clause here?
     117              :             // probably not, only actual measures have meaningful numbers
     118           39 :             if (layoutBreak && layoutBreak->startWithMeasureOne()) {
     119            5 :                 state.setMeasureNumber(0);
     120              :             } else {
     121           68 :                 state.setMeasureNumber(state.nextMeasure()->prevMeasure()->measureNumber()                      // will be adjusted later with respect
     122           34 :                                        + (state.nextMeasure()->prevMeasure()->excludeFromNumbering() ? 0 : 1)); // to the user-defined offset.
     123              :             }
     124           39 :             state.setTick(ctx.state().nextMeasure()->tick());
     125              :         }
     126              :     } else {
     127         3373 :         state.setNextMeasure(ctx.conf().isShowVBox() ? score->first() : score->firstMeasure());
     128              :     }
     129              : 
     130         3964 :     state.setPrevMeasure(nullptr);
     131         3964 : }
     132              : 
     133         3964 : void ScorePageViewLayout::prepareScore(Score* score, const LayoutContext& ctx)
     134              : {
     135              :     LAYOUT_CALL();
     136              : 
     137         3964 :     if (!ctx.state().isLayoutAll() && ctx.state().curSystem()) {
     138          591 :         system_idx_t systemIndex = muse::indexOf(score->systems(), ctx.state().curSystem());
     139          591 :         score->systems().erase(score->systems().begin() + systemIndex, score->systems().end());
     140              :     } else {
     141         6293 :         for (System* s : score->systems()) {
     142         3279 :             for (Bracket* b : s->brackets()) {
     143          359 :                 if (b->selected()) {
     144            0 :                     bool selected = b->selected();
     145            0 :                     score->selection().remove(b);
     146            0 :                     BracketItem* item = b->bracketItem();
     147            0 :                     item->setSelected(selected);
     148            0 :                     score->setSelectionChanged(true);
     149              :                 }
     150              :             }
     151              :             // no need to clear the page placement: the systems are deleted below,
     152              :             // and ~System unlinks itself from its page
     153              :         }
     154              : 
     155        32799 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     156        29426 :             mb->setSystem(nullptr);
     157        29426 :             if (mb->isMeasure() && toMeasure(mb)->mmRest()) {
     158          111 :                 toMeasure(mb)->mmRest()->setSystem(nullptr);
     159              :             }
     160              :         }
     161         3373 :         muse::DeleteAll(score->systems());
     162         3373 :         score->systems().clear();
     163              : 
     164         3373 :         muse::DeleteAll(score->pages());
     165         3373 :         score->pages().clear();
     166              :     }
     167         3964 : }
     168              : 
     169         3964 : void ScorePageViewLayout::layoutPageView(Score* score, LayoutContext& ctx, const Fraction& stick, const Fraction& etick)
     170              : {
     171         3979 :     TRACEFUNC;
     172              : 
     173              :     LAYOUT_CALL_CLEAR();
     174              :     LAYOUT_CALL();
     175              : 
     176         3964 :     initLayoutContext(score, ctx, stick, etick);
     177              : 
     178         3964 :     prepareScore(score, ctx);
     179              : 
     180              :     //! NOTE Reset pass need anyway
     181              : //#ifdef MUE_ENABLE_ENGRAVING_LD_PASSES
     182         3964 :     PassResetLayoutData resetPass;
     183         3964 :     resetPass.run(score, ctx);
     184              : //#endif
     185              : 
     186              : #ifdef MUE_ENABLE_ENGRAVING_LD_PASSES
     187              :     if (ctx.state().isLayoutAll()) {
     188              :         PassLayoutIndependentItems independentPass;
     189              :         independentPass.run(score, ctx);
     190              :     }
     191              : #endif
     192              : 
     193         3964 :     doLayout(ctx);
     194              : 
     195         3964 :     layoutFinished(score, ctx);
     196              : 
     197              :     LAYOUT_CALL_PRINT();
     198         3964 : }
     199              : 
     200         3964 : void ScorePageViewLayout::doLayout(LayoutContext& ctx)
     201              : {
     202              :     LAYOUT_CALL();
     203              : 
     204         3964 :     LayoutState& state = ctx.mutState();
     205         3964 :     MeasureLayout::getNextMeasure(ctx);
     206         3964 :     state.setCurSystem(SystemLayout::collectSystem(ctx));
     207              : 
     208         3964 :     const MeasureBase* lmb = nullptr;
     209              :     do {
     210         4125 :         PageLayout::getNextPage(ctx);
     211         4125 :         PageLayout::collectPage(ctx);
     212              : 
     213         4125 :         if (state.page() && !state.page()->systems().empty()) {
     214         4125 :             lmb = state.page()->systems().back()->measures().back();
     215              :         } else {
     216            0 :             lmb = nullptr;
     217              :         }
     218              : 
     219              :         // we can stop collecting pages when:
     220              :         // 1) we reach the end of score (curSystem is nullptr)
     221              :         // or
     222              :         // 2) we have fully processed the range and reached a point of stability:
     223              :         //    a) we have completed layout for the range (rangeDone is true)
     224              :         //    b) we haven't collected a system that will need to go on the next page
     225              :         //    c) this page ends with the same measure as the previous layout
     226              :         //    pageOldMeasure will be last measure from previous layout if range was completed on or before this page
     227              :         //    it will be nullptr if this page was never laid out or if we collected a system for next page
     228         4125 :     } while (state.curSystem() && !(state.rangeDone() && lmb == state.pageOldMeasure()));
     229              :     // && page->system(0)->measures().back()->tick() > endTick // FIXME: perhaps the first measure was meant? Or last system?
     230              : 
     231         3964 :     SystemLayout::centerPendingSystems(ctx);
     232         3964 : }
     233              : 
     234         3964 : void ScorePageViewLayout::layoutFinished(Score* score, LayoutContext& ctx)
     235              : {
     236              :     LAYOUT_CALL();
     237              : 
     238         3964 :     LayoutState& state = ctx.mutState();
     239              : 
     240         3964 :     if (!state.curSystem()) {
     241              :         // The end of the score. The remaining systems are not needed...
     242         3954 :         muse::DeleteAll(state.systemList());
     243         3954 :         state.systemList().clear();
     244              : 
     245              :         // ...and the remaining pages too
     246         7918 :         while (score->npages() > state.pageIdx()) {
     247           10 :             Page* p = score->pages().back();
     248           10 :             score->pages().pop_back();
     249           10 :             delete p;
     250              :         }
     251              :     } else {
     252           10 :         Page* p = state.curSystem()->page();
     253           10 :         if (p && (p != state.page())) {
     254           10 :             p->invalidateBspTree();
     255              :         }
     256              :     }
     257              : 
     258         3964 :     score->systems().insert(score->systems().end(), state.systemList().begin(), state.systemList().end());
     259              : 
     260              :     /* If the headers/footers involve page count, we need to re-calculate them. This needs to
     261              :      * be done after laying out all pages, so that the total number of pages will be known: */
     262         3964 :     if (state.mustRecomputeHeadersFooters()) {
     263            4 :         doLayoutHeadersFooters(ctx);
     264            4 :         state.setMustRecomputeHeadersFooters(false);
     265              :     }
     266         3964 : }
     267              : 
     268            0 : void ScorePageViewLayout::layoutHeadersFooters(LayoutContext& ctx)
     269              : {
     270            0 :     TRACEFUNC;
     271              : 
     272              :     LAYOUT_CALL_CLEAR();
     273              :     LAYOUT_CALL();
     274              : 
     275            0 :     doLayoutHeadersFooters(ctx);
     276              : 
     277              :     LAYOUT_CALL_PRINT();
     278            0 : }
     279              : 
     280            4 : void ScorePageViewLayout::doLayoutHeadersFooters(LayoutContext& ctx)
     281              : {
     282           10 :     for (const auto& page : ctx.dom().pages()) {
     283            6 :         HeaderFooterLayout::layoutHeaderFooter(ctx, page);
     284              :     }
     285            4 : }
        

Generated by: LCOV version 2.0-1