LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - alignmentlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.6 % 115 103
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 "alignmentlayout.h"
      24              : 
      25              : #include <set>
      26              : 
      27              : #include "systemlayout.h"
      28              : 
      29              : #include "dom/dynamic.h"
      30              : #include "dom/expression.h"
      31              : #include "dom/fret.h"
      32              : #include "dom/hairpin.h"
      33              : #include "dom/hammeronpulloff.h"
      34              : #include "dom/harmony.h"
      35              : #include "dom/system.h"
      36              : #include "dom/tapping.h"
      37              : #include "dom/text.h"
      38              : 
      39              : namespace mu::engraving::rendering::score {
      40          738 : void AlignmentLayout::alignItemsGroup(const std::vector<EngravingItem*>& elements, const System* system)
      41              : {
      42          738 :     if (elements.empty()) {
      43          279 :         return;
      44              :     }
      45              : 
      46          459 :     double outermostY = yOpticalCenter(elements.front());
      47         2036 :     for (const EngravingItem* element : elements) {
      48         1577 :         double curY = yOpticalCenter(element);
      49         1577 :         outermostY = isAbove(element) ? std::min(outermostY, curY) : std::max(outermostY, curY);
      50              :     }
      51              : 
      52         2036 :     for (EngravingItem* element : elements) {
      53         1577 :         moveItemToY(element, outermostY, system);
      54              :     }
      55              : }
      56              : 
      57           61 : void AlignmentLayout::alignHopoLetters(const HammerOnPullOff* hopo, const System* system)
      58              : {
      59           61 :     IF_ASSERT_FAILED(!hopo->segmentsEmpty()) {
      60            0 :         return;
      61              :     }
      62              : 
      63           61 :     std::vector<EngravingItem*> itemsToAlign;
      64           61 :     for (SpannerSegment* seg : hopo->spannerSegments()) {
      65           61 :         if (seg->system() == system) {
      66           61 :             HammerOnPullOffSegment* hopoSegment = toHammerOnPullOffSegment(seg);
      67          136 :             for (HammerOnPullOffText* hopoText : hopoSegment->hopoText()) {
      68           75 :                 itemsToAlign.push_back(hopoText);
      69              :             }
      70           61 :             break;
      71              :         }
      72              :     }
      73              : 
      74           61 :     if (itemsToAlign.empty()) {
      75            0 :         return;
      76              :     }
      77              : 
      78           61 :     bool above = isAbove(itemsToAlign.front());
      79          136 :     for (EngravingItem* item : itemsToAlign) {
      80           75 :         if (isAbove(item) != above) {
      81            0 :             return; // Don't try to align items split above/below
      82              :         }
      83              :     }
      84              : 
      85           61 :     ChordRest* scr = toChordRest(hopo->startElement());
      86           61 :     if (scr && scr->isChord()) {
      87           61 :         for (Articulation* art : toChord(scr)->articulations()) {
      88            0 :             if (art->isTapping() && toTapping(art)->text() && isAbove(art) == above) {
      89            0 :                 itemsToAlign.push_back(art);
      90              :             }
      91              :         }
      92              :     }
      93              : 
      94           61 :     alignItemsGroup(itemsToAlign, system);
      95           61 : }
      96              : 
      97        14776 : void AlignmentLayout::alignItemsWithTheirSnappingChain(const std::vector<EngravingItem*>& elements, const System* system)
      98              : {
      99        14776 :     std::set<EngravingItem*> alignedItems;
     100              : 
     101        14776 :     double outermostY = 0.0;
     102        14776 :     bool firstItem = true;
     103         2811 :     auto computeOutermostY = [&outermostY, &firstItem](EngravingItem* item) {
     104         2811 :         double curY = yOpticalCenter(item);
     105         2811 :         if (firstItem) {
     106         2605 :             outermostY = curY;
     107         2605 :             firstItem = false;
     108              :         } else {
     109          206 :             outermostY = item->placeAbove() ? std::min(outermostY, curY) : std::max(outermostY, curY);
     110              :         }
     111         2811 :     };
     112              : 
     113         2811 :     auto moveElementsToOutermostY = [&outermostY, &alignedItems, system](EngravingItem* item) {
     114         2811 :         alignedItems.insert(item);
     115         2811 :         moveItemToY(item, outermostY, system);
     116        17587 :     };
     117              : 
     118        17581 :     for (EngravingItem* item : elements) {
     119         2805 :         if (muse::contains(alignedItems, item)) {
     120          200 :             continue;
     121              :         }
     122         2605 :         firstItem = true;
     123         2605 :         item->ldata()->scanConnectedItemsInSnappingChain(system, computeOutermostY);
     124         2605 :         item->ldata()->scanConnectedItemsInSnappingChain(system, moveElementsToOutermostY);
     125              :     }
     126        14776 : }
     127              : 
     128         4436 : void AlignmentLayout::alignItemsForSystem(const std::vector<EngravingItem*>& elements, const System* system)
     129              : {
     130              :     struct StaffItemGroups {
     131              :         std::vector<EngravingItem*> itemsAbove;
     132              :         std::vector<EngravingItem*> itemsBelow;
     133              :     };
     134              : 
     135         4436 :     std::map<staff_idx_t, StaffItemGroups> staffItems;
     136              : 
     137         5697 :     for (EngravingItem* item : elements) {
     138         1261 :         if (item->addToSkyline() && !item->excludeVerticalAlign()) {
     139         1259 :             item->placeAbove() ? staffItems[item->staffIdx()].itemsAbove.push_back(item)
     140         1259 :             : staffItems[item->staffIdx()].itemsBelow.push_back(item);
     141              :         }
     142              :     }
     143              : 
     144         4720 :     for (const auto& staffItem : staffItems) {
     145          284 :         AlignmentLayout::alignItemsGroup(staffItem.second.itemsAbove, system);
     146          284 :         AlignmentLayout::alignItemsGroup(staffItem.second.itemsBelow, system);
     147              :     }
     148         4436 : }
     149              : 
     150         4388 : void AlignmentLayout::moveItemToY(EngravingItem* item, double y, const System* system)
     151              : {
     152         4388 :     double curY = yOpticalCenter(item);
     153         4388 :     double yDiff = y - curY;
     154         4388 :     item->mutldata()->moveY(yDiff);
     155         4388 :     SystemLayout::updateSkylineForElement(item, system, yDiff);
     156         4388 : }
     157              : 
     158         9235 : double AlignmentLayout::yOpticalCenter(const EngravingItem* item)
     159              : {
     160         9235 :     double curY = item->ldata()->pos().y();
     161         9235 :     switch (item->type()) {
     162         2354 :     case ElementType::DYNAMIC:
     163              :     case ElementType::EXPRESSION:
     164              :     {
     165              :         // Please keep `Dynamic::gripsPositions` in sync with this calculation
     166         2354 :         curY += item->staffOffsetY();
     167         2354 :         AlignV vertAlign = toTextBase(item)->align().vertical;
     168         2354 :         double bboxHeight = item->ldata()->bbox().height();
     169         2354 :         switch (vertAlign) {
     170           16 :         case AlignV::TOP:
     171           16 :             curY += 0.5 * bboxHeight;
     172           16 :             break;
     173            0 :         case AlignV::VCENTER:
     174            0 :             break;
     175            0 :         case AlignV::BOTTOM:
     176            0 :             curY -= 0.5 * bboxHeight;
     177            0 :             break;
     178         2338 :         case AlignV::BASELINE:
     179         2338 :             if (item->isDynamic()) {
     180         2286 :                 curY -= 0.46 * item->spatium() * toDynamic(item)->symbolScale(); // approximated half x-height of dynamic
     181              :             } else {
     182           52 :                 curY -= 0.5 * toExpression(item)->fontMetrics().xHeight();
     183              :             }
     184         2338 :             break;
     185              :         }
     186         2354 :         break;
     187              :     }
     188          952 :     case ElementType::HAIRPIN_SEGMENT:
     189              :     {
     190          952 :         const HairpinSegment* hairpinSeg = toHairpinSegment(item);
     191          952 :         if (hairpinSeg->hairpin()->isLineType()) {
     192           52 :             Text* text = hairpinSeg->text();
     193           52 :             if (!text) {
     194            0 :                 text = hairpinSeg->endText();
     195              :             }
     196           52 :             if (text) {
     197           52 :                 curY -= 0.5 * text->fontMetrics().xHeight();
     198              :             } else {
     199            0 :                 curY -= 0.5 * item->spatium();
     200              :             }
     201              :         }
     202          952 :         break;
     203              :     }
     204          653 :     case ElementType::FRET_DIAGRAM:
     205          653 :         curY += toFretDiagram(item)->ldata()->gridHeight;
     206          653 :         break;
     207         1881 :     case ElementType::HARMONY: {
     208         1881 :         EngravingItem* parent = toHarmony(item)->layoutParent();
     209         1881 :         if (parent && parent->isFretDiagram()) {
     210          509 :             curY += parent->pos().y();
     211              :         }
     212         1881 :         break;
     213              :     }
     214         3395 :     default:
     215         3395 :         break;
     216              :     }
     217         9235 :     return curY;
     218              : }
     219              : 
     220         1713 : bool AlignmentLayout::isAbove(const EngravingItem* item)
     221              : {
     222         1713 :     return item->isArticulationFamily() ? toArticulation(item)->ldata()->up() : item->placeAbove();
     223              : }
     224              : } // namespace mu::engraving::rendering::dev
        

Generated by: LCOV version 2.0-1