LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - dynamicslayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 90.3 % 124 112
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 "dynamicslayout.h"
      24              : #include "layoutcontext.h"
      25              : #include "tlayout.h"
      26              : #include "textlayout.h"
      27              : 
      28              : #include "../dom/hairpin.h"
      29              : #include "../dom/measure.h"
      30              : #include "../dom/staff.h"
      31              : #include "../dom/system.h"
      32              : #include "../types/typesconv.h"
      33              : 
      34              : using namespace mu::engraving::rendering::score;
      35              : 
      36         1145 : void DynamicsLayout::layoutDynamic(Dynamic* item, TextBase::LayoutData* ldata, const LayoutConfiguration& conf)
      37              : {
      38         1145 :     doLayoutDynamic(item, ldata, conf);
      39              : 
      40              :     // If the dynamic contains custom text, keep it aligned
      41         1145 :     double customTextOffset = computeCustomTextOffset(item, ldata, conf);
      42         1145 :     ldata->moveX(-customTextOffset);
      43              : 
      44         1145 :     if (item->autoplace() && item->avoidBarLines()) {
      45         1145 :         manageBarlineCollisions(item, ldata);
      46              :     }
      47         1145 : }
      48              : 
      49         1216 : void DynamicsLayout::doLayoutDynamic(Dynamic* item, Dynamic::LayoutData* ldata, const LayoutConfiguration& conf)
      50              : {
      51         1216 :     ldata->disconnectSnappedItems();
      52              : 
      53         1216 :     HairpinSegment* snapBeforeHairpinAcrossSysBreak = item->findSnapBeforeHairpinAcrossSystemBreak();
      54         1216 :     if (snapBeforeHairpinAcrossSysBreak) {
      55            0 :         ldata->connectItemSnappedBefore(snapBeforeHairpinAcrossSysBreak);
      56              :     }
      57              : 
      58         1216 :     const StaffType* stType = item->staffType();
      59         1216 :     if (stType && stType->isHiddenElementOnTab(Sid::dynamicsShowTabCommon, Sid::dynamicsShowTabSimple)) {
      60            0 :         ldata->setIsSkipDraw(true);
      61            0 :         return;
      62              :     }
      63         1216 :     ldata->setIsSkipDraw(false);
      64              : 
      65         1216 :     item->setPlacementBasedOnVoiceAssignment(conf.styleV(Sid::dynamicsHairpinVoiceBasedPlacement).value<DirectionV>());
      66              : 
      67              :     // If "Center on notehead" is on, override user position. Restore later
      68         1216 :     AlignH userPosition = item->getProperty(Pid::POSITION).value<AlignH>();
      69         1216 :     AlignH hPos = item->centerOnNotehead() ? AlignH::HCENTER : item->position();
      70         1216 :     item->setPosition(hPos);
      71         1216 :     TextLayout::layoutBaseTextBase(item, ldata);
      72         1216 :     item->setPosition(userPosition);
      73              : 
      74         1216 :     const Segment* s = item->segment();
      75         1216 :     if (!s || (!item->centerOnNotehead() && item->position() == AlignH::LEFT)) {
      76            0 :         return;
      77              :     }
      78              : 
      79         1216 :     if (item->anchorToEndOfPrevious()) {
      80            1 :         layoutDynamicToEndOfPrevious(item, ldata);
      81            1 :         return;
      82              :     }
      83              : 
      84         1215 :     if (!item->centerOnNotehead()) {
      85            0 :         return;
      86              :     }
      87              : 
      88              :     // Use SMuFL optical center for dynamic if available
      89         1215 :     SymId symId = TConv::symId(item->dynamicType());
      90         1215 :     double opticalCenter = item->symSmuflAnchor(symId, SmuflAnchorId::opticalCenter).x();
      91         1215 :     if (symId != SymId::noSym && opticalCenter) {
      92         1138 :         double symWidth = item->symBbox(symId).width();
      93         1138 :         double offset = symWidth / 2 - opticalCenter + item->symBbox(symId).left();
      94         1138 :         double spatiumScaling = item->spatium() / conf.spatium();
      95         1138 :         offset *= spatiumScaling;
      96         1138 :         ldata->moveX(offset);
      97              :     }
      98              : }
      99              : 
     100         1145 : double DynamicsLayout::computeCustomTextOffset(Dynamic* item, Dynamic::LayoutData* ldata, const LayoutConfiguration& conf)
     101              : {
     102         1145 :     if (!item->centerOnNotehead() || item->dynamicType() == DynamicType::OTHER) {
     103           20 :         return 0.0;
     104              :     }
     105              : 
     106         1125 :     String referenceString = String::fromUtf8(Dynamic::dynInfo(item->dynamicType()).text);
     107         1125 :     if (item->xmlText() == referenceString) {
     108         1054 :         return 0.0;
     109              :     }
     110              : 
     111           71 :     Dynamic referenceDynamic(*item);
     112           71 :     referenceDynamic.setXmlText(referenceString);
     113           71 :     doLayoutDynamic(&referenceDynamic, referenceDynamic.mutldata(), conf);
     114              : 
     115           71 :     TextFragment referenceFragment;
     116           71 :     if (!referenceDynamic.ldata()->blocks.empty()) {
     117           71 :         TextBlock referenceBlock = referenceDynamic.ldata()->blocks.front();
     118           71 :         if (!referenceBlock.fragments().empty()) {
     119           71 :             referenceFragment = referenceDynamic.ldata()->blocks.front().fragments().front();
     120              :         }
     121           71 :     }
     122              : 
     123          123 :     for (const TextBlock& block : ldata->blocks) {
     124          144 :         for (const TextFragment& fragment : block.fragments()) {
     125           92 :             if (fragment.text == referenceFragment.text) {
     126           19 :                 return fragment.pos.x() - referenceFragment.pos.x();
     127              :             }
     128              :         }
     129              :     }
     130              : 
     131           52 :     return 0.0;
     132         1125 : }
     133              : 
     134            1 : void DynamicsLayout::layoutDynamicToEndOfPrevious(const Dynamic* item, TextBase::LayoutData* ldata)
     135              : {
     136            1 :     Segment* curSegment = item->segment();
     137            1 :     Segment* leftMostSegment = curSegment;
     138            1 :     Segment* prevSeg = curSegment;
     139              :     while (true) {
     140            1 :         prevSeg = prevSeg->prev1enabled();
     141            1 :         if (!prevSeg || prevSeg->tick() != curSegment->tick()) {
     142            1 :             break;
     143              :         }
     144            0 :         if (prevSeg->isActive() && prevSeg->hasElements(item->staffIdx())) {
     145            0 :             leftMostSegment = prevSeg;
     146            0 :             break;
     147              :         }
     148              :     }
     149              : 
     150            1 :     double xDiff = curSegment->x() + curSegment->measure()->x() - (leftMostSegment->x() + leftMostSegment->measure()->x());
     151            1 :     ldata->setPosX(-xDiff - ldata->bbox().right() - 0.50 * item->spatium());
     152            1 : }
     153              : 
     154         1145 : void DynamicsLayout::manageBarlineCollisions(const Dynamic* item, TextBase::LayoutData* ldata)
     155              : {
     156         1145 :     if (item->score()->nstaves() <= 1 || item->anchorToEndOfPrevious()) {
     157         1045 :         return;
     158              :     }
     159              : 
     160          493 :     Segment* thisSegment = item->segment();
     161          493 :     if (!thisSegment) {
     162            0 :         return;
     163              :     }
     164              : 
     165          493 :     System* system = thisSegment->measure()->system();
     166          493 :     if (!system) {
     167            0 :         return;
     168              :     }
     169              : 
     170          493 :     staff_idx_t staffIdx = item->staffIdx();
     171          493 :     staff_idx_t barLineStaff = muse::nidx;
     172          493 :     if (item->placeAbove()) {
     173              :         // need to find the barline from the staff above
     174              :         // taking into account there could be invisible staves
     175          133 :         if (staffIdx == 0) {
     176           43 :             return;
     177              :         }
     178           92 :         for (int staffIndex = static_cast<int>(staffIdx) - 1; staffIndex >= 0; --staffIndex) {
     179           92 :             if (system->staff(staffIndex)->show()) {
     180           90 :                 barLineStaff = staffIndex;
     181           90 :                 break;
     182              :             }
     183              :         }
     184              :     } else {
     185          360 :         barLineStaff = staffIdx;
     186              :     }
     187              : 
     188          450 :     if (barLineStaff == muse::nidx) {
     189            0 :         return;
     190              :     }
     191              : 
     192          450 :     if (!item->score()->staff(barLineStaff)->barLineSpan()) {
     193          350 :         return; // Barline doesn't extend through staves
     194              :     }
     195              : 
     196          100 :     const double minBarLineDistance = 0.25 * item->spatium();
     197              : 
     198          100 :     RectF referenceBBox = ldata->bbox();
     199              : 
     200              :     // Check barlines to the right
     201          100 :     Segment* rightBarLineSegment = nullptr;
     202          523 :     for (Segment* segment = thisSegment; segment && segment->measure()->system() == system; segment = segment->next1enabled()) {
     203          523 :         if (segment->segmentType() & SegmentType::BarLineTypes) {
     204          100 :             rightBarLineSegment = segment;
     205          100 :             break;
     206              :         }
     207              :     }
     208              : 
     209          100 :     if (rightBarLineSegment) {
     210          100 :         EngravingItem* e = rightBarLineSegment->element(barLineStaff * VOICES);
     211          100 :         if (e) {
     212          100 :             double rightMargin = e->ldata()->bbox().translated(e->pagePos()).left()
     213          100 :                                  - referenceBBox.translated(item->pagePos() - item->offset()).right()
     214          100 :                                  - minBarLineDistance;
     215          100 :             if (rightMargin < 0) {
     216            0 :                 ldata->moveX(rightMargin);
     217              :             }
     218              :         }
     219              :     }
     220              : 
     221              :     // Check barlines to the left
     222          100 :     Segment* leftBarLineSegment = nullptr;
     223          357 :     for (Segment* segment = thisSegment; segment && segment->measure()->system() == system; segment = segment->prev1enabled()) {
     224          357 :         if (segment->segmentType() & SegmentType::BarLineTypes) {
     225          100 :             leftBarLineSegment = segment;
     226          100 :             break;
     227              :         }
     228              :     }
     229          100 :     if (leftBarLineSegment) {
     230          100 :         EngravingItem* e = leftBarLineSegment->element(barLineStaff * VOICES);
     231          100 :         if (e) {
     232          100 :             double leftMargin = referenceBBox.translated(item->pagePos() - item->offset()).left()
     233          100 :                                 - e->ldata()->bbox().translated(e->pagePos()).right()
     234          100 :                                 - minBarLineDistance;
     235          100 :             if (leftMargin < 0) {
     236            6 :                 ldata->moveX(-leftMargin);
     237              :             }
     238              :         }
     239              :     }
     240              : }
        

Generated by: LCOV version 2.0-1