LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - accidentalslayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 80.9 % 703 569
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-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 <algorithm>
      23              : #include <cfloat>
      24              : 
      25              : #include "accidentalslayout.h"
      26              : #include "layoutcontext.h"
      27              : #include "../paddingtable.h"
      28              : #include "shape.h"
      29              : #include "tlayout.h"
      30              : 
      31              : #include "dom/accidental.h"
      32              : #include "dom/chord.h"
      33              : #include "dom/ledgerline.h"
      34              : #include "dom/note.h"
      35              : #include "dom/score.h"
      36              : 
      37              : using namespace mu::engraving;
      38              : using namespace mu::engraving::rendering::score;
      39              : 
      40         3043 : AccidentalsLayout::AccidentalsLayoutContext::AccidentalsLayoutContext(std::vector<Accidental*> acc,
      41         3043 :                                                                       std::vector<Chord*> ch)
      42         3043 :     : allAccidentals(std::move(acc)), chords(std::move(ch))
      43              : {
      44         3043 :     sortTopDown(allAccidentals);
      45         3043 :     stackedAccidentals.reserve(allAccidentals.size());
      46         3043 :     stackedAccidentalsShape.elements().reserve(4 * allAccidentals.size());
      47              : 
      48         3043 :     initConstants();
      49         3043 : }
      50              : 
      51         3043 : void AccidentalsLayout::AccidentalsLayoutContext::initConstants()
      52              : {
      53         3043 :     m_spatium = allAccidentals.front()->spatium();
      54              : 
      55              :     // Hardcoded engraving parameters that don't have a style (some of these may get a style in future)
      56         3043 :     m_verticalAccidentalToAccidentalClearance = 0.15 * m_spatium;
      57         3043 :     m_verticalSharpToSharpClearance = 0.05 * m_spatium;
      58         3043 :     m_verticalAccidentalToChordClearance = 0.10 * m_spatium;
      59         3043 :     m_additionalPaddingForVerticals = 0.1 * m_spatium;
      60         3043 :     m_xPosSplitThreshold = 0.5 * m_spatium;
      61         3043 :     m_xVerticalAlignmentThreshold = 0.75 * m_spatium;
      62         3043 :     m_sharpAndNaturalLedgerLinePadding = 0.1 * m_spatium;
      63         3043 :     m_reducedFlatToNotePadding = 0.15 * m_spatium;
      64         3043 :     m_flatKerningOfFourth = -0.15 * m_spatium;
      65         3043 :     m_naturalKerningOfFourth = -0.05 * m_spatium;
      66              : 
      67         3043 :     const MStyle& style = allAccidentals.front()->style();
      68         3043 :     m_accidentalAccidentalDistance = style.styleAbsolute(Sid::accidentalDistance);
      69         3043 :     m_orderFollowNoteDisplacement = style.styleB(Sid::accidentalOrderFollowsNoteDisplacement);
      70         3043 :     m_alignOctavesAcrossSubChords = style.styleB(Sid::alignAccidentalOctavesAcrossSubChords);
      71         3043 :     m_keepSecondsTogether = style.styleB(Sid::keepAccidentalSecondsTogether);
      72         3043 :     m_alignOffsetOctaves = style.styleB(Sid::alignOffsetOctaveAccidentals);
      73         3043 : }
      74              : 
      75        88588 : void AccidentalsLayout::layoutAccidentals(const std::vector<Chord*>& chords, LayoutContext& ctx)
      76              : {
      77        88588 :     std::vector<Accidental*> allAccidentals;
      78        88588 :     std::vector<Accidental*> redundantAccidentals;
      79        88588 :     std::vector<Accidental*> invisibleAccidentals;
      80              : 
      81        88588 :     collectAccidentals(chords, allAccidentals, redundantAccidentals, invisibleAccidentals);
      82              : 
      83        88596 :     for (Accidental* acc : invisibleAccidentals) {
      84            8 :         acc->computeMag();
      85            8 :         TLayout::layoutAccidental(acc, acc->mutldata(), ctx.conf());
      86            8 :         double x = -acc->ldata()->bbox().width();
      87            8 :         x -= acc->note() ? acc->note()->pos().x() : 0.0;
      88            8 :         acc->setPos(x, 0.0);
      89              :     }
      90              : 
      91        88588 :     if (allAccidentals.empty()) {
      92        85545 :         return;
      93              :     }
      94              : 
      95         3043 :     for (Accidental* acc : redundantAccidentals) {
      96            0 :         acc->setPos(0.0, 0.0);
      97            0 :         acc->setbbox(RectF());
      98              :     }
      99              : 
     100         7049 :     for (Accidental* acc : allAccidentals) {
     101         4006 :         acc->setPos(0.0, 0.0);
     102         4006 :         acc->computeMag();
     103         4006 :         TLayout::layoutAccidental(acc, acc->mutldata(), ctx.conf());
     104              :     }
     105              : 
     106         3043 :     AccidentalsLayoutContext accidentalsLayoutContext(std::move(allAccidentals), chords);
     107         3043 :     doAccidentalPlacement(accidentalsLayoutContext);
     108       259678 : }
     109              : 
     110        88588 : void AccidentalsLayout::collectAccidentals(const std::vector<Chord*> chords, std::vector<Accidental*>& allAccidentals,
     111              :                                            std::vector<Accidental*>& redundantAccidentals, std::vector<Accidental*>& invisibleAccidentals)
     112              : {
     113       138368 :     for (const Chord* chord : chords) {
     114       108399 :         for (const Note* note : chord->notes()) {
     115        58619 :             Accidental* acc = note->accidental();
     116        58619 :             if (!acc) {
     117        54605 :                 continue;
     118              :             }
     119         4014 :             if (accidentalIsRedundant(acc, allAccidentals)) {
     120            0 :                 redundantAccidentals.push_back(acc);
     121         4014 :             } else if (!acc->addToSkyline()) {
     122            8 :                 invisibleAccidentals.push_back(acc);
     123              :             } else {
     124         4006 :                 allAccidentals.push_back(acc);
     125              :             }
     126              :         }
     127              :     }
     128        88588 : }
     129              : 
     130         4014 : bool AccidentalsLayout::accidentalIsRedundant(const Accidental* acc, const std::vector<Accidental*>& allAccidentals)
     131              : {
     132         5803 :     for (const Accidental* otherAcc : allAccidentals) {
     133         1789 :         if (otherAcc->accidentalType() == acc->accidentalType() && otherAcc->line() == acc->line()) {
     134            0 :             return true;
     135              :         }
     136              :     }
     137              : 
     138         4014 :     return false;
     139              : }
     140              : 
     141         3043 : void AccidentalsLayout::doAccidentalPlacement(AccidentalsLayoutContext& ctx)
     142              : {
     143         3043 :     createChordsShape(ctx);
     144              : 
     145         3043 :     findOctavesAndSeconds(ctx);
     146         3043 :     splitIntoSubChords(ctx);
     147              : 
     148         6171 :     for (std::vector<Accidental*>& accidentalSubChord : ctx.accidentalSubChords) {
     149         3128 :         layoutSubChord(accidentalSubChord, ctx);
     150              :     }
     151              : 
     152         3043 :     verticallyAlignAccidentals(ctx);
     153         3043 : }
     154              : 
     155         3043 : void AccidentalsLayout::findOctavesAndSeconds(const AccidentalsLayoutContext& ctx)
     156              : {
     157         7049 :     for (Accidental* acc1 : ctx.allAccidentals) {
     158         4006 :         std::vector<Accidental*>& octaves = acc1->mutldata()->octaves.mut_value();
     159         4006 :         octaves.clear();
     160         4006 :         std::vector<Accidental*>& seconds = acc1->mutldata()->seconds.mut_value();
     161         4006 :         seconds.clear();
     162        11590 :         for (Accidental* acc2 : ctx.allAccidentals) {
     163         7584 :             if (acc2 == acc1) {
     164         4006 :                 continue;
     165              :             }
     166         3578 :             if (isOctave(acc1, acc2)) {
     167          416 :                 octaves.push_back(acc2);
     168         3162 :             } else if (abs(acc1->line() - acc2->line()) == 1) {
     169          232 :                 seconds.push_back(acc2);
     170              :             }
     171              :         }
     172              :     }
     173         3043 : }
     174              : 
     175         3043 : void AccidentalsLayout::splitIntoSubChords(AccidentalsLayoutContext& ctx)
     176              : {
     177         3043 :     ctx.accidentalSubChords.reserve(2);
     178              : 
     179         3043 :     std::vector<Accidental*> firstGroup;
     180         3043 :     firstGroup.reserve(ctx.allAccidentals.size());
     181         3043 :     firstGroup.push_back(ctx.allAccidentals.front());
     182         3043 :     ctx.accidentalSubChords.push_back(firstGroup);
     183              : 
     184              :     static constexpr int LINE_DIFF_OF_SEVENTH = 6;
     185              : 
     186         4006 :     for (size_t i = 1; i < ctx.allAccidentals.size(); ++i) {
     187          963 :         Accidental* prevAcc = ctx.allAccidentals[i - 1];
     188          963 :         Accidental* curAcc = ctx.allAccidentals[i];
     189          963 :         bool startNewGroup = curAcc->line() - prevAcc->line() >= LINE_DIFF_OF_SEVENTH;
     190          963 :         if (startNewGroup) {
     191          936 :             ctx.accidentalSubChords.push_back(std::vector<Accidental*> { curAcc });
     192              :         } else {
     193          651 :             ctx.accidentalSubChords.back().push_back(curAcc);
     194              :         }
     195              :     }
     196              : 
     197         3043 :     mergeAdjacentSubGroupsIfTooSmall(ctx);
     198              : 
     199         3043 :     ctx.subChordsCountBeforeOctaveMerge = ctx.accidentalSubChords.size();
     200         3043 :     if (ctx.alignOctavesAcrossSubChords()) {
     201            0 :         mergeSubGroupsWithOctavesAcross(ctx);
     202              :     }
     203         3043 : }
     204              : 
     205         3043 : void AccidentalsLayout::mergeAdjacentSubGroupsIfTooSmall(AccidentalsLayoutContext& ctx)
     206              : {
     207         3043 :     const double approxOffsetNoteThreshold = 0.5 * ctx.spatium();
     208         3043 :     bool groupingChanged = false;
     209         3270 :     do {
     210         3270 :         groupingChanged = false;
     211         3582 :         for (size_t i = 0; i < ctx.accidentalSubChords.size() - 1; ++i) {
     212          312 :             std::vector<Accidental*>& thisGroup = ctx.accidentalSubChords[i];
     213          312 :             std::vector<Accidental*>& nextGroup = ctx.accidentalSubChords[i + 1];
     214          312 :             bool groupIsSmall = thisGroup.size() + nextGroup.size() <= ctx.smallGroupLimit();
     215          312 :             bool nextStartsWithOffsetNote = nextGroup.front()->note()->x() > approxOffsetNoteThreshold;
     216          312 :             if (groupIsSmall && !nextStartsWithOffsetNote) {
     217          227 :                 groupingChanged = true;
     218          227 :                 thisGroup.insert(thisGroup.end(), nextGroup.begin(), nextGroup.end());
     219          227 :                 ctx.accidentalSubChords.erase(ctx.accidentalSubChords.begin() + i + 1);
     220              :             }
     221              :         }
     222              :     } while (groupingChanged);
     223         3043 : }
     224              : 
     225            0 : void AccidentalsLayout::mergeSubGroupsWithOctavesAcross(AccidentalsLayoutContext& ctx)
     226              : {
     227            0 :     auto isOctaveAcrossGroups = [](const std::vector<Accidental*>& group1, const std::vector<Accidental*>& group2) {
     228            0 :         for (const Accidental* acc1 : group1) {
     229            0 :             for (Accidental* octaveAcc : acc1->ldata()->octaves.value()) {
     230            0 :                 if (muse::contains(group2, octaveAcc)) {
     231            0 :                     return true;
     232              :                 }
     233              :             }
     234              :         }
     235            0 :         return false;
     236              :     };
     237              : 
     238            0 :     bool foundOctave = false;
     239            0 :     do {
     240            0 :         foundOctave = false;
     241            0 :         for (size_t i = 0; i < ctx.accidentalSubChords.size() - 1; ++i) {
     242            0 :             std::vector<Accidental*>& thisGroup = ctx.accidentalSubChords[i];
     243            0 :             for (size_t j = i + 1; j < ctx.accidentalSubChords.size(); ++j) {
     244            0 :                 std::vector<Accidental*>& nextGroup = ctx.accidentalSubChords[j];
     245            0 :                 foundOctave = isOctaveAcrossGroups(thisGroup, nextGroup);
     246            0 :                 if (foundOctave) {
     247            0 :                     thisGroup.insert(thisGroup.end(), nextGroup.begin(), nextGroup.end());
     248            0 :                     ctx.accidentalSubChords.erase(ctx.accidentalSubChords.begin() + j);
     249            0 :                     break;
     250              :                 }
     251              :             }
     252            0 :             if (foundOctave) {
     253            0 :                 break;
     254              :             }
     255              :         }
     256              :     } while (foundOctave);
     257            0 : }
     258              : 
     259         3043 : void AccidentalsLayout::createChordsShape(AccidentalsLayoutContext& ctx)
     260              : {
     261         3043 :     ctx.chordsShape.clear();
     262              : 
     263         6237 :     for (const Chord* chord : ctx.chords) {
     264         3194 :         PointF chordPos = keepAccidentalsCloseToChord(chord) ? PointF() : chord->pos();
     265         6767 :         for (const LedgerLine* ledger : chord->ledgerLines()) {
     266         3573 :             ctx.chordsShape.add(ledger->shape().translate(ledger->pos() + chordPos));
     267              :         }
     268         8539 :         for (const Note* note : chord->notes()) {
     269         5345 :             SymId noteSym = note->ldata()->cachedNoteheadSym;
     270         5345 :             if (noteSym == SymId::noSym) {
     271         4445 :                 noteSym = note->noteHead();
     272              :             }
     273         5345 :             ctx.chordsShape.add(note->symShapeWithCutouts(noteSym).translate(note->pos() + chordPos));
     274              :         }
     275         3194 :         const Stem* stem = chord->stem();
     276         3194 :         if (stem && stem->addToSkyline()) {
     277         2560 :             ctx.chordsShape.add(stem->shape().translate(stem->pos() + chordPos));
     278              :         }
     279              :     }
     280         3043 : }
     281              : 
     282         3128 : void AccidentalsLayout::layoutSubChord(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     283              : {
     284         3128 :     computeOrdering(accidentals, ctx);
     285              : 
     286         7134 :     for (Accidental* acc : accidentals) {
     287         4006 :         stackAccidental(acc, ctx);
     288              :     }
     289         3128 : }
     290              : 
     291         4006 : void AccidentalsLayout::stackAccidental(Accidental* acc, AccidentalsLayoutContext& ctx)
     292              : {
     293         4006 :     Shape accShape = acc->shape();
     294         4006 :     accShape.translateY(acc->note()->y());
     295              : 
     296         4006 :     double x = minAccidentalToChordDistance(acc, accShape, ctx);
     297         4006 :     accShape.translateX(-x);
     298              : 
     299         4006 :     double minDistToAccidGroup = minAccidentalToAccidentalGroupDistance(acc, accShape, ctx.stackedAccidentalsShape, ctx);
     300         4006 :     accShape.translateX(-minDistToAccidGroup);
     301         4006 :     x += minDistToAccidGroup;
     302              : 
     303         4006 :     setXposRelativeToSegment(acc, -x);
     304              : 
     305         4006 :     checkZeroColumn(acc, ctx);
     306         4006 :     ctx.stackedAccidentalsShape.add(accShape);
     307         4006 :     ctx.stackedAccidentals.push_back(acc);
     308         4006 : }
     309              : 
     310         4006 : void AccidentalsLayout::checkZeroColumn(Accidental* acc, const AccidentalsLayoutContext& ctx)
     311              : {
     312         4006 :     int column = acc->ldata()->column;
     313         4006 :     if (column != 0) {
     314          557 :         return;
     315              :     }
     316              : 
     317         3449 :     RectF thisBBox = acc->ldata()->bbox().translated(xPosRelativeToSegment(acc), 0.0);
     318         4105 :     for (Accidental* stackedAcc : ctx.stackedAccidentals) {
     319          656 :         RectF placedBBox = stackedAcc->ldata()->bbox().translated(xPosRelativeToSegment(stackedAcc), 0.0);
     320          656 :         if (thisBBox.right() < placedBBox.left()) {
     321           12 :             column = std::max(column, stackedAcc->ldata()->column.value() + 1);
     322              :         }
     323              :     }
     324         3449 :     acc->mutldata()->column = column;
     325              : }
     326              : 
     327         3128 : void AccidentalsLayout::computeOrdering(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     328              : {
     329         3128 :     AccidentalGroups priorityGroups = splitIntoPriorityGroups(accidentals, ctx);
     330              : 
     331         3128 :     accidentals.clear();
     332         6274 :     for (std::vector<Accidental*>& group : priorityGroups) {
     333         3146 :         determineStackingOrder(group, ctx);
     334         3146 :         accidentals.insert(accidentals.end(), group.begin(), group.end());
     335              :     }
     336              : 
     337         3128 :     applyOrderingOffsets(accidentals);
     338         3128 : }
     339              : 
     340         3128 : AccidentalGroups AccidentalsLayout::splitIntoPriorityGroups(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     341              : {
     342         3128 :     AccidentalGroups groups;
     343              : 
     344         3128 :     if (ctx.orderFollowNoteDisplacement()) {
     345            0 :         AccidentalGroups noteSplitGroups = splitAccordingToNoteDisplacement(accidentals, ctx);
     346            0 :         for (auto noteSplitGroup : noteSplitGroups) {
     347            0 :             AccidentalGroups accidSplitGroups = splitAccordingToAccidDisplacement(noteSplitGroup, ctx);
     348            0 :             groups.insert(groups.end(), accidSplitGroups.begin(), accidSplitGroups.end());
     349            0 :         }
     350            0 :     } else {
     351         3128 :         groups = splitAccordingToAccidDisplacement(accidentals, ctx);
     352              :     }
     353              : 
     354         3128 :     size_t curGroupsCount = groups.size();
     355         3128 :     if (ctx.alignOctavesAcrossSubChords()) {
     356            0 :         mergeSubGroupsWithOctavesAcross(ctx);
     357            0 :         if (groups.size() != curGroupsCount) {
     358            0 :             for (auto& subGroup : groups) {
     359            0 :                 sortTopDown(subGroup);
     360              :             }
     361              :         }
     362              :     }
     363              : 
     364         3128 :     return groups;
     365            0 : }
     366              : 
     367         3128 : AccidentalGroups AccidentalsLayout::splitAccordingToAccidDisplacement(std::vector<Accidental*>& accidentals,
     368              :                                                                       const AccidentalsLayoutContext& ctx)
     369              : {
     370         3128 :     AccidentalGroups subGroups = groupAccidentalsByXPos(accidentals, ctx);
     371              : 
     372         3128 :     muse::remove_if(subGroups, [](auto& subGroup) {
     373         6256 :         return subGroup.size() == 0;
     374              :     });
     375              : 
     376         3128 :     if (subGroups.size() > 1 && subGroups[0].size() > 1) {
     377            4 :         moveOctavesToSecondGroup(subGroups);
     378              :     }
     379              : 
     380         6274 :     for (std::vector<Accidental*>& subGroup : subGroups) {
     381         3146 :         sortTopDown(subGroup);
     382              :     }
     383              : 
     384         3128 :     return subGroups;
     385            0 : }
     386              : 
     387         3128 : AccidentalGroups AccidentalsLayout::groupAccidentalsByXPos(std::vector<Accidental*>& accidentals, const AccidentalsLayoutContext& ctx)
     388              : {
     389         3128 :     std::map<double, std::vector<Accidental*> > groupsOfEqualX;
     390              : 
     391         7134 :     for (Accidental* acc : accidentals) {
     392         4006 :         Shape accRoughShape = Shape(acc->symBbox(acc->symId()), acc);
     393         4006 :         accRoughShape.translateY(acc->note()->y());
     394         4006 :         double x = -minAccidentalToChordDistance(acc, accRoughShape, ctx) + acc->symWidth(acc->symId());
     395         4006 :         groupsOfEqualX[std::round(x)].push_back(acc);
     396         4006 :     }
     397              : 
     398         3128 :     double splitPoint = DBL_MAX;
     399         3128 :     double prevX = (*groupsOfEqualX.begin()).first;
     400         6691 :     for (auto& pair : groupsOfEqualX) {
     401         3581 :         double curX = pair.first;
     402         3581 :         if (curX - prevX > ctx.xPosSplitThreshold()) {
     403           18 :             splitPoint = curX;
     404           18 :             break;
     405              :         }
     406         3563 :         prevX = curX;
     407              :     }
     408              : 
     409         3128 :     AccidentalGroups subGroups(2);
     410         6712 :     for (auto& pair : groupsOfEqualX) {
     411         3584 :         if (pair.first >= splitPoint) {
     412           43 :             for (Accidental* acc : pair.second) {
     413           22 :                 subGroups[0].push_back(acc);
     414           22 :                 acc->mutldata()->verticalSubgroup = 0;
     415              :             }
     416              :         } else {
     417         7547 :             for (Accidental* acc : pair.second) {
     418         3984 :                 subGroups[1].push_back(acc);
     419         3984 :                 acc->mutldata()->verticalSubgroup = 1;
     420              :             }
     421              :         }
     422              :     }
     423              : 
     424         3128 :     return subGroups;
     425         3128 : }
     426              : 
     427            4 : void AccidentalsLayout::moveOctavesToSecondGroup(AccidentalGroups& subGroups)
     428              : {
     429            4 :     IF_ASSERT_FAILED(subGroups.size() == 2) {
     430            0 :         return;
     431              :     }
     432              : 
     433            4 :     std::vector<Accidental*>& firstGroup = subGroups[0];
     434            4 :     std::vector<Accidental*>& secondGroup = subGroups[1];
     435            4 :     auto iter = firstGroup.begin();
     436           12 :     while (iter != firstGroup.end()) {
     437            8 :         Accidental* acc = *iter;
     438            8 :         bool removeOne = false;
     439            8 :         for (Accidental* octaveAcc : acc->ldata()->octaves.value()) {
     440            6 :             if (muse::contains(secondGroup, octaveAcc)) {
     441            6 :                 removeOne = true;
     442            6 :                 secondGroup.push_back(acc);
     443            6 :                 acc->mutldata()->verticalSubgroup = 1;
     444            6 :                 break;
     445              :             }
     446              :         }
     447            8 :         if (removeOne) {
     448            6 :             iter = firstGroup.erase(iter);
     449              :         } else {
     450            2 :             ++iter;
     451              :         }
     452              :     }
     453              : }
     454              : 
     455            0 : AccidentalGroups AccidentalsLayout::splitAccordingToNoteDisplacement(std::vector<Accidental*>& accidentals,
     456              :                                                                      const AccidentalsLayoutContext& ctx)
     457              : {
     458            0 :     AccidentalGroups subGroups = groupAccidentalsByNoteXPos(accidentals);
     459              : 
     460            0 :     if (ctx.keepSecondsTogether()) {
     461            0 :         moveSecondsInSameGroup(subGroups);
     462              :     }
     463              : 
     464            0 :     for (size_t i = 0; i < subGroups.size(); ++i) {
     465            0 :         for (Accidental* acc : subGroups[i]) {
     466            0 :             acc->mutldata()->verticalSubgroup = static_cast<int>(i);
     467              :         }
     468              :     }
     469              : 
     470            0 :     return subGroups;
     471            0 : }
     472              : 
     473            0 : AccidentalGroups AccidentalsLayout::groupAccidentalsByNoteXPos(const std::vector<Accidental*>& accidentals)
     474              : {
     475            0 :     AccidentalGroups subGroups;
     476              : 
     477            0 :     std::map<double, std::vector<Accidental*> > groupsOfEqualNoteX;
     478            0 :     for (Accidental* accidental : accidentals) {
     479            0 :         Note* note = accidental->note();
     480            0 :         double x = std::round(note->pos().x() + note->chord()->pos().x());
     481            0 :         groupsOfEqualNoteX[x].push_back(accidental);
     482              :     }
     483              : 
     484            0 :     for (auto iter = groupsOfEqualNoteX.rbegin(); iter != groupsOfEqualNoteX.rend(); ++iter) {
     485            0 :         subGroups.push_back((*iter).second);
     486              :     }
     487              : 
     488            0 :     return subGroups;
     489            0 : }
     490              : 
     491            0 : void AccidentalsLayout::moveSecondsInSameGroup(AccidentalGroups& subGroups)
     492              : {
     493            0 :     bool secondMoved = false;
     494            0 :     do {
     495            0 :         secondMoved = false;
     496            0 :         for (size_t i = 0; i < subGroups.size() - 1; ++i) {
     497            0 :             std::vector<Accidental*>& thisGroup = subGroups[i];
     498            0 :             std::vector<Accidental*>& nextGroup = subGroups[i + 1];
     499            0 :             for (size_t j = 0; j < thisGroup.size(); ++j) {
     500            0 :                 Accidental* acc1 = thisGroup[j];
     501            0 :                 for (Accidental* secondAcc : acc1->ldata()->seconds.value()) {
     502            0 :                     if (muse::remove(nextGroup, secondAcc)) {
     503            0 :                         secondMoved = true;
     504            0 :                         auto position = thisGroup.begin() + j;
     505            0 :                         if (secondAcc->line() < acc1->line()) {
     506            0 :                             thisGroup.insert(position, secondAcc);
     507              :                         } else {
     508            0 :                             thisGroup.insert(position + 1, secondAcc);
     509              :                         }
     510            0 :                         break;
     511              :                     }
     512              :                 }
     513            0 :                 if (secondMoved) {
     514            0 :                     break;
     515              :                 }
     516              :             }
     517            0 :             if (nextGroup.size() == 0) {
     518            0 :                 subGroups.erase(subGroups.begin() + i + 1);
     519              :             }
     520            0 :             if (secondMoved) {
     521            0 :                 break;
     522              :             }
     523              :         }
     524              :     } while (secondMoved);
     525            0 : }
     526              : 
     527         3146 : void AccidentalsLayout::determineStackingOrder(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     528              : {
     529         3146 :     size_t groupSize = accidentals.size();
     530              : 
     531         3146 :     if (groupSize <= ctx.smallGroupLimit()) {
     532         3073 :         computeStandardOrdering(accidentals, ctx);
     533           73 :     } else if (groupSize > ctx.largeGroupLimit()
     534           73 :                || (ctx.alignOctavesAcrossSubChords() && ctx.subChordsCountBeforeOctaveMerge > 1)) {
     535            3 :         computeCompactOrdering(accidentals, ctx);
     536              :     } else {
     537           70 :         computeOrderingWithLeastColumns(accidentals, ctx);
     538              :     }
     539         3146 : }
     540              : 
     541           70 : void AccidentalsLayout::computeOrderingWithLeastColumns(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     542              : {
     543           70 :     std::vector<Accidental*> standardOrdered = accidentals;
     544           70 :     computeStandardOrdering(standardOrdered, ctx);
     545              : 
     546           70 :     std::vector<Accidental*> compactOrdered = accidentals;
     547           70 :     computeCompactOrdering(compactOrdered, ctx);
     548              : 
     549          140 :     auto totalColumns = [&](std::vector<Accidental*>& accGroup) {
     550          140 :         std::map<Accidental*, int> accidentalColumn;
     551          140 :         accidentalColumn[accGroup.front()] = 0;
     552          140 :         int maxColumn = 0;
     553          646 :         for (size_t i = 1; i < accGroup.size(); ++i) {
     554          506 :             Accidental* acc1 = accGroup[i];
     555          506 :             accidentalColumn[acc1] = maxColumn;
     556         1712 :             for (int j = static_cast<int>(i - 1); j >= 0; --j) {
     557         1206 :                 Accidental* acc2 = accGroup[j];
     558         1206 :                 if (accidentalColumn[acc2] < maxColumn) {
     559          601 :                     continue;
     560              :                 }
     561          605 :                 if (!canFitInSameColumn(acc1, acc2, ctx)) {
     562          321 :                     maxColumn += 1;
     563          321 :                     accidentalColumn[acc1] = maxColumn;
     564              :                 }
     565              :             }
     566              :         }
     567          140 :         return maxColumn;
     568          140 :     };
     569              : 
     570           70 :     accidentals = totalColumns(compactOrdered) < totalColumns(standardOrdered) ? compactOrdered : standardOrdered;
     571           70 : }
     572              : 
     573         3143 : void AccidentalsLayout::computeStandardOrdering(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     574              : {
     575         3143 :     std::vector<Accidental*> accidentalsPlaced;
     576         3143 :     accidentalsPlaced.reserve(accidentals.size());
     577              : 
     578         3143 :     AccidentalsVectorView accidentalsToPlace { accidentals };
     579              : 
     580         3143 :     bool pickFromTop = true;
     581         6987 :     while (!accidentalsToPlace.empty()) {
     582         3844 :         Accidental* acc = pickFromTop ? accidentalsToPlace.pop_front() : accidentalsToPlace.pop_back();
     583         3844 :         accidentalsPlaced.push_back(acc);
     584              : 
     585         3844 :         if (ctx.keepSecondsTogether()) {
     586            0 :             findAndInsertSecond(acc, accidentalsPlaced, accidentalsToPlace, ctx);
     587              :         }
     588              : 
     589         3844 :         const bool foundOctave = findAndInsertOctave(acc, accidentalsPlaced, accidentalsToPlace, ctx);
     590         3844 :         if (foundOctave) {
     591          137 :             pickFromTop = true;
     592              :         } else {
     593         3707 :             pickFromTop = !pickFromTop;
     594              :         }
     595              :     }
     596              : 
     597         3143 :     accidentals = accidentalsPlaced;
     598         3143 : }
     599              : 
     600           73 : void AccidentalsLayout::computeCompactOrdering(std::vector<Accidental*>& accidentals, AccidentalsLayoutContext& ctx)
     601              : {
     602           73 :     const size_t totAccidNumber = accidentals.size();
     603           73 :     Accidental* const bottomAcc = accidentals.back();
     604              : 
     605           73 :     std::vector<Accidental*> accidentalsPlaced;
     606           73 :     accidentalsPlaced.reserve(totAccidNumber);
     607              : 
     608           73 :     AccidentalsVectorView accidentalsToPlace { accidentals };
     609              : 
     610           73 :     bool pickFromTop = true;
     611          321 :     while (!accidentalsToPlace.empty()) {
     612          248 :         Accidental* acc = pickFromTop ? accidentalsToPlace.pop_front() : accidentalsToPlace.pop_back();
     613          248 :         accidentalsPlaced.push_back(acc);
     614              : 
     615          248 :         bool foundOneThatFits = false;
     616          248 :         bool hitBottom = false;
     617              : 
     618          248 :         if (findAndInsertOctave(acc, accidentalsPlaced, accidentalsToPlace, ctx, !pickFromTop, pickFromTop)) {
     619           42 :             foundOneThatFits = true;
     620           42 :             acc = accidentalsPlaced.back();
     621           42 :             if (acc == bottomAcc) {
     622           21 :                 hitBottom = true;
     623              :             }
     624              :         }
     625              : 
     626              :         // fit others into same column
     627          443 :         const auto pred = [&](Accidental* acc2) {
     628          443 :             if ((pickFromTop && acc2->line() < acc->line()) || (!pickFromTop && acc2->line() > acc->line())) {
     629           55 :                 return false;
     630              :             }
     631          388 :             if (canFitInSameColumn(acc, acc2, ctx)) {
     632           64 :                 if (acc2->ldata()->octaves.value().size() > 0) {
     633           10 :                     return false;
     634              :                 }
     635           54 :                 foundOneThatFits = true;
     636           54 :                 accidentalsPlaced.push_back(acc2);
     637           54 :                 acc = acc2;
     638           54 :                 if (acc2 == bottomAcc) {
     639           20 :                     hitBottom = true;
     640              :                 }
     641           54 :                 return true;
     642              :             }
     643          324 :             return false;
     644          248 :         };
     645              : 
     646          248 :         if (pickFromTop) {
     647          177 :             accidentalsToPlace.remove_if(pred);
     648              :         } else {
     649           71 :             accidentalsToPlace.reverse_remove_if(pred);
     650              :         }
     651              : 
     652          248 :         if (ctx.keepSecondsTogether()) {
     653            0 :             for (Accidental* alreadyPlacedAcc : accidentalsPlaced) {
     654            0 :                 findAndInsertSecond(alreadyPlacedAcc, accidentalsPlaced, accidentalsToPlace, ctx);
     655              :             }
     656              :         }
     657              : 
     658          248 :         if (hitBottom || (foundOneThatFits && totAccidNumber <= ctx.largeGroupLimit())) {
     659           87 :             pickFromTop = true;
     660              :         } else {
     661          161 :             pickFromTop = !pickFromTop;
     662              :         }
     663              :     }
     664              : 
     665           73 :     accidentals = accidentalsPlaced;
     666           73 : }
     667              : 
     668            0 : void AccidentalsLayout::findAndInsertSecond(Accidental* acc, std::vector<Accidental*>& accidentalsPlaced,
     669              :                                             AccidentalsVectorView& accidentalsToPlace, AccidentalsLayoutContext& ctx)
     670              : {
     671            0 :     const std::vector<Accidental*>& seconds = acc->ldata()->seconds.value();
     672            0 :     for (Accidental* secondAcc : seconds) {
     673            0 :         const bool removed = accidentalsToPlace.remove(secondAcc);
     674            0 :         if (removed) {
     675            0 :             if (ctx.orderFollowNoteDisplacement() && secondAcc->note()->x() > acc->note()->x()) {
     676            0 :                 auto position = std::find(accidentalsPlaced.begin(), accidentalsPlaced.end(), acc);
     677            0 :                 accidentalsPlaced.insert(position, secondAcc);
     678              :             } else {
     679            0 :                 accidentalsPlaced.push_back(secondAcc);
     680              :             }
     681              :         }
     682              :     }
     683            0 : }
     684              : 
     685         4092 : bool AccidentalsLayout::findAndInsertOctave(Accidental* acc, std::vector<Accidental*>& accidentalsPlaced,
     686              :                                             AccidentalsVectorView& accidentalsToPlace, AccidentalsLayoutContext& ctx, bool acceptAbove,
     687              :                                             bool acceptBelow)
     688              : {
     689         4092 :     bool foundOctave = false;
     690         4092 :     int thisLine = acc->line();
     691         4092 :     const std::vector<Accidental*>& octaves = acc->ldata()->octaves.value();
     692         4424 :     for (Accidental* octaveAcc : octaves) {
     693          332 :         const bool removed = accidentalsToPlace.remove_if([&](Accidental* a) {
     694          475 :             if (a == octaveAcc) {
     695          183 :                 int octaveLine = octaveAcc->line();
     696          183 :                 bool acceptOctave = (octaveLine < thisLine && acceptAbove) || (octaveLine > thisLine && acceptBelow);
     697          183 :                 if (acceptOctave) {
     698          183 :                     foundOctave = true;
     699          183 :                     return true;
     700              :                 }
     701              :             }
     702          292 :             return false;
     703              :         });
     704              : 
     705          332 :         if (removed) {
     706          183 :             accidentalsPlaced.push_back(octaveAcc);
     707              : 
     708          183 :             if (ctx.keepSecondsTogether()) {
     709            0 :                 findAndInsertSecond(octaveAcc, accidentalsPlaced, accidentalsToPlace, ctx);
     710              :             }
     711              :         }
     712              :     }
     713              : 
     714         4092 :     return foundOctave;
     715              : }
     716              : 
     717         3128 : void AccidentalsLayout::applyOrderingOffsets(std::vector<Accidental*>& accidentals)
     718              : {
     719              :     // Set default stacking number
     720         3128 :     int stackingNumber = 0;
     721         7134 :     for (Accidental* acc : accidentals) {
     722         4006 :         acc->mutldata()->stackingNumber = stackingNumber;
     723         4006 :         ++stackingNumber;
     724              :     }
     725              : 
     726              :     // Limit stacking offsets within boundaries
     727         7134 :     for (Accidental* acc : accidentals) {
     728         4006 :         int actualStackingOrder = acc->stackingOrder();
     729         4006 :         if (actualStackingOrder < 0) {
     730            0 :             acc->setStackingOrderOffset(acc->stackingOrderOffset() - actualStackingOrder);
     731         4006 :         } else if (actualStackingOrder >= stackingNumber) {
     732            0 :             int diff = actualStackingOrder - stackingNumber;
     733            0 :             acc->setStackingOrderOffset(acc->stackingOrderOffset() - diff - 1);
     734              :         }
     735              :     }
     736              : 
     737              :     // Sort vector
     738         3128 :     std::sort(accidentals.begin(), accidentals.end(), [](const Accidental* acc1, const Accidental* acc2) {
     739         1756 :         int stackingOrder1 = acc1->stackingOrder();
     740         1756 :         int stackingOrder2 = acc2->stackingOrder();
     741         1756 :         return stackingOrder1 == stackingOrder2 ? acc1->stackingOrderOffset() < acc2->stackingOrderOffset() : stackingOrder1 < stackingOrder2;
     742              :     });
     743         3128 : }
     744              : 
     745         8012 : double AccidentalsLayout::minAccidentalToChordDistance(Accidental* acc, const Shape& accShape, const AccidentalsLayoutContext& ctx)
     746              : {
     747         8012 :     double dist = -DBL_MAX;
     748        25769 :     for (const ShapeElement& accidentalElement : accShape.elements()) {
     749        17757 :         double accTop = accidentalElement.top();
     750        17757 :         double accBottom = accidentalElement.bottom();
     751       153792 :         for (const ShapeElement& chordElement : ctx.chordsShape.elements()) {
     752       136035 :             double chordElementTop = chordElement.top();
     753       136035 :             double chordElementBottom = chordElement.bottom();
     754       136035 :             if (mu::engraving::intersects(accTop, accBottom, chordElementTop, chordElementBottom,
     755              :                                           ctx.verticalAccidentalToChordClearance())) {
     756        77983 :                 double padding = computePadding(acc, chordElement.item(), ctx);
     757        77983 :                 padding += additionalPaddingForVerticals(acc, chordElement.item(), ctx);
     758        77983 :                 dist = std::max(dist, accidentalElement.right() - chordElement.left() + padding);
     759              :             }
     760       136035 :             dist = std::max(dist, kerningLimitationsIntoChord(acc, accShape, chordElement, ctx));
     761              :         }
     762              :     }
     763              : 
     764         8012 :     return dist;
     765              : }
     766              : 
     767       136035 : double AccidentalsLayout::kerningLimitationsIntoChord(Accidental* acc, const Shape& accShape, const ShapeElement& chordElement,
     768              :                                                       const AccidentalsLayoutContext& ctx)
     769              : {
     770       136035 :     const EngravingItem* chordItem = chordElement.item();
     771       136035 :     if (!chordItem) {
     772            0 :         return 0.0;
     773              :     }
     774              : 
     775       136035 :     AccidentalType accType = acc->accidentalType();
     776       136035 :     if (chordItem->isLedgerLine() && (accType == AccidentalType::NATURAL || accType == AccidentalType::SHARP)) {
     777        30317 :         if (chordItem->y() > accShape.top() && chordItem->y() < accShape.bottom()) {
     778         8567 :             return accShape.right() - chordElement.left() + ctx.sharpAndNaturalLedgerLinePadding();
     779              :         }
     780              :     }
     781              : 
     782       127468 :     return 0.0;
     783              : }
     784              : 
     785        77983 : double AccidentalsLayout::computePadding(Accidental* acc, const EngravingItem* chordElement, const AccidentalsLayoutContext& ctx)
     786              : {
     787        77983 :     AccidentalType accType = acc->accidentalType();
     788        77983 :     bool isFlat = accType == AccidentalType::FLAT || accType == AccidentalType::FLAT2;
     789              : 
     790        77983 :     bool kernFlatIntoLedger = isFlat && chordElement->isLedgerLine() && chordElement->y() > acc->note()->y();
     791        77983 :     if (kernFlatIntoLedger) {
     792          148 :         return 0.0;
     793              :     }
     794              : 
     795        77835 :     const double yApproxThresholdForSharpLedgerKerning = 1.1 * ctx.spatium();
     796        47207 :     bool kernSharpIntoLedger = accType == AccidentalType::SHARP && chordElement->isLedgerLine()
     797       125042 :                                && chordElement->y() > acc->note()->y() + yApproxThresholdForSharpLedgerKerning;
     798        77835 :     if (kernSharpIntoLedger) {
     799          580 :         return 0.0;
     800              :     }
     801              : 
     802        77255 :     bool kernFlatIntoNote = isFlat && chordElement->isNote() && chordElement->y() > acc->note()->y();
     803        77255 :     if (kernFlatIntoNote) {
     804          550 :         return ctx.reducedFlatToNotePadding();
     805              :     }
     806              : 
     807        76705 :     const PaddingTable& paddingTable = acc->score()->paddingTables().horizontalPaddingTable();
     808              : 
     809        76705 :     return paddingTable.at(ElementType::ACCIDENTAL).at(chordElement->type()) * 0.5 * (acc->mag() + chordElement->mag());
     810              : }
     811              : 
     812         4391 : double AccidentalsLayout::minAccidentalToAccidentalGroupDistance(Accidental* acc, Shape accShape, const Shape& accidentalsShape,
     813              :                                                                  const AccidentalsLayoutContext& ctx)
     814              : {
     815         4391 :     double dist = 0.0;
     816         4391 :     int column = acc->ldata()->column;
     817              : 
     818              :     bool collisionFound;
     819         4391 :     int iter = 0;
     820              :     static constexpr int safetyNet = 50;
     821              :     do {
     822         5263 :         ++iter;
     823         5263 :         double curDist = 0.0;
     824         5263 :         collisionFound = false;
     825        23039 :         for (const ShapeElement& groupElement : accidentalsShape.elements()) {
     826        17776 :             const Accidental* acc2 = toAccidental(groupElement.item());
     827              : 
     828        17776 :             if (isExceptionOfFourth(acc, acc2)) {
     829          135 :                 double distFourth = kerningOfFourth(accShape, groupElement, ctx);
     830          135 :                 if (!muse::RealIsNull(distFourth) && distFourth > curDist) {
     831           20 :                     collisionFound = true;
     832           20 :                     curDist = distFourth;
     833              :                 }
     834          135 :                 column = std::max(column, acc2->ldata()->column + 1);
     835          135 :                 continue;
     836          135 :             }
     837              : 
     838        17641 :             double vertPadding = verticalPadding(acc, acc2, ctx);
     839        17641 :             double horPadding = horizontalPadding(acc, acc2, ctx);
     840              :             // If the tolerance for detecting intersections is the same as the padding,
     841              :             // we may randomly detect wrong intersections just because of rounding errors
     842        17641 :             double horPaddingWithTolerance = 0.999 * horPadding;
     843        17641 :             if (accShape.intersects(groupElement.adjusted(-horPaddingWithTolerance, -vertPadding, horPaddingWithTolerance, vertPadding))) {
     844         2998 :                 collisionFound = true;
     845              :             } else {
     846        14643 :                 continue;
     847              :             }
     848              : 
     849         2998 :             if (!isExceptionOfNaturalsSixth(acc, acc2)) {
     850         2998 :                 column = std::max(column, acc2->ldata()->column + 1);
     851              :             }
     852              : 
     853         2998 :             double groupTop = groupElement.top();
     854         2998 :             double groupBottom = groupElement.bottom();
     855        13877 :             for (ShapeElement& accElement : accShape.elements()) {
     856        10879 :                 if (mu::engraving::intersects(accElement.top(), accElement.bottom(), groupTop, groupBottom, vertPadding)) {
     857         8000 :                     double minDist = accElement.right() - groupElement.left() + horPadding;
     858         8000 :                     minDist += additionalPaddingForVerticals(acc, acc2, ctx);
     859         8000 :                     curDist = std::max(curDist, minDist);
     860              :                 }
     861              :             }
     862              :         }
     863              : 
     864         5263 :         accShape.translateX(-curDist);
     865         5263 :         dist += curDist;
     866         5263 :     } while (collisionFound && iter < safetyNet);
     867              : 
     868         4391 :     IF_ASSERT_FAILED(iter < safetyNet) {
     869            0 :         LOGE() << "Accidental layout error.";
     870              :     }
     871              : 
     872         4391 :     acc->mutldata()->column = column;
     873              : 
     874         4391 :     return dist;
     875              : }
     876              : 
     877        17776 : bool AccidentalsLayout::isExceptionOfFourth(const Accidental* acc1, const Accidental* acc2)
     878              : {
     879        17776 :     if (!acc1 || !acc2) {
     880            0 :         return false;
     881              :     }
     882              : 
     883        35552 :     if ((acc1->bracket() == AccidentalBracket::BRACKET || acc2->bracket() == AccidentalBracket::BRACKET)
     884        35552 :         && acc1->mag() == 1 && acc2->mag() == 1) {
     885            0 :         return false;
     886              :     }
     887              : 
     888        17776 :     AccidentalType accType1 = acc1->accidentalType();
     889        17776 :     AccidentalType accType2 = acc2->accidentalType();
     890              : 
     891        16087 :     return (accType1 == AccidentalType::FLAT || accType1 == AccidentalType::FLAT2)
     892         1779 :            && (accType2 == AccidentalType::FLAT || accType2 == AccidentalType::FLAT2 || accType2 == AccidentalType::NATURAL)
     893        35552 :            && acc1->line() - acc2->line() == 3;
     894              : }
     895              : 
     896         3991 : bool AccidentalsLayout::isExceptionOfNaturalsSixth(const Accidental* acc1, const Accidental* acc2)
     897              : {
     898         3991 :     return acc1->accidentalType() == AccidentalType::NATURAL
     899          124 :            && acc2->accidentalType() == AccidentalType::NATURAL
     900         4115 :            && abs(acc2->line() - acc1->line()) == 5;
     901              : }
     902              : 
     903          993 : bool AccidentalsLayout::canFitInSameColumn(const Accidental* acc1, const Accidental* acc2, AccidentalsLayoutContext& ctx)
     904              : {
     905          993 :     if (isExceptionOfNaturalsSixth(acc1, acc2)) {
     906            2 :         return true;
     907              :     }
     908              : 
     909          991 :     RectF acc1Box = acc1->ldata()->bbox().translated(PointF(0.0, acc1->note()->y()));
     910          991 :     RectF acc2Box = acc2->ldata()->bbox().translated(PointF(0.0, acc2->note()->y()));
     911              : 
     912          991 :     return !mu::engraving::intersects(acc1Box.top(), acc1Box.bottom(), acc2Box.top(), acc2Box.bottom(),
     913          991 :                                       verticalPadding(acc1, acc2, ctx));
     914              : }
     915              : 
     916         3578 : bool AccidentalsLayout::isOctave(const Accidental* acc1, const Accidental* acc2)
     917              : {
     918         3578 :     return acc1->accidentalType() == acc2->accidentalType() && std::abs(acc2->line() - acc1->line()) % 7 == 0;
     919              : }
     920              : 
     921          135 : double AccidentalsLayout::kerningOfFourth(const Shape& accShape, const ShapeElement& accGroupShapeElement,
     922              :                                           const AccidentalsLayoutContext& ctx)
     923              : {
     924          135 :     AccidentalType accType2 = toAccidental(accGroupShapeElement.item())->accidentalType();
     925              : 
     926          135 :     return accShape.right() - accGroupShapeElement.left()
     927          135 :            + (accType2 == AccidentalType::NATURAL ? ctx.naturalKerningOfFourth() : ctx.flatKerningOfFourth());
     928              : }
     929              : 
     930        85983 : double AccidentalsLayout::additionalPaddingForVerticals(const Accidental* acc, const EngravingItem* item,
     931              :                                                         const AccidentalsLayoutContext& ctx)
     932              : {
     933       100970 :     if (acc->accidentalType() != AccidentalType::NATURAL || acc->bracket() != AccidentalBracket::NONE
     934       100970 :         || (!item->isAccidental() && !item->isStem())) {
     935        82934 :         return 0.0;
     936              :     }
     937              : 
     938         3049 :     if (item->isAccidental()) {
     939          239 :         const Accidental* acc2 = toAccidental(item);
     940          239 :         AccidentalType accType2 = acc2->accidentalType();
     941          239 :         if (accType2 != AccidentalType::NATURAL && accType2 != AccidentalType::FLAT && accType2 != AccidentalType::FLAT2) {
     942          102 :             return 0.0;
     943              :         }
     944          137 :         int lineDiff = acc2->line() - acc->line();
     945          137 :         if (lineDiff < -2 || lineDiff > 5) {
     946           56 :             return 0.0;
     947              :         }
     948              :     }
     949              : 
     950         2891 :     return ctx.additionalPaddingForVerticals();
     951              : }
     952              : 
     953         3043 : void AccidentalsLayout::verticallyAlignAccidentals(AccidentalsLayoutContext& ctx)
     954              : {
     955         3043 :     std::set<Accidental*> accidentalsAlreadyGrouped;
     956         3043 :     std::map<Accidental*, std::vector<Accidental*> > verticalSets;
     957              : 
     958         3043 :     if (ctx.alignOffsetOctaves()) {
     959            0 :         collectVerticalSetsOfOffsetOctaves(accidentalsAlreadyGrouped, verticalSets, ctx);
     960              :     }
     961              : 
     962         3043 :     collectVerticalSets(accidentalsAlreadyGrouped, verticalSets, ctx);
     963              : 
     964         3043 :     if (verticalSets.empty()) {
     965         2757 :         return;
     966              :     }
     967              : 
     968              :     // Turn map into vector
     969          286 :     AccidentalGroups vertSets;
     970          286 :     vertSets.reserve(verticalSets.size());
     971          604 :     for (auto& pair : verticalSets) {
     972          318 :         vertSets.push_back(pair.second);
     973              :     }
     974              : 
     975              :     // Sort vertical sets by column
     976          286 :     std::sort(vertSets.begin(), vertSets.end(), [&](auto& set1, auto& set2) {
     977           43 :         return set1.front()->ldata()->column < set2.front()->ldata()->column;
     978              :     });
     979              : 
     980          286 :     alignVerticalSets(vertSets, ctx);
     981         5800 : }
     982              : 
     983            0 : void AccidentalsLayout::collectVerticalSetsOfOffsetOctaves
     984              :     (std::set<Accidental*>& accidentalsAlreadyGrouped, std::map<Accidental*, std::vector<Accidental*> >& verticalSets,
     985              :     AccidentalsLayoutContext& ctx)
     986              : {
     987            0 :     for (std::vector<Accidental*>& accidentalGroup : ctx.accidentalSubChords) {
     988            0 :         for (size_t i = 0; i < accidentalGroup.size(); ++i) {
     989            0 :             Accidental* acc1 = accidentalGroup[i];
     990            0 :             acc1->ldata()->column.value();
     991            0 :             if (muse::contains(accidentalsAlreadyGrouped, acc1)) {
     992            0 :                 continue;
     993              :             }
     994            0 :             double x1 = xPosRelativeToSegment(acc1);
     995            0 :             for (size_t j = i + 1; j < accidentalGroup.size(); ++j) {
     996            0 :                 Accidental* acc2 = accidentalGroup[j];
     997            0 :                 if (!isOctave(acc1, acc2) || muse::contains(accidentalsAlreadyGrouped, acc2)) {
     998            0 :                     continue;
     999              :                 }
    1000            0 :                 double x2 = xPosRelativeToSegment(acc2);
    1001            0 :                 bool alignOctave = abs(x1 - x2) < 2.0 * ctx.spatium();
    1002            0 :                 acc2->ldata()->column.value();
    1003            0 :                 if (alignOctave) {
    1004            0 :                     std::vector<Accidental*>& verticalSet = verticalSets[acc1];
    1005            0 :                     if (verticalSet.empty()) {
    1006            0 :                         verticalSet.push_back(acc1);
    1007              :                     }
    1008            0 :                     verticalSet.push_back(acc2);
    1009            0 :                     accidentalsAlreadyGrouped.insert(acc1);
    1010            0 :                     accidentalsAlreadyGrouped.insert(acc2);
    1011            0 :                     int maxColumn = 0;
    1012            0 :                     for (Accidental* acc : verticalSet) {
    1013            0 :                         maxColumn = std::max(maxColumn, acc->ldata()->column.value());
    1014              :                     }
    1015            0 :                     for (Accidental* acc : verticalSet) {
    1016            0 :                         acc->mutldata()->column = maxColumn;
    1017              :                     }
    1018              :                 }
    1019              :             }
    1020              :         }
    1021              :     }
    1022            0 : }
    1023              : 
    1024         3043 : void AccidentalsLayout::collectVerticalSets(
    1025              :     std::set<Accidental*>& accidentalsAlreadyGrouped, std::map<Accidental*, std::vector<Accidental*> >& verticalSets,
    1026              :     AccidentalsLayoutContext& ctx)
    1027              : {
    1028         7049 :     for (size_t i = 0; i < ctx.allAccidentals.size(); ++i) {
    1029         4006 :         Accidental* acc1 = ctx.allAccidentals[i];
    1030         4006 :         if (muse::contains(accidentalsAlreadyGrouped, acc1)) {
    1031          348 :             continue;
    1032              :         }
    1033         3658 :         int column1 = acc1->ldata()->column;
    1034         3658 :         int verticalSub1 = acc1->ldata()->verticalSubgroup;
    1035         3658 :         double x1 = xPosRelativeToSegment(acc1);
    1036         5359 :         for (size_t j = i + 1; j < ctx.allAccidentals.size(); ++j) {
    1037         1701 :             Accidental* acc2 = ctx.allAccidentals[j];
    1038         2982 :             if (acc2->accidentalType() != acc1->accidentalType() || muse::contains(accidentalsAlreadyGrouped, acc2)
    1039         2982 :                 || !muse::RealIsEqual(acc1->mag(), acc2->mag())) {
    1040          692 :                 continue;
    1041              :             }
    1042         1009 :             int column2 = acc2->ldata()->column;
    1043         1009 :             int verticalSub2 = acc2->ldata()->verticalSubgroup;
    1044         1009 :             double x2 = xPosRelativeToSegment(acc2);
    1045         1009 :             bool alignAccidentals = column1 == column2 && verticalSub1 == verticalSub2 && abs(x1 - x2) < ctx.xVerticalAlignmentThreshold();
    1046         1009 :             if (alignAccidentals) {
    1047          348 :                 std::vector<Accidental*>& verticalSet = verticalSets[acc1];
    1048          348 :                 if (verticalSet.empty()) {
    1049          318 :                     verticalSet.push_back(acc1);
    1050              :                 }
    1051          348 :                 verticalSet.push_back(acc2);
    1052          348 :                 accidentalsAlreadyGrouped.insert(acc1);
    1053          348 :                 accidentalsAlreadyGrouped.insert(acc2);
    1054              :             }
    1055              :         }
    1056              :     }
    1057         3043 : }
    1058              : 
    1059          286 : void AccidentalsLayout::alignVerticalSets(AccidentalGroups& vertSets, AccidentalsLayoutContext& ctx)
    1060              : {
    1061          286 :     AccidentalGroups accidentalColumns;
    1062          653 :     for (std::vector<Accidental*>& group : ctx.accidentalSubChords) {
    1063         1340 :         for (Accidental* acc : group) {
    1064          973 :             size_t column = acc->ldata()->column;
    1065         1546 :             while (accidentalColumns.size() <= column) {
    1066          573 :                 accidentalColumns.push_back(std::vector<Accidental*>());
    1067              :             }
    1068          973 :             accidentalColumns[column].push_back(acc);
    1069              :         }
    1070              :     }
    1071              : 
    1072          604 :     for (std::vector<Accidental*>& vertSet : vertSets) {
    1073              :         // Align the set
    1074          318 :         double x = DBL_MAX;
    1075          984 :         for (Accidental* acc : vertSet) {
    1076          666 :             double rightEdge = acc->ldata()->bbox().right() + xPosRelativeToSegment(acc);
    1077          666 :             x = std::min(x, rightEdge);
    1078              :         }
    1079          984 :         for (Accidental* acc : vertSet) {
    1080          666 :             double bboxRight = acc->ldata()->bbox().right();
    1081          666 :             setXposRelativeToSegment(acc, x - bboxRight);
    1082              :         }
    1083              : 
    1084              :         // Re-check the outer ones for collisions
    1085          318 :         size_t curColumn = vertSet.front()->ldata()->column.value();
    1086          318 :         Shape accidentalGroupShape;
    1087          975 :         for (size_t column = 0; column < accidentalColumns.size(); ++column) {
    1088          657 :             if (column < curColumn) {
    1089           41 :                 continue;
    1090              :             }
    1091         1667 :             for (Accidental* acc : accidentalColumns[column]) {
    1092         1051 :                 double curXPos = xPosRelativeToSegment(acc);
    1093         1051 :                 Shape accShape = acc->shape().translate(PointF(curXPos, acc->note()->y()));
    1094         1051 :                 if (!muse::contains(vertSet, acc)) {
    1095          385 :                     double minDistToAccidGroup = minAccidentalToAccidentalGroupDistance(acc, accShape, accidentalGroupShape, ctx);
    1096          385 :                     accShape.translateX(-minDistToAccidGroup);
    1097          385 :                     setXposRelativeToSegment(acc, curXPos - minDistToAccidGroup);
    1098              :                 }
    1099         1051 :                 accidentalGroupShape.add(accShape);
    1100         1051 :             }
    1101              :         }
    1102          318 :     }
    1103          286 : }
    1104              : 
    1105         5057 : void AccidentalsLayout::setXposRelativeToSegment(Accidental* accidental, double x)
    1106              : {
    1107         5057 :     Note* note = accidental->note();
    1108         5057 :     Chord* chord = note ? note->chord() : nullptr;
    1109         5057 :     if (note) {
    1110         5057 :         x -= note->pos().x();
    1111              :     }
    1112         5057 :     if (chord && !keepAccidentalsCloseToChord(chord)) {
    1113         4990 :         x -= chord->pos().x();
    1114              :     }
    1115         5057 :     accidental->mutldata()->setPosX(x);
    1116         5057 : }
    1117              : 
    1118        10489 : double AccidentalsLayout::xPosRelativeToSegment(const Accidental* accidental)
    1119              : {
    1120        10489 :     Note* note = accidental->note();
    1121        10489 :     Chord* chord = note ? note->chord() : nullptr;
    1122        10489 :     double x = accidental->ldata()->pos().x();
    1123        10489 :     if (note) {
    1124        10489 :         x += note->pos().x();
    1125              :     }
    1126        10489 :     if (chord && !keepAccidentalsCloseToChord(chord)) {
    1127        10356 :         x += chord->pos().x();
    1128              :     }
    1129        10489 :     return x;
    1130              : }
    1131              : 
    1132         6189 : void AccidentalsLayout::sortTopDown(std::vector<Accidental*>& accidentals)
    1133              : {
    1134         6189 :     std::sort(accidentals.begin(), accidentals.end(), [](const Accidental* acc1, const Accidental* acc2){
    1135         2501 :         int line1 = acc1->line();
    1136         2501 :         int line2 = acc2->line();
    1137         2501 :         if (line1 == line2) {
    1138           27 :             Note* note1 = acc1->note();
    1139           27 :             Note* note2 = acc2->note();
    1140           27 :             return note1->x() + note1->chord()->x() > note2->x() + note2->chord()->x();
    1141              :         }
    1142         2474 :         return line1 < line2;
    1143              :     });
    1144         6189 : }
    1145              : 
    1146        18740 : bool AccidentalsLayout::keepAccidentalsCloseToChord(const Chord* chord)
    1147              : {
    1148        18740 :     return chord->isTrillCueNote() || chord->isGraceAfter();
    1149              : }
    1150              : 
    1151        18632 : double AccidentalsLayout::verticalPadding(const Accidental* acc1, const Accidental* acc2, const AccidentalsLayoutContext& ctx)
    1152              : {
    1153        32954 :     double verticalClearance = acc1->accidentalType() == AccidentalType::SHARP && acc2->accidentalType() == AccidentalType::SHARP
    1154        32954 :                                ? ctx.verticalSharpToSharpClearance() : ctx.verticalAccidentalToAccidentalClearance();
    1155        18632 :     return verticalClearance *= 0.5 * (acc1->mag() + acc2->mag());
    1156              : }
    1157              : 
    1158        17641 : double AccidentalsLayout::horizontalPadding(const Accidental* acc1, const Accidental* acc2, const AccidentalsLayoutContext& ctx)
    1159              : {
    1160        17641 :     return ctx.accidentalAccidentalDistance() * (0.5 * (acc1->mag() + acc2->mag()));
    1161              : }
        

Generated by: LCOV version 2.0-1