LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - autoplace.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 90.9 % 110 100
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 "autoplace.h"
      23              : 
      24              : #include "dom/harmony.h"
      25              : #include "style/style.h"
      26              : 
      27              : #include "dom/chordrest.h"
      28              : #include "dom/lyrics.h"
      29              : #include "dom/score.h"
      30              : #include "dom/segment.h"
      31              : #include "dom/spanner.h"
      32              : #include "dom/staff.h"
      33              : #include "dom/system.h"
      34              : #include "dom/measure.h"
      35              : 
      36              : #include "log.h"
      37              : 
      38              : using namespace muse;
      39              : using namespace mu::engraving;
      40              : using namespace mu::engraving::rendering::score;
      41              : 
      42         9882 : void Autoplace::autoplaceSegmentElement(const EngravingItem* item, EngravingItem::LayoutData* ldata, bool above, bool add)
      43              : {
      44         9882 :     const Segment* segment = toSegment(item->findAncestor(ElementType::SEGMENT));
      45         9882 :     const Measure* measure = segment ? segment->measure() : nullptr;
      46         9882 :     const System* system = measure ? measure->system() : nullptr;
      47         9882 :     IF_ASSERT_FAILED(system) {
      48            0 :         return;
      49              :     }
      50         9882 :     LD_CONDITION(segment->ldata()->isSetPos());
      51         9882 :     LD_CONDITION(measure->ldata()->isSetPos());
      52              : 
      53         9882 :     const double spatium = item->style().spatium() * item->staff()->staffMag(item);
      54              :     // TODO: proper item-to-item table for horizontal clearance in skyline
      55         9882 :     const double minSkylineHorizontalClearance = item->isArticulationOrFermata() ? 0.0 : item->style().styleAbsolute(
      56         7149 :         Sid::skylineMinHorizontalClearance) * item->mag();
      57              : 
      58         9882 :     doAutoplaceElement(item, ldata, system, above, add, minSkylineHorizontalClearance, spatium);
      59              : }
      60              : 
      61         6319 : void Autoplace::autoplaceMeasureElement(const EngravingItem* item, EngravingItem::LayoutData* ldata, bool above, bool add)
      62              : {
      63         6319 :     const Measure* measure = toMeasure(item->ownershipParent());
      64         6319 :     const System* system = measure ? measure->system() : nullptr;
      65         6319 :     IF_ASSERT_FAILED(system) {
      66            0 :         return;
      67              :     }
      68         6319 :     LD_CONDITION(measure->ldata()->isSetPos());
      69              : 
      70         6319 :     const double spatium = item->style().spatium() * item->staff()->staffMag(item);
      71              :     // TODO: check this value for measure-owned items
      72              :     // const double minSkylineHorizontalClearance = item->isMeasureNumber() ? 0.0 : item->style().styleAbsolute(
      73              :     // Sid::skylineMinHorizontalClearance) * item->mag();
      74         6319 :     const double minSkylineHorizontalClearance = 0.0;
      75         6319 :     doAutoplaceElement(item, ldata, system, above, add, minSkylineHorizontalClearance, spatium);
      76              : }
      77              : 
      78         1303 : void Autoplace::autoplaceSpannerSegment(const SpannerSegment* item, EngravingItem::LayoutData* ldata)
      79              : {
      80         1303 :     if (item->spanner()->anchor() == Spanner::Anchor::NOTE) {
      81            0 :         return;
      82              :     }
      83         1303 :     const System* system = item->system();
      84         1303 :     IF_ASSERT_FAILED(system) {
      85            0 :         return;
      86              :     }
      87         1303 :     const double mag = !item->systemFlag() && !item->spanner()->systemFlag() ? item->staff()->staffMag(item->spanner()->tick()) : 1.0;
      88         1303 :     const double spatium = item->style().spatium() * mag;
      89              : 
      90         1303 :     const bool above = item->spanner()->placeAbove();
      91         1303 :     doAutoplaceElement(item, ldata, system, above, /* add = */ false, /* minSkylineHorizontalClearance = */ 0.0, spatium);
      92              : }
      93              : 
      94        17498 : double Autoplace::distanceToMoveItem(const EngravingItem* item, const SkylineLine& staffSkyline, const Shape& shape, bool above,
      95              :                                      double minSkylineHorizontalClearance, double minDistance)
      96              : {
      97        34996 :     const SkylineLine filteredSkyline = staffSkyline.getFilteredCopy([item](const ShapeElement& shapeEl) {
      98       426237 :         const EngravingItem* skylineItem = shapeEl.item();
      99       426237 :         if (!skylineItem) {
     100         6095 :             return false;
     101              :         }
     102       420142 :         return itemsShouldIgnoreEachOther(item, skylineItem);
     103        17498 :     });
     104              : 
     105        17498 :     if (filteredSkyline.elements().empty()) {
     106         1315 :         return 0.0;
     107              :     }
     108              : 
     109        16183 :     const double d = above ? filteredSkyline.minDistanceToShapeAbove(shape, minSkylineHorizontalClearance)
     110         3105 :                      : filteredSkyline.minDistanceToShapeBelow(shape, minSkylineHorizontalClearance);
     111              : 
     112        16183 :     if (!(d > -minDistance)) {
     113         9918 :         return 0.0;
     114              :     }
     115              : 
     116         6265 :     const double yd = (d + minDistance) * (above ? -1.0 : 1.0);
     117              : 
     118         6265 :     return yd;
     119        17498 : }
     120              : 
     121        17504 : void Autoplace::doAutoplaceElement(const EngravingItem* item, EngravingItem::LayoutData* ldata, const System* system, bool above, bool add,
     122              :                                    double minSkylineHorizontalClearance, double spatium)
     123              : {
     124        17504 :     DO_ASSERT(autoplaceAppliesToType(item->type()));
     125        17504 :     IF_ASSERT_FAILED(system) {
     126            6 :         return;
     127              :     }
     128              : 
     129        17504 :     if (!item->autoplace() || !item->ownershipParent()) {
     130            6 :         return;
     131              :     }
     132              : 
     133        17498 :     const staff_idx_t staffIdx = item->effectiveStaffIdx();
     134        17498 :     if (staffIdx == muse::nidx) {
     135            0 :         return;
     136              :     }
     137              : 
     138        17498 :     LD_CONDITION(ldata->isSetPos());
     139        17498 :     LD_CONDITION(ldata->isSetBbox());
     140              : 
     141        17498 :     const double minDistance = item->minDistance().toAbsolute(spatium);
     142              : 
     143        17498 :     SysStaff* sysStaff = system->staff(staffIdx);
     144        17498 :     SkylineLine& staffSkyline = above ? sysStaff->skyline().north() : sysStaff->skyline().south();
     145              :     // TODO [J.M] staffOffset will be applied twice for spanner segments.
     146              :     // We really need to sort out staffOffset and have it applied as early and infrequently as possible. Can't keep up with ~30 callsites
     147              :     // Shape shape = item->ldata()->shape().translate(item->systemPos() - item->offset() + item->staffOffset());
     148        17498 :     Shape shape = item->ldata()->shape();
     149        17498 :     if (item->isSpannerSegment()) {
     150         1303 :         shape.translate(item->ldata()->pos());
     151              :     } else {
     152        16195 :         shape.translate(item->systemPos() - item->offset() + item->staffOffset());
     153              :     }
     154              : 
     155        17498 :     const double yd = distanceToMoveItem(item, staffSkyline, shape, above, minSkylineHorizontalClearance, minDistance);
     156              : 
     157        17498 :     ldata->moveY(yd);
     158        17498 :     shape.translateY(yd);
     159              : 
     160        17498 :     if (add && item->addToSkyline()) {
     161        16027 :         staffSkyline.add(shape.translated(item->offset()));
     162              :     }
     163        17498 : }
     164              : 
     165       450194 : bool Autoplace::itemsShouldIgnoreEachOther(const EngravingItem* itemToAutoplace, const EngravingItem* itemInSkyline)
     166              : {
     167       450194 :     if (itemToAutoplace == itemInSkyline) {
     168           29 :         return true;
     169              :     }
     170              : 
     171       450165 :     const EngravingItem* skylineItemOwner = itemInSkyline->ownershipParentItem();
     172       450165 :     if (itemInSkyline->isText() && skylineItemOwner && skylineItemOwner->isSLineSegment()) {
     173            0 :         return itemsShouldIgnoreEachOther(itemToAutoplace, skylineItemOwner);
     174              :     }
     175              : 
     176       450165 :     ElementType type1 = itemToAutoplace->type();
     177       450165 :     ElementType type2 = itemInSkyline->type();
     178              : 
     179       450165 :     if (type1 == ElementType::TIMESIG) {
     180            0 :         return type2 != ElementType::KEYSIG;
     181              :     }
     182              : 
     183       450165 :     if (type1 == ElementType::FRET_DIAGRAM && (type2 == ElementType::FRET_DIAGRAM || type2 == ElementType::HARMONY)) {
     184          766 :         bool isFretDiagAgainstItsOwnHarmony = skylineItemOwner == itemToAutoplace;
     185          766 :         bool areOnDifferentSegments = itemToAutoplace->findAncestor(ElementType::SEGMENT)
     186          766 :                                       != itemInSkyline->findAncestor(ElementType::SEGMENT);
     187          766 :         return isFretDiagAgainstItsOwnHarmony || areOnDifferentSegments;
     188              :     }
     189              : 
     190       449399 :     if ((type1 == ElementType::DYNAMIC || type1 == ElementType::HAIRPIN_SEGMENT)
     191        75524 :         && (type2 == ElementType::DYNAMIC || type2 == ElementType::HAIRPIN_SEGMENT)) {
     192         3921 :         return true;
     193              :     }
     194              : 
     195       445478 :     if (itemToAutoplace->isLyricsLineSegment() && itemInSkyline->isLyricsLineSegment()) {
     196            0 :         const LyricsLineSegment* a = toLyricsLineSegment(itemToAutoplace);
     197            0 :         const LyricsLineSegment* b = toLyricsLineSegment(itemInSkyline);
     198            0 :         return a->verse() == b->verse() && a->lyricsPlaceAbove() == b->lyricsPlaceAbove();
     199              :     }
     200              : 
     201       445478 :     if (type1 == type2) {
     202              :         // Items of same type should ignore each other in most cases
     203              :         static const std::set<ElementType> TEXT_BASED_TYPES_WHICH_IGNORE_EACH_OTHER {
     204              :             ElementType::DYNAMIC,
     205              :             ElementType::EXPRESSION,
     206              :             ElementType::STICKING
     207        45131 :         };
     208        45105 :         return !itemToAutoplace->isTextBase() || muse::contains(TEXT_BASED_TYPES_WHICH_IGNORE_EACH_OTHER, type1);
     209              :     }
     210              : 
     211       400373 :     if ((type1 == ElementType::DYNAMIC || type1 == ElementType::EXPRESSION)
     212        41873 :         && (type2 == ElementType::DYNAMIC || type2 == ElementType::EXPRESSION)) {
     213              :         // Dynamics and expressions should ignore each other if on the same segment
     214           68 :         return itemToAutoplace->parent() == itemInSkyline->parent();
     215              :     }
     216              : 
     217       400305 :     if ((type1 == ElementType::TUPLET || type1 == ElementType::STAFF_LINES)
     218        97641 :         && (type2 == ElementType::STAFF_LINES || type2 == ElementType::TUPLET)) {
     219         6692 :         const Score* score = itemToAutoplace->score();
     220         6692 :         const bool outOfStaff = score ? !score->style().styleB(Sid::tupletOutOfStaff) : false;
     221         6692 :         return outOfStaff;
     222              :     }
     223              : 
     224       393613 :     if ((type1 == ElementType::FIGURED_BASS || type1 == ElementType::FIGURED_BASS_ITEM)
     225         2307 :         && (type2 == ElementType::FIGURED_BASS || type2 == ElementType::FIGURED_BASS_ITEM)) {
     226          651 :         return true;
     227              :     }
     228              : 
     229       392962 :     if (itemToAutoplace->isArticulationOrFermata() && itemInSkyline->isArticulationOrFermata()) {
     230              :         // Ignore fermatas and articulations on other segments
     231         1715 :         return itemToAutoplace->findAncestor(ElementType::SEGMENT) != itemInSkyline->findAncestor(ElementType::SEGMENT);
     232              :     }
     233              : 
     234       391247 :     return itemToAutoplace->ldata()->itemSnappedBefore() == itemInSkyline || itemToAutoplace->ldata()->itemSnappedAfter() == itemInSkyline;
     235              : }
        

Generated by: LCOV version 2.0-1